From b255e029f366d01cd4701ef1ea345e5b41cea6a4 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 16 Sep 2020 10:23:45 -0400 Subject: [PATCH 001/107] MAINT: pass params dict to MorletSpec --- specfn.py | 41 +++++++++++++++++++++-------------------- vislfp.py | 3 ++- vispsd.py | 2 +- visspec.py | 6 +++--- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/specfn.py b/specfn.py index 9582b694a..f3080d2ad 100644 --- a/specfn.py +++ b/specfn.py @@ -23,7 +23,7 @@ # MorletSpec class based on a time vec tvec and a time series vec tsvec class MorletSpec(): - def __init__(self, tvec, tsvec, fparam, f_max=None, p_dict=None, tmin = 50.0, f_min = 1.): + def __init__(self, tvec, tsvec, f_max=None, p_dict=None, tmin = 50.0, f_min = 1.): # Save variable portion of fdata_spec as identifying attribute # self.name = fdata_spec @@ -31,18 +31,14 @@ def __init__(self, tvec, tsvec, fparam, f_max=None, p_dict=None, tmin = 50.0, f_ self.tvec = tvec self.tsvec = tsvec - # function is called this way because paramrw.read() returns 2 outputs - if p_dict is None: - self.p_dict = paramrw.read(fparam)[1] - else: - self.p_dict = p_dict - self.f_min = f_min + self.params = p_dict + # maximum frequency of analysis # Add 1 to ensure analysis is inclusive of maximum frequency if not f_max: - self.f_max = self.p_dict['f_max_spec'] + 1 + self.f_max = self.params['f_max_spec'] + 1 else: self.f_max = f_max + 1 @@ -50,13 +46,13 @@ def __init__(self, tvec, tsvec, fparam, f_max=None, p_dict=None, tmin = 50.0, f_ self.tmin = tmin # truncate these vectors appropriately based on tmin - if self.p_dict['tstop'] > self.tmin: + if self.params['tstop'] > self.tmin: # must be done in this order! timeseries first! self.tsvec = self.tsvec[self.tvec >= self.tmin] self.tvec = self.tvec[self.tvec >= self.tmin] # Check that tstop is greater than tmin - if self.p_dict['tstop'] > self.tmin: + if self.params['tstop'] > self.tmin: # Array of frequencies over which to sort self.f = np.arange(self.f_min, self.f_max) @@ -64,7 +60,7 @@ def __init__(self, tvec, tsvec, fparam, f_max=None, p_dict=None, tmin = 50.0, f_ self.width = 7. # Calculate sampling frequency - self.fs = 1000. / self.p_dict['dt'] + self.fs = 1000. / self.params['dt'] # Generate Spec data self.TFR = self.__traces2TFR() @@ -82,7 +78,7 @@ def save(self, fdata_spec): # plots spec to axis def plot_to_ax(self, ax_spec, dt): # pc = ax.imshow(self.TFR, extent=[xmin, xmax, self.freqvec[-1], self.freqvec[0]], aspect='auto', origin='upper') - pc = ax_spec.imshow(self.TFR, aspect='auto', origin='upper', cmap=plt.get_cmap(self.p_dict['spec_cmap'])) + pc = ax_spec.imshow(self.TFR, aspect='auto', origin='upper', cmap=plt.get_cmap(self.params['spec_cmap'])) return pc @@ -107,7 +103,7 @@ def __traces2TFR(self): # range should probably be 0 to len(self.S_trans) # shift tvec to reflect change # this is in ms - self.t = 1000. * np.arange(1, len(self.S_trans)+1) / self.fs + self.tmin - self.p_dict['dt'] + self.t = 1000. * np.arange(1, len(self.S_trans)+1) / self.fs + self.tmin - self.params['dt'] # preallocation B = np.zeros((len(self.f), len(self.S_trans))) @@ -871,9 +867,12 @@ def generate_missing_spec(ddata, f_max=40): def spec_current_kernel(fparam, fts, fspec, f_max): I_syn = currentfn.SynapticCurrent(fts) + # TODO: replace with function that reads justs params + params = paramrw.read(fparam)[1] + # Generate spec results - spec_L2 = MorletSpec(I_syn.t, I_syn.I_soma_L2Pyr, fparam, f_max) - spec_L5 = MorletSpec(I_syn.t, I_syn.I_soma_L5Pyr, fparam, f_max) + spec_L2 = MorletSpec(I_syn.t, I_syn.I_soma_L2Pyr, f_max, p_dict=params) + spec_L5 = MorletSpec(I_syn.t, I_syn.I_soma_L5Pyr, f_max, p_dict=params) # Save spec data np.savez_compressed(fspec, t_L2=spec_L2.t, f_L2=spec_L2.f, TFR_L2=spec_L2.TFR, t_L5=spec_L5.t, f_L5=spec_L5.f, TFR_L5=spec_L5.TFR) @@ -884,21 +883,23 @@ def spec_dpl_kernel(fparam, fts, fspec, f_max): dpl = dipolefn.Dipole(fts) dpl.units = 'nAm' + # TODO: replace with function that reads justs params + params = paramrw.read(fparam)[1] + # Do the conversion prior to generating these spec # dpl.convert_fAm_to_nAm() # Generate various spec results - spec_agg = MorletSpec(dpl.t, dpl.dpl['agg'], fparam, f_max) - spec_L2 = MorletSpec(dpl.t, dpl.dpl['L2'], fparam, f_max) - spec_L5 = MorletSpec(dpl.t, dpl.dpl['L5'], fparam, f_max) + spec_agg = MorletSpec(dpl.t, dpl.dpl['agg'], f_max, p_dict=params) + spec_L2 = MorletSpec(dpl.t, dpl.dpl['L2'], f_max, p_dict=params) + spec_L5 = MorletSpec(dpl.t, dpl.dpl['L5'], f_max, p_dict=params) # Get max spectral power data # for now, only doing this for agg max_agg = spec_agg.max() # Generate periodogram resutls - p_dict = paramrw.read(fparam)[1] - pgram = Welch(dpl.t, dpl.dpl['agg'], p_dict['dt']) + pgram = Welch(dpl.t, dpl.dpl['agg'], params['dt']) # Save spec results np.savez_compressed(fspec, time=spec_agg.t, freq=spec_agg.f, TFR=spec_agg.TFR, max_agg=max_agg, t_L2=spec_L2.t, f_L2=spec_L2.f, TFR_L2=spec_L2.TFR, t_L5=spec_L5.t, f_L5=spec_L5.f, TFR_L5=spec_L5.TFR, pgram_p=pgram.P, pgram_f=pgram.f) diff --git a/vislfp.py b/vislfp.py index 9b85c1e11..64b6a694a 100644 --- a/vislfp.py +++ b/vislfp.py @@ -111,7 +111,8 @@ def getCSD (dlfp,sampr,minf=0.1,maxf=300.0): print('Extracting Wavelet spectrogram(s).') for i in range(maxlfp+1): for trial in range(ntrial): - ddat['spec'][(trial,i)] = MorletSpec(tvec, ddat['lfp'][(trial,i)][:,1],None,None,waveprm,minwavet) + ddat['spec'][(trial,i)] = MorletSpec(tvec, ddat['lfp'][(trial,i)][:,1], None, + p_dict=waveprm, tmin=minwavet) if ntrial > 1: if debug: print('here') davglfp = {}; davgspec = {} diff --git a/vispsd.py b/vispsd.py index 269fce5dd..b23a8e8e4 100644 --- a/vispsd.py +++ b/vispsd.py @@ -56,7 +56,7 @@ def extractpsd (dat, fmax=120.0): tstop = tvec[-1] prm = {'f_max_spec':fmax,'dt':dt,'tstop':tstop} for col in range(1,dat.shape[1],1): - ms = MorletSpec(tvec,dat[:,col],None,None,prm) + ms = MorletSpec(tvec,dat[:,col],None,p_dict=prm) lpsd.append(np.mean(ms.TFR,axis=1)) return ms.f, np.array(lpsd) diff --git a/visspec.py b/visspec.py index 4100e150c..22cbc0e28 100644 --- a/visspec.py +++ b/visspec.py @@ -48,10 +48,10 @@ def extractspec (dat, fmax=40.0): if dat.shape[1] > 2: for col in range(1,dat.shape[1],1): - ms = MorletSpec(tvec,dat[:,col],None,None,prm) + ms = MorletSpec(tvec,dat[:,col],None,p_dict=prm) lspec.append(ms) else: - ms = MorletSpec(tvec,dat[:,1],None,None,prm) + ms = MorletSpec(tvec,dat[:,1],None,p_dict=prm) lspec.append(ms) ntrial = len(lspec) @@ -61,7 +61,7 @@ def extractspec (dat, fmax=40.0): else: avgdipole = dat[:,1] - avgspec = MorletSpec(tvec,avgdipole,None,None,prm) # !!should fix to average of individual spectrograms!! + avgspec = MorletSpec(tvec,avgdipole,None,p_dict=prm) # !!should fix to average of individual spectrograms!! ltfr = [ms.TFR for ms in lspec] npspec = np.array(ltfr) From f289bc415d3ff646b79000290435cd1979eb0f56 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 16 Sep 2020 10:19:26 -0400 Subject: [PATCH 002/107] MAINT: use hnn-core to reduce parameter file reads Known bug with loading parameter files with rythmic inputs. Something with displaying the spectrogram. Prep for hnn-core integration by first reducing the number of times a paramter file is opened. Instead pass the params dictionary where convenient. This commit replaces some calls to read functions in paramrw with hnn_core.read_params, but some calls remain. Parameter typing is done in hnn_core, use that when possible instead of adding checks in the code (note paramrw.py functions). --- hnn_qt5.py | 87 +++++++++++++++++++++------------- paramrw.py | 100 +++++++++++++++++++++++--------------- run.py | 2 +- simdat.py | 137 +++++++++++++++++++++++------------------------------ 4 files changed, 176 insertions(+), 150 deletions(-) diff --git a/hnn_qt5.py b/hnn_qt5.py index d3c801c91..2483e82bb 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -22,8 +22,8 @@ from math import ceil, isclose import spikefn import params_default -from paramrw import quickreadprm, usingOngoingInputs, countEvokedInputs, usingEvokedInputs, ExpParams -from paramrw import chunk_evinputs, get_inputs, trans_input, find_param, validate_param_file +from paramrw import usingOngoingInputs, countEvokedInputs, usingEvokedInputs, ExpParams +from paramrw import chunk_evinputs, get_inputs, trans_input, validate_param_file from simdat import SIMCanvas, getinputfiles, updatedat from gutils import setscalegeom, lowresdisplay, setscalegeomcenter, getmplDPI, getscreengeom import nlopt @@ -32,6 +32,8 @@ import traceback from collections import namedtuple +from hnn_core import read_params + prtime = False def isWindows (): @@ -512,7 +514,7 @@ def bringwintotop (win): # based on https://nikolak.com/pyqt-threading-tutorial/ class RunSimThread (QThread): - def __init__ (self,c,d,ntrial,ncore,waitsimwin,opt=False,baseparamwin=None,mainwin=None,onNSG=False): + def __init__ (self,c,d,ntrial,ncore,waitsimwin,paramf,opt=False,baseparamwin=None,mainwin=None,onNSG=False): QThread.__init__(self) self.c = c self.d = d @@ -521,6 +523,7 @@ def __init__ (self,c,d,ntrial,ncore,waitsimwin,opt=False,baseparamwin=None,mainw self.ntrial = ntrial self.ncore = ncore self.waitsimwin = waitsimwin + self.paramf = paramf self.opt = opt self.baseparamwin = baseparamwin self.mainwin = mainwin @@ -539,6 +542,8 @@ def __init__ (self,c,d,ntrial,ncore,waitsimwin,opt=False,baseparamwin=None,mainw self.lock = Lock() + self.params = read_params(paramf) + def updatewaitsimwin (self, txt): # print('RunSimThread updatewaitsimwin, txt=',txt) self.txtComm.tsig.emit(txt) @@ -606,7 +611,7 @@ def killproc (self): self.lock.release() def spawn_sim (self, simlength, banner=False): - global paramf, hyperthreading + global hyperthreading import simdat @@ -621,11 +626,11 @@ def spawn_sim (self, simlength, banner=False): nrniv_cmd = ' nrniv -python -mpi -nobanner ' if self.onNSG: - cmd = 'python nsgr.py ' + paramf + ' ' + str(self.ntrial) + ' 710.0' + cmd = 'python nsgr.py ' + self.paramf + ' ' + str(self.ntrial) + ' 710.0' elif not simlength is None: - cmd = mpicmd + str(self.ncore) + nrniv_cmd + simf + ' ' + paramf + ' ntrial ' + str(self.ntrial) + ' simlength ' + str(simlength) + cmd = mpicmd + str(self.ncore) + nrniv_cmd + simf + ' ' + self.paramf + ' ntrial ' + str(self.ntrial) + ' simlength ' + str(simlength) else: - cmd = mpicmd + str(self.ncore) + nrniv_cmd + simf + ' ' + paramf + ' ntrial ' + str(self.ntrial) + cmd = mpicmd + str(self.ncore) + nrniv_cmd + simf + ' ' + self.paramf + ' ntrial ' + str(self.ntrial) cmdargs = shlex.split(cmd,posix="win" not in sys.platform) # https://github.com/maebert/jrnl/issues/348 if debug: print("cmd:",cmd,"cmdargs:",cmdargs) if prtime: @@ -652,7 +657,7 @@ def get_proc_stream (self, stream, print_to_console=False): def runsim (self, is_opt=False, banner=True, simlength=None): import simdat - global defncore, paramf, hyperthreading + global defncore, hyperthreading self.lock.acquire() self.killed = False self.lock.release() @@ -700,13 +705,14 @@ def runsim (self, is_opt=False, banner=True, simlength=None): #rtime = cend - cstart #if debug: print('sim finished in %.3f s'%rtime) + # TODO: fix exception raising for updatesimdat try: - simdat.updatedat(paramf) + simdat.updatedat(self.params) except ValueError: - print("Warning: failed to load simulation results for %s" % paramf) + print("Warning: failed to load simulation results for %s" % self.paramf) if not is_opt and 'dpl' in simdat.ddat: - simdat.updatelsimdat(paramf,simdat.ddat['dpl']) # update lsimdat and its current sim index + simdat.updatelsimdat(self.paramf,simdat.ddat['dpl']) # update lsimdat and its current sim index def optmodel (self): @@ -716,7 +722,7 @@ def optmodel (self): need_initial_ddat = False # initialize RNG with seed from config - seed = int(find_param(paramf,'prng_seedcore_opt')) + seed = self.params['prng_seedcore_opt'] nlopt.srand(seed) # initial_ddat stores the initial fit (from "Run Simulation"). @@ -731,7 +737,7 @@ def optmodel (self): # save initial parameters file param_out = os.path.join(basedir,'before_opt.param') - shutil.copyfile(paramf, param_out) + shutil.copyfile(self.paramf, param_out) self.updatewaitsimwin('Optimizing model. . .') @@ -771,7 +777,7 @@ def optmodel (self): if need_initial_ddat: simdat.initial_ddat = deepcopy(simdat.ddat) - simdat.updateoptdat(paramf,simdat.ddat['dpl']) # update optdat with best from this step + simdat.updateoptdat(self.paramf,simdat.ddat['dpl']) # update optdat with best from this step # put best opt results into GUI and save to param file push_values = OrderedDict() @@ -786,8 +792,8 @@ def optmodel (self): # one final sim with the best parameters to update display self.runsim(is_opt=True, banner=False) - simdat.updatelsimdat(paramf,simdat.ddat['dpl']) # update lsimdat and its current sim index - simdat.updateoptdat(paramf,simdat.ddat['dpl']) # update optdat with the final best + simdat.updatelsimdat(self.paramf,simdat.ddat['dpl']) # update lsimdat and its current sim index + simdat.updateoptdat(self.paramf,simdat.ddat['dpl']) # update optdat with the final best # re-enable all the range sliders self.baseparamwin.optparamwin.toggleEnableUserFields(step, enable=True) @@ -798,7 +804,7 @@ def optmodel (self): def runOptStep (self, step): import simdat - global basedir, paramf + global basedir self.optsim = 0 self.minopterr = 1e9 @@ -867,7 +873,7 @@ def optrun (new_params, grad=0): fpopt.write(str(simdat.ddat['errtot'])+os.linesep) # write error # save copy param file - param_fname = os.path.basename(paramf) + param_fname = os.path.basename(self.paramf) curr_paramf = os.path.join(basedir, param_fname) # save params numbered by optsim @@ -3106,10 +3112,12 @@ def __init__ (self, parent, optrun_func): self.lsubwin = [self.runparamwin, self.cellparamwin, self.netparamwin, self.proxparamwin, self.distparamwin, self.evparamwin, self.poisparamwin, self.tonicparamwin, self.optparamwin] - self.updateDispParam() + self.params = self.updateDispParam() self.parent = parent def updateDispParam (self): + global paramf + # now update the GUI components to reflect the param file selected try: validate_param_file(paramf) @@ -3117,15 +3125,18 @@ def updateDispParam (self): QMessageBox.information(self, "HNN", "WARNING: could not retrieve parameters from %s" % paramf) return - din = quickreadprm(paramf) + params = read_params(paramf) - if usingEvokedInputs(din): # default for evoked is to show average dipole + if usingEvokedInputs(params): # default for evoked is to show average dipole conf.dconf['drawavgdpl'] = True - elif usingOngoingInputs(din): # default for ongoing is NOT to show average dipole + elif usingOngoingInputs(params): # default for ongoing is NOT to show average dipole conf.dconf['drawavgdpl'] = False - for dlg in self.lsubwin: dlg.setfromdin(din) # update to values from file - self.qle.setText(paramf.split(os.path.sep)[-1].split('.param')[0]) # update simulation name + for dlg in self.lsubwin: dlg.setfromdin(params) # update to values from file + self.qle.setText(params['sim_prefix']) # update simulation name + + # return the param dict for use by caller + return params def setrunparam (self): bringwintotop(self.runparamwin) def setcellparam (self): bringwintotop(self.cellparamwin) @@ -3332,7 +3343,7 @@ class HNNGUI (QMainWindow): # main HNN GUI class def __init__ (self): # initialize the main HNN GUI - global paramf, basedir + super().__init__() self.runningsim = False self.runthread = None @@ -3343,6 +3354,7 @@ def __init__ (self): self.schemwin = SchematicDialog(self) self.m = self.toolbar = None self.baseparamwin = BaseParamDialog(self, self.startoptmodel) + self.params = None self.optMode = False self.initUI() self.visnetwin = VisnetDialog(self) @@ -3439,7 +3451,7 @@ def selParamFileDialog (self): return # now update the GUI components to reflect the param file selected - self.baseparamwin.updateDispParam() + self.params = self.baseparamwin.updateDispParam() self.initSimCanvas() # recreate canvas # self.m.plot() # replot data self.setWindowTitle(paramf) @@ -3644,7 +3656,7 @@ def updateDatCanv (self,fn): except: pass # now update the GUI components to reflect the param file selected - self.baseparamwin.updateDispParam() + self.params = self.baseparamwin.updateDispParam() self.initSimCanvas() # recreate canvas self.setWindowTitle(fn) @@ -3702,10 +3714,13 @@ def nextSim (self): def clearSimulationData (self): # clear the simulation data - global paramf import simdat + + global paramf, basedir paramf = '' # set paramf to empty so no data gets loaded basedir = None + + self.params = None simdat.ddat = {} # clear data in simdat.ddat simdat.lsimdat = [] simdat.lsimidx = 0 @@ -4002,6 +4017,9 @@ def addParamImageButtons (self,gRow): def initUI (self): # initialize the user interface (UI) + # default paramf + global paramf + self.initMenu() self.statusBar() @@ -4025,6 +4043,8 @@ def initUI (self): gRow += 1 + if paramf and self.params is None: + self.params = read_params(paramf) self.initSimCanvas(gRow=gRow, reInit=False) gRow += 2 @@ -4093,15 +4113,14 @@ def populateSimCB (self): def initSimCanvas (self,recalcErr=True,optMode=False,gRow=1,reInit=True): # initialize the simulation canvas, loading any required data - + global paramf gCol = 0 if reInit == True: self.grid.itemAtPosition(gRow, gCol).widget().deleteLater() self.grid.itemAtPosition(gRow + 1, gCol).widget().deleteLater() - if debug: print('paramf in initSimCanvas:',paramf) - self.m = SIMCanvas(paramf, parent = self, width=10, height=1, dpi=getmplDPI(), optMode=optMode) # also loads data + self.m = SIMCanvas(paramf, self.params, parent = self, width=10, height=1, dpi=getmplDPI(), optMode=optMode) # also loads data # this is the Navigation widget # it takes the Canvas widget and a parent self.toolbar = NavigationToolbar(self.m, self) @@ -4167,6 +4186,8 @@ def stopsim (self): self.setcursors(Qt.ArrowCursor) def optmodel (self, ntrial, ncore): + global paramf + # make sure params saved and ok to run if not self.baseparamwin.saveparams(): return @@ -4185,7 +4206,7 @@ def optmodel (self, ntrial, ncore): self.statusBar().showMessage("Optimizing model. . .") - self.runthread = RunSimThread(self.c, self.d, ntrial, ncore, self.waitsimwin, opt=True, baseparamwin=self.baseparamwin, mainwin=self, onNSG=False) + self.runthread = RunSimThread(self.c, self.d, ntrial, ncore, self.waitsimwin, paramf, opt=True, baseparamwin=self.baseparamwin, mainwin=self, onNSG=False) # We have all the events we need connected we can start the thread self.runthread.start() @@ -4209,7 +4230,7 @@ def startsim (self, ntrial, ncore, onNSG=False): else: self.statusBar().showMessage("Running simulation. . .") - self.runthread=RunSimThread(self.c,self.d,ntrial,ncore,self.waitsimwin,opt=False,baseparamwin=None,mainwin=None,onNSG=onNSG) + self.runthread=RunSimThread(self.c,self.d,ntrial,ncore,self.waitsimwin,paramf,opt=False,baseparamwin=None,mainwin=None,onNSG=onNSG) # We have all the events we need connected we can start the thread self.runthread.start() diff --git a/paramrw.py b/paramrw.py index 3d1556705..4f812fde9 100644 --- a/paramrw.py +++ b/paramrw.py @@ -11,6 +11,8 @@ # from cartesian import cartesian from params_default import get_params_default +from hnn_core import read_params + # get dict of ':' separated params from fn; ignore lines starting with # def quickreadprm (fn): d = {} @@ -54,20 +56,26 @@ def quickgetprm (fn,k,ty): return ty(d[k]) # check if using ongoing inputs -def usingOngoingInputs (d, lty = ['_prox', '_dist']): - if type(d)==str: d = quickreadprm(d) - tstop = float(d['tstop']) +def usingOngoingInputs (params, lty = ['_prox', '_dist']): + if params is None: + return False + + try: + tstop = float(params['tstop']) + except KeyError: + return False + dpref = {'_prox':'input_prox_A_','_dist':'input_dist_A_'} try: for postfix in lty: - if float(d['t0_input'+postfix])<= tstop and \ - float(d['tstop_input'+postfix])>=float(d['t0_input'+postfix]) and \ - float(d['f_input'+postfix])>0.: + if float(params['t0_input'+postfix])<= tstop and \ + float(params['tstop_input'+postfix])>=float(params['t0_input'+postfix]) and \ + float(params['f_input'+postfix])>0.: for k in ['weight_L2Pyr_ampa','weight_L2Pyr_nmda',\ 'weight_L5Pyr_ampa','weight_L5Pyr_nmda',\ 'weight_inh_ampa','weight_inh_nmda']: - if float(d[dpref[postfix]+k])>0.: - #print('usingOngoingInputs:',d[dpref[postfix]+k]) + if float(params[dpref[postfix]+k])>0.: + # print('usingOngoingInputs:',params[dpref[postfix]+k]) return True except: return False @@ -75,22 +83,28 @@ def usingOngoingInputs (d, lty = ['_prox', '_dist']): # return number of evoked inputs (proximal, distal) # using dictionary d (or if d is a string, first load the dictionary from filename d) -def countEvokedInputs (d): - if type(d) == str: d = quickreadprm(d) +def countEvokedInputs (params): nprox = ndist = 0 - for k,v in d.items(): - if k.startswith('t_'): - if k.count('evprox') > 0: - nprox += 1 - elif k.count('evdist') > 0: - ndist += 1 + if params is not None: + for k,v in params.items(): + if k.startswith('t_'): + if k.count('evprox') > 0: + nprox += 1 + elif k.count('evdist') > 0: + ndist += 1 return nprox, ndist # check if using any evoked inputs -def usingEvokedInputs (d, lsuffty = ['_evprox_', '_evdist_']): - if type(d) == str: d = quickreadprm(d) - nprox,ndist = countEvokedInputs(d) - tstop = float(d['tstop']) +def usingEvokedInputs (params, lsuffty = ['_evprox_', '_evdist_']): + nprox,ndist = countEvokedInputs(params) + if nprox == 0 and ndist == 0: + return False + + try: + tstop = float(params['tstop']) + except KeyError: + return False + lsuff = [] if '_evprox_' in lsuffty: for i in range(1,nprox+1,1): lsuff.append('_evprox_'+str(i)) @@ -98,34 +112,45 @@ def usingEvokedInputs (d, lsuffty = ['_evprox_', '_evdist_']): for i in range(1,ndist+1,1): lsuff.append('_evdist_'+str(i)) for suff in lsuff: k = 't' + suff - if k not in d: continue - if float(d[k]) > tstop: continue + if k not in params: continue + if float(params[k]) > tstop: continue k = 'gbar' + suff - for k1 in d.keys(): + for k1 in params.keys(): if k1.startswith(k): - if float(d[k1]) > 0.0: return True + if float(params[k1]) > 0.0: return True return False # check if using any poisson inputs -def usingPoissonInputs (d): - if type(d)==str: d = quickreadprm(d) - tstop = float(d['tstop']) - if 't0_pois' in d and 'T_pois' in d: - t0_pois = float(d['t0_pois']) - if t0_pois > tstop: return False - T_pois = float(d['T_pois']) - if t0_pois > T_pois and T_pois != -1.0: - return False +def usingPoissonInputs (params): + if params is None: + return False + + try: + tstop = float(params['tstop']) + + if 't0_pois' in params and 'T_pois' in params: + t0_pois = float(params['t0_pois']) + if t0_pois > tstop: return False + T_pois = float(params['T_pois']) + if t0_pois > T_pois and T_pois != -1.0: + return False + except KeyError: + return False + for cty in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: for sy in ['ampa','nmda']: k = cty+'_Pois_A_weight_'+sy - if k in d: - if float(d[k]) != 0.0: return True + if k in params: + if float(params[k]) != 0.0: + return True + return False # check if using any tonic (IClamp) inputs def usingTonicInputs (d): - if type(d)==str: d = quickreadprm(d) + if d is None: + return False + tstop = float(d['tstop']) for cty in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: k = 'Itonic_A_' + cty + '_soma' @@ -148,6 +173,7 @@ class ExpParams(): def __init__ (self, f_psim, debug=False): self.debug = debug + self.paramf = f_psim self.expmt_group_params = [] @@ -185,7 +211,7 @@ def return_pdict(self, expmt_group, i): p_sim[param] = val_list[i] # go through the expmt group-based params - for param, val in self.p_group[expmt_group].items(): + for param, val in read_params(self.paramf).items(): p_sim[param] = val # add alpha distributions. A bit hack-y diff --git a/run.py b/run.py index 6e390839e..f9ba2c6f0 100755 --- a/run.py +++ b/run.py @@ -447,7 +447,7 @@ def runsim (): if pcID == 0: if debug: print("Simulation run time: %4.4f s" % (time.time()-t0)) if debug: print("Simulation directory is: %s" % ddir.dsim) - if paramrw.find_param(doutf['file_param'],'save_spec_data') or usingOngoingInputs(doutf['file_param']): + if paramrw.find_param(doutf['file_param'],'save_spec_data') or usingOngoingInputs(p): runanalysis(p, doutf['file_param'], doutf['file_dpl_norm'], doutf['file_spec']) # run spectral analysis if paramrw.find_param(doutf['file_param'],'save_figs'): savefigs(ddir,p,p_exp) # save output figures diff --git a/simdat.py b/simdat.py index c8fccdd3f..153c87e0c 100644 --- a/simdat.py +++ b/simdat.py @@ -10,7 +10,7 @@ from conf import dconf import conf import spikefn -from paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs, usingTonicInputs, find_param, quickgetprm, countEvokedInputs, ExpParams +from paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs, usingTonicInputs, countEvokedInputs, ExpParams from scipy import signal from gutils import getscreengeom @@ -71,11 +71,11 @@ def readdpltrials (basedir,ntrial): return ldpl -def getinputfiles (paramf): +def getinputfiles (sim_prefix): # get a dictionary of input files based on simulation parameter file paramf global dfile,basedir dfile = {} - basedir = os.path.join(dconf['datdir'],paramf.split(os.path.sep)[-1].split('.param')[0]) + basedir = os.path.join(dconf['datdir'], sim_prefix) # print('basedir:',basedir) dfile['dpl'] = os.path.join(basedir,'dpl.txt') dfile['spec'] = os.path.join(basedir,'rawspec.npz') @@ -97,12 +97,11 @@ def readtxt (fn, silent=False): return contents -def updatedat (paramf): +def updatedat (params): # update data dictionary (ddat) from the param file global basedir - if debug: print('paramf:',paramf) - getinputfiles(paramf) + getinputfiles(params['sim_prefix']) for k in ['dpl','spk']: if k in ddat: @@ -115,24 +114,13 @@ def updatedat (paramf): if not 'dpl' in ddat or not 'spk' in ddat: raise ValueError - ddat['dpltrials'] = readdpltrials(basedir,quickgetprm(paramf,'N_trials',int)) + ddat['dpltrials'] = readdpltrials(basedir,params['N_trials']) if os.path.isfile(dfile['spec']): ddat['spec'] = np.load(dfile['spec']) else: ddat['spec'] = None -def getscalefctr (paramf): - # get dipole scaling factor parameter value from paramf file - try: - xx = quickgetprm(paramf,'dipole_scalefctr',float) - if type(xx) == float: return xx - except: - pass - if 'dipole_scalefctr' in dconf: - return dconf['dipole_scalefctr'] - return 30e3 - def drawraster (): # draw raster to standalone matplotlib figure - for debugging (not used in main HNN GUI) if 'spk' in ddat: @@ -253,7 +241,7 @@ class SIMCanvas (FigureCanvas): # matplotlib/pyqt-compatible canvas for drawing simulation & external data # based on https://pythonspot.com/en/pyqt5-matplotlib/ - def __init__ (self, paramf, parent=None, width=5, height=4, dpi=40, optMode=False, title='Simulation Viewer'): + def __init__ (self, paramf, params, parent=None, width=5, height=4, dpi=40, optMode=False, title='Simulation Viewer'): FigureCanvas.__init__(self, Figure(figsize=(width, height), dpi=dpi)) self.title = title @@ -265,6 +253,7 @@ def __init__ (self, paramf, parent=None, width=5, height=4, dpi=40, optMode=Fals FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) self.paramf = paramf + self.params = params self.initaxes() self.G = gridspec.GridSpec(10,1) @@ -288,8 +277,11 @@ def plotinputhist (self, xl, dinty): extinputs = None plot_distribs = False - sim_tstop = quickgetprm(self.paramf,'tstop',float) - sim_dt = quickgetprm(self.paramf,'dt',float) + if self.params is None: + raise ValueError("No valid params found") + + sim_tstop = self.params['tstop'] + sim_dt = self.params['tstop'] num_step = ceil(sim_tstop / sim_dt) + 1 times = np.linspace(0, sim_tstop, num_step) @@ -392,35 +384,14 @@ def clearaxes (self): if ax: ax.cla() - - def getNTrials (self): - # get the number of trials - N_trials = 1 - try: - xx = quickgetprm(self.paramf,'N_trials',int) - if type(xx) == int: N_trials = xx - except: - pass - return N_trials - - def getNPyr (self): - # get the number of pyramidal neurons used in the simulation - try: - x = quickgetprm(self.paramf,'N_pyr_x',int) - y = quickgetprm(self.paramf,'N_pyr_y',int) - if type(x)==int and type(y)==int: - return int(x * y * 2) - except: - return 0 - def getInputDistrib (self): import scipy.stats as stats dinput = {'evprox': [], 'evdist': [], 'prox': [], 'dist': [], 'pois': []} try: - sim_tstop = quickgetprm(self.paramf,'tstop',float) - sim_dt = quickgetprm(self.paramf,'dt',float) - except FileNotFoundError: + sim_tstop = self.params['tstop'] + sim_dt = self.params['dt'] + except KeyError: return dinput num_step = ceil(sim_tstop / sim_dt) + 1 @@ -436,15 +407,19 @@ def getInputDistrib (self): def getEVInputTimes (self): # get the evoked input times - nprox, ndist = countEvokedInputs(self.paramf) + + if self.params is None: + raise ValueError("No valid params found") + + nprox, ndist = countEvokedInputs(self.params) ltprox, ltdist = [], [] for i in range(nprox): - input_mu = quickgetprm(self.paramf,'t_evprox_' + str(i+1), float) - input_sigma = quickgetprm(self.paramf,'sigma_t_evprox_' + str(i+1), float) + input_mu = self.params['t_evprox_' + str(i+1)] + input_sigma = self.params['sigma_t_evprox_' + str(i+1)] ltprox.append((input_mu, input_sigma)) for i in range(ndist): - input_mu = quickgetprm(self.paramf,'t_evdist_' + str(i+1), float) - input_sigma = quickgetprm(self.paramf,'sigma_t_evdist_' + str(i+1), float) + input_mu = self.params['t_evdist_' + str(i+1)] + input_sigma = self.params['sigma_t_evdist_' + str(i+1)] ltdist.append((input_mu, input_sigma)) return ltprox, ltdist @@ -464,17 +439,14 @@ def getInputs (self): dinty = {'Evoked':False,'Ongoing':False,'Poisson':False,'Tonic':False,'EvokedDist':False,\ 'EvokedProx':False,'OngoingDist':False,'OngoingProx':False} - try: - dinty['Evoked'] = usingEvokedInputs(self.paramf) - dinty['EvokedDist'] = usingEvokedInputs(self.paramf, lsuffty = ['_evdist_']) - dinty['EvokedProx'] = usingEvokedInputs(self.paramf, lsuffty = ['_evprox_']) - dinty['Ongoing'] = usingOngoingInputs(self.paramf) - dinty['OngoingDist'] = usingOngoingInputs(self.paramf, lty = ['_dist']) - dinty['OngoingProx'] = usingOngoingInputs(self.paramf, lty = ['_prox']) - dinty['Poisson'] = usingPoissonInputs(self.paramf) - dinty['Tonic'] = usingTonicInputs(self.paramf) - except FileNotFoundError: - pass + dinty['Evoked'] = usingEvokedInputs(self.params) + dinty['EvokedDist'] = usingEvokedInputs(self.params, lsuffty = ['_evdist_']) + dinty['EvokedProx'] = usingEvokedInputs(self.params, lsuffty = ['_evprox_']) + dinty['Ongoing'] = usingOngoingInputs(self.params) + dinty['OngoingDist'] = usingOngoingInputs(self.params, lty = ['_dist']) + dinty['OngoingProx'] = usingOngoingInputs(self.params, lty = ['_prox']) + dinty['Poisson'] = usingPoissonInputs(self.params) + dinty['Tonic'] = usingTonicInputs(self.params) return dinty @@ -595,11 +567,12 @@ def clearlextdatobj (self): def plotsimdat (self): # plot the simulation data + failed_loading = False self.gRow = 0 bottom = 0.0 only_create_axes = False - if not os.path.isfile(self.paramf): + if self.params is None: only_create_axes = True DrawSpec = False xl = (0.0, 1.0) @@ -609,22 +582,20 @@ def plotsimdat (self): # try loading data. ignore failures try: - updatedat(self.paramf) - loaded_dat = True - except ValueError: - loaded_dat = False - pass + updatedat(self.params) + except IOError: + failed_loading = True - xl = (0.0, quickgetprm(self.paramf,'tstop',float)) + xl = (0.0, self.params['tstop']) if dinty['Ongoing'] or dinty['Evoked'] or dinty['Poisson']: xo = self.plotinputhist(xl, dinty) if xo: self.gRow = xo[1] # whether to draw the specgram - should draw if user saved it or have ongoing, poisson, or tonic inputs - DrawSpec = loaded_dat and \ + DrawSpec = (not failed_loading) and \ 'spec' in ddat and \ - (find_param(dfile['outparam'],'save_spec_data') or dinty['Ongoing'] or dinty['Poisson'] or dinty['Tonic']) + (self.params['save_spec_data'] or dinty['Ongoing'] or dinty['Poisson'] or dinty['Tonic']) if DrawSpec: # dipole axis takes fewer rows if also drawing specgram self.axdipole = self.figure.add_subplot(self.G[self.gRow:5,0]) # dipole @@ -641,11 +612,13 @@ def plotsimdat (self): if w < 2800: left = 0.1 self.figure.subplots_adjust(left=left,right=0.99,bottom=bottom,top=0.99,hspace=0.1,wspace=0.1) # reduce padding - if only_create_axes: + if failed_loading or only_create_axes: return try: - updatedat(self.paramf) + updatedat(self.params) + except IOError: + return except ValueError: if 'dpl' not in ddat: # failed to load dipole data, nothing more to plot @@ -666,9 +639,6 @@ def plotsimdat (self): sampr = 1e3/dt # dipole sampling rate sidx, eidx = int(sampr*xl[0]/1e3), int(sampr*xl[1]/1e3) # use these indices to find dipole min,max - N_trials = self.getNTrials() - if debug: print('simdat: N_trials:',N_trials) - yl = [0,0] yl[0] = min(yl[0],np.amin(ddat['dpl'][sidx:eidx,1])) yl[1] = max(yl[1],np.amax(ddat['dpl'][sidx:eidx,1])) @@ -680,13 +650,13 @@ def plotsimdat (self): if debug: print('olddpl has shape ',olddpl.shape,len(olddpl[:,0]),len(olddpl[:,1])) self.axdipole.plot(olddpl[:,0],olddpl[:,1],'--',color='black',linewidth=self.gui.linewidth) - if N_trials>1 and dconf['drawindivdpl'] and len(ddat['dpltrials']) > 0: # plot dipoles from individual trials + if self.params['N_trials']>1 and dconf['drawindivdpl'] and len(ddat['dpltrials']) > 0: # plot dipoles from individual trials for dpltrial in ddat['dpltrials']: self.axdipole.plot(dpltrial[:,0],dpltrial[:,1],color='gray',linewidth=self.gui.linewidth) yl[0] = min(yl[0],dpltrial[sidx:eidx,1].min()) yl[1] = max(yl[1],dpltrial[sidx:eidx,1].max()) - if conf.dconf['drawavgdpl'] or N_trials <= 1: + if conf.dconf['drawavgdpl'] or self.params['N_trials'] <= 1: # this is the average dipole (across trials) # it's also the ONLY dipole when running a single trial self.axdipole.plot(ddat['dpl'][:,0],ddat['dpl'][:,1],'k',linewidth=self.gui.linewidth+1) @@ -707,8 +677,17 @@ def plotsimdat (self): yl[0] = min(yl[0],initial_ddat['dpl'][sidx:eidx,1].min()) yl[1] = max(yl[1],initial_ddat['dpl'][sidx:eidx,1].max()) - scalefctr = getscalefctr(self.paramf) - NEstPyr = int(self.getNPyr() * scalefctr) + scalefctr = float(self.params['dipole_scalefctr']) + + # get the number of pyramidal neurons used in the simulation + try: + x = self.params['N_pyr_x'] + y = self.params['N_pyr_y'] + num_pyr = int(x * y * 2) + except KeyError: + num_pyr = 0 + + NEstPyr = int(num_pyr * scalefctr) if NEstPyr > 0: self.axdipole.set_ylabel(r'Dipole (nAm $\times$ '+str(scalefctr)+')\nFrom Estimated '+str(NEstPyr)+' Cells',fontsize=dconf['fontsize']) From c2abd07cd9bd5a37071007273ca249f0c0e3299e Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 16 Sep 2020 11:39:03 -0400 Subject: [PATCH 003/107] MAINT: infer number of trials in readdpltrials Doesn't require passing a parameter anymore. Also, the number of data files may not match the number of trials in the configuration (e.g. failure on previous simulation) --- simdat.py | 21 ++++++++++++--------- visdipole.py | 2 +- visspec.py | 3 ++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/simdat.py b/simdat.py index 153c87e0c..a6b5b776f 100644 --- a/simdat.py +++ b/simdat.py @@ -56,18 +56,21 @@ def rmse (a1, a2): if debug: print('len1:',len1,'len2:',len2,'ty1:',type(a1),'ty2:',type(a2)) return np.sqrt(((a1[0:sz] - a2[0:sz]) ** 2).mean()) -def readdpltrials (basedir,ntrial): +def readdpltrials(basedir): # read dipole data files for individual trials - if debug: print('in readdpltrials',basedir,ntrial) + if debug: print('in readdpltrials',basedir) ldpl = [] - for i in range(ntrial): + + i = 0 + while True: fn = os.path.join(basedir,'dpl_'+str(i)+'.txt') - if not os.path.exists(fn): break - ldpl.append(readtxt(fn)) - if debug: print('loaded ', fn) + if not os.path.exists(fn): + break + + ldpl.append(np.loadtxt(fn)) - if len(ldpl) < ntrial and ntrial > 1: - print("Warning: only read data for %d trials" % len(ldpl)) + # try reading another trial + i += 1 return ldpl @@ -114,7 +117,7 @@ def updatedat (params): if not 'dpl' in ddat or not 'spk' in ddat: raise ValueError - ddat['dpltrials'] = readdpltrials(basedir,params['N_trials']) + ddat['dpltrials'] = readdpltrials(basedir) if os.path.isfile(dfile['spec']): ddat['spec'] = np.load(dfile['spec']) diff --git a/visdipole.py b/visdipole.py index dad52ea0d..9992af1f3 100644 --- a/visdipole.py +++ b/visdipole.py @@ -41,7 +41,7 @@ basedir = os.path.join(dconf['datdir'],paramf.split(os.path.sep)[-1].split('.param')[0]) ddat = {} -ddat['dpltrials'] = readdpltrials(basedir,ntrial) +ddat['dpltrials'] = readdpltrials(basedir) try: ddat['dpl'] = np.loadtxt(os.path.join(basedir,'dpl.txt')) except: diff --git a/visspec.py b/visspec.py index 22cbc0e28..ffbd2b06b 100644 --- a/visspec.py +++ b/visspec.py @@ -69,6 +69,7 @@ def extractspec (dat, fmax=40.0): return ms.f, lspec, avgdipole, avgspec +# TODO: can we import this from simdat instead? def loaddat (fname): try: if fname.endswith('.txt'): @@ -81,7 +82,7 @@ def loaddat (fname): #simdat.updatedat(paramf) #return paramf,simdat.ddat if ntrial > 1: - ddat = readdpltrials(basedir,quickgetprm(paramf,'N_trials',int)) + ddat = readdpltrials(basedir) #print('read dpl trials',ddat[0].shape) dout = np.zeros((ddat[0].shape[0],1+ntrial)) #print('set dout shape',dout.shape) From a624e6f33fb1e44548dd01dfbaff66bfe27a23f0 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 16 Sep 2020 13:43:15 -0400 Subject: [PATCH 004/107] ENH: run simulations with hnn-core Changes to run.py to call hnn_core.simulate_dipole using MPIBackend. Updated hnn_qt5.py to not fork a mpiexec process, but instead call simulatio() within run.py --- hnn_qt5.py | 151 ++++++----------- run.py | 488 ++++++++++------------------------------------------- 2 files changed, 138 insertions(+), 501 deletions(-) diff --git a/hnn_qt5.py b/hnn_qt5.py index 2483e82bb..59280abb8 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -18,6 +18,8 @@ from time import time, sleep from conf import dconf import conf +from run import simulate + import numpy as np from math import ceil, isclose import spikefn @@ -70,21 +72,21 @@ def parseargs (): param_fname = os.path.splitext(os.path.basename(paramf)) basedir = os.path.join(dconf['datdir'], param_fname[0]) -# get default number of cores -defncore = 0 -hyperthreading=False +def get_defncore(): + """ get default number of cores """ -try: - defncore = len(os.sched_getaffinity(0)) -except AttributeError: - physical_cores = cpu_count(logical=False) - logical_cores = multiprocessing.cpu_count() + try: + defncore = len(os.sched_getaffinity(0)) + except AttributeError: + defncore = cpu_count(logical=False) - if logical_cores is not None and logical_cores > physical_cores: - hyperthreading=True - defncore = logical_cores - else: - defncore = physical_cores + if defncore is None or defncore == 0: + # in case psutil is not supported (e.g. BSD) + defncore = multiprocessing.cpu_count() + + return defncore + +defncore = get_defncore() if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] else: plt.rcParams['font.size'] = dconf['fontsize'] = 10 @@ -519,7 +521,6 @@ def __init__ (self,c,d,ntrial,ncore,waitsimwin,paramf,opt=False,baseparamwin=Non self.c = c self.d = d self.killed = False - self.proc = None self.ntrial = ntrial self.ncore = ncore self.waitsimwin = waitsimwin @@ -542,7 +543,7 @@ def __init__ (self,c,d,ntrial,ncore,waitsimwin,paramf,opt=False,baseparamwin=Non self.lock = Lock() - self.params = read_params(paramf) + self.params = read_params(self.paramf) def updatewaitsimwin (self, txt): # print('RunSimThread updatewaitsimwin, txt=',txt) @@ -586,23 +587,8 @@ def run (self): def killproc (self): - if self.proc is None: - # any nrniv processes found are not part of current sim - return - if debug: print('Thread killing sim. . .') - # try the nice way to stop the mpiexec proc - self.proc.terminate() - - retries = 0 - while self.proc.poll() is None and retries < 5: - # mpiexec still running - self.proc.kill() - if self.proc.poll() is None: - sleep(1) - retries += 1 - # make absolute sure all nrniv procs have been killed kill_and_check_nrniv_procs() @@ -610,34 +596,6 @@ def killproc (self): self.killed = True self.lock.release() - def spawn_sim (self, simlength, banner=False): - global hyperthreading - import simdat - - - if isWindows() or not hyperthreading: - mpicmd = 'mpiexec -np ' - else: - mpicmd = 'mpiexec --use-hwthread-cpus -np ' - - if banner: - nrniv_cmd = ' nrniv -python -mpi ' - else: - nrniv_cmd = ' nrniv -python -mpi -nobanner ' - - if self.onNSG: - cmd = 'python nsgr.py ' + self.paramf + ' ' + str(self.ntrial) + ' 710.0' - elif not simlength is None: - cmd = mpicmd + str(self.ncore) + nrniv_cmd + simf + ' ' + self.paramf + ' ntrial ' + str(self.ntrial) + ' simlength ' + str(simlength) - else: - cmd = mpicmd + str(self.ncore) + nrniv_cmd + simf + ' ' + self.paramf + ' ntrial ' + str(self.ntrial) - cmdargs = shlex.split(cmd,posix="win" not in sys.platform) # https://github.com/maebert/jrnl/issues/348 - if debug: print("cmd:",cmd,"cmdargs:",cmdargs) - if prtime: - self.proc = Popen(cmdargs,cwd=os.getcwd()) - else: - self.proc = Popen(cmdargs,stdout=PIPE,stderr=PIPE,cwd=os.getcwd(),universal_newlines=True) - def get_proc_stream (self, stream, print_to_console=False): try: for line in iter(stream.readline, ""): @@ -656,63 +614,52 @@ def get_proc_stream (self, stream, print_to_console=False): # run sim command via mpi, then delete the temp file. def runsim (self, is_opt=False, banner=True, simlength=None): import simdat + global defncore - global defncore, hyperthreading self.lock.acquire() self.killed = False self.lock.release() - self.spawn_sim(simlength, banner=banner) - retried = False - - #cstart = time() while True: - status = self.proc.poll() - if not status is None: - if status == 0: - # success, use same number of cores next time - defncore = self.ncore - break - elif status == 1 and not retried: - self.ncore = ceil(self.ncore/2) - txt = "INFO: Failed starting mpiexec, retrying with %d cores" % self.ncore - print(txt) - self.updatewaitsimwin(txt) - self.spawn_sim(simlength, banner=banner) - retried = True - else: - txt = "Simulation exited with return code %d. Stderr from console:"%status + if self.ncore == 0: + raise RuntimeError("No cores available for simulation") + + try: + simulate(self.params, self.ncore) + except RuntimeError: + if self.ncore == 1: + # can't reduce ncore any more + txt = "Simulation failed to start" print(txt) self.updatewaitsimwin(txt) - self.get_proc_stream(self.proc.stderr, print_to_console=True) kill_and_check_nrniv_procs() raise RuntimeError - self.get_proc_stream(self.proc.stdout, print_to_console=False) - - # check if proc was killed - self.lock.acquire() - if self.killed: - self.lock.release() - # exit using RuntimeError - raise RuntimeError - else: - self.lock.release() + self.ncore = ceil(self.ncore/2) + txt = "INFO: Failed starting simulation, retrying with %d cores" % self.ncore + print(txt) + self.updatewaitsimwin(txt) - sleep(1) + # success + defncore = self.ncore + break - #cend = time() - #rtime = cend - cstart - #if debug: print('sim finished in %.3f s'%rtime) + # check if proc was killed + self.lock.acquire() + if self.killed: + self.lock.release() + # exit using RuntimeError + raise RuntimeError + else: + self.lock.release() - # TODO: fix exception raising for updatesimdat try: - simdat.updatedat(self.params) - except ValueError: - print("Warning: failed to load simulation results for %s" % self.paramf) + updatedat(self.params) + except IOError: + print('WARN: could not read simulation output for %s' % self.params['sim_prefix']) - if not is_opt and 'dpl' in simdat.ddat: - simdat.updatelsimdat(self.paramf,simdat.ddat['dpl']) # update lsimdat and its current sim index + if not is_opt: + simdat.updatelsimdat(self.paramf, simdat.ddat['dpl']) # update lsimdat and its current sim index def optmodel (self): @@ -4043,8 +3990,6 @@ def initUI (self): gRow += 1 - if paramf and self.params is None: - self.params = read_params(paramf) self.initSimCanvas(gRow=gRow, reInit=False) gRow += 2 @@ -4120,6 +4065,10 @@ def initSimCanvas (self,recalcErr=True,optMode=False,gRow=1,reInit=True): self.grid.itemAtPosition(gRow, gCol).widget().deleteLater() self.grid.itemAtPosition(gRow + 1, gCol).widget().deleteLater() + # if just initialized or after clearSimulationData, self.params will be empty + if paramf and self.params is None: + self.params = read_params(paramf) + self.m = SIMCanvas(paramf, self.params, parent = self, width=10, height=1, dpi=getmplDPI(), optMode=optMode) # also loads data # this is the Navigation widget # it takes the Canvas widget and a parent diff --git a/run.py b/run.py index f9ba2c6f0..3005c3739 100755 --- a/run.py +++ b/run.py @@ -11,178 +11,50 @@ import time import shutil import numpy as np -from neuron import h -h.load_file("stdrun.hoc") # Cells are defined in other files -import network -import fileio as fio -import paramrw as paramrw -from paramrw import usingOngoingInputs -import plotfn as plotfn +from paramrw import usingOngoingInputs import specfn as specfn -import pickle -from dipolefn import Dipole +#import pickle +import datetime from conf import readconf from L5_pyramidal import L5Pyr from L2_pyramidal import L2Pyr from L2_basket import L2Basket from L5_basket import L5Basket -from lfp import LFPElectrode -from morphology import shapeplot, getshapecoords -import traceback -dconf = readconf() - -# data directory - ./data -dproj = dconf['datdir'] # fio.return_data_dir(dconf['datdir']) -debug = dconf['debug'] -pc = h.ParallelContext() -pcID = int(pc.id()) -f_psim = '' -ntrial = 1 -simlength = 0.0 -testLFP = dconf['testlfp']; -testlaminarLFP = dconf['testlaminarlfp'] -lelec = [] # list of LFP electrodes - -# reads the specified param file -foundprm = False -for i in range(len(sys.argv)): - if sys.argv[i].endswith('.param'): - f_psim = sys.argv[i] - foundprm = True - if pcID==0 and debug: print('using ',f_psim,' param file.') - elif sys.argv[i] == 'ntrial' and i+1 0: - expmt_group = p_exp.expmt_groups[0] -else: - expmt_group = None -simparams = p = p_exp.return_pdict(expmt_group, 0) # return the param dict for this simulation - -pc.barrier() # get all nodes to this place before continuing -pc.gid_clear() - -# global variables, should be node-independent -h("dp_total_L2 = 0."); h("dp_total_L5 = 0.") - -# Set tstop before instantiating any classes -if simlength > 0.0: - h.tstop = simlength -else: - h.tstop = p['tstop'] # simulation duration - -h.dt = p['dt'] # simulation time-step -h.celsius = p['celsius'] # 37.0 # p['celsius'] # set temperature -# spike file needs to be known by all nodes -file_spikes_tmp = fio.file_spike_tmp(dproj) -net = network.NetworkOnNode(p) # create node-specific network - -t_vec = h.Vector(); t_vec.record(h._ref_t) # time recording -dp_rec_L2 = h.Vector(); dp_rec_L2.record(h._ref_dp_total_L2) # L2 dipole recording -dp_rec_L5 = h.Vector(); dp_rec_L5.record(h._ref_dp_total_L5) # L5 dipole recording - -net.movecellstopos() # position cells in 2D grid - def expandbbox (boxA, boxB): return [(min(boxA[i][0],boxB[i][0]),max(boxA[i][1],boxB[i][1])) for i in range(3)] -def arrangelayers (): +def arrangelayers (net): + # NOTE: will not work with hnn-core as-is. this code modifies NetworkBuilder attributes + # offsets for L2, L5 cells so that L5 below L2 in display dyoff = {L2Pyr: 1000, 'L2_pyramidal' : 1000, L5Pyr: -1000-149.39990234375, 'L5_pyramidal' : -1000-149.39990234375, L2Basket: 1000, 'L2_basket' : 1000, L5Basket: -1000-149.39990234375, 'L5_basket' : -1000-149.39990234375} for cell in net.cells: cell.translate3d(0,dyoff[cell.celltype],0) - dcheck = {x:False for x in dyoff.keys()} dbbox = {x:[[1e9,-1e9],[1e9,-1e9],[1e9,-1e9]] for x in dyoff.keys()} for cell in net.cells: - dbbox[cell.celltype] = expandbbox(dbbox[cell.celltype], cell.getbbox()) - #if dcheck[cell.celltype]: continue - """ - bbox = cell.getbbox() - lx,ly,lz = getshapecoords(h,cell.get_sections()) - if cell.celltype == L2Pyr or cell.celltype == 'L2_pyramidal': - print('L2Pyr bbox:',bbox)#,lx,ly,lz) - elif cell.celltype == L5Pyr or cell.celltype == 'L5_pyramidal': - print('L5Pyr bbox:',bbox)#,lx,ly,lz) - elif cell.celltype == L2Basket or cell.celltype == 'L2_basket': - print('L2Basket bbox:',bbox)#,lx,ly,lz) - elif cell.celltype == L5Basket or cell.celltype == 'L5_basket': - print('L5Basket bbox:',bbox)#,lx,ly,lz) - dcheck[cell.celltype]=True - """ - # for ty in ['L2_basket', 'L2_pyramidal', 'L5_basket', 'L5_pyramidal']: print(ty, dbbox[ty]) -arrangelayers() # arrange cells in layers - for visualization purposes - -pc.barrier() - -# save spikes from the individual trials in a single file -def catspks (): - lf = [os.path.join(datdir,'spk_'+str(i)+'.txt') for i in range(ntrial)] - if debug: print('catspk lf:',lf) - lspk = [[],[]] - for f in lf: - xarr = np.loadtxt(f) - for i in range(2): - lspk[i].extend(xarr[:,i]) - if debug: print('xarr.shape:',xarr.shape) - lspk = np.array(lspk).T - # lspk.sort(axis=1) # not multidim sort - can fix if want spikes across trials in temporal order - fout = os.path.join(datdir,'spk.txt') - with open(fout, 'w') as fspkout: - for i in range(lspk.shape[0]): - fspkout.write('%3.2f\t%d\n' % (lspk[i,0], lspk[i,1])) - if debug: print('lspk.shape:',lspk.shape) - return lspk - -# save average dipole from individual trials in a single file -def catdpl (): - ldpl = [] - for pre in ['dpl','rawdpl']: - lf = [os.path.join(datdir,pre+'_'+str(i)+'.txt') for i in range(ntrial)] - dpl_dat = np.array([np.loadtxt(f) for f in lf]) - try: - dpl = np.mean(dpl_dat,axis=0) - except ValueError: - print("ERROR: could not caluclate mean. Inconsistent trial lengths?") - with open(os.path.join(datdir,pre+'.txt'), 'w') as fp: - for i in range(dpl.shape[0]): - fp.write("%03.3f\t" % dpl[i,0]) - fp.write("%9.8f\t" % dpl[i,1]) - fp.write("%9.8f\t" % dpl[i,2]) - fp.write("%9.8f\n" % dpl[i,3]) - ldpl.append(dpl) - return ldpl - -# save average spectrogram from individual trials in a single file -def catspec (): - lf = [os.path.join(datdir,'rawspec_'+str(i)+'.npz') for i in range(ntrial)] - dspecin = {} - dout = {} - try: - for f in lf: dspecin[f] = np.load(f) - except: - return None - for k in ['t_L5', 'f_L5', 't_L2', 'f_L2', 'time', 'freq']: dout[k] = dspecin[lf[0]][k] - for k in ['TFR', 'TFR_L5', 'TFR_L2']: dout[k] = np.mean(np.array([dspecin[f][k] for f in lf]),axis=0) - with open(os.path.join(datdir,'rawspec.npz'), 'wb') as fdpl: - np.savez_compressed(fdpl,t_L5=dout['t_L5'],f_L5=dout['f_L5'],t_L2=dout['t_L2'],f_L2=dout['f_L2'],time=dout['time'],freq=dout['freq'],TFR=dout['TFR'],TFR_L5=dout['TFR_L5'],TFR_L2=dout['TFR_L2']) - return dout - -# gather trial outputs via either raw concatenation or averaging -def cattrialoutput (): - global doutf - lspk = catspks() # concatenate spikes from different trials to a single file - ldpl = catdpl() - dspec = catspec() - del lspk,ldpl,dspec # do not need these variables; returned for testing - -# run individual trials via runsim, then calc/save average dipole/specgram -# evinputinc is an increment (in milliseconds) that gets added to the evoked inputs on each -# successive trial. the default value is 0.0. -def runtrials (ntrial, inc_evinput=0.0): - global doutf - if pcID==0: print('Running', ntrial, 'trials.') - for i in range(ntrial): - if pcID==0: print(os.linesep+'Running trial',i+1,'...') - doutf = setoutfiles(ddir,i,ntrial) - # initrands(ntrial+(i+1)**ntrial) # reinit for each trial - net.state_init() # initialize voltages - runsim() # run the simulation - net.reset_src_event_times(inc_evinput = inc_evinput * (i + 1)) # adjusts the rng seeds and then the feed/event input times - doutf = setoutfiles(ddir,0,1) # reset output files based on sim name - if pcID==0: cattrialoutput() # get/save the averages - -def initrands (s=0): # fix to use s - # if there are N_trials, then randomize the seed - # establishes random seed for the seed seeder (yeah.) - # this creates a prng_tmp on each, but only the value from 0 will be used - prng_tmp = np.random.RandomState() - if pcID == 0: - r = h.Vector(1, s) # initialize vector to 1 element, with a 0 - if ntrial == 1: - prng_base = np.random.RandomState(pcID + s) - else: - # Create a random seed value - r.x[0] = prng_tmp.randint(1e9) - else: r = h.Vector(1, s) # create the vector 'r' but don't change its init value - pc.broadcast(r, 0) # broadcast random seed value in r to everyone - # set object prngbase to random state for the seed value - # other random seeds here will then be based on the gid - prng_base = np.random.RandomState(int(r.x[0])) - # seed list is now a list of seeds to be changed on each run - # otherwise, its originally set value will remain - # give a random int seed from [0, 1e9] - for param in p_exp.prng_seed_list: # this list empty for single experiment/trial - p[param] = prng_base.randint(1e9) - # print('simparams[prng_seedcore]:',simparams['prng_seedcore']) - - -initrands(0) # init once - -def setupLFPelectrodes (): - lelec = [] - if testlaminarLFP: - for y in np.linspace(1466.0,-72.0,16): lelec.append(LFPElectrode([370.0, y, 450.0], pc = pc)) - elif testLFP: - lelec.append(LFPElectrode([370.0, 1050.0, 450.0], pc = pc)) - lelec.append(LFPElectrode([370.0, 208.0, 450.0], pc = pc)) - return lelec - -lelec = setupLFPelectrodes() - # All units for time: ms -def runsim (): - t0 = time.time() # clock start time - - pc.set_maxstep(10) # sets the default max solver step in ms (purposefully large) - - for elec in lelec: - elec.setup() - elec.LFPinit() +def simulate (params, n_core): - h.finitialize() # initialize cells to -65 mV, after all the NetCon delays have been specified - if pcID == 0: - for tt in range(0,int(h.tstop),printdt): h.cvode.event(tt, prsimtime) # print time callbacks - - h.fcurrent() - h.frecord_init() # set state variables if they have been changed since h.finitialize - pc.psolve(h.tstop) # actual simulation - run the solver - pc.barrier() + # create the network from the parameter file. note, NEURON objects haven't been created yet + net = Network(params) - # these calls aggregate data across procs/nodes - pc.allreduce(dp_rec_L2, 1); - pc.allreduce(dp_rec_L5, 1) # combine dp_rec on every node, 1=add contributions together - for elec in lelec: elec.lfp_final() - net.aggregate_currents() # aggregate the currents independently on each proc - # combine net.current{} variables on each proc - pc.allreduce(net.current['L5Pyr_soma'], 1); pc.allreduce(net.current['L2Pyr_soma'], 1) + # TODO: add arrangelayers() to hnn-core or remove + # arrange cells in layers - for visualization purposes + # arrangelayers(net) + ddir = setupsimdir(params) - pc.barrier() + # run the simulation with MPI because the user is waiting for it to complete + with MPIBackend(n_procs=n_core, mpi_cmd='mpiexec'): + dpls = simulate_dipole(net, params['N_trials']) - # write time and calculated dipole to data file only if on the first proc - # only execute this statement on one proc - savedat(p, pcID, t_vec, dp_rec_L2, dp_rec_L5, net) + if len(dpls) > 1: + avg_dpl = average_dipoles(dpls) + else: + avg_dpl = dpls - for elec in lelec: print('end; t_vec.size()',t_vec.size(),'elec.lfp_t.size()',elec.lfp_t.size()) + # HNN workflow requires some files to be written to disk. This sets up the directory for all output files + ddir = setupsimdir(params) - if pcID == 0: - if debug: print("Simulation run time: %4.4f s" % (time.time()-t0)) - if debug: print("Simulation directory is: %s" % ddir.dsim) - if paramrw.find_param(doutf['file_param'],'save_spec_data') or usingOngoingInputs(p): - runanalysis(p, doutf['file_param'], doutf['file_dpl_norm'], doutf['file_spec']) # run spectral analysis - if paramrw.find_param(doutf['file_param'],'save_figs'): - savefigs(ddir,p,p_exp) # save output figures + # now write the files + net.spikes.write(os.path.join(ddir, 'spk_%d.txt')) - pc.barrier() # make sure all done in case multiple trials + # TODO: the gid_dict is needed forsome plotting functions. Can this be removed if spk.txt + # is new hnn-core format with 3 columns (including spike type)? + # write_gid_dict(os.path.join(ddir,'gid_dict.txt'), net.gid_dict) -def excepthook(exc_type, exc_value, exc_tb): - traceback.print_exception(exc_type, exc_value, exc_tb, file=sys.stdout, chain=False) - traceback.print_exception(exc_type, exc_value, exc_tb, file=sys.stderr, chain=False) - pc.runworker() - pc.done() - exit(-1) + for trial_idx, dpl in enumerate(dpls): + file_dipole = getfname(ddir,'normdpl', trial_idx, params['N_trials']) + dpl.write(file_dipole) -if __name__ == "__main__": - sys.excepthook = excepthook - if dconf['dorun']: - if ntrial > 1: runtrials(ntrial,p['inc_evinput']) - else: runsim() - pc.runworker() - pc.done() - if dconf['doquit']: h.quit() + # TODO: this should be moved to Network class within hnn-core + # write the somatic current to a file + # for now does not write the total but just L2 somatic and L5 somatic + # X = np.r_[[dpl.t, net.current['L2Pyr_soma'].x, net.current['L5Pyr_soma'].x]].T + # file_current = getfname(ddir, 'rawcurrent', trial_idx, params['N_trials']) + # np.savetxt(file_current, X, fmt=['%3.3f', '%5.4f', '%5.4f'], + # delimiter='\t') + + # TODO: save_vsoma is coded to work in parallel, so it should be moved to + # hnn_core.parallel_backends + # if p['save_vsoma']: + # save_vsoma()() + + if params['save_spec_data'] or usingOngoingInputs(params): + specfn = getfname(ddir, 'rawspec', trial_idx, params['N_trials']) + spec_opts = {'type': 'dpl_laminar', + 'f_max': params['f_max_spec'], + 'save_data': 0, + 'runtype': 'parallel', + } + specfn.analysis_simp(spec_opts, params, dpl, specfn) # run the spectral analysis + + # NOTE: the savefigs functionality is quite complicated and rewriting from scratch in hnn-core is probably + # a much better option that allows deprecating the large amount of legacy code + + # if params['save_figs']: + # savefigs(params) # save output figures From db06efc2d85f21c95cb0ad697d52c080171580ef Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Thu, 17 Sep 2020 07:39:31 -0400 Subject: [PATCH 005/107] MAINT: more hnn-core integration pieces Parameter handling: - remove read, quickreadprm, and quickgetparm from paramrw.py - use params['sim_prefix'] where possible Simulation output files: - write spk, dpl, param, and gid_dict files after simulation - fix error handling in simdat.updatedat Misc: - mark files and functions to be deprecated/moved - add excepthook to RunSimThread --- averaging.py | 2 ++ hnn_nrnui.py | 3 +- hnn_qt5.py | 56 ++++++++++++++++++++----------- paramrw.py | 93 ++++++++++++++-------------------------------------- pspec.py | 2 ++ run.py | 66 ++++++++++++++++++++++++++++--------- simdat.py | 49 +++++++++++++-------------- visdipole.py | 13 ++++---- vislfp.py | 13 ++++---- vispsd.py | 13 +++++--- visspec.py | 18 +++++----- visvolt.py | 19 ++++++----- 12 files changed, 181 insertions(+), 166 deletions(-) diff --git a/averaging.py b/averaging.py index a7a74b9ac..2fd728a44 100644 --- a/averaging.py +++ b/averaging.py @@ -1,3 +1,5 @@ +# DEPRECATE + # averaging.py - routine to perform averaging # # v 1.10.0-py35 diff --git a/hnn_nrnui.py b/hnn_nrnui.py index d3ed708c6..22c29ed10 100644 --- a/hnn_nrnui.py +++ b/hnn_nrnui.py @@ -1,3 +1,5 @@ +# DEPRECATE + import logging from neuron import h @@ -6,7 +8,6 @@ from neuron_ui import neuron_geometries_utils import params_default -from paramrw import quickreadprm from conf import fcfg,dconf from simdat import * diff --git a/hnn_qt5.py b/hnn_qt5.py index 59280abb8..c5a5c06e7 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -88,6 +88,17 @@ def get_defncore(): defncore = get_defncore() +def _add_missing_frames(tb): + fake_tb = namedtuple( + 'fake_tb', ('tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next') + ) + result = fake_tb(tb.tb_frame, tb.tb_lasti, tb.tb_lineno, tb.tb_next) + frame = tb.tb_frame.f_back + while frame: + result = fake_tb(frame, frame.f_lasti, frame.f_lineno, result) + frame = frame.f_back + return result + if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] else: plt.rcParams['font.size'] = dconf['fontsize'] = 10 @@ -530,6 +541,8 @@ def __init__ (self,c,d,ntrial,ncore,waitsimwin,paramf,opt=False,baseparamwin=Non self.mainwin = mainwin self.onNSG = onNSG + sys.excepthook = self.excepthook + self.txtComm = TextSignal() self.txtComm.tsig.connect(self.waitsimwin.updatetxt) @@ -545,6 +558,21 @@ def __init__ (self,c,d,ntrial,ncore,waitsimwin,paramf,opt=False,baseparamwin=Non self.params = read_params(self.paramf) + + def excepthook(self, exc_type, exc_value, exc_tb): + enriched_tb = _add_missing_frames(exc_tb) if exc_tb else exc_tb + # Note: sys.__excepthook__(...) would not work here. + # We need to use print_exception(...): + traceback.print_exception(exc_type, exc_value, enriched_tb, file=sys.stderr, chain=False) + msgBox = QMessageBox(self) + msgBox.information(self, "Exception", "WARNING: an exception occurred! Details can be " + "found in the console output or (if using Docker) in hnn_docker.log. We would " + "greatly appreciate reporting this issue to our development " + "team: " + "https://github.com/jonescompneurolab/hnn/issues") + + + def updatewaitsimwin (self, txt): # print('RunSimThread updatewaitsimwin, txt=',txt) self.txtComm.tsig.emit(txt) @@ -653,10 +681,7 @@ def runsim (self, is_opt=False, banner=True, simlength=None): else: self.lock.release() - try: - updatedat(self.params) - except IOError: - print('WARN: could not read simulation output for %s' % self.params['sim_prefix']) + updatedat(self.params) if not is_opt: simdat.updatelsimdat(self.paramf, simdat.ddat['dpl']) # update lsimdat and its current sim index @@ -3045,6 +3070,7 @@ class BaseParamDialog (QDialog): def __init__ (self, parent, optrun_func): super(BaseParamDialog, self).__init__(parent) self.proxparamwin = self.distparamwin = self.netparamwin = self.syngainparamwin = None + self.params = {'sim_prefix': ''} self.initUI() self.runparamwin = RunParamDialog(self) self.cellparamwin = CellParamDialog(self) @@ -3096,7 +3122,7 @@ def setpoisparam (self): bringwintotop(self.poisparamwin) def settonicparam (self): bringwintotop(self.tonicparamwin) def initUI (self): - + global params grid = QGridLayout() grid.setSpacing(10) @@ -3108,7 +3134,7 @@ def initUI (self): self.lbl.setToolTip('Simulation Name used to save parameter file and simulation data') grid.addWidget(self.lbl, row, 0) self.qle = QLineEdit(self) - self.qle.setText(paramf.split(os.path.sep)[-1].split('.param')[0]) + self.qle.setText(self.params['sim_prefix']) grid.addWidget(self.qle, row, 1) row+=1 @@ -3292,6 +3318,8 @@ def __init__ (self): # initialize the main HNN GUI super().__init__() + sys.excepthook = self.excepthook + self.runningsim = False self.runthread = None self.fontsize = dconf['fontsize'] @@ -3323,28 +3351,16 @@ def __init__ (self): else: self.statusBar().showMessage("Loaded %s"%default_param) # successful initialization, catch all further exceptions - sys.excepthook = self.excepthook - - def _add_missing_frames(self, tb): - fake_tb = namedtuple( - 'fake_tb', ('tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next') - ) - result = fake_tb(tb.tb_frame, tb.tb_lasti, tb.tb_lineno, tb.tb_next) - frame = tb.tb_frame.f_back - while frame: - result = fake_tb(frame, frame.f_lasti, frame.f_lineno, result) - frame = frame.f_back - return result def excepthook(self, exc_type, exc_value, exc_tb): - enriched_tb = self._add_missing_frames(exc_tb) if exc_tb else exc_tb + enriched_tb = _add_missing_frames(exc_tb) if exc_tb else exc_tb # Note: sys.__excepthook__(...) would not work here. # We need to use print_exception(...): traceback.print_exception(exc_type, exc_value, enriched_tb) msgBox = QMessageBox(self) msgBox.information(self, "Exception", "WARNING: an exception occurred! " "Details can be found in the console output. Please " - "include this output when opening an issue og GitHub: " + "include this output when opening an issue on GitHub: " "" "https://github.com/jonescompneurolab/hnn/issues") diff --git a/paramrw.py b/paramrw.py index 4f812fde9..89ae40b7e 100644 --- a/paramrw.py +++ b/paramrw.py @@ -13,20 +13,8 @@ from hnn_core import read_params -# get dict of ':' separated params from fn; ignore lines starting with # -def quickreadprm (fn): - d = {} - with open(fn,'r') as fp: - ln = fp.readlines() - for l in ln: - s = l.strip() - if s.startswith('#'): continue - sp = s.split(':') - if len(sp) > 1: - d[sp[0].strip()]=str(sp[1]).strip() - return d - def validate_param_file (fn): + # DEPRECATE - should validate file when opening for the first time (hnn-core) try: fp = open(fn, 'r') except OSError: @@ -50,11 +38,6 @@ def validate_param_file (fn): print("ERROR: parameter file not valid. Could not find 'tstop'") raise ValueError -# get dict of ':' separated params from fn; ignore lines starting with # -def quickgetprm (fn,k,ty): - d = quickreadprm(fn) - return ty(d[k]) - # check if using ongoing inputs def usingOngoingInputs (params, lty = ['_prox', '_dist']): if params is None: @@ -170,6 +153,8 @@ def usingTonicInputs (d): # class controlling multiple simulation files (.param) class ExpParams(): + # DEPRECATE + # need to add 'spec_cmap' to hnn-core def __init__ (self, f_psim, debug=False): self.debug = debug @@ -478,10 +463,8 @@ def get_key_types(self): return key_dict -# reads params from a generated txt file and returns gid dict and p dict -def read (fparam): +def read_gids_param (fparam): lines = fio.clean_lines(fparam) - p = {} gid_dict = {} for line in lines: if line.startswith('#'): continue @@ -495,26 +478,11 @@ def read (fparam): gid_dict[key] = np.arange(ind_start, ind_end) else: gid_dict[key] = np.array([]) - else: - try: - p[key] = float(val) - except ValueError: - p[key] = str(val) - return gid_dict, p + + return gid_dict # write the params to a filename -def write(fparam, p, gid_list): - """ now sorting - """ - # sort the items in the dict by key - # p_sorted = [item for item in p.items()] - p_keys = [key for key, val in p.items()] - p_sorted = [(key, p[key]) for key in p_keys] - # for some reason this is now crashing in python/mpi - # specifically, lambda sorting in place? - # p_sorted = [item for item in p.items()] - # p_sorted.sort(key=lambda x: x[0]) - # open the file for writing +def write_gids_param(fparam, gid_list): with open(fparam, 'w') as f: pstring = '%26s: ' # write the gid info first @@ -525,39 +493,21 @@ def write(fparam, p, gid_list): else: f.write('[]') f.write('\n') - # do the params in p_sorted - for param in p_sorted: - key, val = param - f.write(pstring % key) - if key.startswith('N_'): - f.write('%i\n' % val) - else: - f.write(str(val)+'\n') - -# Searches f_param for any match of p -def find_param(fparam, param_key): - _, p = read(fparam) - - try: - return p[param_key] - - except KeyError: - return "There is no key by the name %s" % param_key # reads the simgroup name from fparam def read_sim_prefix(fparam): - lines = fio.clean_lines(fparam) - param_list = [line for line in lines if line.split(': ')[0].startswith('sim_prefix')] + # DEPRECATE + params = read_params(fparam) - # Assume we found something ... - if param_list: - return param_list[0].split(" ")[1] - else: + try: + return params['sim_prefix'] + except KeyError: print("No sim_prefix found") return 0 # Finds the experiments list from the simulation param file (.param) def read_expmt_groups(fparam): + # DEPRECATE lines = fio.clean_lines(fparam) lines = [line for line in lines if line.split(': ')[0] == 'expmt_groups'] @@ -569,6 +519,7 @@ def read_expmt_groups(fparam): # qnd function to add feeds if they are sensible def feed_validate(p_ext, d, tstop): + # DEPRECATE """ whips into shape ones that are not could be properly made into a meaningful class. """ @@ -606,6 +557,7 @@ def feed_validate(p_ext, d, tstop): # def checkevokedsynkeys (p, nprox, ndist): + # DEPRECATE # make sure ampa,nmda gbar values are in the param dict for evoked inputs(for backwards compatibility) lctprox = ['L2Pyr','L5Pyr','L2Basket','L5Basket'] # evoked distal target cell types lctdist = ['L2Pyr','L5Pyr','L2Basket'] # evoked proximal target cell types @@ -622,6 +574,7 @@ def checkevokedsynkeys (p, nprox, ndist): # def checkpoissynkeys (p): + # DEPRECATE # make sure ampa,nmda gbar values are in the param dict for Poisson inputs (for backwards compatibility) lct = ['L2Pyr','L5Pyr','L2Basket','L5Basket'] # target cell types lsy = ['ampa','nmda'] # synapse types used in Poisson inputs @@ -634,6 +587,7 @@ def checkpoissynkeys (p): # creates the external feed params based on individual simulation params p def create_pext (p, tstop): + # DEPRECATE # indexable py list of param dicts for parallel # turn off individual feeds by commenting out relevant line here. # always valid, no matter the length @@ -777,6 +731,7 @@ def create_pext (p, tstop): # brittle in that the match string needs to be correct to find all the changed params # is redundant with(?) get_key_types() dynamic keys information def changed_vars(fparam): + # DEPRECATE # Strip empty lines and comments lines = fio.clean_lines(fparam) lines = [line for line in lines if line[0] != '#'] @@ -805,6 +760,7 @@ def changed_vars(fparam): # if any match, updates the (key, value) pair of d1 to match that of d2 # not real happy with variable names, but will have to do for now def compare_dictionaries(d1, d2): + # DEPRECATE # iterate over intersection of key sets (i.e. any common keys) for key in d1.keys() and d2.keys(): # update d1 to have same (key, value) pair as d2 @@ -814,6 +770,7 @@ def compare_dictionaries(d1, d2): # get diff on 2 dictionaries def diffdict (d1, d2, verbose=True): + # DEPRECATE print('d1,d2 num keys - ', len(d1.keys()), len(d2.keys())) for k in d1.keys(): if not k in d2: @@ -827,6 +784,7 @@ def diffdict (d1, d2, verbose=True): print('d1[',k,']=',d1[k],' d2[',k,']=',d2[k]) def consolidate_chunks(input_dict): + # MOVE to hnn-core # get a list of sorted chunks sorted_inputs = sorted(input_dict.items(), key=lambda x: x[1]['user_start']) @@ -859,6 +817,7 @@ def consolidate_chunks(input_dict): return consolidated_chunks def combine_chunks(input_chunks): + # MOVE to hnn-core # Used for creating the opt params of the last step with all inputs final_chunk = {'inputs': [], @@ -879,6 +838,7 @@ def combine_chunks(input_chunks): return final_chunk def chunk_evinputs(opt_params, sim_tstop, sim_dt): + # MOVE to hnn-core """ Take dictionary (opt_params) sorted by input and return a sorted list of dictionaries describing @@ -974,6 +934,7 @@ def chunk_evinputs(opt_params, sim_tstop, sim_dt): return chunk_list def get_inputs (params): + # MOVE import re input_list = [] @@ -987,6 +948,7 @@ def get_inputs (params): return input_list def trans_input (input_var): + # MOVE import re input_str = input_var @@ -998,9 +960,4 @@ def trans_input (input_var): input_str = 'Distal ' + input_match.group(2) return input_str -# debug test function -if __name__ == '__main__': - fparam = 'param/debug.param' - p = ExpParams(fparam,debug=True) - # print(find_param(fparam, 'WhoDat')) # ? diff --git a/pspec.py b/pspec.py index c88ab0bf6..72c0b9f87 100644 --- a/pspec.py +++ b/pspec.py @@ -1,3 +1,5 @@ +# DEPRECATE + # pspec.py - Very long plotting methods having to do with spec. # # v 1.10.0-py35 diff --git a/run.py b/run.py index 3005c3739..c6bd52c19 100755 --- a/run.py +++ b/run.py @@ -9,10 +9,10 @@ import os import sys import time -import shutil +import json import numpy as np # Cells are defined in other files -from paramrw import usingOngoingInputs +from paramrw import usingOngoingInputs, write_gids_param import specfn as specfn #import pickle import datetime @@ -24,7 +24,7 @@ import os.path as op -from hnn_core import simulate_dipole, read_params, Network, MPIBackend +from hnn_core import simulate_dipole, read_params, Network, MPIBackend, read_spikes from hnn_core.dipole import average_dipoles dconf = readconf() @@ -111,21 +111,54 @@ def simulate (params, n_core): with MPIBackend(n_procs=n_core, mpi_cmd='mpiexec'): dpls = simulate_dipole(net, params['N_trials']) - if len(dpls) > 1: + ntrial = len(dpls) + # save average dipole from individual trials in a single file + if ntrial > 1: avg_dpl = average_dipoles(dpls) + elif ntrial == 1: + avg_dpl = dpls[0] else: - avg_dpl = dpls + raise RuntimeError("No dipole(s) rertuned from simulation") + + # TODO: rawdpl in hnn-core + avg_dpl.write(os.path.join(ddir, 'dpl.txt')) # HNN workflow requires some files to be written to disk. This sets up the directory for all output files ddir = setupsimdir(params) # now write the files - net.spikes.write(os.path.join(ddir, 'spk_%d.txt')) + params.write(os.path.join(ddir, params['sim_prefix'] + '.json')) - # TODO: the gid_dict is needed forsome plotting functions. Can this be removed if spk.txt - # is new hnn-core format with 3 columns (including spike type)? - # write_gid_dict(os.path.join(ddir,'gid_dict.txt'), net.gid_dict) + # TODO: Can below be removed if spk.txt is new hnn-core format with 3 columns (including spike type)? + write_gids_param(getfname(ddir,'param'), net.gid_dict) + # save spikes by trial + net.spikes.write(os.path.join(ddir, 'spk_%d.txt')) + + # save spikes from the individual trials in a single file + fout = os.path.join(ddir,'spk.txt') + with open(fout, 'w') as fspkout: + for trial_idx in range(len(net.spikes.times)): + for spike_idx in range(len(net.spikes.times[trial_idx])): + fspkout.write('{:.3f}\t{}\t{}\n'.format( + net.spikes.times[trial_idx][spike_idx], + int(net.spikes.gids[trial_idx][spike_idx]), + net.spikes.types[trial_idx][spike_idx])) + + # save average spectrogram from individual trials in a single file + lf = [os.path.join(ddir,'rawspec_'+str(i)+'.npz') for i in range(ntrial)] + dspecin = {} + dout = {} + try: + for f in lf: dspecin[f] = np.load(f) + except: + return None + for k in ['t_L5', 'f_L5', 't_L2', 'f_L2', 'time', 'freq']: dout[k] = dspecin[lf[0]][k] + for k in ['TFR', 'TFR_L5', 'TFR_L2']: dout[k] = np.mean(np.array([dspecin[f][k] for f in lf]),axis=0) + with open(os.path.join(ddir,'rawspec.npz'), 'wb') as fdpl: + np.savez_compressed(fdpl,t_L5=dout['t_L5'],f_L5=dout['f_L5'],t_L2=dout['t_L2'],f_L2=dout['f_L2'],time=dout['time'],freq=dout['freq'],TFR=dout['TFR'],TFR_L5=dout['TFR_L5'],TFR_L2=dout['TFR_L2']) + + # save dipole for each trial and perform spectral analysis for trial_idx, dpl in enumerate(dpls): file_dipole = getfname(ddir,'normdpl', trial_idx, params['N_trials']) dpl.write(file_dipole) @@ -141,19 +174,20 @@ def simulate (params, n_core): # TODO: save_vsoma is coded to work in parallel, so it should be moved to # hnn_core.parallel_backends # if p['save_vsoma']: - # save_vsoma()() + # save_vsoma() if params['save_spec_data'] or usingOngoingInputs(params): - specfn = getfname(ddir, 'rawspec', trial_idx, params['N_trials']) spec_opts = {'type': 'dpl_laminar', 'f_max': params['f_max_spec'], 'save_data': 0, 'runtype': 'parallel', } - specfn.analysis_simp(spec_opts, params, dpl, specfn) # run the spectral analysis - # NOTE: the savefigs functionality is quite complicated and rewriting from scratch in hnn-core is probably - # a much better option that allows deprecating the large amount of legacy code + # run the spectral analysis + specfn.analysis_simp(spec_opts, params, dpl, + getfname(ddir, 'rawspec', trial_idx, params['N_trials'])) - # if params['save_figs']: - # savefigs(params) # save output figures + # NOTE: the savefigs functionality is quite complicated and rewriting from scratch in hnn-core is probably + # a much better option that allows deprecating the large amount of legacy code + # if params['save_figs']: + # savefigs(params) # save output figures \ No newline at end of file diff --git a/simdat.py b/simdat.py index a6b5b776f..62c8c0a4a 100644 --- a/simdat.py +++ b/simdat.py @@ -1,5 +1,5 @@ import os -from PyQt5.QtWidgets import QMenu, QSizePolicy +from PyQt5.QtWidgets import QSizePolicy from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import matplotlib.pyplot as plt @@ -13,6 +13,9 @@ from paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs, usingTonicInputs, countEvokedInputs, ExpParams from scipy import signal from gutils import getscreengeom +import traceback + +from hnn_core import read_spikes # dconf has settings from hnn.cfg if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] @@ -86,17 +89,15 @@ def getinputfiles (sim_prefix): dfile['outparam'] = os.path.join(basedir,'param.txt') return dfile -def readtxt (fn, silent=False): +def readtxt (fn): contents = [] try: contents = np.loadtxt(fn) except OSError: - if not silent: - print('Warning: could not read file:', fn) + print('Warning: could not read file:', fn) except ValueError: - if not silent: - print('Warning: error reading data from:', fn) + print('Warning: error reading data from:', fn) return contents @@ -109,13 +110,18 @@ def updatedat (params): for k in ['dpl','spk']: if k in ddat: del ddat[k] - silent = not os.path.exists(basedir) - ddat[k] = readtxt(dfile[k], silent) - if len(ddat[k]) == 0: - del ddat[k] + + if os.path.exists(basedir): + ddat['dpl'] = readtxt(dfile['dpl']) + if len(ddat['dpl']) == 0: + del ddat['dpl'] + print('WARN: could not read dipole data for simulation %s' %params['sim_prefix']) - if not 'dpl' in ddat or not 'spk' in ddat: - raise ValueError + try: + spikes = read_spikes(dfile['spk']) + ddat['spk'] = np.r_[spikes.times, spikes.gids].T + except ValueError: + ddat['spk'] = readtxt(dfile['spk']) ddat['dpltrials'] = readdpltrials(basedir) @@ -289,7 +295,7 @@ def plotinputhist (self, xl, dinty): times = np.linspace(0, sim_tstop, num_step) try: - extinputs = spikefn.ExtInputs(dfile['spk'], dfile['outparam']) + extinputs = spikefn.ExtInputs(dfile['spk'], dfile['outparam'], self.params) extinputs.add_delay_times() dinput = extinputs.inputs except ValueError: @@ -570,10 +576,10 @@ def clearlextdatobj (self): def plotsimdat (self): # plot the simulation data - failed_loading = False self.gRow = 0 bottom = 0.0 + failed_loading = False only_create_axes = False if self.params is None: only_create_axes = True @@ -583,10 +589,8 @@ def plotsimdat (self): # setup the figure axis for drawing the dipole signal dinty = self.getInputs() - # try loading data. ignore failures - try: - updatedat(self.params) - except IOError: + updatedat(self.params) + if 'dpl' not in ddat: failed_loading = True xl = (0.0, self.params['tstop']) @@ -618,15 +622,6 @@ def plotsimdat (self): if failed_loading or only_create_axes: return - try: - updatedat(self.params) - except IOError: - return - except ValueError: - if 'dpl' not in ddat: - # failed to load dipole data, nothing more to plot - return - ds = None xl = (0,ddat['dpl'][-1,0]) dt = ddat['dpl'][1,0] - ddat['dpl'][0,0] diff --git a/visdipole.py b/visdipole.py index 9992af1f3..7df47117b 100644 --- a/visdipole.py +++ b/visdipole.py @@ -15,8 +15,6 @@ import pylab as plt import matplotlib.gridspec as gridspec from DataViewGUI import DataViewGUI -from neuron import h -from run import net import paramrw from filt import boxfilt, hammfilt import spikefn @@ -24,6 +22,8 @@ from simdat import readdpltrials from conf import dconf +from hnn_core import read_params + if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] else: dconf['fontsize'] = 10 @@ -33,12 +33,13 @@ dplpath = sys.argv[i] elif sys.argv[i].endswith('.param'): paramf = sys.argv[i] - scalefctr = paramrw.find_param(paramf,'dipole_scalefctr') + params = read_params(paramf) + scalefctr = params['dipole_scalefctr'] if type(scalefctr)!=float and type(scalefctr)!=int: scalefctr=30e3 - tstop = paramrw.find_param(paramf,'tstop') - ntrial = paramrw.quickgetprm(paramf,'N_trials',int) + tstop = params['tstop'] + ntrial = params['N_trials'] -basedir = os.path.join(dconf['datdir'],paramf.split(os.path.sep)[-1].split('.param')[0]) +basedir = os.path.join(dconf['datdir'], params['sim_prefix']) ddat = {} ddat['dpltrials'] = readdpltrials(basedir) diff --git a/vislfp.py b/vislfp.py index 64b6a694a..b04e9a321 100644 --- a/vislfp.py +++ b/vislfp.py @@ -15,8 +15,6 @@ import pylab as plt import matplotlib.gridspec as gridspec from DataViewGUI import DataViewGUI -from neuron import h -from run import net import paramrw from filt import boxfilt, hammfilt, lowpass import spikefn @@ -24,6 +22,8 @@ from conf import dconf from specfn import MorletSpec +from hnn_core import read_params + if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] debug = True @@ -34,12 +34,13 @@ lfppath = sys.argv[i] elif sys.argv[i].endswith('.param'): paramf = sys.argv[i] - scalefctr = paramrw.find_param(paramf,'dipole_scalefctr') + params = read_params(paramf) + scalefctr = params['dipole_scalefctr'] if type(scalefctr)!=float and type(scalefctr)!=int: scalefctr=30e3 - tstop = paramrw.find_param(paramf,'tstop') - ntrial = paramrw.quickgetprm(paramf,'N_trials',int) + tstop = params['tstop'] + ntrial = params['N_trials'] -basedir = os.path.join(dconf['datdir'],paramf.split(os.path.sep)[-1].split('.param')[0]) +basedir = os.path.join(dconf['datdir'],params['sim_prefix']) ddat = {}; tvec = None; dspec = None diff --git a/vispsd.py b/vispsd.py index b23a8e8e4..3abee51eb 100644 --- a/vispsd.py +++ b/vispsd.py @@ -15,8 +15,6 @@ import pylab as plt import matplotlib.gridspec as gridspec from DataViewGUI import DataViewGUI -from neuron import h -from run import net import paramrw from filt import boxfilt, hammfilt import spikefn @@ -24,6 +22,8 @@ from specfn import MorletSpec from conf import dconf +from hnn_core import read_params + if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] else: dconf['fontsize'] = 10 @@ -33,9 +33,10 @@ specpath = sys.argv[i] elif sys.argv[i].endswith('.param'): paramf = sys.argv[i] - ntrial = paramrw.quickgetprm(paramf,'N_trials',int) + params = read_params(paramf) + ntrial = params['N_trials'] -basedir = os.path.join(dconf['datdir'],paramf.split(os.path.sep)[-1].split('.param')[0]) +basedir = os.path.join(dconf['datdir'],params['sim_prefix']) print('basedir:',basedir) ddat = {} @@ -171,12 +172,14 @@ def plotextdat (self, lF, lextpsd, lextfiles): # plot 'external' data (e.g. from self.lextdatobj.append(ax.legend(handles=self.lpatch)) def plot (self): + global params + #self.clearaxes() #plt.close(self.figure) if self.index == 0: self.lax = self.drawpsd(ddat['spec'],self.figure, self.G, ltextra='All Trials') else: - specpathtrial = os.path.join(dconf['datdir'],paramf.split('.param')[0].split(os.path.sep)[-1],'rawspec_'+str(self.index)+'.npz') + specpathtrial = os.path.join(dconf['datdir'], params['sim_prefix'],'rawspec_'+str(self.index)+'.npz') if 'spec'+str(self.index) not in ddat: ddat['spec'+str(self.index)] = np.load(specpath) self.lax=self.drawpsd(ddat['spec'+str(self.index)],self.figure, self.G, ltextra='Trial '+str(self.index)); diff --git a/visspec.py b/visspec.py index ffbd2b06b..653613eb7 100644 --- a/visspec.py +++ b/visspec.py @@ -20,7 +20,8 @@ import simdat from simdat import readdpltrials import paramrw -from paramrw import quickgetprm + +from hnn_core import read_params if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] @@ -30,9 +31,10 @@ specpath = sys.argv[i] elif sys.argv[i].endswith('.param'): paramf = sys.argv[i] - ntrial = paramrw.quickgetprm(paramf,'N_trials',int) + params = read_params(paramf) + ntrial = params['N_trials'] -basedir = os.path.join(dconf['datdir'],paramf.split(os.path.sep)[-1].split('.param')[0]) +basedir = os.path.join(dconf['datdir'],params['sim_prefix']) #print('basedir:',basedir,'paramf:',paramf,'ntrial:',ntrial) # assumes column 0 is time, rest of columns are time-series @@ -69,7 +71,6 @@ def extractspec (dat, fmax=40.0): return ms.f, lspec, avgdipole, avgspec -# TODO: can we import this from simdat instead? def loaddat (fname): try: if fname.endswith('.txt'): @@ -77,10 +78,9 @@ def loaddat (fname): print('Loaded data in ' + fname + '. Extracting Spectrograms.') return dat elif fname.endswith('.param'): - ntrial = paramrw.quickgetprm(paramf,'N_trials',int) - basedir = os.path.join(dconf['datdir'],paramf.split(os.path.sep)[-1].split('.param')[0]) - #simdat.updatedat(paramf) - #return paramf,simdat.ddat + ntrial = params['N_trials'] + basedir = os.path.join(dconf['datdir'],params['sim_prefix']) + if ntrial > 1: ddat = readdpltrials(basedir) #print('read dpl trials',ddat[0].shape) @@ -249,7 +249,7 @@ def loadDisplayData (self, fname=None): self.dat = dat try: try: - fmax = quickgetprm(paramf,'f_max_spec',float) + fmax = params['f_max_spec'] except: fmax = 40. f, lspec, avgdipole, avgspec = extractspec(dat,fmax=fmax) diff --git a/visvolt.py b/visvolt.py index a8058170d..27c6cd4a5 100644 --- a/visvolt.py +++ b/visvolt.py @@ -14,13 +14,13 @@ from matplotlib.figure import Figure import pylab as plt import matplotlib.gridspec as gridspec -from neuron import h -from run import net import paramrw import pickle from conf import dconf from gutils import getmplDPI +from hnn_core import read_params + if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] else: dconf['fontsize'] = 10 @@ -37,16 +37,17 @@ for i in range(len(sys.argv)): if sys.argv[i].endswith('.param'): paramf = sys.argv[i] - tstop = paramrw.quickgetprm(paramf,'tstop',float) - ntrial = paramrw.quickgetprm(paramf,'N_trials',int) - outparamf = os.path.join(dconf['datdir'],paramf.split('.param')[0].split(os.path.sep)[-1],'param.txt') + params = read_params(paramf) + tstop = params['tstop'] + ntrial = params['N_trials'] + outparamf = os.path.join(dconf['datdir'],params['sim_prefix'],'param.txt') elif sys.argv[i] == 'maxperty': maxperty = int(sys.argv[i]) if ntrial <= 1: - voltpath = os.path.join(dconf['datdir'],paramf.split('.param')[0].split(os.path.sep)[-1],'vsoma.pkl') + voltpath = os.path.join(dconf['datdir'],params['sim_prefix'],'vsoma.pkl') else: - voltpath = os.path.join(dconf['datdir'],paramf.split('.param')[0].split(os.path.sep)[-1],'vsoma_1.pkl') + voltpath = os.path.join(dconf['datdir'],params['sim_prefix'],'vsoma_1.pkl') class VoltCanvas (FigureCanvas): def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, title='Voltage Viewer'): @@ -107,6 +108,8 @@ def drawvolt (self, dvolt, fig, G, sz=8, ltextra=''): return lax def plot (self): + global params + if self.index == 0: if ntrial == 1: dvolt = pickle.load(open(voltpath,'rb')) @@ -114,7 +117,7 @@ def plot (self): dvolt = pickle.load(open(voltpath,'rb')) self.lax = self.drawvolt(dvolt,self.figure, self.G, 5, ltextra='All Trials') else: - voltpathtrial = os.path.join(dconf['datdir'],paramf.split('.param')[0].split(os.path.sep)[-1],'vsoma_'+str(self.index)+'.pkl') + voltpathtrial = os.path.join(dconf['datdir'],params['sim_prefix'],'vsoma_'+str(self.index)+'.pkl') dvolttrial = pickle.load(open(voltpathtrial,'rb')) self.lax=self.drawvolt(dvolttrial,self.figure, self.G, 5, ltextra='Trial '+str(self.index)); self.draw() From 17f1c5122e93cd38c45e60179fc9ec2fe909d1eb Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Thu, 17 Sep 2020 21:45:20 -0400 Subject: [PATCH 006/107] MAINT: better exception handling in hnn_qt5.py --- hnn_qt5.py | 110 +++++++++++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/hnn_qt5.py b/hnn_qt5.py index c5a5c06e7..35438e7af 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -459,7 +459,7 @@ class Communicate (QObject): commsig = pyqtSignal() class DoneSignal (QObject): - finishSim = pyqtSignal(bool, bool) + finishSim = pyqtSignal(bool, str) # for signaling - passing text class TextSignal (QObject): @@ -541,7 +541,9 @@ def __init__ (self,c,d,ntrial,ncore,waitsimwin,paramf,opt=False,baseparamwin=Non self.mainwin = mainwin self.onNSG = onNSG - sys.excepthook = self.excepthook + # it would be ideal to display a dialog box, but we have to get that event back to the main window + # the next best thing is to print to the console and not crash the application + sys.excepthook = traceback.print_exception self.txtComm = TextSignal() self.txtComm.tsig.connect(self.waitsimwin.updatetxt) @@ -558,21 +560,6 @@ def __init__ (self,c,d,ntrial,ncore,waitsimwin,paramf,opt=False,baseparamwin=Non self.params = read_params(self.paramf) - - def excepthook(self, exc_type, exc_value, exc_tb): - enriched_tb = _add_missing_frames(exc_tb) if exc_tb else exc_tb - # Note: sys.__excepthook__(...) would not work here. - # We need to use print_exception(...): - traceback.print_exception(exc_type, exc_value, enriched_tb, file=sys.stderr, chain=False) - msgBox = QMessageBox(self) - msgBox.information(self, "Exception", "WARNING: an exception occurred! Details can be " - "found in the console output or (if using Docker) in hnn_docker.log. We would " - "greatly appreciate reporting this issue to our development " - "team: " - "https://github.com/jonescompneurolab/hnn/issues") - - - def updatewaitsimwin (self, txt): # print('RunSimThread updatewaitsimwin, txt=',txt) self.txtComm.tsig.emit(txt) @@ -594,13 +581,13 @@ def __del__ (self): self.wait() def run (self): - failed=False + msg='' if self.opt and self.baseparamwin is not None: try: self.optmodel() # run optimization - except RuntimeError: - failed = True + except RuntimeError as e: + msg = str(e) self.baseparamwin.optparamwin.toggleEnableUserFields(self.cur_step, enable=True) self.baseparamwin.optparamwin.clear_initial_opt_ranges() self.baseparamwin.optparamwin.optimization_running = False @@ -608,10 +595,10 @@ def run (self): try: self.runsim() # run simulation self.updatedispparam() # update params in all windows (optimization) - except RuntimeError: - failed = True + except RuntimeError as e: + msg = str(e) - self.d.finishSim.emit(self.opt, failed) # send the finish signal + self.d.finishSim.emit(self.opt, msg) # send the finish signal def killproc (self): @@ -654,33 +641,40 @@ def runsim (self, is_opt=False, banner=True, simlength=None): try: simulate(self.params, self.ncore) - except RuntimeError: + # success, make default ncore + defncore = self.ncore + break + except RuntimeError as e: if self.ncore == 1: # can't reduce ncore any more - txt = "Simulation failed to start" - print(txt) - self.updatewaitsimwin(txt) + print(str(e)) + self.updatewaitsimwin(str(e)) kill_and_check_nrniv_procs() - raise RuntimeError - - self.ncore = ceil(self.ncore/2) - txt = "INFO: Failed starting simulation, retrying with %d cores" % self.ncore - print(txt) + raise RuntimeError("Simulation failed to start") + except: + # if it's something else we still want to print the error and then + # handle it like a RuntimeError instead of crashing the whole application + txt = traceback.format_exc() + sys.stderr.write(txt) self.updatewaitsimwin(txt) + # pop up dialog pop + raise RuntimeError("Unknown error") + + # check if proc was killed before retrying with fewer cores + self.lock.acquire() + if self.killed: + self.lock.release() + # exit using RuntimeError + raise RuntimeError("Terminated") + else: + self.lock.release() - # success - defncore = self.ncore - break - - # check if proc was killed - self.lock.acquire() - if self.killed: - self.lock.release() - # exit using RuntimeError - raise RuntimeError - else: - self.lock.release() + self.ncore = ceil(self.ncore/2) + txt = "INFO: Failed starting simulation, retrying with %d cores" % self.ncore + print(txt) + self.updatewaitsimwin(txt) + # should have good data written to files at this point updatedat(self.params) if not is_opt: @@ -2679,9 +2673,19 @@ def initExtra (self): self.spec_map_cb.currentIndexChanged.connect(self.selectionchange) self.ltabs[1].layout.addRow(self.transvar('spec_cmap'),self.spec_map_cb) - def getntrial (self): return int(self.dqline['N_trials'].text().strip()) + def getntrial (self): + ntrial = int(self.dqline['N_trials'].text().strip()) + if ntrial < 1: + self.dqline['N_trials'].setText(str(1)) + ntrial = 1 + return ntrial - def getncore (self): return int(self.dqextra['NumCores'].text().strip()) + def getncore (self): + ncore = int(self.dqextra['NumCores'].text().strip()) + if ncore < 1: + self.dqline['NumCores'].setText(str(1)) + ncore = 1 + return ncore def setfromdin (self,din): global defncore @@ -4209,7 +4213,7 @@ def startsim (self, ntrial, ncore, onNSG=False): bringwintotop(self.waitsimwin) - def done (self, optMode, failed): + def done (self, optMode, except_msg): # called when the simulation completes running if debug: print('done') self.runningsim = False @@ -4221,8 +4225,11 @@ def done (self, optMode, failed): # self.m.plot() global basedir self.setcursors(Qt.ArrowCursor) - if failed: - msg = "Failed " + + failed=False + if len(except_msg) > 0: + failed = True + msg = "%s: Failed " % except_msg else: msg = "Finished " @@ -4234,7 +4241,10 @@ def done (self, optMode, failed): else: msg += "running sim " - QMessageBox.information(self, "Done!", msg + "using " + paramf + '. Saved data/figures in: ' + basedir) + if failed: + QMessageBox.information(self, "Failed!", msg + "using " + paramf + '. Check simulation log or console for error messages') + else: + QMessageBox.information(self, "Done!", msg + "using " + paramf + '. Saved data/figures in: ' + basedir) self.setWindowTitle(paramf) self.populateSimCB() # populate the combobox From 11dec6f66053b72fb83ad410f96586e925cd191a Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Thu, 17 Sep 2020 21:47:16 -0400 Subject: [PATCH 007/107] MAINT: use hnn-core dipole in specfn.py --- specfn.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/specfn.py b/specfn.py index f3080d2ad..6e6b04469 100644 --- a/specfn.py +++ b/specfn.py @@ -16,7 +16,6 @@ import fileio as fio import currentfn -import dipolefn import spikefn import axes_create as ac from conf import dconf @@ -216,6 +215,8 @@ class PhaseLock(): Might be a newer version in fieldtrip """ def __init__(self, tsarray1, tsarray2, fparam, f_max=60.): + raise DeprecationWarning + # Save time-series arrays as self variables # ohhhh. Do not use 1-indexed keys of a dict! self.ts = { @@ -825,6 +826,8 @@ def spec_max(ddata, expmt_group, layer='agg'): # common function to generate spec if it appears to be missing def generate_missing_spec(ddata, f_max=40): + raise DeprecationWarning + # just check first expmt_group expmt_group = ddata.expmt_groups[0] @@ -865,6 +868,8 @@ def generate_missing_spec(ddata, f_max=40): # Kernel for spec analysis of current data # necessary for parallelization def spec_current_kernel(fparam, fts, fspec, f_max): + raise DeprecationWarning + I_syn = currentfn.SynapticCurrent(fts) # TODO: replace with function that reads justs params @@ -879,32 +884,27 @@ def spec_current_kernel(fparam, fts, fspec, f_max): # Kernel for spec analysis of dipole data # necessary for parallelization -def spec_dpl_kernel(fparam, fts, fspec, f_max): - dpl = dipolefn.Dipole(fts) - dpl.units = 'nAm' - - # TODO: replace with function that reads justs params - params = paramrw.read(fparam)[1] +def spec_dpl_kernel(params, dpl, fspec, f_max): # Do the conversion prior to generating these spec # dpl.convert_fAm_to_nAm() # Generate various spec results - spec_agg = MorletSpec(dpl.t, dpl.dpl['agg'], f_max, p_dict=params) - spec_L2 = MorletSpec(dpl.t, dpl.dpl['L2'], f_max, p_dict=params) - spec_L5 = MorletSpec(dpl.t, dpl.dpl['L5'], f_max, p_dict=params) + spec_agg = MorletSpec(dpl.times, dpl.data['agg'], f_max, p_dict=params) + spec_L2 = MorletSpec(dpl.times, dpl.data['L2'], f_max, p_dict=params) + spec_L5 = MorletSpec(dpl.times, dpl.data['L5'], f_max, p_dict=params) # Get max spectral power data # for now, only doing this for agg max_agg = spec_agg.max() # Generate periodogram resutls - pgram = Welch(dpl.t, dpl.dpl['agg'], params['dt']) + pgram = Welch(dpl.times, dpl.data['agg'], params['dt']) # Save spec results np.savez_compressed(fspec, time=spec_agg.t, freq=spec_agg.f, TFR=spec_agg.TFR, max_agg=max_agg, t_L2=spec_L2.t, f_L2=spec_L2.f, TFR_L2=spec_L2.TFR, t_L5=spec_L5.t, f_L5=spec_L5.f, TFR_L5=spec_L5.TFR, pgram_p=pgram.P, pgram_f=pgram.f) -def analysis_simp (opts, fparam, fdpl, fspec): +def analysis_simp (opts, params, fdpl, fspec): opts_run = {'type': 'dpl_laminar', 'f_max': 100., 'save_data': 0, @@ -913,7 +913,7 @@ def analysis_simp (opts, fparam, fdpl, fspec): if opts: for key, val in opts.items(): if key in opts_run.keys(): opts_run[key] = val - spec_dpl_kernel(fparam, fdpl, fspec, opts_run['f_max']) + spec_dpl_kernel(params, fdpl, fspec, opts_run['f_max']) # Does spec analysis for all files in simulation directory # ddata comes from fileio @@ -1094,6 +1094,8 @@ def pfreqpwr_with_hist(file_name, freqpwr_result, f_spk, gid_dict, p_dict, key_t f.close() def pmaxpwr(file_name, results_list, fparam_list): + raise DeprecationWarning + f = ac.FigStd() f.ax0.hold(True) From 89675e81772153918fc924ed6f661d0d92f905cd Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Thu, 17 Sep 2020 21:49:47 -0400 Subject: [PATCH 008/107] MAINT: use hnn-core read_spikes in spikefn.py --- spikefn.py | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/spikefn.py b/spikefn.py index 9f0acf232..ef29f2faf 100644 --- a/spikefn.py +++ b/spikefn.py @@ -12,6 +12,8 @@ import os import paramrw +from hnn_core import read_spikes + # meant as a class for ONE cell type class Spikes(): def __init__ (self, s_all, ranges): @@ -60,38 +62,45 @@ def ppsth (self, a): # Class to handle extinput event times class ExtInputs (Spikes): # class for external inputs - extracts gids and times - def __init__ (self, fspk, fparam, evoked=False): - # load gid and param dicts + def __init__ (self, fspk, fgids, params, evoked=False): + + self.p_dict = params try: - self.gid_dict, self.p_dict = paramrw.read(fparam) - except OSError: + self.gid_dict = paramrw.read_gids_param(fgids) + except FileNotFoundError: raise ValueError + + if 'common' in self.gid_dict: + extinput_key = 'common' + else: + extinput_key = 'extinput' + self.evoked = evoked + # parse evoked prox and dist input gids from gid_dict - # print('getting evokedinput gids') self.gid_evprox, self.gid_evdist = self.__get_evokedinput_gids() - # print('got evokedinput gids') + # parse ongoing prox and dist input gids from gid_dict - self.gid_prox, self.gid_dist = self.__get_extinput_gids() + self.gid_prox, self.gid_dist = self.__get_extinput_gids(extinput_key) # poisson input gids #print('getting pois input gids') self.gid_pois = self.__get_poisinput_gids() # self.inputs is dict of input times with keys 'prox' and 'dist' self.inputs = self.__get_extinput_times(fspk) - def __get_extinput_gids (self): + def __get_extinput_gids (self, extinput_key): # Determine if both feeds exist in this sim # If they do, self.gid_dict['extinput'] has length 2 # If so, first gid is guaraneteed to be prox feed, second to be dist feed - if len(self.gid_dict['extinput']) == 2: - return self.gid_dict['extinput'] + if len(self.gid_dict[extinput_key]) == 2: + return self.gid_dict[extinput_key] # Otherwise, only one feed exists in this sim # Must use param file to figure out which one... - elif len(self.gid_dict['extinput']) > 0: + elif len(self.gid_dict[extinput_key]) > 0: if self.p_dict['t0_input_prox'] < self.p_dict['tstop']: - return self.gid_dict['extinput'][0], None + return self.gid_dict[extinput_key][0], None elif self.p_dict['t0_input_dist'] < self.p_dict['tstop']: - return None, self.gid_dict['extinput'][0] + return None, self.gid_dict[extinput_key][0] else: return None, None @@ -153,7 +162,15 @@ def get_times (self, gid, s_all): def __get_extinput_times (self, fspk): # load all spike times from file - s_all = np.loadtxt(open(fspk, 'rb')) + s_all = [] + try: + spikes = read_spikes(fspk) + s_all = np.r_[spikes.times, spikes.gids].T + except ValueError: + s_all = np.loadtxt(open(fspk, 'rb')) + except OSError: + print('Warning: could not read file:', fspk) + if len(s_all) == 0: # couldn't read spike times raise ValueError @@ -229,7 +246,7 @@ def plot_hist (self, ax, extinput, tvec, bins='auto', xlim=None, color='green', if bins is 'auto': bins = hist_bin_opt(self.inputs[extinput], 1) if not xlim: - xlim = (0., p_dict['tstop']) + xlim = (0., self.p_dict['tstop']) if len(self.inputs[extinput]): #print("plot_hist bins:",bins,type(bins)) hist = ax.hist(self.inputs[extinput], bins, range=xlim, color=color, label=extinput, histtype=hty,linewidth=lw) @@ -289,6 +306,8 @@ def hist_bin_opt(x, N_trials): # "purely" from files, this is the new way to replace the old way def spikes_from_file(fparam, fspikes): + raise DeprecationWarning + gid_dict, _ = paramrw.read(fparam) # cell list - requires cell to start with L2/L5 src_list = [] From 000555ad595e19a8336a3fcedccdabb5102280c2 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Thu, 17 Sep 2020 21:50:32 -0400 Subject: [PATCH 009/107] MAINT: update visrast.py for hnn-core --- visrast.py | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/visrast.py b/visrast.py index 49cbbee7c..a7e347cfd 100644 --- a/visrast.py +++ b/visrast.py @@ -14,8 +14,6 @@ from matplotlib.figure import Figure import pylab as plt import matplotlib.gridspec as gridspec -from neuron import h -from run import net import paramrw from filt import boxfilt, hammfilt import spikefn @@ -23,6 +21,8 @@ from conf import dconf from gutils import getmplDPI +from hnn_core import read_params, read_spikes + #plt.rcParams['lines.markersize'] = 15 plt.rcParams['lines.linewidth'] = 1 rastmarksz = 5 # raster dot size @@ -42,20 +42,19 @@ spkpath = sys.argv[i] elif sys.argv[i].endswith('.param'): paramf = sys.argv[i] - tstop = paramrw.quickgetprm(paramf,'tstop',float) - ntrial = paramrw.quickgetprm(paramf,'N_trials',int) - EvokedInputs = paramrw.usingEvokedInputs(paramf) - OngoingInputs = paramrw.usingOngoingInputs(paramf) - PoissonInputs = paramrw.usingPoissonInputs(paramf) - outparamf = os.path.join(dconf['datdir'],paramf.split('.param')[0].split(os.path.sep)[-1],'param.txt') - -extinputs = spikefn.ExtInputs(spkpath, outparamf) + params = read_params(paramf) + tstop = params['tstop'] + ntrial = params['N_trials'] + EvokedInputs = paramrw.usingEvokedInputs(params) + OngoingInputs = paramrw.usingOngoingInputs(params) + PoissonInputs = paramrw.usingPoissonInputs(params) + outparamf = os.path.join(dconf['datdir'],params['sim_prefix'],'param.txt') + +extinputs = spikefn.ExtInputs(spkpath, outparamf, params) extinputs.add_delay_times() alldat = {} -ncell = len(net.cells) - binsz = 5.0 smoothsz = 0 # no smoothing @@ -73,9 +72,17 @@ def adjustinputgid (extinputs, gid): return 3 return gid +def gid_to_type(extinputs, gid): + for gidtype, gids in extinputs.gid_dict.items(): + if gid in gids: + return gidtype + def getdspk (fn): ddat = {} try: + spikes = read_spikes(fn) + ddat['spk'] = np.r_[spikes.times, spikes.gids].T + except ValueError: ddat['spk'] = np.loadtxt(fn) except: print('Could not load',fn) @@ -85,7 +92,7 @@ def getdspk (fn): for ty in dclr.keys(): dhist[ty] = [] haveinputs = False for (t,gid) in ddat['spk']: - ty = net.gid_to_type(gid) + ty = gid_to_type(extinputs,gid) if ty in dclr: dspk['Cell'][0].append(t) dspk['Cell'][1].append(gid) @@ -166,6 +173,10 @@ def drawrast (dspk, fig, G, sz=8): axp.set_ylabel('Poisson Input') else: # local circuit neuron spiking + ncell = len(extinputs.gid_dict['L2_pyramidal']) + \ + len(extinputs.gid_dict['L2_basket']) + \ + len(extinputs.gid_dict['L5_pyramidal']) + \ + len(extinputs.gid_dict['L5_basket']) endrow = -1 if bDrawHist: endrow = -4 @@ -207,12 +218,12 @@ def clearaxes (self): pass def loadspk (self,idx): - global haveinputs,extinputs + global haveinputs,extinputs,params if idx in alldat: return alldat[idx] = {} if idx == 0: try: - extinputs = spikefn.ExtInputs(spkpath, outparamf) + extinputs = spikefn.ExtInputs(spkpath, outparamf, params) except ValueError: print("Error: could not load spike timings from %s" % spkpath) return @@ -223,10 +234,10 @@ def loadspk (self,idx): alldat[idx]['dhist'] = dhist alldat[idx]['extinputs'] = extinputs else: - spkpathtrial = os.path.join(dconf['datdir'],paramf.split('.param')[0].split(os.path.sep)[-1],'spk_'+str(self.index-1)+'.txt') + spkpathtrial = os.path.join(dconf['datdir'], params['sim_prefix'], 'spk_'+str(self.index-1)+'.txt') dspktrial,haveinputs,dhisttrial = getdspk(spkpathtrial) # show spikes from first trial try: - extinputs = spikefn.ExtInputs(spkpathtrial, outparamf) + extinputs = spikefn.ExtInputs(spkpathtrial, outparamf, params) except ValueError: print("Error: could not load spike timings from %s" % spkpath) return From 508e9df708ff4b357f6f9ca5c973c5783efef2a4 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Thu, 17 Sep 2020 22:48:19 -0400 Subject: [PATCH 010/107] MAINT: don't use -1 for 'T_pois' param --- param/Alpha.param | 2 +- param/AlphaAndBeta.param | 2 +- param/AlphaAndBeta2.param | 2 +- param/AlphaAndBetaJitter0.param | 2 +- param/AlphaAndBetaSpike.param | 2 +- param/AlphaAndMoreBeta.param | 2 +- param/ERPNo100Trials.param | 2 +- param/ERPYes100Trials.param | 2 +- param/ERPYesSupraT.param | 2 +- param/OnlyRhythmicDist.param | 2 +- param/OnlyRhythmicProx.param | 2 +- param/SRJ_2007_Fig5A_Super.param | 2 +- param/SRJ_2007_Fig5A_ThreshNonP.param | 2 +- param/SRJ_2007_Fig6_ThreshP.param | 2 +- param/default.param | 2 +- param/gamma_L5ping_L2ping.param | 2 +- param/gamma_L5weak_L2weak.param | 2 +- param/gamma_L5weak_L2weak_bursty.param | 2 +- param/gamma_rhythmic_drive.param | 2 +- param/gamma_rhythmic_drive_more_noise.param | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/param/Alpha.param b/param/Alpha.param index 75078df30..ced4c5e2c 100644 --- a/param/Alpha.param +++ b/param/Alpha.param @@ -210,7 +210,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 710.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/AlphaAndBeta.param b/param/AlphaAndBeta.param index ac04295fa..3721bb727 100644 --- a/param/AlphaAndBeta.param +++ b/param/AlphaAndBeta.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 710.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/AlphaAndBeta2.param b/param/AlphaAndBeta2.param index c4a3e6fc0..3dbaa3ed8 100644 --- a/param/AlphaAndBeta2.param +++ b/param/AlphaAndBeta2.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 710.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/AlphaAndBetaJitter0.param b/param/AlphaAndBetaJitter0.param index b24775913..c240802ac 100644 --- a/param/AlphaAndBetaJitter0.param +++ b/param/AlphaAndBetaJitter0.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 710.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/AlphaAndBetaSpike.param b/param/AlphaAndBetaSpike.param index 17a02926a..5b90bcf99 100644 --- a/param/AlphaAndBetaSpike.param +++ b/param/AlphaAndBetaSpike.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 710.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/AlphaAndMoreBeta.param b/param/AlphaAndMoreBeta.param index 483bf52c0..94d347eea 100644 --- a/param/AlphaAndMoreBeta.param +++ b/param/AlphaAndMoreBeta.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 710.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/ERPNo100Trials.param b/param/ERPNo100Trials.param index c9bb9a691..3e21909c8 100644 --- a/param/ERPNo100Trials.param +++ b/param/ERPNo100Trials.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 170.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/ERPYes100Trials.param b/param/ERPYes100Trials.param index dbdd0a27d..222e39db2 100644 --- a/param/ERPYes100Trials.param +++ b/param/ERPYes100Trials.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 170.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/ERPYesSupraT.param b/param/ERPYesSupraT.param index 4bc809cff..08ca4d818 100644 --- a/param/ERPYesSupraT.param +++ b/param/ERPYesSupraT.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 170.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/OnlyRhythmicDist.param b/param/OnlyRhythmicDist.param index 5bf917798..078226e9a 100644 --- a/param/OnlyRhythmicDist.param +++ b/param/OnlyRhythmicDist.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 710.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/OnlyRhythmicProx.param b/param/OnlyRhythmicProx.param index ef75a7ea2..a7e482f18 100644 --- a/param/OnlyRhythmicProx.param +++ b/param/OnlyRhythmicProx.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 710.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/SRJ_2007_Fig5A_Super.param b/param/SRJ_2007_Fig5A_Super.param index a18da02be..a2afb3318 100644 --- a/param/SRJ_2007_Fig5A_Super.param +++ b/param/SRJ_2007_Fig5A_Super.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 170.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/SRJ_2007_Fig5A_ThreshNonP.param b/param/SRJ_2007_Fig5A_ThreshNonP.param index 75f47ad49..86f620b3e 100644 --- a/param/SRJ_2007_Fig5A_ThreshNonP.param +++ b/param/SRJ_2007_Fig5A_ThreshNonP.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 170.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/SRJ_2007_Fig6_ThreshP.param b/param/SRJ_2007_Fig6_ThreshP.param index ef5b15e01..52906329a 100644 --- a/param/SRJ_2007_Fig6_ThreshP.param +++ b/param/SRJ_2007_Fig6_ThreshP.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 170.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/default.param b/param/default.param index d492c869f..51cfaf865 100644 --- a/param/default.param +++ b/param/default.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 170.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/gamma_L5ping_L2ping.param b/param/gamma_L5ping_L2ping.param index 1241e861f..385bc2c74 100644 --- a/param/gamma_L5ping_L2ping.param +++ b/param/gamma_L5ping_L2ping.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 550.0 Itonic_A_L2Pyr_soma: 4.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/gamma_L5weak_L2weak.param b/param/gamma_L5weak_L2weak.param index d4d93cf3e..cff75b722 100644 --- a/param/gamma_L5weak_L2weak.param +++ b/param/gamma_L5weak_L2weak.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 550.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/gamma_L5weak_L2weak_bursty.param b/param/gamma_L5weak_L2weak_bursty.param index c92af3721..b1a514b29 100644 --- a/param/gamma_L5weak_L2weak_bursty.param +++ b/param/gamma_L5weak_L2weak_bursty.param @@ -212,7 +212,7 @@ L5Basket_Pois_A_weight_ampa: 0 L5Basket_Pois_A_weight_nmda: 0 L5Basket_Pois_lamtha: 0 t0_pois: 0.0 -T_pois: -1 +T_pois: 700.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/gamma_rhythmic_drive.param b/param/gamma_rhythmic_drive.param index 4070ebf8c..91032898d 100644 --- a/param/gamma_rhythmic_drive.param +++ b/param/gamma_rhythmic_drive.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 550.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 diff --git a/param/gamma_rhythmic_drive_more_noise.param b/param/gamma_rhythmic_drive_more_noise.param index 495717abc..c7438caff 100644 --- a/param/gamma_rhythmic_drive_more_noise.param +++ b/param/gamma_rhythmic_drive_more_noise.param @@ -211,7 +211,7 @@ L5Basket_Pois_A_weight_ampa: 0.0 L5Basket_Pois_A_weight_nmda: 0.0 L5Basket_Pois_lamtha: 0.0 t0_pois: 0.0 -T_pois: -1 +T_pois: 550.0 Itonic_A_L2Pyr_soma: 0.0 Itonic_t0_L2Pyr_soma: 0.0 Itonic_T_L2Pyr_soma: -1.0 From af141a0363465d44220064cd24131c5da1afcd08 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Thu, 17 Sep 2020 23:56:40 -0400 Subject: [PATCH 011/107] MAINT: deprecate unused code --- L2_basket.py | 215 ------ L2_pyramidal.py | 618 ---------------- L5_basket.py | 213 ------ L5_pyramidal.py | 720 ------------------ PT_example.py | 42 -- ac_manu_gamma.py | 1166 ----------------------------- averaging.py | 69 -- axes_create.py | 832 --------------------- cartesian.py | 70 -- cell.py | 402 ---------- cfg.py | 76 -- cli.py | 1064 --------------------------- clidefs.py | 1499 ------------------------------------- conf.py | 13 +- ctune.py | 361 --------- dipolefn.py | 1123 ---------------------------- example_analysis.py | 74 -- feed.py | 224 ------ fileio.py | 368 ---------- filt.py | 444 ----------- hnn_nrnui.py | 52 -- init.py | 8 - lfp.py | 274 ------- loadmodel_nrnui.py | 6 - morphology.py | 624 ---------------- netParams.py | 127 ---- network.py | 426 ----------- nsgr.py | 270 ------- paramrw.py | 614 +--------------- params_default.py | 365 --------- plotfn.py | 196 ----- pmanu_gamma.py | 1713 ------------------------------------------- ppsth.py | 230 ------ praster.py | 67 -- praw.py | 154 ---- pspec.py | 289 -------- ptest.py | 22 - run.py | 5 - seg3d.py | 91 --- specfn.py | 572 --------------- spikefn.py | 58 -- visdipole.py | 2 - vislfp.py | 301 -------- visnet.py | 231 ------ vispsd.py | 5 +- visrast.py | 16 +- visvolt.py | 1 - 47 files changed, 35 insertions(+), 16277 deletions(-) delete mode 100644 L2_basket.py delete mode 100644 L2_pyramidal.py delete mode 100644 L5_basket.py delete mode 100644 L5_pyramidal.py delete mode 100644 PT_example.py delete mode 100644 ac_manu_gamma.py delete mode 100644 averaging.py delete mode 100644 axes_create.py delete mode 100644 cartesian.py delete mode 100644 cell.py delete mode 100644 cfg.py delete mode 100644 cli.py delete mode 100644 clidefs.py delete mode 100644 ctune.py delete mode 100644 dipolefn.py delete mode 100644 example_analysis.py delete mode 100644 feed.py delete mode 100644 fileio.py delete mode 100644 filt.py delete mode 100644 hnn_nrnui.py delete mode 100644 init.py delete mode 100644 lfp.py delete mode 100644 loadmodel_nrnui.py delete mode 100644 morphology.py delete mode 100644 netParams.py delete mode 100644 network.py delete mode 100644 nsgr.py delete mode 100644 params_default.py delete mode 100644 plotfn.py delete mode 100644 pmanu_gamma.py delete mode 100644 ppsth.py delete mode 100644 praster.py delete mode 100644 praw.py delete mode 100644 pspec.py delete mode 100644 ptest.py delete mode 100644 seg3d.py delete mode 100644 vislfp.py delete mode 100644 visnet.py diff --git a/L2_basket.py b/L2_basket.py deleted file mode 100644 index 801192d55..000000000 --- a/L2_basket.py +++ /dev/null @@ -1,215 +0,0 @@ -# L2_basket.py - establish class def for layer 2 basket cells -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: removed dependence on it.izip) -# last rev: (SL: toward python3) - -from neuron import h as nrn -from cell import BasketSingle - -# Units for e: mV -# Units for gbar: S/cm^2 unless otherwise noted - -# Layer 2 basket cell class -class L2Basket(BasketSingle): - def __init__(self, gid = -1, pos = -1): - # BasketSingle.__init__(self, pos, L, diam, Ra, cm) - # Note: Basket cell properties set in BasketSingle()) - BasketSingle.__init__(self, gid, pos, 'L2Basket') - self.celltype = 'L2_basket' - - self.__synapse_create() - self.__biophysics() - - # creation of synapses - def __synapse_create(self): - # creates synapses onto this cell - self.soma_ampa = self.syn_ampa_create(self.soma(0.5)) - self.soma_gabaa = self.syn_gabaa_create(self.soma(0.5)) - self.soma_nmda = self.syn_nmda_create(self.soma(0.5)) - - def __biophysics(self): - self.soma.insert('hh2') - - # insert IClamps in all situations - def create_all_IClamp(self, p): - # list of sections for this celltype - sect_list_IClamp = [ - 'soma', - ] - - # some parameters - t_delay = p['Itonic_t0_L2Basket'] - - # T = -1 means use nrn.tstop - if p['Itonic_T_L2Basket'] == -1: - t_dur = nrn.tstop - t_delay - - else: - t_dur = p['Itonic_T_L2Basket'] - t_delay - - # t_dur must be nonnegative, I imagine - if t_dur < 0.: - t_dur = 0. - - # properties of the IClamp - props_IClamp = { - 'loc': 0.5, - 'delay': t_delay, - 'dur': t_dur, - 'amp': p['Itonic_A_L2Basket'] - } - - # iterate through list of sect_list_IClamp to create a persistent IClamp object - # the insert_IClamp procedure is in Cell() and checks on names - # so names must be actual section names, or else it will fail silently - # self.list_IClamp as a variable is guaranteed in Cell() - self.list_IClamp = [self.insert_IClamp(sect_name, props_IClamp) for sect_name in sect_list_IClamp] - - # par connect between all presynaptic cells - # no connections from L5Pyr or L5Basket to L2Baskets - def parconnect(self, gid, gid_dict, pos_dict, p): - # FROM L2 pyramidals TO this cell - for gid_src, pos in zip(gid_dict['L2_pyramidal'], pos_dict['L2_pyramidal']): - nc_dict = { - 'pos_src': pos, - 'A_weight': p['gbar_L2Pyr_L2Basket'], - 'A_delay': 1., - 'lamtha': 3., - 'threshold': p['threshold'], - 'type_src' : 'L2_pyramidal' - } - - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict, self.soma_ampa)) - - # FROM other L2Basket cells - for gid_src, pos in zip(gid_dict['L2_basket'], pos_dict['L2_basket']): - # no autapses - # if gid_src != gid: - nc_dict = { - 'pos_src': pos, - 'A_weight': p['gbar_L2Basket_L2Basket'], - 'A_delay': 1., - 'lamtha': 20., - 'threshold': p['threshold'], - 'type_src' : 'L2_basket' - } - - self.ncfrom_L2Basket.append(self.parconnect_from_src(gid_src, nc_dict, self.soma_gabaa)) - - # this function might make more sense as a method of net? - # par: receive from external inputs - def parreceive(self, gid, gid_dict, pos_dict, p_ext): - # for some gid relating to the input feed: - for gid_src, p_src, pos in zip(gid_dict['extinput'], p_ext, pos_dict['extinput']): - # check if AMPA params are defined in the p_src - if 'L2Basket_ampa' in p_src.keys(): - # create an nc_dict - nc_dict_ampa = { - 'pos_src': pos, - 'A_weight': p_src['L2Basket_ampa'][0], - 'A_delay': p_src['L2Basket_ampa'][1], - 'lamtha': p_src['lamtha'], - 'threshold': p_src['threshold'], - 'type_src' : 'ext' - } - - # AMPA synapse - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_ampa, self.soma_ampa)) - - # Check if NMDA params are defined in p_src - if 'L2Basket_nmda' in p_src.keys(): - nc_dict_nmda = { - 'pos_src': pos, - 'A_weight': p_src['L2Basket_nmda'][0], - 'A_delay': p_src['L2Basket_nmda'][1], - 'lamtha': p_src['lamtha'], - 'threshold': p_src['threshold'], - 'type_src' : 'ext' - } - - # NMDA synapse - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_nmda, self.soma_nmda)) - - # one parreceive function to handle all types of external parreceives - # types must be defined explicitly here - def parreceive_ext(self, type, gid, gid_dict, pos_dict, p_ext): - if type.startswith(('evprox', 'evdist')): - if self.celltype in p_ext.keys(): - gid_ev = gid + gid_dict[type][0] - - nc_dict_ampa = { - 'pos_src': pos_dict[type][gid], - 'A_weight': p_ext[self.celltype][0], # index 0 is ampa weight - 'A_delay': p_ext[self.celltype][2], # index 2 is delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - nc_dict_nmda = { - 'pos_src': pos_dict[type][gid], - 'A_weight': p_ext[self.celltype][1], # index 1 is nmda weight - 'A_delay': p_ext[self.celltype][2], # index 2 is delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - # connections depend on location of input - why only for L2 basket and not L5 basket? - if p_ext['loc'] is 'proximal': - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_ampa, self.soma_ampa)) - # NEW: note that default/original is 0 nmda weight for the soma (for prox evoked) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_nmda, self.soma_nmda)) - - elif p_ext['loc'] is 'distal': - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_ampa, self.soma_ampa)) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_nmda, self.soma_nmda)) - - elif type == 'extgauss': - # gid is this cell's gid - # gid_dict is the whole dictionary, including the gids of the extgauss - # pos_list is also the pos of the extgauss (net origin) - # p_ext_gauss are the params (strength, etc.) - # I recognize this is ugly (hack) - if self.celltype in p_ext.keys(): - # since gid ids are unique, then these will all be shifted. - # if order of extgauss random feeds ever matters (likely) - # then will have to preserve order - # of creation based on gid ids of the cells - # this is a dumb place to put this information - gid_extgauss = gid + gid_dict['extgauss'][0] - - # gid works here because there are as many pos items in pos_dict['extgauss'] as there are cells - nc_dict = { - 'pos_src': pos_dict['extgauss'][gid], - 'A_weight': p_ext[self.celltype][0], # index 0 is ampa weight - 'A_delay': p_ext[self.celltype][1], # index 2 is delay - 'lamtha': p_ext['lamtha'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - self.ncfrom_extgauss.append(self.parconnect_from_src(gid_extgauss, nc_dict, self.soma_ampa)) - - elif type == 'extpois': - if self.celltype in p_ext.keys(): - gid_extpois = gid + gid_dict['extpois'][0] - - nc_dict = { - 'pos_src': pos_dict['extpois'][gid], - 'A_weight': p_ext[self.celltype][0], # index 0 is ampa weight - 'A_delay': p_ext[self.celltype][2], # index 2 is delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois, nc_dict, self.soma_ampa)) - - if p_ext[self.celltype][1] > 0.0: - nc_dict['A_weight'] = p_ext[self.celltype][1] # index 1 for nmda weight - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois, nc_dict, self.soma_nmda)) - - else: - print("Warning, type def not specified in L2Basket") diff --git a/L2_pyramidal.py b/L2_pyramidal.py deleted file mode 100644 index ce5d95d42..000000000 --- a/L2_pyramidal.py +++ /dev/null @@ -1,618 +0,0 @@ -# L2_pyramidal.py - est class def for layer 2 pyramidal cells -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: removed dep on it.izip) -# last rev: (SL: toward python3, moved cells) - -import sys -import os -import numpy as np - -from neuron import h -from cell import Pyr -import paramrw -import params_default as p_default - -# Units for e: mV -# Units for gbar: S/cm^2 unless otherwise noted - -# Layer 2 pyramidal cell class -class L2Pyr(Pyr): - - def __init__(self, gid = -1, pos = -1, p={}): - # Get default L2Pyr params and update them with any corresponding params in p - p_all_default = p_default.get_L2Pyr_params_default() - self.p_all = paramrw.compare_dictionaries(p_all_default, p) - - # Get somatic, dendritic, and synapse properties - p_soma = self.__get_soma_props(pos) - p_dend = self.__get_dend_props() - p_syn = self.__get_syn_props() - # p_dend_props, dend_names = self.__get_dend_props() - - # usage: Pyr.__init__(self, soma_props) - Pyr.__init__(self, gid, p_soma) - self.celltype = 'L2_pyramidal' - - # geometry - # creates dict of dends: self.dends - self.create_dends(p_dend) - self.topol() # sets the connectivity between sections - self.geom(p_dend) # sets geom properties; adjusted after translation from hoc (2009 model) - - # biophysics - self.__biophys_soma() - self.__biophys_dends() - - # dipole_insert() comes from Cell() - self.yscale = self.get_sectnames() - self.dipole_insert(self.yscale) - - # create synapses - self.__synapse_create(p_syn) - # self.__synapse_create() - - # run record_current_soma(), defined in Cell() - self.record_current_soma() - - # insert IClamps in all situations - # temporarily an external function taking the p dict - def create_all_IClamp(self, p): - # list of sections for this celltype - sect_list_IClamp = [ - 'soma', - ] - - # some parameters - t_delay = p['Itonic_t0_L2Pyr_soma'] - - # T = -1 means use h.tstop - if p['Itonic_T_L2Pyr_soma'] == -1: - # t_delay = 50. - t_dur = h.tstop - t_delay - - else: - t_dur = p['Itonic_T_L2Pyr_soma'] - t_delay - - # t_dur must be nonnegative, I imagine - if t_dur < 0.: - t_dur = 0. - - # properties of the IClamp - props_IClamp = { - 'loc': 0.5, - 'delay': t_delay, - 'dur': t_dur, - 'amp': p['Itonic_A_L2Pyr_soma'] - } - - # iterate through list of sect_list_IClamp to create a persistent IClamp object - # the insert_IClamp procedure is in Cell() and checks on names - # so names must be actual section names, or else it will fail silently - self.list_IClamp = [self.insert_IClamp(sect_name, props_IClamp) for sect_name in sect_list_IClamp] - - # Returns hardcoded somatic properties - def __get_soma_props(self, pos): - return { - 'pos': pos, - 'L': self.p_all['L2Pyr_soma_L'], - 'diam': self.p_all['L2Pyr_soma_diam'], - 'cm': self.p_all['L2Pyr_soma_cm'], - 'Ra': self.p_all['L2Pyr_soma_Ra'], - 'name': 'L2Pyr', - } - - # Returns hardcoded dendritic properties - def __get_dend_props(self): - return { - 'apical_trunk': { - 'L': self.p_all['L2Pyr_apicaltrunk_L'] , - 'diam': self.p_all['L2Pyr_apicaltrunk_diam'], - 'cm': self.p_all['L2Pyr_dend_cm'], - 'Ra': self.p_all['L2Pyr_dend_Ra'], - }, - 'apical_1': { - 'L': self.p_all['L2Pyr_apical1_L'], - 'diam': self.p_all['L2Pyr_apical1_diam'], - 'cm': self.p_all['L2Pyr_dend_cm'], - 'Ra': self.p_all['L2Pyr_dend_Ra'], - }, - 'apical_tuft': { - 'L': self.p_all['L2Pyr_apicaltuft_L'], - 'diam': self.p_all['L2Pyr_apicaltuft_diam'], - 'cm': self.p_all['L2Pyr_dend_cm'], - 'Ra': self.p_all['L2Pyr_dend_Ra'], - }, - 'apical_oblique': { - 'L': self.p_all['L2Pyr_apicaloblique_L'], - 'diam': self.p_all['L2Pyr_apicaloblique_diam'], - 'cm': self.p_all['L2Pyr_dend_cm'], - 'Ra': self.p_all['L2Pyr_dend_Ra'], - }, - 'basal_1': { - 'L': self.p_all['L2Pyr_basal1_L'], - 'diam': self.p_all['L2Pyr_basal1_diam'], - 'cm': self.p_all['L2Pyr_dend_cm'], - 'Ra': self.p_all['L2Pyr_dend_Ra'], - }, - 'basal_2': { - 'L': self.p_all['L2Pyr_basal2_L'], - 'diam': self.p_all['L2Pyr_basal2_diam'], - 'cm': self.p_all['L2Pyr_dend_cm'], - 'Ra': self.p_all['L2Pyr_dend_Ra'], - }, - 'basal_3': { - 'L': self.p_all['L2Pyr_basal3_L'], - 'diam': self.p_all['L2Pyr_basal3_diam'], - 'cm': self.p_all['L2Pyr_dend_cm'], - 'Ra': self.p_all['L2Pyr_dend_Ra'], - }, - } - - # This order matters! - # dend_order = ['apical_trunk', 'apical_1', 'apical_tuft', 'apical_oblique', - # 'basal_1', 'basal_2', 'basal_3'] - - # return dend_props, dend_order - - def __get_syn_props(self): - return { - 'ampa': { - 'e': self.p_all['L2Pyr_ampa_e'], - 'tau1': self.p_all['L2Pyr_ampa_tau1'], - 'tau2': self.p_all['L2Pyr_ampa_tau2'], - }, - 'nmda': { - 'e': self.p_all['L2Pyr_nmda_e'], - 'tau1': self.p_all['L2Pyr_nmda_tau1'], - 'tau2': self.p_all['L2Pyr_nmda_tau2'], - }, - 'gabaa': { - 'e': self.p_all['L2Pyr_gabaa_e'], - 'tau1': self.p_all['L2Pyr_gabaa_tau1'], - 'tau2': self.p_all['L2Pyr_gabaa_tau2'], - }, - 'gabab': { - 'e': self.p_all['L2Pyr_gabab_e'], - 'tau1': self.p_all['L2Pyr_gabab_tau1'], - 'tau2': self.p_all['L2Pyr_gabab_tau2'], - } - } - - def geom (self, p_dend): - soma = self.soma; dend = self.list_dend; - # increased by 70% for human - soma.L = 22.1 - dend[0].L = 59.5 - dend[1].L = 340 - dend[2].L = 306 - dend[3].L = 238 - dend[4].L = 85 - dend[5].L = 255 - dend[6].L = 255 - soma.diam = 23.4 - dend[0].diam = 4.25 - dend[1].diam = 3.91 - dend[2].diam = 4.08 - dend[3].diam = 3.4 - dend[4].diam = 4.25 - dend[5].diam = 2.72 - dend[6].diam = 2.72 - self.set_dend_props(p_dend) # resets length,diam,etc. based on param specification - - # Connects sections of THIS cell together - def topol (self): - """ original topol - connect dend(0), soma(1) - for i = 1, 2 connect dend[i](0), dend(1) - connect dend[3](0), dend[2](1) - connect dend[4](0), soma(0) //was soma(1), 0 is correct! - for i = 5, 6 connect dend[i](0), dend[4](1) - - """ - - # child.connect(parent, parent_end, {child_start=0}) - # Distal (Apical) - self.dends['apical_trunk'].connect(self.soma, 1, 0) - self.dends['apical_1'].connect(self.dends['apical_trunk'], 1, 0) - self.dends['apical_tuft'].connect(self.dends['apical_1'], 1, 0) - - # apical_oblique comes off distal end of apical_trunk - self.dends['apical_oblique'].connect(self.dends['apical_trunk'], 1, 0) - - # Proximal (basal) - self.dends['basal_1'].connect(self.soma, 0, 0) - self.dends['basal_2'].connect(self.dends['basal_1'], 1, 0) - self.dends['basal_3'].connect(self.dends['basal_1'], 1, 0) - - self.basic_shape() # translated from original hoc (2009 model) - - def basic_shape (self): - # THESE AND LENGHTHS MUST CHANGE TOGETHER!!! - pt3dclear=h.pt3dclear; pt3dadd=h.pt3dadd; soma = self.soma; dend = self.list_dend - pt3dclear(sec=soma); pt3dadd(-50, 765, 0, 1,sec=soma); pt3dadd(-50, 778, 0, 1,sec=soma) - pt3dclear(sec=dend[0]); pt3dadd(-50, 778, 0, 1,sec=dend[0]); pt3dadd(-50, 813, 0, 1,sec=dend[0]) - pt3dclear(sec=dend[1]); pt3dadd(-50, 813, 0, 1,sec=dend[1]); pt3dadd(-250, 813, 0, 1,sec=dend[1]) - pt3dclear(sec=dend[2]); pt3dadd(-50, 813, 0, 1,sec=dend[2]); pt3dadd(-50, 993, 0, 1,sec=dend[2]) - pt3dclear(sec=dend[3]); pt3dadd(-50, 993, 0, 1,sec=dend[3]); pt3dadd(-50, 1133, 0, 1,sec=dend[3]) - pt3dclear(sec=dend[4]); pt3dadd(-50, 765, 0, 1,sec=dend[4]); pt3dadd(-50, 715, 0, 1,sec=dend[4]) - pt3dclear(sec=dend[5]); pt3dadd(-50, 715, 0, 1,sec=dend[5]); pt3dadd(-156, 609, 0, 1,sec=dend[5]) - pt3dclear(sec=dend[6]); pt3dadd(-50, 715, 0, 1,sec=dend[6]); pt3dadd(56, 609, 0, 1,sec=dend[6]) - - # Adds biophysics to soma - def __biophys_soma (self): - # set soma biophysics specified in Pyr - # self.pyr_biophys_soma() - - # Insert 'hh2' mechanism - self.soma.insert('hh2') - self.soma.gkbar_hh2 = self.p_all['L2Pyr_soma_gkbar_hh2'] - self.soma.gl_hh2 = self.p_all['L2Pyr_soma_gl_hh2'] - self.soma.el_hh2 = self.p_all['L2Pyr_soma_el_hh2'] - self.soma.gnabar_hh2 = self.p_all['L2Pyr_soma_gnabar_hh2'] - - # Insert 'km' mechanism - # Units: pS/um^2 - self.soma.insert('km') - self.soma.gbar_km = self.p_all['L2Pyr_soma_gbar_km'] - - # Defining biophysics for dendrites - def __biophys_dends (self): - # set dend biophysics - # iterate over keys in self.dends and set biophysics for each dend - for key in self.dends: - # neuron syntax is used to set values for mechanisms - # sec.gbar_mech = x sets value of gbar for mech to x for all segs - # in a section. This method is significantly faster than using - # a for loop to iterate over all segments to set mech values - - # Insert 'hh' mechanism - self.dends[key].insert('hh2') - self.dends[key].gkbar_hh2 = self.p_all['L2Pyr_dend_gkbar_hh2'] - self.dends[key].gl_hh2 = self.p_all['L2Pyr_dend_gl_hh2'] - self.dends[key].gnabar_hh2 = self.p_all['L2Pyr_dend_gnabar_hh2'] - self.dends[key].el_hh2 = self.p_all['L2Pyr_dend_el_hh2'] - - # Insert 'km' mechanism - # Units: pS/um^2 - self.dends[key].insert('km') - self.dends[key].gbar_km = self.p_all['L2Pyr_dend_gbar_km'] - - def __synapse_create (self, p_syn): - # creates synapses onto this cell - # Somatic synapses - self.synapses = { - 'soma_gabaa': self.syn_create(self.soma(0.5), p_syn['gabaa']), - 'soma_gabab': self.syn_create(self.soma(0.5), p_syn['gabab']), - } - - # Dendritic synapses - self.apicaloblique_ampa = self.syn_create(self.dends['apical_oblique'](0.5), p_syn['ampa']) - self.apicaloblique_nmda = self.syn_create(self.dends['apical_oblique'](0.5), p_syn['nmda']) - - self.basal2_ampa = self.syn_create(self.dends['basal_2'](0.5), p_syn['ampa']) - self.basal2_nmda = self.syn_create(self.dends['basal_2'](0.5), p_syn['nmda']) - - self.basal3_ampa = self.syn_create(self.dends['basal_3'](0.5), p_syn['ampa']) - self.basal3_nmda = self.syn_create(self.dends['basal_3'](0.5), p_syn['nmda']) - - self.apicaltuft_ampa = self.syn_create(self.dends['apical_tuft'](0.5), p_syn['ampa']) - self.apicaltuft_nmda = self.syn_create(self.dends['apical_tuft'](0.5), p_syn['nmda']) - - # self.synapses = { - # 'soma_gabaa': self.syn_gabaa_create(self.soma(0.5)), - # 'soma_gabab': self.syn_gabab_create(self.soma(0.5)), - # } - - # Dendritic synapses - # self.apicaloblique_ampa = self.syn_ampa_create(self.dends['apical_oblique'](0.5), p_syn['ampa']) - # self.apicaloblique_nmda = self.syn_create(self.dends['apical_oblique'](0.5), p_syn['nmda']) - - # self.basal2_ampa = self.syn_ampa_create(self.dends['basal_2'](0.5)) - # self.basal2_nmda = self.syn_nmda_create(self.dends['basal_2'](0.5)) - - # self.basal3_ampa = self.syn_ampa_create(self.dends['basal_3'](0.5)) - # self.basal3_nmda = self.syn_nmda_create(self.dends['basal_3'](0.5)) - - # self.apicaltuft_ampa = self.syn_ampa_create(self.dends['apical_tuft'](0.5)) - # self.apicaltuft_nmda = self.syn_nmda_create(self.dends['apical_tuft'](0.5)) - - # collect receptor-type-based connections here - def parconnect (self, gid, gid_dict, pos_dict, p): - # init dict of dicts - # nc_dict for ampa and nmda may be the same for this cell type - nc_dict = { - 'ampa': None, - 'nmda': None, - } - - # Connections FROM all other L2 Pyramidal cells to this one - for gid_src, pos in zip(gid_dict['L2_pyramidal'], pos_dict['L2_pyramidal']): - # don't be redundant, this is only possible for LIKE cells, but it might not hurt to check - if gid_src != gid: - nc_dict['ampa'] = { - 'pos_src': pos, - 'A_weight': p['gbar_L2Pyr_L2Pyr_ampa'], - 'A_delay': 1., - 'lamtha': 3., - 'threshold': p['threshold'], - 'type_src' : 'L2_pyramidal' - } - - # parconnect_from_src(gid_presyn, nc_dict, postsyn) - # ampa connections - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict['ampa'], self.apicaloblique_ampa)) - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict['ampa'], self.basal2_ampa)) - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict['ampa'], self.basal3_ampa)) - - nc_dict['nmda'] = { - 'pos_src': pos, - 'A_weight': p['gbar_L2Pyr_L2Pyr_nmda'], - 'A_delay': 1., - 'lamtha': 3., - 'threshold': p['threshold'], - 'type_src' : 'L2_pyramidal' - } - - # parconnect_from_src(gid_presyn, nc_dict, postsyn) - # nmda connections - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict['nmda'], self.apicaloblique_nmda)) - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict['nmda'], self.basal2_nmda)) - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict['nmda'], self.basal3_nmda)) - - # connections FROM L2 basket cells TO this L2Pyr cell - for gid_src, pos in zip(gid_dict['L2_basket'], pos_dict['L2_basket']): - nc_dict['gabaa'] = { - 'pos_src': pos, - 'A_weight': p['gbar_L2Basket_L2Pyr_gabaa'], - 'A_delay': 1., - 'lamtha': 50., - 'threshold': p['threshold'], - 'type_src' : 'L2_basket' - } - - nc_dict['gabab'] = { - 'pos_src': pos, - 'A_weight': p['gbar_L2Basket_L2Pyr_gabab'], - 'A_delay': 1., - 'lamtha': 50., - 'threshold': p['threshold'], - 'type_src' : 'L2_basket' - } - - self.ncfrom_L2Basket.append(self.parconnect_from_src(gid_src, nc_dict['gabaa'], self.synapses['soma_gabaa'])) - self.ncfrom_L2Basket.append(self.parconnect_from_src(gid_src, nc_dict['gabab'], self.synapses['soma_gabab'])) - - # connections FROM L5 basket cells TO this L2Pyr cell - # for gid_src in gid_dict['L5_basket']: - # nc_dict = { - # 'pos_src': pos_list[gid_src], - # 'A_weight': 2.5e-2, - # 'A_delay': 1., - # 'lamtha': 70. - # } - - # self.ncfrom_L5Basket.append(self.parconnect_from_src(gid_src, nc_dict, self.synapes['soma_gabaa'])) - # self.ncfrom_L5Basket.append(self.parconnect_from_src(gid_src, nc_dict, self.synapes['soma_gabab'])) - - # may be reorganizable - def parreceive (self, gid, gid_dict, pos_dict, p_ext): - for gid_src, p_src, pos in zip(gid_dict['extinput'], p_ext, pos_dict['extinput']): - # Check if AMPA params defined in p_src - if 'L2Pyr_ampa' in p_src.keys(): - nc_dict_ampa = { - 'pos_src': pos, - 'A_weight': p_src['L2Pyr_ampa'][0], - 'A_delay': p_src['L2Pyr_ampa'][1], - 'lamtha': p_src['lamtha'], - 'threshold': p_src['threshold'], - 'type_src': 'ext' - } - - # Proximal feed AMPA synapses - if p_src['loc'] is 'proximal': - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_ampa, self.basal2_ampa)) - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_ampa, self.basal3_ampa)) - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_ampa, self.apicaloblique_ampa)) - # Distal feed AMPA synapses - elif p_src['loc'] is 'distal': - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_ampa, self.apicaltuft_ampa)) - - # Check is NMDA params defined in p_src - if 'L2Pyr_nmda' in p_src.keys(): - nc_dict_nmda = { - 'pos_src': pos, - 'A_weight': p_src['L2Pyr_nmda'][0], - 'A_delay': p_src['L2Pyr_nmda'][1], - 'lamtha': p_src['lamtha'], - 'threshold': p_src['threshold'], - 'type_src': 'ext' - } - - # Proximal feed NMDA synapses - if p_src['loc'] is 'proximal': - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_nmda, self.basal2_nmda)) - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_nmda, self.basal3_nmda)) - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_nmda, self.apicaloblique_nmda)) - # Distal feed NMDA synapses - elif p_src['loc'] is 'distal': - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_nmda, self.apicaltuft_nmda)) - - # one parreceive function to handle all types of external parreceives - # types must be defined explicitly here - # this function handles evoked, gaussian, and poisson inputs - def parreceive_ext (self, type, gid, gid_dict, pos_dict, p_ext): - if type.startswith(('evprox', 'evdist')): - if self.celltype in p_ext.keys(): - gid_ev = gid + gid_dict[type][0] - - # separate dictionaries for ampa and nmda evoked inputs - nc_dict_ampa = { - 'pos_src': pos_dict[type][gid], - 'A_weight': p_ext[self.celltype][0], # index 0 for ampa weight - 'A_delay': p_ext[self.celltype][2], # index 2 for delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src': type - } - - nc_dict_nmda = { - 'pos_src': pos_dict[type][gid], - 'A_weight': p_ext[self.celltype][1], # index 1 for nmda weight - 'A_delay': p_ext[self.celltype][2], # index 2 for delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src': type - } - - if p_ext['loc'] is 'proximal': - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_ampa, self.basal2_ampa)) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_ampa, self.basal3_ampa)) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_ampa, self.apicaloblique_ampa)) - - # NEW: note that default/original is 0 nmda weight for these proximal dends - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_nmda, self.basal2_nmda)) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_nmda, self.basal3_nmda)) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_nmda, self.apicaloblique_nmda)) - - elif p_ext['loc'] is 'distal': - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_ampa, self.apicaltuft_ampa)) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_nmda, self.apicaltuft_nmda)) - - elif type == 'extgauss': - # gid is this cell's gid - # gid_dict is the whole dictionary, including the gids of the extgauss - # pos_list is also the pos of the extgauss (net origin) - # p_ext_gauss are the params (strength, etc.) - - # gid shift is based on L2_pyramidal cells NOT L5 - # I recognize this is ugly (hack) - # gid_shift = gid_dict['extgauss'][0] - gid_dict['L2_pyramidal'][0] - if 'L2_pyramidal' in p_ext.keys(): - gid_extgauss = gid + gid_dict['extgauss'][0] - - nc_dict = { - 'pos_src': pos_dict['extgauss'][gid], - 'A_weight': p_ext['L2_pyramidal'][0], # index 0 for ampa weight (nmda not yet used in Gauss) - 'A_delay': p_ext['L2_pyramidal'][2], # index 2 for delay - 'lamtha': p_ext['lamtha'], - 'threshold': p_ext['threshold'], - 'type_src': type - } - - self.ncfrom_extgauss.append(self.parconnect_from_src(gid_extgauss,nc_dict,self.basal2_ampa)) - self.ncfrom_extgauss.append(self.parconnect_from_src(gid_extgauss,nc_dict,self.basal3_ampa)) - self.ncfrom_extgauss.append(self.parconnect_from_src(gid_extgauss,nc_dict,self.apicaloblique_ampa)) - - elif type == 'extpois': - if self.celltype in p_ext.keys(): - gid_extpois = gid + gid_dict['extpois'][0] - - nc_dict = { - 'pos_src': pos_dict['extpois'][gid], - 'A_weight': p_ext[self.celltype][0], # index 0 for ampa weight - 'A_delay': p_ext[self.celltype][2], # index 2 for delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src': type - } - - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois,nc_dict,self.basal2_ampa)) - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois,nc_dict,self.basal3_ampa)) - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois,nc_dict,self.apicaloblique_ampa)) - - if p_ext[self.celltype][1] > 0.0: - nc_dict['A_weight'] = p_ext[self.celltype][1] # index 1 for nmda weight - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois,nc_dict,self.basal2_nmda)) - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois,nc_dict,self.basal3_nmda)) - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois,nc_dict,self.apicaloblique_nmda)) - - else: - print("Warning, ext type def does not exist in L2Pyr") - - # Define 3D shape and position of cell. By default neuron uses xy plane for - # height and xz plane for depth. This is opposite for model as a whole, but - # convention is followed in this function for ease use of gui. - def __set_3Dshape (self): - # set 3d shape of soma by calling shape_soma from class Cell - # print("Warning: You are setiing 3d shape geom. You better be doing") - # print("gui analysis and not numerical analysis!!") - self.shape_soma() - - # soma proximal coords - x_prox = 0 - y_prox = 0 - - # soma distal coords - x_distal = 0 - y_distal = self.soma.L - - # dend 0-2 are major axis, dend 3 is branch - # deal with distal first along major cable axis - # the way this is assigning variables is ugly/lazy right now - for i in range(0, 3): - h.pt3dclear(sec=self.list_dend[i]) - - # x_distal and y_distal are the starting points for each segment - # these are updated at the end of the loop - h.pt3dadd(0, y_distal, 0, self.dend_diam[i], sec=self.list_dend[i]) - - # update x_distal and y_distal after setting them - # x_distal += dend_dx[i] - y_distal += self.dend_L[i] - - # add next point - h.pt3dadd(0, y_distal, 0, self.dend_diam[i], sec=self.list_dend[i]) - - # now deal with dend 3 - # dend 3 will ALWAYS be positioned at the end of dend[0] - h.pt3dclear(sec=self.list_dend[3]) - - # activate this section with 'sec =' notation - # self.list_dend[0].push() - x_start = h.x3d(1, sec = self.list_dend[0]) - y_start = h.y3d(1, sec = self.list_dend[0]) - # h.pop_section() - - h.pt3dadd(x_start, y_start, 0, self.dend_diam[3], sec=self.list_dend[3]) - # self.dend_L[3] is subtracted because lengths always positive, - # and this goes to negative x - h.pt3dadd(x_start-self.dend_L[3], y_start, 0, self.dend_diam[3], sec=self.list_dend[3]) - - # now deal with proximal dends - for i in range(4, 7): - h.pt3dclear(sec=self.list_dend[i]) - - # deal with dend 4, ugly. sorry. - h.pt3dadd(x_prox, y_prox, 0, self.dend_diam[i], sec=self.list_dend[4]) - y_prox += -self.dend_L[4] - - h.pt3dadd(x_prox, y_prox, 0, self.dend_diam[4], sec=self.list_dend[4]) - - # x_prox, y_prox are now the starting points for BOTH last 2 sections - - # dend 5 - # Calculate x-coordinate for end of dend - dend5_x = -self.dend_L[5] * np.sqrt(2) / 2. - h.pt3dadd(x_prox, y_prox, 0, self.dend_diam[5], sec=self.list_dend[5]) - h.pt3dadd(dend5_x, y_prox-self.dend_L[5] * np.sqrt(2) / 2., - 0, self.dend_diam[5], sec=self.list_dend[5]) - - # dend 6 - # Calculate x-coordinate for end of dend - dend6_x = self.dend_L[6] * np.sqrt(2) / 2. - h.pt3dadd(x_prox, y_prox, 0, self.dend_diam[6], sec=self.list_dend[6]) - h.pt3dadd(dend6_x, y_prox-self.dend_L[6] * np.sqrt(2) / 2., - 0, self.dend_diam[6], sec=self.list_dend[6]) - - # set 3D position - # z grid position used as y coordinate in h.pt3dchange() to satisfy - # gui convention that y is height and z is depth. In h.pt3dchange() - # x and z components are scaled by 100 for visualization clarity - self.soma.push() - for i in range(0, int(h.n3d())): - h.pt3dchange(i, self.pos[0]*100 + h.x3d(i), self.pos[2] + - h.y3d(i), self.pos[1] * 100 + h.z3d(i), - h.diam3d(i)) - - h.pop_section() diff --git a/L5_basket.py b/L5_basket.py deleted file mode 100644 index b69fe1a70..000000000 --- a/L5_basket.py +++ /dev/null @@ -1,213 +0,0 @@ -# L5_basket.py - establish class def for layer 5 basket cells -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: removed izip dep) -# last rev: (SL: toward python3) - -from neuron import h as nrn -from cell import BasketSingle - -# Units for e: mV -# Units for gbar: S/cm^2 unless otherwise noted - -# Layer 5 basket cell class -class L5Basket(BasketSingle): - def __init__(self, gid = -1, pos = -1): - # Note: Cell properties are set in BasketSingle() - BasketSingle.__init__(self, gid, pos, 'L5Basket') - self.celltype = 'L5_basket' - - self.__synapse_create() - self.__biophysics() - - # creates synapses - def __synapse_create(self): - # creates synapses onto this cell - self.soma_ampa = self.syn_ampa_create(self.soma(0.5)) - self.soma_nmda = self.syn_nmda_create(self.soma(0.5)) - self.soma_gabaa = self.syn_gabaa_create(self.soma(0.5)) - - # insert IClamps in all situations - def create_all_IClamp(self, p): - """ temporarily an external function taking the p dict - """ - # list of sections for this celltype - sect_list_IClamp = [ - 'soma', - ] - - # some parameters - t_delay = p['Itonic_t0_L5Basket'] - - # T = -1 means use nrn.tstop - if p['Itonic_T_L5Basket'] == -1: - t_dur = nrn.tstop - t_delay - - else: - t_dur = p['Itonic_T_L5Basket'] - t_delay - - # t_dur must be nonnegative, I imagine - if t_dur < 0.: - t_dur = 0. - - # properties of the IClamp - props_IClamp = { - 'loc': 0.5, - 'delay': t_delay, - 'dur': t_dur, - 'amp': p['Itonic_A_L5Basket'] - } - - # iterate through list of sect_list_IClamp to create a persistent IClamp object - # the insert_IClamp procedure is in Cell() and checks on names - # so names must be actual section names, or else it will fail silently - self.list_IClamp = [self.insert_IClamp(sect_name, props_IClamp) for sect_name in sect_list_IClamp] - - # defines biophysics - def __biophysics(self): - self.soma.insert('hh2') - - # connections FROM other cells TO this cell - # there are no connections from the L2Basket cells. congrats! - def parconnect(self, gid, gid_dict, pos_dict, p): - # FROM other L5Basket cells TO this cell - for gid_src, pos in zip(gid_dict['L5_basket'], pos_dict['L5_basket']): - if gid_src != gid: - nc_dict = { - 'pos_src': pos, - 'A_weight': p['gbar_L5Basket_L5Basket'], - 'A_delay': 1., - 'lamtha': 20., - 'threshold': p['threshold'], - 'type_src' : 'L5_basket' - } - - self.ncfrom_L5Basket.append(self.parconnect_from_src(gid_src, nc_dict, self.soma_gabaa)) - - # FROM other L5Pyr cells TO this cell - for gid_src, pos in zip(gid_dict['L5_pyramidal'], pos_dict['L5_pyramidal']): - nc_dict = { - 'pos_src': pos, - 'A_weight': p['gbar_L5Pyr_L5Basket'], - 'A_delay': 1., - 'lamtha': 3., - 'threshold': p['threshold'], - 'type_src' : 'L5_pyramidal' - } - - self.ncfrom_L5Pyr.append(self.parconnect_from_src(gid_src, nc_dict, self.soma_ampa)) - - # FROM other L2Pyr cells TO this cell - for gid_src, pos in zip(gid_dict['L2_pyramidal'], pos_dict['L2_pyramidal']): - nc_dict = { - 'pos_src': pos, - 'A_weight': p['gbar_L2Pyr_L5Basket'], - 'A_delay': 1., - 'lamtha': 3., - 'threshold': p['threshold'], - 'type_src' : 'L2_pyramidal' - } - - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict, self.soma_ampa)) - - # parallel receive function parreceive() - def parreceive(self, gid, gid_dict, pos_dict, p_ext): - for gid_src, p_src, pos in zip(gid_dict['extinput'], p_ext, pos_dict['extinput']): - # Check if AMPA params are define in p_src - if 'L5Basket_ampa' in p_src.keys(): - nc_dict_ampa = { - 'pos_src': pos, - 'A_weight': p_src['L5Basket_ampa'][0], - 'A_delay': p_src['L5Basket_ampa'][1], # right index?? - 'lamtha': p_src['lamtha'], - 'threshold': p_src['threshold'], - 'type_src' : 'ext' - } - - # AMPA synapse - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_ampa, self.soma_ampa)) - - # Check if nmda params are define in p_src - if 'L5Basket_nmda' in p_src.keys(): - nc_dict_nmda = { - 'pos_src': pos, - 'A_weight': p_src['L5Basket_nmda'][0], - 'A_delay': p_src['L5Basket_nmda'][1], # right index?? - 'lamtha': p_src['lamtha'], - 'threshold': p_src['threshold'], - 'type_src' : 'ext' - } - - # NMDA synapse - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_nmda, self.soma_nmda)) - - # one parreceive function to handle all types of external parreceives - # types must be defined explicitly here - def parreceive_ext(self, type, gid, gid_dict, pos_dict, p_ext): - if type.startswith(('evprox', 'evdist')): # shouldn't this just check for evprox? - if self.celltype in p_ext.keys(): - gid_ev = gid + gid_dict[type][0] - - nc_dict_ampa = { - 'pos_src': pos_dict[type][gid], - 'A_weight': p_ext[self.celltype][0], # index 0 is ampa weight - 'A_delay': p_ext[self.celltype][2], # index 2 is delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - nc_dict_nmda = { - 'pos_src': pos_dict[type][gid], - 'A_weight': p_ext[self.celltype][1], # index 1 is nmda weight - 'A_delay': p_ext[self.celltype][2], # index 2 is delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_ampa, self.soma_ampa)) - - # NEW: note that default/original is 0 nmda weight for the soma (both prox and distal evoked) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_nmda, self.soma_nmda)) - - elif type == 'extgauss': - # gid is this cell's gid - # gid_dict is the whole dictionary, including the gids of the extgauss - # pos_dict is also the pos of the extgauss (net origin) - # p_ext_gauss are the params (strength, etc.) - if 'L5_basket' in p_ext.keys(): - gid_extgauss = gid + gid_dict['extgauss'][0] - - nc_dict = { - 'pos_src': pos_dict['extgauss'][gid], - 'A_weight': p_ext['L5_basket'][0], # index 0 is ampa weight - 'A_delay': p_ext['L5_basket'][2], # index 2 is delay - 'lamtha': p_ext['lamtha'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - self.ncfrom_extgauss.append(self.parconnect_from_src(gid_extgauss, nc_dict, self.soma_ampa)) - - elif type == 'extpois': - if self.celltype in p_ext.keys(): - gid_extpois = gid + gid_dict['extpois'][0] - - nc_dict = { - 'pos_src': pos_dict['extpois'][gid], - 'A_weight': p_ext[self.celltype][0], # index 0 is ampa weight - 'A_delay': p_ext[self.celltype][2], # index 2 is delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois, nc_dict, self.soma_ampa)) - - if p_ext[self.celltype][1] > 0.0: - nc_dict['A_weight'] = p_ext[self.celltype][1] # index 1 for nmda weight - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois, nc_dict, self.soma_nmda)) - - else: - print("Warning, type def not specified in L2Basket") diff --git a/L5_pyramidal.py b/L5_pyramidal.py deleted file mode 100644 index 2f338e61f..000000000 --- a/L5_pyramidal.py +++ /dev/null @@ -1,720 +0,0 @@ -# L5_pyramidal.py - establish class def for layer 5 pyramidal cells -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: removed it.izip dep) -# last rev: (SL: toward python3, moved cells) - -import sys -import numpy as np - -from neuron import h -from cell import Pyr -import paramrw -import params_default as p_default - -# Units for e: mV -# Units for gbar: S/cm^2 unless otherwise noted -# units for taur: ms - -class L5Pyr(Pyr): - - def basic_shape (self): - # THESE AND LENGHTHS MUST CHANGE TOGETHER!!! - pt3dclear=h.pt3dclear; pt3dadd=h.pt3dadd; dend = self.list_dend - pt3dclear(sec=self.soma); pt3dadd(0, 0, 0, 1, sec=self.soma); pt3dadd(0, 23, 0, 1, sec=self.soma) - pt3dclear(sec=dend[0]); pt3dadd(0, 23, 0, 1,sec=dend[0]); pt3dadd(0, 83, 0, 1,sec=dend[0]) - pt3dclear(sec=dend[1]); pt3dadd(0, 83, 0, 1,sec=dend[1]); pt3dadd(-150, 83, 0, 1,sec=dend[1]) - pt3dclear(sec=dend[2]); pt3dadd(0, 83, 0, 1,sec=dend[2]); pt3dadd(0, 483, 0, 1,sec=dend[2]) - pt3dclear(sec=dend[3]); pt3dadd(0, 483, 0, 1,sec=dend[3]); pt3dadd(0, 883, 0, 1,sec=dend[3]) - pt3dclear(sec=dend[4]); pt3dadd(0, 883, 0, 1,sec=dend[4]); pt3dadd(0, 1133, 0, 1,sec=dend[4]) - pt3dclear(sec=dend[5]); pt3dadd(0, 0, 0, 1,sec=dend[5]); pt3dadd(0, -50, 0, 1,sec=dend[5]) - pt3dclear(sec=dend[6]); pt3dadd(0, -50, 0, 1,sec=dend[6]); pt3dadd(-106, -156, 0, 1,sec=dend[6]) - pt3dclear(sec=dend[7]); pt3dadd(0, -50, 0, 1,sec=dend[7]); pt3dadd(106, -156, 0, 1,sec=dend[7]) - - def geom (self, p_dend): - soma = self.soma; dend = self.list_dend; - # soma.L = 13 # BUSH 1999 spike amp smaller - soma.L=39 # Bush 1993 - dend[0].L = 102 - dend[1].L = 255 - dend[2].L = 680 # default 400 - dend[3].L = 680 # default 400 - dend[4].L = 425 - dend[5].L = 85 - dend[6].L = 255 # default 150 - dend[7].L = 255 # default 150 - # soma.diam = 18.95 # Bush 1999 - soma.diam = 28.9 # Bush 1993 - dend[0].diam = 10.2 - dend[1].diam = 5.1 - dend[2].diam = 7.48 # default 4.4 - dend[3].diam = 4.93 # default 2.9 - dend[4].diam = 3.4 - dend[5].diam = 6.8 - dend[6].diam = 8.5 - dend[7].diam = 8.5 - self.set_dend_props(p_dend) # resets length,diam,etc. based on param specification - - def __init__(self, gid = -1, pos = -1, p={}): - # Get default L5Pyr params and update them with corresponding params in p - p_all_default = p_default.get_L5Pyr_params_default() - self.p_all = paramrw.compare_dictionaries(p_all_default, p) - - # Get somatic, dendirtic, and synapse properties - p_soma = self.__get_soma_props(pos) - p_dend = self.__get_dend_props() - p_syn = self.__get_syn_props() - - Pyr.__init__(self, gid, p_soma) - self.celltype = 'L5_pyramidal' - - # Geometry - # dend Cm and dend Ra set using soma Cm and soma Ra - self.create_dends(p_dend) # just creates the sections - self.topol() # sets the connectivity between sections - self.geom(p_dend) # sets geom properties; adjusted after translation from hoc (2009 model) - - # biophysics - self.__biophys_soma() - self.__biophys_dends() - - # Dictionary of length scales to calculate dipole without 3d shape. Comes from Pyr(). - # dipole_insert() comes from Cell() - self.yscale = self.get_sectnames() - self.dipole_insert(self.yscale) - - # create synapses - self.__synapse_create(p_syn) - - # insert iclamp - self.list_IClamp = [] - - # run record current soma, defined in Cell() - self.record_current_soma() - - # insert IClamps in all situations - # temporarily an external function taking the p dict - def create_all_IClamp(self, p): - # list of sections for this celltype - sect_list_IClamp = ['soma',] - - # some parameters - t_delay = p['Itonic_t0_L5Pyr_soma'] - - # T = -1 means use h.tstop - if p['Itonic_T_L5Pyr_soma'] == -1: - # t_delay = 50. - t_dur = h.tstop - t_delay - else: - t_dur = p['Itonic_T_L5Pyr_soma'] - t_delay - - # t_dur must be nonnegative, I imagine - if t_dur < 0.: - t_dur = 0. - - # properties of the IClamp - props_IClamp = { - 'loc': 0.5, - 'delay': t_delay, - 'dur': t_dur, - 'amp': p['Itonic_A_L5Pyr_soma'] - } - - # iterate through list of sect_list_IClamp to create a persistent IClamp object - # the insert_IClamp procedure is in Cell() and checks on names - # so names must be actual section names, or else it will fail silently - self.list_IClamp = [self.insert_IClamp(sect_name, props_IClamp) for sect_name in sect_list_IClamp] - - # Sets somatic properties. Returns dictionary. - def __get_soma_props(self, pos): - return { - 'pos': pos, - 'L': self.p_all['L5Pyr_soma_L'], - 'diam': self.p_all['L5Pyr_soma_diam'], - 'cm': self.p_all['L5Pyr_soma_cm'], - 'Ra': self.p_all['L5Pyr_soma_Ra'], - 'name': 'L5Pyr', - } - - # Returns dictionary of dendritic properties and list of dendrite names - def __get_dend_props(self): - # def __set_dend_props(self): - # Hard coded dend properties - # dend_props = { - return { - 'apical_trunk': { - 'L': self.p_all['L5Pyr_apicaltrunk_L'] , - 'diam': self.p_all['L5Pyr_apicaltrunk_diam'], - 'cm': self.p_all['L5Pyr_dend_cm'], - 'Ra': self.p_all['L5Pyr_dend_Ra'], - }, - 'apical_1': { - 'L': self.p_all['L5Pyr_apical1_L'], - 'diam': self.p_all['L5Pyr_apical1_diam'], - 'cm': self.p_all['L5Pyr_dend_cm'], - 'Ra': self.p_all['L5Pyr_dend_Ra'], - }, - 'apical_2': { - 'L': self.p_all['L5Pyr_apical2_L'], - 'diam': self.p_all['L5Pyr_apical2_diam'], - 'cm': self.p_all['L5Pyr_dend_cm'], - 'Ra': self.p_all['L5Pyr_dend_Ra'], - }, - 'apical_tuft': { - 'L': self.p_all['L5Pyr_apicaltuft_L'], - 'diam': self.p_all['L5Pyr_apicaltuft_diam'], - 'cm': self.p_all['L5Pyr_dend_cm'], - 'Ra': self.p_all['L5Pyr_dend_Ra'], - }, - 'apical_oblique': { - 'L': self.p_all['L5Pyr_apicaloblique_L'], - 'diam': self.p_all['L5Pyr_apicaloblique_diam'], - 'cm': self.p_all['L5Pyr_dend_cm'], - 'Ra': self.p_all['L5Pyr_dend_Ra'], - }, - 'basal_1': { - 'L': self.p_all['L5Pyr_basal1_L'], - 'diam': self.p_all['L5Pyr_basal1_diam'], - 'cm': self.p_all['L5Pyr_dend_cm'], - 'Ra': self.p_all['L5Pyr_dend_Ra'], - }, - 'basal_2': { - 'L': self.p_all['L5Pyr_basal2_L'], - 'diam': self.p_all['L5Pyr_basal2_diam'], - 'cm': self.p_all['L5Pyr_dend_cm'], - 'Ra': self.p_all['L5Pyr_dend_Ra'], - }, - 'basal_3': { - 'L': self.p_all['L5Pyr_basal3_L'], - 'diam': self.p_all['L5Pyr_basal3_diam'], - 'cm': self.p_all['L5Pyr_dend_cm'], - 'Ra': self.p_all['L5Pyr_dend_Ra'], - }, - } - - # These MUST match order the above keys in exact order! - # dend_names = [ - # 'apical_trunk', 'apical_1', 'apical_2', - # 'apical_tuft', 'apical_oblique', 'basal_1', - # 'basal_2', 'basal_3' - # ] - - # return dend_props, dend_names - - # self.dend_L = [102, 680, 680, 425, 255, 85, 255, 255] - # self.dend_diam = [10.2, 7.48, 4.93, 3.4, 5.1, 6.8, 8.5, 8.5] - - # # check lengths for congruity - # if len(self.dend_L) == len(self.dend_diam): - # # Zip above lists together - # self.dend_props = zip(self.dend_names, self.dend_L, self.dend_diam) - # else: - # print "self.dend_L and self.dend_diam are not the same length" - # print "please fix in L5_pyramidal.py" - # sys.exit() - - def __get_syn_props(self): - return { - 'ampa': { - 'e': self.p_all['L5Pyr_ampa_e'], - 'tau1': self.p_all['L5Pyr_ampa_tau1'], - 'tau2': self.p_all['L5Pyr_ampa_tau2'], - }, - 'nmda': { - 'e': self.p_all['L5Pyr_nmda_e'], - 'tau1': self.p_all['L5Pyr_nmda_tau1'], - 'tau2': self.p_all['L5Pyr_nmda_tau2'], - }, - 'gabaa': { - 'e': self.p_all['L5Pyr_gabaa_e'], - 'tau1': self.p_all['L5Pyr_gabaa_tau1'], - 'tau2': self.p_all['L5Pyr_gabaa_tau2'], - }, - 'gabab': { - 'e': self.p_all['L5Pyr_gabab_e'], - 'tau1': self.p_all['L5Pyr_gabab_tau1'], - 'tau2': self.p_all['L5Pyr_gabab_tau2'], - } - } - - # connects sections of this cell together - def topol (self): - - """ original topol - connect dend(0), soma(1) // dend[0] is apical trunk - for i = 1, 2 connect dend[i](0), dend(1) // dend[1] is oblique, dend[2] is apic1 - for i = 3, 4 connect dend[i](0), dend[i-1](1) // dend[3],dend[4] are apic2,apic tuft - connect dend[5](0), soma(0) //was soma(1)this is correct! - for i = 6, 7 connect dend[i](0), dend[5](1) - """ - - # child.connect(parent, parent_end, {child_start=0}) - # Distal (apical) - self.dends['apical_trunk'].connect(self.soma, 1, 0) - self.dends['apical_1'].connect(self.dends['apical_trunk'], 1, 0) - self.dends['apical_2'].connect(self.dends['apical_1'], 1, 0) - self.dends['apical_tuft'].connect(self.dends['apical_2'], 1, 0) - - # apical_oblique comes off distal end of apical_trunk - self.dends['apical_oblique'].connect(self.dends['apical_trunk'], 1, 0) - - # Proximal (basal) - self.dends['basal_1'].connect(self.soma, 0, 0) - self.dends['basal_2'].connect(self.dends['basal_1'], 1, 0) - self.dends['basal_3'].connect(self.dends['basal_1'], 1, 0) - - self.basic_shape() # translated from original hoc (2009 model) - - # # Distal - # self.list_dend[0].connect(self.soma, 1, 0) - # self.list_dend[1].connect(self.list_dend[0], 1, 0) - - # self.list_dend[2].connect(self.list_dend[1], 1, 0) - # self.list_dend[3].connect(self.list_dend[2], 1, 0) - - # # dend[4] comes off of dend[0](1) - # self.list_dend[4].connect(self.list_dend[0], 1, 0) - - # # Proximal - # self.list_dend[5].connect(self.soma, 0, 0) - # self.list_dend[6].connect(self.list_dend[5], 1, 0) - # self.list_dend[7].connect(self.list_dend[5], 1, 0) - - # adds biophysics to soma - def __biophys_soma(self): - # set soma biophysics specified in Pyr - # self.pyr_biophys_soma() - - # Insert 'hh2' mechanism - self.soma.insert('hh2') - self.soma.gkbar_hh2 = self.p_all['L5Pyr_soma_gkbar_hh2'] - self.soma.gnabar_hh2 = self.p_all['L5Pyr_soma_gnabar_hh2'] - self.soma.gl_hh2 = self.p_all['L5Pyr_soma_gl_hh2'] - self.soma.el_hh2 = self.p_all['L5Pyr_soma_el_hh2'] - - # insert 'ca' mechanism - # Units: pS/um^2 - self.soma.insert('ca') - self.soma.gbar_ca = self.p_all['L5Pyr_soma_gbar_ca'] - - # insert 'cad' mechanism - # units of tau are ms - self.soma.insert('cad') - self.soma.taur_cad = self.p_all['L5Pyr_soma_taur_cad'] - - # insert 'kca' mechanism - # units are S/cm^2? - self.soma.insert('kca') - self.soma.gbar_kca = self.p_all['L5Pyr_soma_gbar_kca'] - - # Insert 'km' mechanism - # Units: pS/um^2 - self.soma.insert('km') - self.soma.gbar_km = self.p_all['L5Pyr_soma_gbar_km'] - - # insert 'cat' mechanism - self.soma.insert('cat') - self.soma.gbar_cat = self.p_all['L5Pyr_soma_gbar_cat'] - - # insert 'ar' mechanism - self.soma.insert('ar') - self.soma.gbar_ar = self.p_all['L5Pyr_soma_gbar_ar'] - - def __biophys_dends(self): - # set dend biophysics specified in Pyr() - # self.pyr_biophys_dends() - - # set dend biophysics not specified in Pyr() - for key in self.dends: - # Insert 'hh2' mechanism - self.dends[key].insert('hh2') - self.dends[key].gkbar_hh2 = self.p_all['L5Pyr_dend_gkbar_hh2'] - self.dends[key].gl_hh2 = self.p_all['L5Pyr_dend_gl_hh2'] - self.dends[key].gnabar_hh2 = self.p_all['L5Pyr_dend_gnabar_hh2'] - self.dends[key].el_hh2 = self.p_all['L5Pyr_dend_el_hh2'] - - # Insert 'ca' mechanims - # Units: pS/um^2 - self.dends[key].insert('ca') - self.dends[key].gbar_ca = self.p_all['L5Pyr_dend_gbar_ca'] - - # Insert 'cad' mechanism - self.dends[key].insert('cad') - self.dends[key].taur_cad = self.p_all['L5Pyr_dend_taur_cad'] - - # Insert 'kca' mechanism - self.dends[key].insert('kca') - self.dends[key].gbar_kca = self.p_all['L5Pyr_dend_gbar_kca'] - - # Insert 'km' mechansim - # Units: pS/um^2 - self.dends[key].insert('km') - self.dends[key].gbar_km = self.p_all['L5Pyr_dend_gbar_km'] - - # insert 'cat' mechanism - self.dends[key].insert('cat') - self.dends[key].gbar_cat = self.p_all['L5Pyr_dend_gbar_cat'] - - # insert 'ar' mechanism - self.dends[key].insert('ar') - - # set gbar_ar - # Value depends on distance from the soma. Soma is set as - # origin by passing self.soma as a sec argument to h.distance() - # Then iterate over segment nodes of dendritic sections - # and set gbar_ar depending on h.distance(seg.x), which returns - # distance from the soma to this point on the CURRENTLY ACCESSED - # SECTION!!! - h.distance(sec=self.soma) - - for key in self.dends: - self.dends[key].push() - for seg in self.dends[key]: - seg.gbar_ar = 1e-6 * np.exp(3e-3 * h.distance(seg.x)) - - h.pop_section() - - def __synapse_create(self, p_syn): - # creates synapses onto this cell - # Somatic synapses - self.synapses = { - 'soma_gabaa': self.syn_create(self.soma(0.5), p_syn['gabaa']), - 'soma_gabab': self.syn_create(self.soma(0.5), p_syn['gabab']), - } - - # Dendritic synapses - self.apicaltuft_gabaa = self.syn_create(self.dends['apical_tuft'](0.5), p_syn['gabaa']) - #self.apicaltuft_gabaa = self.syn_create(self.dends['apical_tuft'](0.5), p_syn['gabab'])#RL version - - self.apicaltuft_ampa = self.syn_create(self.dends['apical_tuft'](0.5), p_syn['ampa']) - self.apicaltuft_nmda = self.syn_create(self.dends['apical_tuft'](0.5), p_syn['nmda']) - - self.apicaloblique_ampa = self.syn_create(self.dends['apical_oblique'](0.5), p_syn['ampa']) - self.apicaloblique_nmda = self.syn_create(self.dends['apical_oblique'](0.5), p_syn['nmda']) - - self.basal2_ampa = self.syn_create(self.dends['basal_2'](0.5), p_syn['ampa']) - self.basal2_nmda = self.syn_create(self.dends['basal_2'](0.5), p_syn['nmda']) - - self.basal3_ampa = self.syn_create(self.dends['basal_3'](0.5), p_syn['ampa']) - self.basal3_nmda = self.syn_create(self.dends['basal_3'](0.5), p_syn['nmda']) - - # parallel connection function FROM all cell types TO here - def parconnect(self, gid, gid_dict, pos_dict, p): - # init dict of dicts - # nc_dict for ampa and nmda may be the same for this cell type - nc_dict = { - 'ampa': None, - 'nmda': None, - } - - # connections FROM L5Pyr TO here - for gid_src, pos in zip(gid_dict['L5_pyramidal'], pos_dict['L5_pyramidal']): - # no autapses - if gid_src != gid: - nc_dict['ampa'] = { - 'pos_src': pos, - 'A_weight': p['gbar_L5Pyr_L5Pyr_ampa'], - 'A_delay': 1., - 'lamtha': 3., - 'threshold': p['threshold'], - 'type_src' : 'L5_pyramidal' - } - - # ampa connections - self.ncfrom_L5Pyr.append(self.parconnect_from_src(gid_src, nc_dict['ampa'], self.apicaloblique_ampa)) - self.ncfrom_L5Pyr.append(self.parconnect_from_src(gid_src, nc_dict['ampa'], self.basal2_ampa)) - self.ncfrom_L5Pyr.append(self.parconnect_from_src(gid_src, nc_dict['ampa'], self.basal3_ampa)) - - nc_dict['nmda'] = { - 'pos_src': pos, - 'A_weight': p['gbar_L5Pyr_L5Pyr_nmda'], - 'A_delay': 1., - 'lamtha': 3., - 'threshold': p['threshold'], - 'type_src' : 'L5_pyramidal' - } - - # nmda connections - self.ncfrom_L5Pyr.append(self.parconnect_from_src(gid_src, nc_dict['nmda'], self.apicaloblique_nmda)) - self.ncfrom_L5Pyr.append(self.parconnect_from_src(gid_src, nc_dict['nmda'], self.basal2_nmda)) - self.ncfrom_L5Pyr.append(self.parconnect_from_src(gid_src, nc_dict['nmda'], self.basal3_nmda)) - - # connections FROM L5Basket TO here - for gid_src, pos in zip(gid_dict['L5_basket'], pos_dict['L5_basket']): - nc_dict['gabaa'] = { - 'pos_src': pos, - 'A_weight': p['gbar_L5Basket_L5Pyr_gabaa'], - 'A_delay': 1., - 'lamtha': 70., - 'threshold': p['threshold'], - 'type_src' : 'L5_basket' - } - - nc_dict['gabab'] = { - 'pos_src': pos, - 'A_weight': p['gbar_L5Basket_L5Pyr_gabab'], - 'A_delay': 1., - 'lamtha': 70., - 'threshold': p['threshold'], - 'type_src' : 'L5_basket' - } - - # soma synapses are defined in Pyr() - self.ncfrom_L5Basket.append(self.parconnect_from_src(gid_src, nc_dict['gabaa'], self.synapses['soma_gabaa'])) - self.ncfrom_L5Basket.append(self.parconnect_from_src(gid_src, nc_dict['gabab'], self.synapses['soma_gabab'])) - - # connections FROM L2Pyr TO here - for gid_src, pos in zip(gid_dict['L2_pyramidal'], pos_dict['L2_pyramidal']): - # this delay is longer than most - nc_dict = { - 'pos_src': pos, - 'A_weight': p['gbar_L2Pyr_L5Pyr'], - 'A_delay': 1., - 'lamtha': 3., - 'threshold': p['threshold'], - 'type_src' : 'L2_pyramidal' - } - - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict, self.basal2_ampa)) - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict, self.basal3_ampa)) - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict, self.apicaltuft_ampa)) - self.ncfrom_L2Pyr.append(self.parconnect_from_src(gid_src, nc_dict, self.apicaloblique_ampa)) - - # connections FROM L2Basket TO here - for gid_src, pos in zip(gid_dict['L2_basket'], pos_dict['L2_basket']): - nc_dict = { - 'pos_src': pos, - 'A_weight': p['gbar_L2Basket_L5Pyr'], - 'A_delay': 1., - 'lamtha': 50., - 'threshold': p['threshold'], - 'type_src' : 'L2_basket' - } - - self.ncfrom_L2Basket.append(self.parconnect_from_src(gid_src, nc_dict, self.apicaltuft_gabaa)) - - # receive from external inputs - def parreceive(self, gid, gid_dict, pos_dict, p_ext): - for gid_src, p_src, pos in zip(gid_dict['extinput'], p_ext, pos_dict['extinput']): - # Check if AMPA params defined in p_src - if 'L5Pyr_ampa' in p_src.keys(): - nc_dict_ampa = { - 'pos_src': pos, - 'A_weight': p_src['L5Pyr_ampa'][0], - 'A_delay': p_src['L5Pyr_ampa'][1], - 'lamtha': p_src['lamtha'], - 'threshold': p_src['threshold'], - 'type_src' : 'ext' - } - - # Proximal feed AMPA synapses - if p_src['loc'] is 'proximal': - # basal2_ampa, basal3_ampa, apicaloblique_ampa - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_ampa, self.basal2_ampa)) - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_ampa, self.basal3_ampa)) - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_ampa, self.apicaloblique_ampa)) - # Distal feed AMPA synsapes - elif p_src['loc'] is 'distal': - # apical tuft - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_ampa, self.apicaltuft_ampa)) - - # Check if NMDA params defined in p_src - if 'L5Pyr_nmda' in p_src.keys(): - nc_dict_nmda = { - 'pos_src': pos, - 'A_weight': p_src['L5Pyr_nmda'][0], - 'A_delay': p_src['L5Pyr_nmda'][1], - 'lamtha': p_src['lamtha'], - 'threshold': p_src['threshold'], - 'type_src' : 'ext' - } - - # Proximal feed NMDA synapses - if p_src['loc'] is 'proximal': - # basal2_nmda, basal3_nmda, apicaloblique_nmda - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_nmda, self.basal2_nmda)) - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_nmda, self.basal3_nmda)) - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_nmda, self.apicaloblique_nmda)) - # Distal feed NMDA synsapes - elif p_src['loc'] is 'distal': - # apical tuft - self.ncfrom_extinput.append(self.parconnect_from_src(gid_src, nc_dict_nmda, self.apicaltuft_nmda)) - - # one parreceive function to handle all types of external parreceives - # types must be defined explicitly here - def parreceive_ext(self, type, gid, gid_dict, pos_dict, p_ext): - if type.startswith(('evprox', 'evdist')): - if self.celltype in p_ext.keys(): - gid_ev = gid + gid_dict[type][0] - - nc_dict_ampa = { - 'pos_src': pos_dict[type][gid], - 'A_weight': p_ext[self.celltype][0], # index 0 for ampa weight - 'A_delay': p_ext[self.celltype][2], # index 2 for delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - nc_dict_nmda = { - 'pos_src': pos_dict[type][gid], - 'A_weight': p_ext[self.celltype][1], # index 1 for nmda weight - 'A_delay': p_ext[self.celltype][2], # index 2 for delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - #print('L5pyr:',type,'w:',nc_dict['A_weight']) - - if p_ext['loc'] is 'proximal': - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_ampa, self.basal2_ampa)) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_ampa, self.basal3_ampa)) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_ampa, self.apicaloblique_ampa)) - - # NEW: note that default/original is 0 nmda weight for these proximal dends - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_nmda, self.basal2_nmda)) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_nmda, self.basal3_nmda)) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_nmda, self.apicaloblique_nmda)) - - elif p_ext['loc'] is 'distal': - # apical tuft - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_ampa, self.apicaltuft_ampa)) - self.ncfrom_ev.append(self.parconnect_from_src(gid_ev, nc_dict_nmda, self.apicaltuft_nmda)) - - elif type == 'extgauss': - # gid is this cell's gid - # gid_dict is the whole dictionary, including the gids of the extgauss - # pos_dict is also the pos of the extgauss (net origin) - # p_ext_gauss are the params (strength, etc.) - # doesn't matter if this doesn't do anything - - # gid shift is based on L2_pyramidal cells NOT L5 - # I recognize this is ugly (hack) - # gid_shift = gid_dict['extgauss'][0] - gid_dict['L2_pyramidal'][0] - if 'L5_pyramidal' in p_ext.keys(): - gid_extgauss = gid + gid_dict['extgauss'][0] - - nc_dict = { - 'pos_src': pos_dict['extgauss'][gid], - 'A_weight': p_ext['L5_pyramidal'][0], # index 0 for ampa weight - 'A_delay': p_ext['L5_pyramidal'][2], # index 2 for delay - 'lamtha': p_ext['lamtha'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - self.ncfrom_extgauss.append(self.parconnect_from_src(gid_extgauss, nc_dict, self.basal2_ampa)) - self.ncfrom_extgauss.append(self.parconnect_from_src(gid_extgauss, nc_dict, self.basal3_ampa)) - self.ncfrom_extgauss.append(self.parconnect_from_src(gid_extgauss, nc_dict, self.apicaloblique_ampa)) - - elif type == 'extpois': - if self.celltype in p_ext.keys(): - gid_extpois = gid + gid_dict['extpois'][0] - - nc_dict = { - 'pos_src': pos_dict['extpois'][gid], - 'A_weight': p_ext[self.celltype][0], # index 0 for ampa weight - 'A_delay': p_ext[self.celltype][2], # index 2 for delay - 'lamtha': p_ext['lamtha_space'], - 'threshold': p_ext['threshold'], - 'type_src' : type - } - - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois, nc_dict, self.basal2_ampa)) - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois, nc_dict, self.basal3_ampa)) - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois, nc_dict, self.apicaloblique_ampa)) - - if p_ext[self.celltype][1] > 0.0: - nc_dict['A_weight'] = p_ext[self.celltype][1] # index 1 for nmda weight - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois, nc_dict, self.basal2_nmda)) - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois, nc_dict, self.basal3_nmda)) - self.ncfrom_extpois.append(self.parconnect_from_src(gid_extpois, nc_dict, self.apicaloblique_nmda)) - - # Define 3D shape and position of cell. By default neuron uses xy plane for - # height and xz plane for depth. This is opposite for model as a whole, but - # convention is followed in this function for ease use of gui. - def __set_3Dshape(self): - # set 3D shape of soma by calling shape_soma from class Cell - # print "WARNING: You are setting 3d shape geom. You better be doing" - # print "gui analysis and not numerical analysis!!" - self.shape_soma() - - # soma proximal coords - x_prox = 0 - y_prox = 0 - - # soma distal coords - x_distal = 0 - y_distal = self.soma.L - - # dend 0-3 are major axis, dend 4 is branch - # deal with distal first along major cable axis - # the way this is assigning variables is ugly/lazy right now - for i in range(0, 4): - h.pt3dclear(sec=self.list_dend[i]) - - # x_distal and y_distal are the starting points for each segment - # these are updated at the end of the loop - sec=self.list_dend[i] - h.pt3dadd(0, y_distal, 0, sec.diam, sec=sec) - - # update x_distal and y_distal after setting them - # x_distal += dend_dx[i] - y_distal += sec.L - - # add next point - h.pt3dadd(0, y_distal, 0, sec.diam, sec=sec) - - # now deal with dend 4 - # dend 4 will ALWAYS be positioned at the end of dend[0] - h.pt3dclear(sec=self.list_dend[4]) - - # activate this section with 'sec=self.list_dend[i]' notation - x_start = h.x3d(1, sec=self.list_dend[0]) - y_start = h.y3d(1, sec=self.list_dend[0]) - - sec=self.list_dend[4] - h.pt3dadd(x_start, y_start, 0, sec.diam, sec=sec) - # self.dend_L[4] is subtracted because lengths always positive, - # and this goes to negative x - h.pt3dadd(x_start-sec.L, y_start, 0, sec.diam, sec=sec) - - # now deal with proximal dends - for i in range(5, 8): - h.pt3dclear(sec=self.list_dend[i]) - - # deal with dend 5, ugly. sorry. - sec=self.list_dend[5] - h.pt3dadd(x_prox, y_prox, 0, sec.diam, sec=sec) - y_prox += -sec.L - - h.pt3dadd(x_prox, y_prox, 0, sec.diam,sec=sec) - - # x_prox, y_prox are now the starting points for BOTH of last 2 sections - # dend 6 - # Calculate x-coordinate for end of dend - sec=self.list_dend[6] - dend6_x = -sec.L * np.sqrt(2) / 2. - h.pt3dadd(x_prox, y_prox, 0, sec.diam, sec=sec) - h.pt3dadd(dend6_x, y_prox-sec.L * np.sqrt(2) / 2., - 0, sec.diam, sec=sec) - - # dend 7 - # Calculate x-coordinate for end of dend - sec=self.list_dend[7] - dend7_x = sec.L * np.sqrt(2) / 2. - h.pt3dadd(x_prox, y_prox, 0, sec.diam, sec=sec) - h.pt3dadd(dend7_x, y_prox-sec.L * np.sqrt(2) / 2., - 0, sec.diam, sec=sec) - - # set 3D position - # z grid position used as y coordinate in h.pt3dchange() to satisfy - # gui convention that y is height and z is depth. In h.pt3dchange() - # x and z components are scaled by 100 for visualization clarity - self.soma.push() - for i in range(0, int(h.n3d())): - h.pt3dchange(i, self.pos[0]*100 + h.x3d(i), -self.pos[2] + h.y3d(i), - self.pos[1] * 100 + h.z3d(i), h.diam3d(i)) - - h.pop_section() diff --git a/PT_example.py b/PT_example.py deleted file mode 100644 index 58f8b63e6..000000000 --- a/PT_example.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -# PT_example.py - Plot Template example -# -# v 1.9.4 -# rev 2016-02-02 (SL: created) -# last major: () - -import numpy as np -import matplotlib as mpl -import matplotlib.pyplot as plt -import matplotlib.gridspec as gridspec -import axes_create as ac - -# spec plus dipole -class FigExample(ac.FigBase): - def __init__(self): - ac.FigBase.__init__(self) - self.f = plt.figure(figsize=(8, 6)) - - font_prop = {'size': 8} - mpl.rc('font', **font_prop) - - # the right margin is a hack and NOT guaranteed! - # it's making space for the stupid colorbar that creates a new grid to replace gs1 - # when called, and it doesn't update the params of gs1 - self.gs = { - 'dpl': gridspec.GridSpec(2, 1, height_ratios=[1, 3], bottom=0.85, top=0.95, left=0.1, right=0.82), - 'spec': gridspec.GridSpec(1, 4, wspace=0.05, hspace=0., bottom=0.30, top=0.80, left=0.1, right=1.), - 'pgram': gridspec.GridSpec(2, 1, height_ratios=[1, 3], bottom=0.05, top=0.25, left=0.1, right=0.82), - } - - self.ax = { - 'dipole': self.f.add_subplot(self.gs['dpl'][:, :]), - 'spec': self.f.add_subplot(self.gs['spec'][:, :]), - 'pgram': self.f.add_subplot(self.gs['pgram'][:, :]), - } - -if __name__ == '__main__': - fig = FigExample() - fig.ax['dipole'].plot(np.random.rand(1000)) - fig.savepng('testing.png') - fig.close() diff --git a/ac_manu_gamma.py b/ac_manu_gamma.py deleted file mode 100644 index aea803804..000000000 --- a/ac_manu_gamma.py +++ /dev/null @@ -1,1166 +0,0 @@ -# ac_manu_gamma.py - axes for gamma manuscript paper figs -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: removed izip dep) -# last major: (MS: commented out mpl.use('agg') to prevent conflict ...) - -import matplotlib as mpl -import axes_create as ac -import matplotlib.pyplot as plt -import matplotlib.gridspec as gridspec -import numpy as np - -class FigSimpleSpec(ac.FigBase): - def __init__(self): - self.f = plt.figure(figsize=(8, 6)) - font_prop = {'size': 8} - mpl.rc('font', **font_prop) - - # the right margin is a hack and NOT guaranteed! - # it's making space for the stupid colorbar that creates a new grid to replace gs1 - # when called, and it doesn't update the params of gs1 - self.gspec = { - 'dpl': gridspec.GridSpec(2, 1, height_ratios=[1, 3], bottom=0.85, top=0.95, left=0.1, right=0.82), - 'spec': gridspec.GridSpec(1, 4, wspace=0.05, hspace=0., bottom=0.10, top=0.80, left=0.1, right=1.), - } - - self.ax = {} - self.ax['dipole'] = self.f.add_subplot(self.gspec['dpl'][:, :]) - self.ax['spec'] = self.f.add_subplot(self.gspec['spec'][:, :]) - -class FigLaminarComparison(ac.FigBase): - def __init__(self, runtype='debug'): - # ac.FigBase.__init__() - self.f = plt.figure(figsize=(9, 7)) - - # set_fontsize() is part of FigBase() - self.set_fontsize(8) - - # various gridspecs - self.gspec = { - 'left': gridspec.GridSpec(8, 50), - 'middle': gridspec.GridSpec(8, 50), - 'right': gridspec.GridSpec(8, 50), - 'bottom_left': gridspec.GridSpec(1, 50), - 'bottom_middle': gridspec.GridSpec(1, 50), - 'bottom_right': gridspec.GridSpec(1, 50), - } - - # reposition the gridspecs - l = np.arange(0.12, 0.80, 0.27) - r = l + 0.24 - - # update the gridspecs - # um, left is going right ... - self.gspec['left'].update(wspace=0, hspace=0.30, bottom=0.25, top=0.94, left=l[2], right=r[2]) - self.gspec['middle'].update(wspace=0, hspace=0.30, bottom=0.25, top=0.94, left=l[0], right=r[0]) - self.gspec['right'].update(wspace=0, hspace=0.30, bottom=0.25, top=0.94, left=l[1], right=r[1]) - - # bottom are going to mirror the top, despite the names - self.gspec['bottom_left'].update(wspace=0, hspace=0.0, bottom=0.1, top=0.22, left=l[2], right=r[2]) - self.gspec['bottom_middle'].update(wspace=0, hspace=0.0, bottom=0.1, top=0.22, left=l[0], right=r[0]) - self.gspec['bottom_right'].update(wspace=0, hspace=0.0, bottom=0.1, top=0.22, left=l[1], right=r[1]) - - # create axes and handles - self.ax = { - 'dpl_L': self.f.add_subplot(self.gspec['left'][3:5, :40]), - 'dpl_M': self.f.add_subplot(self.gspec['middle'][3:5, :40]), - 'dpl_R': self.f.add_subplot(self.gspec['right'][3:5, :40]), - - 'spk_M': self.f.add_subplot(self.gspec['middle'][:2, :40]), - 'spk_R': self.f.add_subplot(self.gspec['right'][:2, :40]), - - 'current_M': self.f.add_subplot(self.gspec['middle'][2:3, :40]), - 'current_R': self.f.add_subplot(self.gspec['right'][2:3, :40]), - - 'spec_L': None, - 'spec_M': None, - 'spec_R': self.f.add_subplot(self.gspec['right'][5:7, :]), - - 'pgram_L': self.f.add_subplot(self.gspec['bottom_left'][:, :40]), - 'pgram_M': self.f.add_subplot(self.gspec['bottom_middle'][:, :40]), - 'pgram_R': self.f.add_subplot(self.gspec['bottom_right'][:, :40]), - } - - if runtype in ('debug', 'pub2'): - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][5:7, :]) - self.ax['spec_M'] = self.f.add_subplot(self.gspec['middle'][5:7, :]) - - elif runtype == 'pub': - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][5:7, :]) - self.ax['spec_M'] = self.f.add_subplot(self.gspec['middle'][5:7, :]) - - # remove xtick labels - list_ax_noxtick = [ax_handle for ax_handle in self.ax.keys() if ax_handle.startswith(('dpl', 'current', 'spk'))] - - # function defined in FigBase() - self.remove_tick_labels(list_ax_noxtick, 'x') - - # remove ytick labels - self.ax['spk_M'].set_yticklabels('') - self.ax['spk_R'].set_yticklabels('') - list_ax_noytick = [] - - # write list of no y tick axes - # if runtype == 'pub': - # list_ax_noytick.extend([ax_h for ax_h in self.ax.keys() if ax_h.startswith('spk')]) - # list_ax_noytick.extend(['spec_R', 'spec_L']) - - # function defined in FigBase() - self.remove_tick_labels(list_ax_noytick, 'y') - self.create_ax_bounds_dict() - self.create_y_centers_dict() - self.__add_labels_subfig(l) - self.__change_formatting() - - def __change_formatting(self): - list_axes = ['pgram_L', 'pgram_M', 'pgram_R'] - self.set_notation_scientific(list_axes, 2) - - # add text labels - def __add_labels_subfig(self, l): - # top labels - self.f.text(self.ax_bounds['spk_M'][0], self.ax_bounds['spk_M'][-1] + 0.005, 'A.') - self.f.text(self.ax_bounds['spk_R'][0], self.ax_bounds['spk_R'][-1] + 0.005, 'B.') - self.f.text(self.ax_bounds['dpl_L'][0], self.ax_bounds['dpl_L'][-1] + 0.005, 'C.') - - # left labels - labels_left = { - 'va': 'center', - 'ma': 'center', - 'rotation': 90, - } - self.f.text(0.025, self.y_centers['spec_M'], 'Frequency (Hz)', **labels_left) - self.f.text(0.025, self.y_centers['dpl_M'], 'Current Dipole \n (nAm)', **labels_left) - self.f.text(0.025, self.y_centers['pgram_M'], 'Welch Spectral \n Power ((nAm)$^2$)', **labels_left) - self.f.text(0.025, self.y_centers['spk_M'], 'Cells', **labels_left) - self.f.text(0.025, self.y_centers['current_M'], 'Current \n ($\mu$A)', **labels_left) - - # bottom labels - self.f.text(self.ax_bounds['spec_M'][0], self.ax_bounds['spec_M'][1] - 0.05, 'Time (ms)', ha='left') - self.f.text(self.ax_bounds['pgram_M'][0], self.ax_bounds['pgram_M'][1] - 0.05, 'Frequency (Hz)', ha='left') - - # right labels - self.f.text(0.95, self.y_centers['spec_L'], 'Spectral Power \n ((nAm)$^2$)', rotation=270, ma='center', va='center') - - def set_axes_pingping(self): - self.ax['current_R'].set_ylim((-2000., 0.)) - -# strong ping and weak ping examples in Layer 5: Fig 2 -class FigL5PingExample(ac.FigBase): - def __init__(self, runtype='debug'): - ac.FigBase.__init__(self) - self.f = plt.figure(figsize=(7, 8)) - - # set_fontsize() is part of FigBase() - self.set_fontsize(8) - - # various gridspecs - self.gspec = { - 'left': gridspec.GridSpec(7, 50), - 'right': gridspec.GridSpec(7, 50), - 'left_welch': gridspec.GridSpec(1, 50), - 'right_welch': gridspec.GridSpec(1, 50), - } - - # repositioning the gspec - l = np.arange(0.125, 0.90, 0.45) - r = l + 0.33 - - # create the gridspec - if runtype.startswith('pub'): - hspace_set = 0.30 - - else: - hspace_set = 0.30 - - self.gspec['left'].update(wspace=0, hspace=hspace_set, bottom=0.29, top=0.94, left=l[0], right=r[0]) - self.gspec['right'].update(wspace=0, hspace=hspace_set, bottom=0.29, top=0.94, left=l[1], right=r[1]) - self.gspec['left_welch'].update(wspace=0, hspace=0, bottom=0.1, top=0.2, left=l[0], right=r[0]) - self.gspec['right_welch'].update(wspace=0, hspace=0, bottom=0.1, top=0.2, left=l[1], right=r[1]) - - # create axes and handles - # spec_L will be conditional on debug or production - self.ax = { - 'raster_L': self.f.add_subplot(self.gspec['left'][:2, :40]), - 'hist_L': self.f.add_subplot(self.gspec['left'][2:3, :40]), - 'current_L': self.f.add_subplot(self.gspec['left'][3:4, :40]), - 'dpl_L': self.f.add_subplot(self.gspec['left'][4:5, :40]), - 'spec_L': None, - 'pgram_L': self.f.add_subplot(self.gspec['left_welch'][:, :]), - - 'raster_R': self.f.add_subplot(self.gspec['right'][:2, :40]), - 'hist_R': self.f.add_subplot(self.gspec['right'][2:3, :40]), - 'current_R': self.f.add_subplot(self.gspec['right'][3:4, :40]), - 'dpl_R': self.f.add_subplot(self.gspec['right'][4:5, :40]), - 'spec_R': self.f.add_subplot(self.gspec['right'][5:7, :]), - 'pgram_R': self.f.add_subplot(self.gspec['right_welch'][:, :]), - } - - # different spec_L depending on mode - if runtype in ('debug', 'pub2'): - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][5:7, :]) - - elif runtype == 'pub': - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][5:7, :40]) - - # print dir(self.ax['pgram_L'].get_position()) - # print self.ax['pgram_L'].get_position().get_points() - # print self.ax['pgram_L'].get_position().y0 - - # create twinx for the hist - for key in self.ax.keys(): - if key.startswith('hist_'): - # this creates an f.ax_twinx dict with the appropriate key names - self.create_axis_twinx(key) - - # pgram_axes - list_handles_pgram = [h for h in self.ax.keys() if h.startswith('pgram_')] - # self.fmt = self.set_notation_scientific(list_handles_pgram) - self.fmt = None - - # remove ytick labels for the rasters - for h in [ax for ax in self.ax if ax.startswith('raster_')]: - self.ax[h].set_yticklabels('') - - if runtype.startswith('pub'): - self.no_xaxis = ['raster', 'hist', 'dpl', 'current'] - self.no_yaxis = ['raster', 'spec', 'current'] - self.__remove_labels() - - self.__add_labels_subfig(l) - - self.list_sci = ['pgram_L', 'pgram_R'] - self.set_notation_scientific(self.list_sci) - - # add text labels - # ma is 'multialignment' for multiple lines - def __add_labels_subfig(self, l): - self.ax_pos = dict.fromkeys(self.ax) - - # create the ycenter dict - self.create_y_centers_dict() - - # first get all of the positions - for ax_h in self.ax.keys(): - self.ax_pos[ax_h] = self.return_axis_bounds(ax_h) - - # raster is on top - y_pad = 0.005 - label_y = self.ax_pos['raster_L'][-1] + y_pad - self.f.text(l[0], label_y, 'A.') - self.f.text(l[1], label_y, 'B.') - - # ylabel x pos - x_pos = 0.025 - label_props = { - 'fontsize': 7, - 'rotation': 90, - 'va': 'center', - 'ma': 'center', - } - - # cool, this uses a dict to fill in props. cool, cool, cool. - self.f.text(x_pos, self.y_centers['raster_L'], 'Cell no.', **label_props) - self.f.text(x_pos, self.y_centers['hist_L'], 'Spike\nhistogram\n(Left: I, Right: E)', **label_props) - self.f.text(x_pos, self.y_centers['spec_L'], 'Frequency (Hz)', **label_props) - self.f.text(x_pos, self.y_centers['dpl_L'], 'Current dipole \n (nAm)', **label_props) - self.f.text(x_pos, self.y_centers['pgram_L'], 'Welch Spectral \n Power ((nAm)$^2$)', **label_props) - self.f.text(x_pos, self.y_centers['current_L'], 'Total network \n GABA$_A$ current \n ($\mu$A)', **label_props) - - # xlabels - self.f.text(l[0], 0.05, 'Frequency (Hz)') - self.f.text(l[0], 0.25, 'Time (ms)') - - # find the spec_R coords and the associated center - # and create the text label there - coords_spec_R = self.return_axis_bounds('spec_R') - ycenter = coords_spec_R[1] + (coords_spec_R[-1] - coords_spec_R[1]) / 2. - self.f.text(0.97, ycenter, 'Spectral Power \n ((nAm)$^2$)', rotation=270, va='center', ha='center') - - # function to remove labels when not testing - def __remove_labels(self): - for ax in self.ax.keys(): - for label_prefix in self.no_xaxis: - # if ((ax.startswith('dpl')) or (ax.startswith('current'))): - if ax.startswith(label_prefix): - self.ax[ax].set_xticklabels('') - - if ax.endswith('_R'): - for label_prefix in self.no_yaxis: - if ax.startswith(label_prefix): - self.ax[ax].set_yticklabels('') - -# 3 examples of different phases and the aggregate spectral power as a function of delay -class FigDistalPhase(ac.FigBase): - def __init__(self): - ac.FigBase.__init__(self) - self.f = plt.figure(figsize=(15, 4)) - - # set_fontsize() is part of FigBase() - self.set_fontsize(8) - - # various gridspecs - self.gspec = { - 'left0': gridspec.GridSpec(4, 50), - 'left1': gridspec.GridSpec(4, 50), - 'middle': gridspec.GridSpec(4, 50), - 'right': gridspec.GridSpec(1, 1), - } - - # number of cols are the number of gridspecs - n_cols = len(self.gspec.keys()) - - # find the start values by making a linspace from L margin to R margin - # and then remove the R margin's element - # this is why you need n_cols+1 - l = np.linspace(0.1, 0.95, n_cols+1)[:-1] - - # ensure first element of the unique on the diff of l to find - # the width of each panel - # remove the width of some margin - w_margin = 0.05 - w = np.unique(np.diff(l))[0] - w_margin - - # to find the right position, just add w to the l - r = l + w - - # create the gridspecs - self.gspec['left0'].update(wspace=0, hspace=0.15, bottom=0.1, top=0.91, left=l[0], right=r[0]) - self.gspec['left1'].update(wspace=0, hspace=0.15, bottom=0.1, top=0.91, left=l[1], right=r[1]) - self.gspec['middle'].update(wspace=0, hspace=0.15, bottom=0.1, top=0.91, left=l[2], right=r[2]) - self.gspec['right'].update(wspace=0, hspace=0.15, bottom=0.1, top=0.91, left=l[3], right=r[3]) - - # create axes and handles - self.ax = { - 'spec_L': self.f.add_subplot(self.gspec['left0'][:2, :]), - 'spec_M': self.f.add_subplot(self.gspec['left1'][:2, :]), - 'spec_R': self.f.add_subplot(self.gspec['middle'][:2, :]), - - 'dpl_L': self.f.add_subplot(self.gspec['left0'][2:3, :40]), - 'dpl_M': self.f.add_subplot(self.gspec['left1'][2:3, :40]), - 'dpl_R': self.f.add_subplot(self.gspec['middle'][2:3, :40]), - - 'hist_L': self.f.add_subplot(self.gspec['left0'][3:, :40]), - 'hist_M': self.f.add_subplot(self.gspec['left1'][3:, :40]), - 'hist_R': self.f.add_subplot(self.gspec['middle'][3:, :40]), - - 'aggregate': self.f.add_subplot(self.gspec['right'][:, :]), - } - - self.__create_hist_twinx() - self.__add_labels_subfig(l) - - def __create_hist_twinx(self): - # ax_handles_hist = [ax for ax in self.ax.keys() if ax.startswith('hist')] - for ax in self.ax.keys(): - if ax.startswith('hist'): - self.create_axis_twinx(ax) - - # add text labels - def __add_labels_subfig(self, l): - self.f.text(l[0], 0.95, 'A.') - self.f.text(l[1], 0.95, 'B.') - self.f.text(l[2], 0.95, 'C.') - self.f.text(l[3], 0.95, 'D.') - -class FigStDev(ac.FigBase): - def __init__(self, runtype='debug'): - ac.FigBase.__init__(self) - self.f = plt.figure(figsize=(8, 3)) - - # set_fontsize() is part of FigBase() - self.set_fontsize(8) - - # various gridspecs - self.gspec = { - 'left': gridspec.GridSpec(4, 50), - 'middle': gridspec.GridSpec(4, 50), - 'right': gridspec.GridSpec(4, 50), - 'farright': gridspec.GridSpec(4, 50), - } - - # reposition the gridspecs - l = np.arange(0.1, 0.9, 0.2) - # l = np.arange(0.05, 0.95, 0.3) - r = l + 0.175 - - # create the gridspecs - self.gspec['left'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[0], right=r[0]) - self.gspec['middle'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[1], right=r[1]) - self.gspec['right'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[2], right=r[2]) - self.gspec['farright'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[3], right=r[3]) - - self.ax = { - 'hist_L': self.f.add_subplot(self.gspec['left'][:1, :40]), - 'hist_M': self.f.add_subplot(self.gspec['middle'][:1, :40]), - 'hist_R': self.f.add_subplot(self.gspec['right'][:1, :40]), - 'hist_FR': self.f.add_subplot(self.gspec['farright'][:1, :40]), - - 'dpl_L': self.f.add_subplot(self.gspec['left'][1:2, :40]), - 'dpl_M': self.f.add_subplot(self.gspec['middle'][1:2, :40]), - 'dpl_R': self.f.add_subplot(self.gspec['right'][1:2, :40]), - 'dpl_FR': self.f.add_subplot(self.gspec['farright'][1:2, :40]), - - # these are set differently depending on runtype, below - 'spec_L': None, - 'spec_M': None, - 'spec_R': None, - 'spec_FR': None, - } - - if runtype in ('debug', 'pub2'): - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][2:, :]) - self.ax['spec_M'] = self.f.add_subplot(self.gspec['middle'][2:, :]) - self.ax['spec_R'] = self.f.add_subplot(self.gspec['right'][2:, :]) - self.ax['spec_FR'] = self.f.add_subplot(self.gspec['farright'][2:, :]) - - elif runtype == 'pub': - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][2:, :40]) - self.ax['spec_M'] = self.f.add_subplot(self.gspec['middle'][2:, :40]) - self.ax['spec_R'] = self.f.add_subplot(self.gspec['right'][2:, :40]) - self.ax['spec_FR'] = self.f.add_subplot(self.gspec['farright'][2:, :]) - - if runtype.startswith('pub'): - self.__remove_labels() - - self.__create_twinx() - - # methods come from the FigBase() - self.create_ax_bounds_dict() - self.create_y_centers_dict() - self.__add_labels_subfig(l) - - def __create_twinx(self): - for ax_handle in self.ax.keys(): - if ax_handle.startswith('hist'): - self.create_axis_twinx(ax_handle) - - # function to remove labels when not testing - def __remove_labels(self): - for ax in self.ax.keys(): - if ax.startswith(('dpl', 'hist')): - self.ax[ax].set_xticklabels('') - - if ax.endswith(('_M', '_R', '_FR')): - self.ax[ax].set_yticklabels('') - - def remove_twinx_labels(self): - for ax in self.ax_twinx.keys(): - self.ax_twinx[ax].set_yticklabels('') - - # add text labels - def __add_labels_subfig(self, l): - self.f.text(self.ax_bounds['hist_L'][0], 0.95, 'A.') - self.f.text(self.ax_bounds['hist_M'][0], 0.95, 'B.') - self.f.text(self.ax_bounds['hist_R'][0], 0.95, 'C.') - self.f.text(self.ax_bounds['hist_FR'][0], 0.95, 'D.') - - labels_left = { - 'va': 'center', - 'ma': 'center', - 'rotation': 90, - } - self.f.text(0.025, self.y_centers['spec_L'], 'Frequency \n (Hz)', **labels_left) - self.f.text(0.025, self.y_centers['dpl_L'], 'Current dipole \n (nAm)', **labels_left) - self.f.text(0.025, self.y_centers['hist_L'], 'EPSPs', **labels_left) - - self.f.text(self.ax_bounds['spec_L'][0], 0.025, 'Time (ms)', ha='left') - self.f.text(self.ax_bounds['spec_FR'][2] + 0.05, self.y_centers['spec_FR'], 'Power spectral density \n ((nAm)$^2$/Hz)', rotation=270, va='center', ma='center') - -class FigPanel4(ac.FigBase): - def __init__(self, runtype='debug'): - ac.FigBase.__init__(self) - self.f = plt.figure(figsize=(8, 3)) - - # set_fontsize() is part of FigBase() - self.set_fontsize(8) - - # various gridspecs - self.gspec = { - 'left': gridspec.GridSpec(4, 50), - 'middle': gridspec.GridSpec(4, 50), - 'right': gridspec.GridSpec(4, 50), - 'farright': gridspec.GridSpec(4, 50), - } - - # reposition the gridspecs - l = np.arange(0.1, 0.9, 0.2) - # l = np.arange(0.05, 0.95, 0.3) - r = l + 0.175 - - # create the gridspecs - self.gspec['left'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[0], right=r[0]) - self.gspec['middle'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[1], right=r[1]) - self.gspec['right'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[2], right=r[2]) - self.gspec['farright'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[3], right=r[3]) - - self.ax = { - 'hist_L': self.f.add_subplot(self.gspec['left'][:1, :40]), - 'hist_M': self.f.add_subplot(self.gspec['middle'][:1, :40]), - 'hist_R': self.f.add_subplot(self.gspec['right'][:1, :40]), - 'hist_FR': self.f.add_subplot(self.gspec['farright'][:1, :40]), - - 'dpl_L': self.f.add_subplot(self.gspec['left'][1:2, :40]), - 'dpl_M': self.f.add_subplot(self.gspec['middle'][1:2, :40]), - 'dpl_R': self.f.add_subplot(self.gspec['right'][1:2, :40]), - 'dpl_FR': self.f.add_subplot(self.gspec['farright'][1:2, :40]), - - # these are set differently depending on runtype, below - 'spec_L': None, - 'spec_M': None, - 'spec_R': None, - 'spec_FR': None, - } - - if runtype in ('debug', 'pub2'): - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][2:, :]) - self.ax['spec_M'] = self.f.add_subplot(self.gspec['middle'][2:, :]) - self.ax['spec_R'] = self.f.add_subplot(self.gspec['right'][2:, :]) - self.ax['spec_FR'] = self.f.add_subplot(self.gspec['farright'][2:, :]) - - elif runtype == 'pub': - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][2:, :40]) - self.ax['spec_M'] = self.f.add_subplot(self.gspec['middle'][2:, :40]) - self.ax['spec_R'] = self.f.add_subplot(self.gspec['right'][2:, :40]) - self.ax['spec_FR'] = self.f.add_subplot(self.gspec['farright'][2:, :]) - - if runtype.startswith('pub'): - self.__remove_labels() - - self.__create_twinx() - - # methods come from the FigBase() - self.create_ax_bounds_dict() - self.create_y_centers_dict() - self.__add_labels_subfig(l) - - def __create_twinx(self): - for ax_handle in self.ax.keys(): - if ax_handle.startswith('hist'): - self.create_axis_twinx(ax_handle) - - # function to remove labels when not testing - def __remove_labels(self): - for ax in self.ax.keys(): - if ax.startswith(('dpl', 'hist')): - self.ax[ax].set_xticklabels('') - - if ax.endswith(('_M', '_R', '_FR')): - self.ax[ax].set_yticklabels('') - - def remove_twinx_labels(self): - for ax in self.ax_twinx.keys(): - self.ax_twinx[ax].set_yticklabels('') - - # add text labels - def __add_labels_subfig(self, l): - self.f.text(self.ax_bounds['hist_L'][0], 0.95, 'A.') - self.f.text(self.ax_bounds['hist_M'][0], 0.95, 'B.') - self.f.text(self.ax_bounds['hist_R'][0], 0.95, 'C.') - self.f.text(self.ax_bounds['hist_FR'][0], 0.95, 'D.') - - labels_left = { - 'va': 'center', - 'ma': 'center', - 'rotation': 90, - } - self.f.text(0.025, self.y_centers['spec_L'], 'Frequency \n (Hz)', **labels_left) - self.f.text(0.025, self.y_centers['dpl_L'], 'Current dipole \n (nAm)', **labels_left) - self.f.text(0.025, self.y_centers['hist_L'], 'EPSPs', **labels_left) - - self.f.text(self.ax_bounds['spec_L'][0], 0.025, 'Time (ms)', ha='left') - self.f.text(self.ax_bounds['spec_FR'][2] + 0.05, self.y_centers['spec_FR'], 'Power spectral density \n ((nAm)$^2$/Hz)', rotation=270, va='center', ma='center') - -class Fig3PanelPlusAgg(ac.FigBase): - def __init__(self, runtype='debug'): - ac.FigBase.__init__(self) - self.f = plt.figure(figsize=(10, 3)) - - # set_fontsize() is part of FigBase() - self.set_fontsize(6) - - # various gridspecs - self.gspec = { - 'left': gridspec.GridSpec(5, 50), - 'middle': gridspec.GridSpec(5, 50), - 'right': gridspec.GridSpec(5, 50), - 'farright': gridspec.GridSpec(5, 50), - } - - # reposition the gridspecs - # l = np.arange(0.075, 0.8, 0.22) - l = np.array([0.075, 0.295, 0.515, 0.740]) - # l = np.arange(0.05, 0.95, 0.3) - r = l + 0.2 - # r[-1] += 0.025 - - # create the gridspecs - self.gspec['left'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[0], right=r[0]) - self.gspec['middle'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[1], right=r[1]) - self.gspec['right'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[2], right=r[2]) - self.gspec['farright'].update(wspace=0, hspace=0.30, bottom=0.15, top=0.94, left=l[3], right=r[3]) - - self.ax = { - 'hist_L': self.f.add_subplot(self.gspec['left'][:1, :40]), - 'hist_M': self.f.add_subplot(self.gspec['middle'][:1, :40]), - 'hist_R': self.f.add_subplot(self.gspec['right'][:1, :40]), - - 'dpl_L': self.f.add_subplot(self.gspec['left'][1:3, :40]), - 'dpl_M': self.f.add_subplot(self.gspec['middle'][1:3, :40]), - 'dpl_R': self.f.add_subplot(self.gspec['right'][1:3, :40]), - - # aggregate welch - 'pgram': self.f.add_subplot(self.gspec['farright'][:, :]), - - # these are set differently depending on runtype, below - 'spec_L': None, - 'spec_M': None, - 'spec_R': None, - } - - if runtype in ('debug', 'pub2'): - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][3:, :]) - self.ax['spec_M'] = self.f.add_subplot(self.gspec['middle'][3:, :]) - self.ax['spec_R'] = self.f.add_subplot(self.gspec['right'][3:, :]) - - elif runtype == 'pub': - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][3:, :40]) - self.ax['spec_M'] = self.f.add_subplot(self.gspec['middle'][3:, :40]) - self.ax['spec_R'] = self.f.add_subplot(self.gspec['right'][3:, :]) - - if runtype.startswith('pub'): - self.__remove_labels() - - # periodogram hold on - self.ax['pgram'].hold(True) - self.ax['pgram'].yaxis.tick_right() - - self.__create_twinx() - self.create_ax_bounds_dict() - self.create_y_centers_dict() - self.__add_labels_subfig(l) - - def __create_twinx(self): - for ax_handle in self.ax.keys(): - if ax_handle.startswith('hist'): - self.create_axis_twinx(ax_handle) - - # function to remove labels when not testing - def __remove_labels(self): - for ax in self.ax.keys(): - if ax.startswith(('dpl', 'hist')): - self.ax[ax].set_xticklabels('') - - if ax.endswith(('_M', '_R')): - self.ax[ax].set_yticklabels('') - - def remove_twinx_labels(self): - for ax in self.ax_twinx.keys(): - self.ax_twinx[ax].set_yticklabels('') - - # add text labels - def __add_labels_subfig(self, l): - self.f.text(l[0], 0.95, 'A.') - self.f.text(l[1], 0.95, 'B.') - self.f.text(l[2], 0.95, 'C.') - self.f.text(l[3], 0.95, 'D.') - - ylabel_props = { - 'rotation': 90, - 'va': 'center', - 'ma': 'center', - } - - ylabel_right_props = { - 'rotation': 270, - 'va': 'center', - 'ma': 'center', - 'ha': 'center', - } - - xoffset = 0.0675 - - # y labels - self.f.text(l[0] - xoffset, self.y_centers['hist_L'], 'EPSP \n Count', **ylabel_props) - self.f.text(l[0] - xoffset, self.y_centers['dpl_L'], 'Current Dipole \n (nAm)', **ylabel_props) - self.f.text(l[0] - xoffset, self.y_centers['spec_L'], 'Frequency \n (Hz)', **ylabel_props) - - # self.ax['spec_L'].set_ylabel('Frequency (Hz)') - # self.ax['dpl_L'].set_ylabel('Current dipole (nAm)') - # self.ax['hist_L'].set_ylabel('EPSP count') - - self.f.text(l[0], 0.025, 'Time (ms)') - self.f.text(l[-1], 0.025, 'Frequency (Hz)') - self.f.text(0.975, self.y_centers['pgram'], 'Welch Spectral Power \n ((nAm)$^2$ x10$^{-7}$)', **ylabel_right_props) - self.f.text(self.ax_bounds['spec_R'][2] + 0.005, self.y_centers['spec_R'], 'Spectral Power \n ((nAm)$^2$)', fontsize=5, **ylabel_right_props) - # self.f.text(self.ax_bounds['hist_L'][-2]+0.05, self.y_centers['hist_L'], 'Distal EPSP Count', rotation=270, va='center', ma='center', ha='center') - # self.f.text(0.925, 0.40, 'Power spectral density ((nAm)$^2$/Hz)', rotation=270) - -class FigSubDistExample(ac.FigBase): - def __init__(self, runtype='debug'): - ac.FigBase.__init__(self) - self.f = plt.figure(figsize=(6, 5)) - - # set_fontsize() is part of FigBase() - self.set_fontsize(8) - - # various gridspecs - self.gspec = { - 'left': gridspec.GridSpec(4, 50), - 'right': gridspec.GridSpec(4, 50), - } - - # reposition the gridspecs - l = np.array([0.1, 0.52]) - # l = np.arange(0.1, 0.9, 0.45) - r = l + 0.39 - - # create the gridspecs - self.gspec['left'].update(wspace=0, hspace=0.30, bottom=0.1, top=0.94, left=l[0], right=r[0]) - self.gspec['right'].update(wspace=0, hspace=0.30, bottom=0.1, top=0.94, left=l[1], right=r[1]) - - self.ax = { - 'hist_L': self.f.add_subplot(self.gspec['left'][:1, :40]), - 'hist_R': self.f.add_subplot(self.gspec['right'][:1, :40]), - - 'dpl_L': self.f.add_subplot(self.gspec['left'][1:2, :40]), - 'dpl_R': self.f.add_subplot(self.gspec['right'][1:2, :40]), - - # these are set differently depending on runtype, below - 'spec_L': None, - 'spec_R': None, - } - - if runtype in ('debug', 'pub2'): - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][2:, :]) - self.ax['spec_R'] = self.f.add_subplot(self.gspec['right'][2:, :]) - - elif runtype == 'pub': - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][2:, :40]) - self.ax['spec_R'] = self.f.add_subplot(self.gspec['right'][2:, :]) - - if runtype.startswith('pub'): - self.__remove_labels() - - self.__create_twinx() - self.create_ax_bounds_dict() - self.create_y_centers_dict() - self.__add_labels_subfig(l) - - def __create_twinx(self): - for ax_handle in self.ax.keys(): - if ax_handle.startswith('hist'): - self.create_axis_twinx(ax_handle) - - # function to remove labels when not testing - def __remove_labels(self): - for ax in self.ax.keys(): - if ax.startswith(('dpl', 'hist')): - self.ax[ax].set_xticklabels('') - - if ax.endswith('_R'): - self.ax[ax].set_yticklabels('') - - def remove_twinx_labels(self): - for ax in self.ax_twinx.keys(): - if ax.endswith('_R'): - self.ax_twinx[ax].set_yticklabels('') - - # add text labels - def __add_labels_subfig(self, l): - # left labels - labels_left = { - 'va': 'center', - 'ma': 'center', - 'rotation': 90, - } - self.f.text(0.02, self.y_centers['spec_L'], 'Frequency (Hz)', **labels_left) - self.f.text(0.02, self.y_centers['dpl_L'], 'Current Dipole \n (nAm)', **labels_left) - self.f.text(0.02, self.y_centers['hist_L'], 'Proximal EPSP Count', **labels_left) - # self.f.text(self.ax_bounds['spec_M'][0], self.ax_bounds['spec_M'][1] - 0.05, 'Time (ms)', ha='left') - self.f.text(self.ax_bounds['hist_L'][-2]+0.05, self.y_centers['hist_L'], 'Distal EPSP Count', rotation=270, va='center', ma='center', ha='center') - self.f.text(0.95, self.y_centers['spec_R'], 'Power spectral density \n ((nAm)$^2$/Hz)', rotation=270, ha='center', ma='center', va='center') - - self.f.text(l[0], 0.95, 'A.') - self.f.text(l[1], 0.95, 'B.') - - # self.ax['spec_L'].set_ylabel('Frequency (Hz)') - # self.ax['dpl_L'].set_ylabel('Current dipole (nAm)') - # self.ax['hist_L'].set_ylabel('Proximal EPSP count') - # self.ax_twinx['hist_L'].set_ylabel('Distal EPSP count') - - self.f.text(l[0], 0.025, 'Time (ms)') - -class FigPeaks(ac.FigBase): - def __init__(self, runtype='debug'): - ac.FigBase.__init__(self) - self.f = plt.figure(figsize=(4, 5)) - - # set_fontsize() is part of FigBase() - self.set_fontsize(8) - - # various gridspecs - self.gspec = { - 'left': gridspec.GridSpec(4, 50), - 'right': gridspec.GridSpec(4, 50), - } - - # reposition the gridspecs - l = np.arange(0.1, 0.9, 0.45) - r = l + 0.8 - - # create the gridspecs - self.gspec['left'].update(wspace=0, hspace=0.30, bottom=0.1, top=0.94, left=l[0], right=r[0]) - self.gspec['right'].update(wspace=0, hspace=0.30, bottom=0.1, top=0.94, left=l[1], right=r[1]) - - self.ax = { - 'dpl_L': self.f.add_subplot(self.gspec['left'][:1, :40]), - 'hist_L': self.f.add_subplot(self.gspec['left'][1:2, :40]), - - # these are set differently depending on runtype, below - 'spec_L': None, - } - - if runtype in ('debug', 'pub2'): - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][2:, :]) - - elif runtype == 'pub': - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][2:, :40]) - - if runtype.startswith('pub'): - self.__remove_labels() - - # self.__create_twinx() - # self.__add_labels_subfig(l) - - def __create_twinx(self): - for ax_handle in self.ax.keys(): - if ax_handle.startswith('hist'): - self.create_axis_twinx(ax_handle) - - # function to remove labels when not testing - def __remove_labels(self): - for ax in self.ax.keys(): - if ax.startswith(('dpl', 'hist')): - self.ax[ax].set_xticklabels('') - - if ax.endswith('_R'): - self.ax[ax].set_yticklabels('') - - def remove_twinx_labels(self): - for ax in self.ax_twinx.keys(): - self.ax_twinx[ax].set_yticklabels('') - - # add text labels - def __add_labels_subfig(self, l): - self.f.text(l[0], 0.95, 'A.') - self.f.text(l[1], 0.95, 'B.') - - self.ax['spec_L'].set_ylabel('Frequency (Hz)') - self.ax['dpl_L'].set_ylabel('Current dipole (nAm)') - self.ax['hist_L'].set_ylabel('EPSP count') - - self.f.text(l[0], 0.025, 'Time (ms)') - self.f.text(0.95, 0.40, 'Power spectral density ((nAm)$^2$/Hz)', rotation=270) - -class FigHF(ac.FigBase): - def __init__(self, runtype='debug'): - ac.FigBase.__init__(self) - self.f = plt.figure(figsize=(5, 7)) - - # set_fontsize() is part of FigBase() - self.set_fontsize(8) - - # various gridspecs - self.gspec = { - 'left': gridspec.GridSpec(5, 50), - } - - # reposition the gridspecs - # l = np.arange(0.1, 0.9, 0.28) - # l = np.arange(0.05, 0.95, 0.3) - # r = l + 0.275 - - # create the gridspecs - self.gspec['left'].update(wspace=0, hspace=0.30, bottom=0.1, top=0.94, left=0.2, right=0.95) - l = 0.1 - - self.ax = { - 'spk': self.f.add_subplot(self.gspec['left'][:1, :40]), - 'hist_L': self.f.add_subplot(self.gspec['left'][1:2, :40]), - 'dpl_L': self.f.add_subplot(self.gspec['left'][2:4, :40]), - - # these are set differently depending on runtype, below - 'spec_L': None, - } - - if runtype in ('debug', 'pub2'): - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][4:, :]) - - elif runtype == 'pub': - self.ax['spec_L'] = self.f.add_subplot(self.gspec['left'][4:, :40]) - - # if runtype.startswith('pub'): - # self.__remove_labels() - - self.__create_twinx() - self.__add_labels_subfig(l) - - def __create_twinx(self): - for ax_handle in self.ax.keys(): - if ax_handle.startswith('dpl'): - self.create_axis_twinx(ax_handle) - - # function to remove labels when not testing - def __remove_labels(self): - for ax in self.ax.keys(): - if ax.startswith(('dpl', 'hist')): - self.ax[ax].set_xticklabels('') - - if ax.endswith(('_M', '_R')): - self.ax[ax].set_yticklabels('') - - def remove_twinx_labels(self): - for ax in self.ax_twinx.keys(): - self.ax_twinx[ax].set_yticklabels('') - - # add text labels - def __add_labels_subfig(self, l): - # self.f.text(l, 0.95, 'A.') - - self.ax['spk'].set_ylabel('Cells') - self.ax['spec_L'].set_ylabel('Frequency (Hz)') - self.ax['spec_L'].set_xlabel('Time (ms)') - self.ax['dpl_L'].set_ylabel('Current dipole (nAm)') - self.ax['hist_L'].set_ylabel('L5 Pyramidal Spikes') - self.ax_twinx['dpl_L'].set_ylabel('Current (nA)', rotation=270) - - # self.f.text(l, 0.025, 'Time (ms)') - # self.f.text(0.925, 0.40, 'Power spectral density ((nAm)$^2$/Hz)', rotation=270) - -# high frequency epochs fig -class FigHFEpochs(ac.FigBase): - def __init__(self, runtype='pub'): - ac.FigBase.__init__(self) - self.f = plt.figure(figsize=(9.5, 7)) - - # set_fontsize() is part of FigBase() - self.set_fontsize(9) - - # called L_gspec so the ax keys can be (a) sortably grouped and (b) congruent with gspec - # I want it to be called gspec_L, but you can't have everything you want in life. - self.L_gspec = gridspec.GridSpec(5, 50) - self.L_gspec.update(wspace=0, hspace=0.1, bottom=0.1, top=0.95, left=0.07, right=0.32) - - self.gspec_ex = [ - gridspec.GridSpec(7, 50), - gridspec.GridSpec(7, 50), - gridspec.GridSpec(7, 50), - gridspec.GridSpec(7, 50), - ] - - # reposition the gridspecs. there are cleverer ways of doing this - w = 0.25 - l_split = np.array([0.05, 0.36, 0.63]) - # l_split = np.arange(0.05, 0.95, 0.3) - self.l_ex = np.array([l_split[1], l_split[2], l_split[1], l_split[2]]) - self.r_ex = self.l_ex + w - - # bottom and tops - h = 0.4 - b_ex = np.array([0.55, 0.55, 0.10, 0.10]) - # b_ex = np.array([0.1, 0.1, 0.55, 0.55]) - t_ex = b_ex + h - - # l = np.arange(0.25, 0.9, 0.2) - # r = l + 0.1 - # r = l + 0.275 - - # create the gridspecs - for i in range(len(self.gspec_ex)): - self.gspec_ex[i].update(wspace=0, hspace=0.30, bottom=b_ex[i], top=t_ex[i], left=self.l_ex[i], right=self.r_ex[i]) - - self.ax = { - 'L_spk': self.f.add_subplot(self.L_gspec[:2, :40]), - 'L_dpl': self.f.add_subplot(self.L_gspec[2:3, :40]), - 'L_spec': self.f.add_subplot(self.L_gspec[3:, :40]), - 'hist': [ - self.f.add_subplot(self.gspec_ex[0][:1, :40]), - self.f.add_subplot(self.gspec_ex[1][:1, :40]), - self.f.add_subplot(self.gspec_ex[2][:1, :40]), - self.f.add_subplot(self.gspec_ex[3][:1, :40]), - ], - - 'spk': [ - self.f.add_subplot(self.gspec_ex[0][1:3, :40]), - self.f.add_subplot(self.gspec_ex[1][1:3, :40]), - self.f.add_subplot(self.gspec_ex[2][1:3, :40]), - self.f.add_subplot(self.gspec_ex[3][1:3, :40]), - ], - - 'dpl': [ - self.f.add_subplot(self.gspec_ex[0][3:5, :40]), - self.f.add_subplot(self.gspec_ex[1][3:5, :40]), - self.f.add_subplot(self.gspec_ex[2][3:5, :40]), - self.f.add_subplot(self.gspec_ex[3][3:5, :40]), - ], - - # these are set differently depending on runtype, below - 'spec': None, - } - - if runtype in ('debug', 'pub2'): - self.ax['spec'] = [ - self.f.add_subplot(self.gspec_ex[0][5:, :]), - self.f.add_subplot(self.gspec_ex[1][5:, :]), - self.f.add_subplot(self.gspec_ex[2][5:, :]), - self.f.add_subplot(self.gspec_ex[3][5:, :]), - ] - - elif runtype == 'pub': - self.ax['spec'] = [ - self.f.add_subplot(self.gspec_ex[0][5:, :40]), - self.f.add_subplot(self.gspec_ex[1][5:, :40]), - self.f.add_subplot(self.gspec_ex[2][5:, :40]), - self.f.add_subplot(self.gspec_ex[3][5:, :]), - ] - - self.__create_twinx() - - if runtype.startswith('pub'): - self.__remove_labels() - - self.create_ax_bounds_dict() - self.create_y_centers_dict() - self.__add_labels_left() - self.__add_labels_subfig() - - # takes a specific ax and a well-formed props_dict - def add_sine(self, ax, props_dict): - # will create fake sine waves with various properties - # t_center = props_dict['t_center'] - f = props_dict['f'] - # t_half = 0.5 * (1000. / f) - t0 = props_dict['t'][0] - T = props_dict['t'][1] - # t0 = t_center - t_half - # T = t_center + t_half - t = np.arange(t0, T, props_dict['dt']) - x = props_dict['A'] * np.sin(2 * np.pi * f * (t - t0) / 1000.) - # x = 0.01 * np.sin(2 * np.pi * f * (t - t0) / 1000.) - ax.plot(t, x, 'r--') - - # the general twinx function cannot be used, due to the structure of the axes here - def __create_twinx(self): - # just create the twinx list for the dipole - self.ax_twinx['dpl'] = [ax_h.twinx() for ax_h in self.ax['dpl']] - - # function to remove labels when not testing - def __remove_labels(self): - for ax_h in self.ax.keys(): - if ax_h.startswith('L_'): - if ax_h.endswith(('spk', 'dpl')): - self.ax[ax_h].set_xticklabels('') - - if ax_h.endswith('spk'): - self.ax[ax_h].set_yticklabels('') - - for ax_h in self.ax.keys(): - if not ax_h.startswith(('L', 'spec')): - for i in range(1, 4): - self.ax[ax_h][i].set_xticklabels('') - - if not ax_h.startswith('L'): - if not ax_h.startswith('spec'): - for i in range(0, 2): - self.ax[ax_h][i].set_xticklabels('') - - for i in range(1, 4): - self.ax[ax_h][i].set_yticklabels('') - - # remove the spk ones - if ax_h == 'spk': - self.ax[ax_h][0].set_yticklabels('') - - for i in range(1, 4): - self.ax_twinx['dpl'][i].set_yticklabels('') - - def remove_twinx_labels(self): - for ax in self.ax_twinx.keys(): - self.ax_twinx[ax].set_yticklabels('') - - # add text labels - def __add_labels_left(self): - label_props = { - 'rotation': 90, - 'va': 'center', - 'ma': 'center', - } - self.f.text(0.018, self.y_centers['L_spec'], 'Frequency (Hz)', **label_props) - self.f.text(0.018, self.y_centers['L_dpl'], 'Current Dipole (nAm)', **label_props) - self.f.text(0.018, self.y_centers['L_spk'], 'Cells', **label_props) - self.f.text(0.07, 0.04, 'Time (ms)', ha='left') - - x = self.ax_bounds['L_spk'][0] - y = self.ax_bounds['L_spk'][-1] + 0.005 - self.f.text(x, y, 'A.', ha='left') - - def __add_labels_subfig(self): - list_labels = ['B.', 'C.', 'D.', 'E.'] - - label_props = { - 'ha': 'left', - } - - # for ax_h, lbl in zip(self.ax['hist'], list_labels): - for i in range(len(self.ax['hist'])): - # get the x coord - x = self.ax_bounds['hist'][i][0] - y = self.ax_bounds['hist'][i][-1] + 0.005 - self.f.text(x, y, list_labels[i], **label_props) - - # for x_l, lbl in zip(self.l_ex, list_labels): - # self.f.text(x_l, 0.95, lbl+'.') - - ylabel_props = { - 'rotation': 90, - 'va': 'center', - 'ma': 'center', - } - - xoffset = 0.0675 - - # y labels - self.f.text(self.l_ex[0] - xoffset, self.y_centers['spec'][0], 'Frequency \n (Hz)', **ylabel_props) - self.f.text(self.l_ex[0] - xoffset, self.y_centers['dpl'][0], 'Current Dipole \n (nAm)', **ylabel_props) - self.f.text(self.l_ex[0] - xoffset, self.y_centers['hist'][0], 'L5 Pyramidal \n Spike Count', **ylabel_props) - self.f.text(self.l_ex[0] - xoffset, self.y_centers['spk'][0], 'Cells', **ylabel_props) - self.f.text(self.r_ex[0] - 0.02, self.y_centers['dpl'][0], 'Somatic current \n ($\mu$A)', rotation=270, ma='center', va='center') - self.f.text(0.925, self.y_centers['spec'][-1], 'Spectral Power \n ((nAm)$^2$)', rotation=270, ma='center', va='center') - - # time label - self.f.text(self.l_ex[0], 0.04, 'Time (ms)') - -if __name__ == '__main__': - x = np.random.rand(100) - - f_test = 'testing.png' - - print mpl.get_backend() - - # testfig for FigDipoleExp() - # testfig = ac.FigDipoleExp(ax_handles) - # testfig.create_colorbar_axis('spec') - # testfig.ax['spec'].plot(x) - - # testfig = FigTest() - # testfig = FigStDev() - # testfig = FigL5PingExample() - # testfig = FigSubDistExample() - # testfig = FigLaminarComparison() - # testfig = FigDistalPhase() - # testfig = FigPeaks() - testfig = FigSimpleSpec() - testfig.savepng(f_test) - testfig.close() diff --git a/averaging.py b/averaging.py deleted file mode 100644 index 2fd728a44..000000000 --- a/averaging.py +++ /dev/null @@ -1,69 +0,0 @@ -# DEPRECATE - -# averaging.py - routine to perform averaging -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: using new return_data_dir()) -# last major: (SL: pushed for CSM and CB) - -import fileio as fio -import dipolefn -import matplotlib.pyplot as plt -import numpy as np -import os - -# routine to average the dipoles found in the dsim directory -def average_dipole(dsim): - dproj = fio.return_data_dir() - - ddata = fio.SimulationPaths() - ddata.read_sim(dproj, dsim) - - # grab the first experimental group - expmt_group = ddata.expmt_groups[0] - - flist = ddata.file_match(expmt_group, 'rawdpl') - N_dpl = len(flist) - - # grab the time and the length - dpl_time = dipolefn.Dipole(flist[0]).t - length_dpl = dipolefn.Dipole(flist[0]).N - - # preallocation of the total dipole - # dpl_agg = np.zeros((N_dpl, length_dpl)) - dpl_sum = np.zeros(length_dpl) - - # the specific dipole to use - dpl_specific = 'agg' - - for f in flist: - dpl_f = dipolefn.Dipole(f) - dpl_sum = dpl_sum + dpl_f.dpl[dpl_specific] - - dpl_scaled = dpl_sum * 1e-6 - dpl_mean = dpl_scaled / N_dpl - - print dpl_sum - print ' ' - print dpl_scaled - print ' ' - print dpl_mean - - figure_create(dpl_time, dpl_mean) - -# simple plot of the mean dipole -def figure_create(dpl_time, dpl_agg): - fig = plt.figure() - ax = { - 'dpl_agg': fig.add_subplot(1, 1, 1), - } - - # example - ax['dpl_agg'].plot(dpl_time, dpl_agg, linewidth=0.5, color='k') - fig.savefig('testing_dpl.png', dpi=200) - plt.close(fig) - -if __name__ == '__main__': - droot = fio.return_data_dir() - dsim = os.path.join(droot, '2015-12-02/tonic_L5Pyr-000') - average_dipole(dsim) diff --git a/axes_create.py b/axes_create.py deleted file mode 100644 index a92690e46..000000000 --- a/axes_create.py +++ /dev/null @@ -1,832 +0,0 @@ -# axes_create.py - simple axis creation -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: checked all the divides for compatibility) -# last major: (SL: toward python3) - -# usage: -# testfig = FigStd() -# testfig.ax0.plot(somedata) -# plt.savefig('testfig.png') -# testfig.close() - -import paramrw -import matplotlib as mpl -from matplotlib import ticker -import matplotlib.pyplot as plt -import matplotlib.gridspec as gridspec -import itertools as it -import numpy as np -import os, sys - -# Base figure class -class FigBase(): - def __init__(self): - # self.f is typically set by the super class - self.f = None - - # only use LaTeX (latex) if on Mac - # kind of kludgy temporary fix for now. - # if sys.platform.startswith('darwin'): - # mpl.rc('text', usetex=True) - # elif sys.platform.startswith('linux'): - # pass - - # axis dicts are guaranteed to exist at least, sheesh - self.ax = {} - self.ax_twinx = {} - - # creates a twinx axis for the specified axis - def create_axis_twinx(self, ax_name): - if ax_name in self.ax.keys(): - self.ax_twinx[ax_name] = self.ax[ax_name].twinx() - - # returns the index of most recently added element (now the length) - return ax_name - - else: - # returns valid axis name ONLY if it existed - # otherwise, None will break other code - return None - - # returns axis bounds for an arbitrary axis handle - # ax_h must be defined as a key in self.ax - def return_axis_bounds(self, ax_h): - if ax_h in self.ax.keys(): - # check to see if this axis handle is actually a list - if isinstance(self.ax[ax_h], list): - # create a list of coords - list_coords_bbox = [] - - # iterate through axes in the list - for ax_item in self.ax[ax_h]: - # get the coords for the axis - coords = ax_item.get_position().get_points() - - # append the cleaned up version - list_coords_bbox.append(np.reshape(coords, (1, 4))[0]) - - return list_coords_bbox - - else: - # these are *not* beatbox coordinates - coords_bbox = self.ax[ax_h].get_position().get_points() - - # reshape the coords - return np.reshape(coords_bbox, (1, 4))[0] - - else: - print("Axis not found by return_axis_bounds()") - return 0 - - # needs to be run externally, after self.ax is established - def create_ax_bounds_dict(self): - # make a dict - self.ax_bounds = dict.fromkeys(self.ax) - - # iterate through keys and use return_axis_bounds() to get the axis - # this is now working for lists - for ax_h in self.ax_bounds.keys(): - self.ax_bounds[ax_h] = self.return_axis_bounds(ax_h) - - # creates a dict of axes that gives the center y pos - # needs to be run externally, after self.ax is established - # can utilize create_ax_bounds_dict() in the future - def create_y_centers_dict(self): - self.y_centers = dict.fromkeys(self.ax) - - for ax_h in self.y_centers.keys(): - if isinstance(self.ax[ax_h], list): - list_ax_bounds = self.return_axis_bounds(ax_h) - list_y_top = [ax_bounds[-1] for ax_bounds in list_ax_bounds] - list_y_bot = [ax_bounds[1] for ax_bounds in list_ax_bounds] - self.y_centers[ax_h] = [y_bot + (y_top - y_bot) / 2. for y_top, y_bot in zip(list_y_top, list_y_bot)] - - else: - # get the axis bounds - ax_bounds = self.return_axis_bounds(ax_h) - y_top = ax_bounds[-1] - y_bot = ax_bounds[1] - - self.y_centers[ax_h] = y_bot + (y_top - y_bot) / 2. - - # function to set the scientific notation limits - def set_notation_scientific(self, list_ax_handles, n=3): - # set the formatter - fmt = ticker.ScalarFormatter() - fmt.set_powerlimits((-n, n)) - for h in list_ax_handles: - self.ax[h].yaxis.set_major_formatter(fmt) - - return fmt - - # generic function to take an axis handle and make the y-axis even - def ysymmetry(self, ax): - ylim = ax.get_ylim() - yabs_max = np.max(np.abs(ylim)) - ylim_new = (-yabs_max, yabs_max) - ax.set_ylim(ylim_new) - - return ylim_new - - # equalizes SIZE of the ylim but keeps the center of the axis - # whatever makes sense for the data - def equalize_ylim_size(self, list_handles): - list_ylim_size = [] - - # create a dict of ylims from list_handles - ylim = dict.fromkeys(list_handles) - - # grab the current sizes - for h in list_handles: - # outputs of tuples for dict entries in ylim - ylim[h] = f.ax[h].get_ylim() - ylim_size = np.abs(ylim[h][-1] - ylim[h][0]) - list_ylim_size.append(ylim_size) - - # figure out which was the biggest - ylim_size_max = np.max(list_ylim_size) - - # iterate through the handles again, if the size is less than the max size, - # then adjust it appropriately - - # checks on all yaxes and then sets them - def equalize_ylim(self, list_handles): - list_ylim = [] - - # assumes axes are in a self.ax dictionary, and the keys of the dict - # are the names given in list_handles - for h in list_handles: - ymin_local, ymax_local = self.ax[h].get_ylim() - - # append to list - list_ylim.extend([ymin_local, ymax_local]) - - # calculate the ylim - ylim = [np.min(list_ylim), np.max(list_ylim)] - - # now set for all handles - for h in list_handles: - self.ax[h].set_ylim(ylim) - - return ylim - - # equalizing the color lims is a slightly different process that requires the pc_dict to be passed - def equalize_speclim(self, pc_dict): - list_lim_spec = [] - - # assume that the handles have clims that will be assigned by the pc_dict - for h in pc_dict.keys(): - vmin, vmax = pc_dict[h].get_clim() - list_lim_spec.extend([vmin, vmax]) - - # create a ylim from list_lim_spec - ylim = (np.min(list_lim_spec), np.max(list_lim_spec)) - - for h in pc_dict.keys(): - # can this be done: set_clim(ylim) - pc_dict[h].set_clim(ylim[0], ylim[1]) - - return ylim - - # set the font size globally - def set_fontsize(self, s): - font_prop = { - 'size': s, - } - mpl.rc('font', **font_prop) - - # sets the FIRST line found to black for a given axis or list of axes - def set_linecolor(self, ax_key, str_color): - if ax_key in self.ax.keys(): - if isinstance(self.ax[ax_key], list): - for item in self.ax[ax_key]: - item.lines[0].set_color(str_color) - else: - self.ax[ax_key].lines[0].set_color(str_color) - - # creates title string based on params that change during simulation - # title_str = ac.create_title(blah) - # title_str = f.create_title(blah) - def set_title(self, fparam, key_types): - # get param dict - p_dict = paramrw.read(fparam)[1] - - # create_title() is external fn - title = create_title(p_dict, key_types) - self.f.suptitle(title) - - # turns off top and right frame of an axis - def set_frame_off(self, ax_handle): - self.ax[ax_handle].spines['right'].set_visible(False) - self.ax[ax_handle].spines['top'].set_visible(False) - - self.ax[ax_handle].xaxis.set_ticks_position('bottom') - self.ax[ax_handle].yaxis.set_ticks_position('left') - - # generic function to remove xticklabels from a bunch of axes based on handle - def remove_tick_labels(self, list_ax_handles, ax_xy='x'): - for ax_handle in list_ax_handles: - if ax_handle in self.ax.keys(): - if ax_xy == 'x': - self.ax[ax_handle].set_xticklabels('') - elif ax_xy == 'y': - self.ax[ax_handle].set_yticklabels('') - - # generic save png function to file_name at dpi=dpi_set - def savepng(self, file_name, dpi_set=300): - self.f.savefig(file_name, dpi=dpi_set) - - # new png save - def savepng_new(self, dpng, fprefix, dpi_set=300): - # add png - fname = os.path.join(dpng, fprefix+'.png') - self.f.savefig(fname, dpi=dpi_set) - - # generic save, works for png but supposed to be for eps - def saveeps(self, deps, fprefix): - fname = os.path.join(deps, fprefix+'.eps') - self.f.savefig(fname) - - # obligatory close function - def close(self): - plt.close(self.f) - -# Simple one axis window -class FigStd(FigBase): - def __init__(self): - FigBase.__init__(self) - - self.f = plt.figure(figsize=(12, 6)) - self.set_fontsize(8) - - gs0 = gridspec.GridSpec(1, 1) - self.ax = { - 'ax0': self.f.add_subplot(gs0[:]), - } - - # this is a bad way of ensuring backward compatibility - self.ax0 = self.ax['ax0'] - -class FigDplWithHist(FigBase): - def __init__(self): - self.f = plt.figure(figsize=(12, 6)) - font_prop = {'size': 8} - mpl.rc('font', **font_prop) - - # dipole gridpec - self.gs0 = gridspec.GridSpec(1, 1, wspace=0.05, hspace=0, bottom=0.10, top=0.55, left = 0.1, right = 0.90) - - # hist gridspec - self.gs1 = gridspec.GridSpec(2, 1, hspace=0.14 , bottom=0.60, top=0.95, left = 0.1, right = 0.90) - - # create axes - self.ax = {} - self.ax['dipole'] = self.f.add_subplot(self.gs0[:, :]) - self.ax['feed_prox'] = self.f.add_subplot(self.gs1[1, :]) - self.ax['feed_dist'] = self.f.add_subplot(self.gs1[0, :]) - - # setting the properties of a histogram - def set_hist_props(self, hist_data): - for key in self.ax.keys(): - if 'feed' in key: - if hist_data[key] is not None: - max_n = max(hist_data[key][0]) - self.ax[key].set_yticks(np.arange(0, max_n+2, np.ceil((max_n+2.) / 4.))) - - if 'feed_dist' in key: - self.ax[key].set_xticklabels('') - - def save(self, file_name): - self.f.savefig(file_name) - -# spec plus dipole plus alpha feed histograms -class FigSpecWithHist(FigBase): - def __init__(self): - self.f = plt.figure(figsize=(8, 8)) - font_prop = {'size': 8} - mpl.rc('font', **font_prop) - - # the right margin is a hack and NOT guaranteed! - # it's making space for the stupid colorbar that creates a new grid to replace gs1 - # when called, and it doesn't update the params of gs1 - self.gs0 = gridspec.GridSpec(1, 4, wspace=0.05, hspace=0., bottom=0.05, top=0.45, left=0.1, right=1.) - self.gs1 = gridspec.GridSpec(2, 1, height_ratios=[1, 3], bottom=0.50, top=0.70, left=0.1, right=0.82) - self.gs2 = gridspec.GridSpec(2, 1, hspace=0.14, bottom=0.75, top=0.95, left = 0.1, right = 0.82) - - self.ax = {} - self.ax['spec'] = self.f.add_subplot(self.gs0[:, :]) - self.ax['dipole'] = self.f.add_subplot(self.gs1[:, :]) - self.ax['feed_prox'] = self.f.add_subplot(self.gs2[1, :]) - self.ax['feed_dist'] = self.f.add_subplot(self.gs2[0, :]) - - # self.__set_hist_props() - - def set_hist_props(self, hist_data): - for key in self.ax.keys(): - if 'feed' in key: - if hist_data[key] is not None: - max_n = max(hist_data[key][0]) - self.ax[key].set_yticks(np.arange(0, max_n+2, np.ceil((max_n+2.) / 4.))) - - if 'feed_dist' in key: - self.ax[key].set_xticklabels('') - -# spec plus dipole plus alpha feed histograms -class FigPhase(FigBase): - def __init__(self): - self.f = plt.figure(figsize=(8, 12)) - font_prop = {'size': 8} - mpl.rc('font', **font_prop) - - # the right margin is a hack and NOT guaranteed! - # it's making space for the stupid colorbar that creates a new grid to replace gs1 - # when called, and it doesn't update the params of gs1 - self.gs0 = gridspec.GridSpec(1, 4, wspace=0.05, hspace=0., bottom=0.05, top=0.3, left=0.1, right=1.) - self.gs1 = gridspec.GridSpec(1, 4, wspace=0.05, hspace=0., bottom=0.35, top=0.6, left=0.1, right=1.) - self.gs2 = gridspec.GridSpec(2, 1, height_ratios=[1, 3], bottom=0.65, top=0.775, left=0.1, right=0.82) - self.gs3 = gridspec.GridSpec(2, 1, hspace=0.14, bottom=0.825, top=0.95, left = 0.1, right = 0.82) - - self.ax = {} - self.ax['phase'] = self.f.add_subplot(self.gs0[:, :]) - self.ax['spec'] = self.f.add_subplot(self.gs1[:, :]) - self.ax['dipole'] = self.f.add_subplot(self.gs2[:, :]) - self.ax['input'] = self.f.add_subplot(self.gs3[:, :]) - -# spec plus dipole -class FigSpec(FigBase): - def __init__(self): - self.f = plt.figure(figsize=(8, 6)) - font_prop = {'size': 8} - mpl.rc('font', **font_prop) - - # the right margin is a hack and NOT guaranteed! - # it's making space for the stupid colorbar that creates a new grid to replace gs1 - # when called, and it doesn't update the params of gs1 - self.gspec = { - 'dpl': gridspec.GridSpec(2, 1, height_ratios=[1, 3], bottom=0.85, top=0.95, left=0.1, right=0.82), - 'spec': gridspec.GridSpec(1, 4, wspace=0.05, hspace=0., bottom=0.30, top=0.80, left=0.1, right=1.), - 'pgram': gridspec.GridSpec(2, 1, height_ratios=[1, 3], bottom=0.05, top=0.25, left=0.1, right=0.82), - } - - self.ax = {} - self.ax['dipole'] = self.f.add_subplot(self.gspec['dpl'][:, :]) - self.ax['spec'] = self.f.add_subplot(self.gspec['spec'][:, :]) - self.ax['pgram'] = self.f.add_subplot(self.gspec['pgram'][:, :]) - -class FigInterval(FigBase): - def __init__(self, N_trials): - self.f = plt.figure(figsize=(4, N_trials)) - self.set_fontsize(12) - - self.gspec = gridspec.GridSpec(1, 1, right=0.5) - - self.ax = {} - self.ax['ts'] = self.f.add_subplot(self.gspec[:, :]) - self.ax['ts'].hold(True) - self.ax['ts'].set_yticklabels([]) - self.set_frame_off('ts') - -class FigFreqpwrWithHist(FigBase): - def __init__(self): - self.f = plt.figure(figsize = (12, 6)) - font_prop = {'size': 8} - mpl.rc('font', **font_prop) - - # One gridspec for both plots - self.gs0 = gridspec.GridSpec(1, 2, bottom=0.20, top = 0.80, left=0.1, right=0.90, wspace = 0.1) - - self.ax = {} - self.ax['freqpwr'] = self.f.add_subplot(self.gs0[0, 1]) - self.ax['hist'] = self.f.add_subplot(self.gs0[0, 0]) - - def set_hist_props(self, hist_data): - max_n = max(hist_data) - self.ax['hist'].set_yticks(np.arange(0, max_n+2, np.ceil((max_n+2.) / 4.))) - - def save(self, file_name): - self.f.savefig(file_name) - -class FigRaster(FigBase): - def __init__(self, tstop): - self.tstop = tstop - self.f = plt.figure(figsize=(6, 8)) - - grid0 = gridspec.GridSpec(5, 1) - grid0.update(wspace=0.05, hspace=0., bottom=0.05, top=0.45) - - grid1 = gridspec.GridSpec(5, 1) - grid1.update(wspace=0.05, hspace=0., bottom=0.50, top=0.95) - - self.ax = {} - - self.__panel_create(grid1, 'L2') - self.__panel_create(grid0, 'L5') - - for key in self.ax.keys(): - if key == 'L5_extinput': - self.__bottom_panel_prop(self.ax[key]) - - else: - self.__raster_prop(self.ax[key]) - - def __panel_create(self, grid, layer): - self.ax[layer] = self.f.add_subplot(grid[:2, :]) - self.ax[layer+'_extgauss'] = self.f.add_subplot(grid[2:3, :]) - self.ax[layer+'_extpois'] = self.f.add_subplot(grid[3:4, :]) - self.ax[layer+'_extinput'] = self.f.add_subplot(grid[4:, :]) - - def __bottom_panel_prop(self, ax): - ax.set_yticklabels('') - ax.set_xlim(0, self.tstop) - - def __raster_prop(self, ax): - ax.set_yticklabels('') - ax.set_xticklabels('') - ax.set_xlim(0, self.tstop) - -class FigPSTH(FigBase): - def __init__(self, tstop): - self.tstop = tstop - self.f = plt.figure(figsize=(6, 5)) - font_prop = {'size': 8} - mpl.rc('font', **font_prop) - - grid0 = gridspec.GridSpec(6, 2) - grid0.update(wspace=0.05, hspace=0., bottom=0.05, top=0.95) - - self.ax = {} - - self.ax['L2'] = self.f.add_subplot(grid0[:2, :1], title='Layer 2') - self.ax['L2_psth'] = self.f.add_subplot(grid0[2:4, :1]) - self.ax['L2_extgauss'] = self.f.add_subplot(grid0[4:5, :1]) - self.ax['L2_extpois'] = self.f.add_subplot(grid0[5:, :1], xlabel='Time (ms)') - - self.ax['L5'] = self.f.add_subplot(grid0[:2, 1:], title='Layer 5') - self.ax['L5_psth'] = self.f.add_subplot(grid0[2:4, 1:]) - self.ax['L5_extgauss'] = self.f.add_subplot(grid0[4:5, 1:]) - self.ax['L5_extpois'] = self.f.add_subplot(grid0[5:, 1:], xlabel='Time (ms)') - - for key in self.ax.keys(): - if key.endswith('_extpois'): - self.__bottom_panel_prop(self.ax[key]) - - elif key.endswith('_psth'): - self.__psth_prop(self.ax[key]) - - else: - self.__raster_prop(self.ax[key]) - - grid0.tight_layout(self.f, rect=[0, 0, 1, 1], h_pad=0., w_pad=1) - - def __bottom_panel_prop(self, ax): - ax.set_yticklabels('') - ax.set_xlim(0, self.tstop) - ax.get_xticklabels() - # locs, labels = plt.xticks() - # plt.setp(labels, rotation=45) - - def __psth_prop(self, ax): - # ax.set_yticklabels('') - ax.set_xticklabels('') - ax.set_xlim(0, self.tstop) - - for tick in ax.yaxis.get_major_ticks(): - tick.label1On = False - tick.label2On = True - - def __raster_prop(self, ax): - ax.set_yticklabels('') - ax.set_xticklabels('') - ax.set_xlim(0, self.tstop) - -# create a grid of psth figures, and rasters(?) -class FigGrid(FigBase): - def __init__(self, N_rows, N_cols, tstop): - self.tstop = tstop - - # changes over rows and cols to inches (?) and scales - self.f = plt.figure(figsize=(2*N_cols, 2*N_rows)) - font_prop = {'size': 8} - mpl.rc('font', **font_prop) - - self.grid_list = [] - self.__create_grids(N_rows, N_cols) - - # axes are a list of lists here - self.ax = [] - self.__create_axes() - - def __create_grids(self, N_rows, gs_cols): - gs_rows = 3 - self.grid_list = [gridspec.GridSpec(gs_rows, gs_cols) for i in range(N_rows)] - ytop = 0.075 - ybottom = 0.05 - ypad = 0.02 - ypanel = (1 - ytop - ybottom - ypad*(N_rows-1)) / N_rows - # print ypanel - - i = 0 - ystart = 1-ytop - - # used to pre-calculate this, but whatever - for grid in self.grid_list: - # start at the top to order the rows down - grid.update(wspace=0.05, hspace=0., bottom=ystart-ypanel, top=ystart) - # grid.update(wspace=0.05, hspace=0., bottom=0.05, top=0.95) - ystart -= ypanel+ypad - i += 1 - - # creates a list of lists of axes - def __create_axes(self): - for grid in self.grid_list: - ax_list = [] - for i in range(grid._ncols): - ax_list.append(self.f.add_subplot(grid[:, i:i+1])) - ax_list[-1].set_yticks([0, 100., 200., 300., 400., 500.]) - - # clear y-tick labels for everyone but the bottom - for ax in ax_list: - ax.set_xticklabels('') - - # clear y-tick labels for everyone but the left side - for ax in ax_list[1:]: - ax.set_yticklabels('') - self.ax.append(ax_list) - - # set a timescale for just the last axis - self.ax[-1][-1].set_xticks([0., 250., 500.]) - self.ax[-1][-1].set_xticklabels([0., 250., 500.]) - - # testing usage of string in title - # self.ax[0][0].set_title(r'$\lambda_i$ = %d' % 0) - -class FigAggregateSpecWithHist(FigBase): - def __init__(self, N_rows, N_cols): - self.N_rows = N_rows - self.N_cols = N_cols - - self.f = plt.figure(figsize=(2+8*N_cols, 1+8*N_rows), dpi=300) - font_prop = {'size': 8} - mpl.rc('font', **font_prop) - - # margins - self.top_margin = 1. / (2 + 8 * N_rows) - self.left_margin = 2. / (2 + 8 * N_cols) - - # Height is measured from top of figure - # i.e. row at top of figure is considered row 0 - # This is the opposite of matplotlib conventions - # White space accounting is kind of wierd. Sorry. - self.gap_height = 0.1 / (N_rows + 1) - height = (0.9 - self.top_margin) / N_rows - top = 1. - self.top_margin - self.gap_height - bottom = top - height - - # Width is measured from left of figure - # This is inline with matplotlib conventions - # White space accounting it kind of wierd. Sorry - self.gap_width = 0.15 / (N_cols + 1.) - width = (0.85 - self.left_margin) / N_cols - left = self.left_margin + self.gap_width - right = left + width - - # Preallocate some lists - self.gs0_list = [] - self.gs1_list = [] - self.gs2_list = [] - self.ax_list = [] - - # iterate over all rows/cols and create axes for each location - for row, col in it.product(range(0, N_rows), range(0, N_cols)): - # left and right margins for this set of axes - tmp_left = left + width * col + self.gap_width * col - tmp_right = right + width * col + self.gap_width * col - - # top and bottom margins for this set of axes - bottom_spec = bottom - height * row - self.gap_height * row - top_spec = bottom_spec + (0.4 - self.top_margin / 5.) / N_rows - - bottom_dpl = top_spec + (0.05 - self.top_margin / 5.) / N_rows - top_dpl = bottom_dpl + (0.2 - self.top_margin / 5.) / N_rows - - bottom_hist = top_dpl + (0.05 - self.top_margin / 5.) / N_rows - top_hist = bottom_hist + (0.2 - self.top_margin / 5.) / N_rows - - # tmp_top = top - height * row - self.gap_height * row - # tmp_bottom = bottom - height * row - self.gap_height * row - - # Create gridspecs - self.gs0_list.append(gridspec.GridSpec(1, 4, wspace=0., hspace=0., bottom=bottom_spec, top=top_spec, left=tmp_left, right=tmp_right)) - self.gs1_list.append(gridspec.GridSpec(2, 1, bottom=bottom_dpl, top=top_dpl, left=tmp_left, right=tmp_right-0.18 / N_cols)) - self.gs2_list.append(gridspec.GridSpec(2, 1, hspace=0.14, bottom=bottom_hist, top=top_hist, left=tmp_left, right = tmp_right-0.18 / N_cols)) - - # create axes - ax = {} - ax['spec'] = self.f.add_subplot(self.gs0_list[-1][:, :]) - ax['dipole'] = self.f.add_subplot(self.gs1_list[-1][:, :]) - ax['feed_prox'] = self.f.add_subplot(self.gs2_list[-1][1, :]) - ax['feed_dist'] = self.f.add_subplot(self.gs2_list[-1][0, :]) - - # store axes - # SUPER IMPORTANT: this list iterates across rows!!!!! - self.ax_list.append(ax) - - def set_hist_props(self, ax, hist_data): - for key in ax.keys(): - if 'feed' in key: - max_n = max(hist_data[key][0]) - ax[key].set_yticks(np.arange(0, max_n+2, np.ceil((max_n+2.) / 4.))) - - if 'feed_dist' in key: - ax[key].set_xticklabels('') - - # def add_column_labels(self, param_list): - def add_column_labels(self, param_list, key): - # override = {'fontsize': 8*self.N_cols} - - gap = (0.85 - self.left_margin) / self.N_cols + self.gap_width - - for i in range(0, self.N_cols): - p_dict = paramrw.read(param_list[i])[1] - - x = self.left_margin + gap / 2. + gap * i - y = 1 - self.top_margin / 2. - - self.f.text(x, y, key+' :%2.1f' %p_dict[key], fontsize=36, horizontalalignment='center', verticalalignment='top') - - # self.ax_list[i]['feed_dist'].set_title(key + ': %2.1f' %p_dict[key], **override) - - def add_row_labels(self, param_list, key): - gap = (0.9 - self.top_margin) / self.N_rows + self.gap_height - - for i in range(0, self.N_rows): - ind = self.N_cols * i - p_dict = paramrw.read(param_list[ind])[1] - - # place text in middle of each row of axes - x = self.left_margin / 2. - y = 1. - self.top_margin - self.gap_height - gap / 2. - gap * i - - # self.f.text(x, y, key+': %s' %p_dict[key], fontsize=36, rotation='vertical', horizontalalignment='left', verticalalignment='center') - - # try using key as a key in param dict - try: - self.f.text(x, y, key+': %s' %p_dict[key], fontsize=36, rotation='vertical', horizontalalignment='left', verticalalignment='center') - - # if this doesn't work, use individual parts of key as labels - except: - # check to see if there are enough args in key - if len(key) == self.N_rows: - self.f.text(x, y, key[i], fontsize=36, rotation='vertical', horizontalalignment='left', verticalalignment='center') - - # if not, do nothing - else: - print("Dude, the number of labels don't match the number of rows. I can't do nothing now.") - return 0 - - def save(self, file_name): - self.f.savefig(file_name, dpi=250) - -# aggregate figures for the experiments -class FigDipoleExp(FigBase): - def __init__(self, ax_handles): - FigBase.__init__(self) - # ax_handles is a list of axis handles in order - # previously called N_expmt_groups for legacy reasons (original intention) - # now generally repurposed for arbitrary numbers of axes with these handle names - self.ax_handles = ax_handles - self.N_expmt_groups = len(ax_handles) - self.f = plt.figure(figsize=(8, 2*self.N_expmt_groups)) - font_prop = {'size': 8} - mpl.rc('font', **font_prop) - - # create a gridspec that has width of "50" - # there is some dark magic here whereby colorbars change the original axis by some - # unspecified dimension. Rescaling non-spec axes to 40/50 is not the same as rescaling - # non-spec axes 4/5, for reason that's unclear to me at the time of this writing - # 40/50 works though - # 'spec' must be specified in the name of the spec - self.gspec = gridspec.GridSpec(self.N_expmt_groups, 55) - self.__create_axes() - self.__set_ax_props() - - def __create_axes(self): - # self.ax = [self.f.add_subplot(self.gspec[i:(i+1)]) for i in range(self.N_expmt_groups)] - self.ax = dict.fromkeys(self.ax_handles) - - # iterating like this because indices are useful in defining the recursive gspec locations - i = 0 - for ax in self.ax_handles: - if 'spec' not in ax: - self.ax[ax] = self.f.add_subplot(self.gspec[i:(i+1), :20]) - self.ax[ax+'_L5'] = self.f.add_subplot(self.gspec[i:(i+1), 30:50]) - # self.ax[ax] = self.f.add_subplot(self.gspec[i:(i+1), :40]) - else: - self.ax[ax] = self.f.add_subplot(self.gspec[i:(i+1), :25]) - self.ax[ax+'_L5'] = self.f.add_subplot(self.gspec[i:(i+1), 30:]) - # self.ax[ax] = self.f.add_subplot(self.gspec[i:(i+1), :]) - - i += 1 - - # if ax_twinx keys exist, the keys will mirror those in self.ax - self.ax_twinx = {} - - # extern function to create a colorbar on an arbitrary axis - # creates and rescales the specified axis and then scales down the rest of the axes accordingly - # I hope - def create_colorbar_axis(self, ax_name): - # print self.ax[N_ax] - cax, kw = mpl.colorbar.make_axes_gridspec(self.ax[ax_name]) - # a = self.ax[N_ax].get_axes() - # for item in dir(self.ax[N_ax]): - # if not item.startswith('__'): - # print item - - # take an external list of dipoles and plot them - # such a list is created externally - def plot(self, t, dpl_list): - if len(dpl_list) == self.N_expmt_groups: - # list of max and min dipoles for each in dpl_list - dpl_max = [] - dpl_min = [] - - # check on all the mins and maxes - for dpl, ax_name in zip(dpl_list, self.ax_handles): - self.ax[ax_name].plot(t, dpl) - ylim_tmp = ax.get_ylim() - - dpl_min.append(ylim_tmp[0]) - dpl_max.append(ylim_tmp[1]) - - # find the overall min and max - ymin = np.min(dpl_min) - ymax = np.max(dpl_max) - - # set the ylims for all, the same - for ax_name in self.ax.keys(): - self.ax[ax_name].set_ylim((ymin, ymax)) - - def __set_ax_props(self): - # remove xtick labels for everyone but the last axis - for ax_name in self.ax_handles[:-1]: - self.ax[ax_name].set_xticklabels('') - # for ax in self.ax[:-1]: - # ax.set_xticklabels('') - -# creates title string based on params that change during simulation -# title_str = ac.create_title(blah) -# title_str = f.create_title(blah) -def create_title(p_dict, key_types): - title = [] - - for key in key_types['dynamic_keys']: - # Rules for when to use scientific notation - if p_dict[key] >= 0.1 or p_dict[key] == 0: - title.append(key + ': %2.1f' %p_dict[key]) - else: - title.append(key + ': %2.1e' %p_dict[key]) - - # Return string in alphabetical order - title.sort() - return title - -# just a quick test for running this function -def testfn(): - x = np.random.rand(100) - - # testfig = FigStd() - # testfig.ax0.plot(x) - - ax_handles = [ - 'spec', - 'test1', - 'test2', - ] - - # testfig = FigDipoleExp(ax_handles) - # testfig.create_colorbar_axis('spec') - # testfig.create_colorbar_axis('spectest') - # testfig.ax['spec'].plot(x) - - # testfig = FigSpecWithHist() - # testfig = FigAggregateSpecWithHist(3, 3) - # testfig.ax['spec'].plot(x) - - # testfig = FigSpecWithHist() - # testfig.ax['spec'].plot(x) - # testfig.ax0.plot(x) - - # testfig = FigGrid(3, 3, 100) - - # testfig = FigPSTH(100) - # testfig.ax['L5_extpois'].plot(x) - - testfig = FigPhase() - # testfig.ax['dipole'].plot(x) - - plt.savefig('testing.png', dpi=250) - testfig.close() - -if __name__ == '__main__': - testfn() diff --git a/cartesian.py b/cartesian.py deleted file mode 100644 index 509aa1007..000000000 --- a/cartesian.py +++ /dev/null @@ -1,70 +0,0 @@ -# cartesian.py - returns cartesian product of a list of np arrays -# from StackOverflow.com -# -# v 1.10.0-py35 -# rev 2015-05-01 (SL: deprecated, tested) -# last major: (SL: imported from Stack Overflow) - -import numpy as np -import itertools as it - -def cartesian(arrays, out=None): - """ Parameters - ---------- - arrays : list of array-like - 1-D arrays to form the cartesian product of. - out : ndarray - Array to place the cartesian product in. - - Returns - ------- - out : ndarray - 2-D array of shape (M, len(arrays)) containing cartesian products - formed of input arrays. - - Examples - -------- - >>> cartesian(([1, 2], [4, 5], [6, 7])) - array([[1, 4, 6], - [1, 4, 7], - [1, 5, 6], - [1, 5, 7], - [2, 4, 6], - [2, 4, 7], - [2, 5, 6], - [2, 5, 7]) - """ - try: - xrange - - except: - xrange = range - - arrays = [np.asarray(x) for x in arrays] - - n = np.prod([x.size for x in arrays]) - if out is None: - out = np.zeros([n, len(arrays)], dtype='float64') - - m = int(n / arrays[0].size) - - out[:, 0] = np.repeat(arrays[0], m) - - if arrays[1:]: - cartesian(arrays[1:], out=out[:m, 1:]) - - for j in xrange(1, arrays[0].size): - out[j*m:(j+1)*m,1:] = out[:m, 1:] - - return out - -if __name__ == '__main__': - arrs = [np.arange(3), np.array([0]), np.array([5, 6, 7]), np.array([0, 1])] - out = cartesian(arrs) - print('old way') - print(out) - - out_it = [item for item in it.product(*arrs)] - print('it way') - for item in out_it: - print(item) diff --git a/cell.py b/cell.py deleted file mode 100644 index be1839ce3..000000000 --- a/cell.py +++ /dev/null @@ -1,402 +0,0 @@ -# cell.py - establish class def for general cell features -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: python3 compatibility) -# last rev: (SL: added list_IClamp as a pre-defined variable) - -import numpy as np -from neuron import h - -# global variables, should be node-independent -h("dp_total_L2 = 0."); h("dp_total_L5 = 0.") # put here since these variables used in cells - -# Units for e: mV -# Units for gbar: S/cm^2 - -# Create a cell class -class Cell (): - - def __init__ (self, gid, soma_props): - self.gid = gid - self.pc = h.ParallelContext() # Parallel methods - # make L_soma and diam_soma elements of self - # Used in shape_change() b/c func clobbers self.soma.L, self.soma.diam - self.L = soma_props['L'] - self.diam = soma_props['diam'] - self.pos = soma_props['pos'] - # create soma and set geometry - self.soma = h.Section(cell=self, name=soma_props['name']+'_soma') - self.soma.L = soma_props['L'] - self.soma.diam = soma_props['diam'] - self.soma.Ra = soma_props['Ra'] - self.soma.cm = soma_props['cm'] - # variable for the list_IClamp - self.list_IClamp = None - # par: create arbitrary lists of connections FROM other cells - # TO this cell instantiation - # these lists are allowed to be empty - # this should be a dict - self.ncfrom_L2Pyr = [] - self.ncfrom_L2Basket = [] - self.ncfrom_L5Pyr = [] - self.ncfrom_L5Basket = [] - self.ncfrom_extinput = [] - self.ncfrom_extgauss = [] - self.ncfrom_extpois = [] - self.ncfrom_ev = [] - - def record_volt_soma (self): - self.vsoma = h.Vector() - self.vsoma.record(self.soma(0.5)._ref_v) - - def get_sections (self): return [self.soma] - - def get3dinfo (self): - ls = self.get_sections() - lx,ly,lz,ldiam=[],[],[],[] - for s in ls: - for i in range(s.n3d()): - lx.append(s.x3d(i)) - ly.append(s.y3d(i)) - lz.append(s.z3d(i)) - ldiam.append(s.diam3d(i)) - return lx,ly,lz,ldiam - - # get cell's bounding box - def getbbox (self): - lx,ly,lz,ldiam = self.get3dinfo() - minx,miny,minz = 1e9,1e9,1e9 - maxx,maxy,maxz = -1e9,-1e9,-1e9 - for x,y,z in zip(lx,ly,lz): - minx = min(x,minx) - miny = min(y,miny) - minz = min(z,minz) - maxx = max(x,maxx) - maxy = max(y,maxy) - maxz = max(z,maxz) - return ((minx,maxx), (miny,maxy), (minz,maxz)) - - def translate3d (self, dx, dy, dz): - #s = self.soma - #for i in range(s.n3d()): - # h.pt3dchange(i,s.x3d(i)+dx,s.y3d(i)+dy,s.z3d(i)+dz,s.diam3d(i),sec=s) - for s in self.get_sections(): - for i in range(s.n3d()): - #print(s,i,s.x3d(i)+dx,s.y3d(i)+dy,s.z3d(i)+dz,s.diam3d(i)) - h.pt3dchange(i,s.x3d(i)+dx,s.y3d(i)+dy,s.z3d(i)+dz,s.diam3d(i),sec=s) - - def translateto (self, x, y, z): - x0 = self.soma.x3d(0) - y0 = self.soma.y3d(0) - z0 = self.soma.z3d(0) - dx = x - x0 - dy = y - y0 - dz = z - z0 - # print('dx:',dx,'dy:',dy,'dz:',dz) - self.translate3d(dx,dy,dz) - - def movetopos (self): - self.translateto(self.pos[0]*100,self.pos[2],self.pos[1]*100) - - # two things need to happen here for h: - # 1. dipole needs to be inserted into each section - # 2. a list needs to be created with a Dipole (Point Process) in each section at position 1 - # In Cell() and not Pyr() for future possibilities - def dipole_insert (self, yscale): - # insert dipole into each section of this cell - # dends must have already been created!! - # it's easier to use wholetree here, this includes soma - seclist = h.SectionList() - seclist.wholetree(sec=self.soma) - # create a python section list list_all - self.list_all = [sec for sec in seclist] - for sect in self.list_all: - sect.insert('dipole') - # Dipole is defined in dipole_pp.mod - self.dipole_pp = [h.Dipole(1, sec=sect) for sect in self.list_all] - # setting pointers and ztan values - for sect, dpp in zip(self.list_all, self.dipole_pp): - # assign internal resistance values to dipole point process (dpp) - dpp.ri = h.ri(1, sec=sect) - # sets pointers in dipole mod file to the correct locations - # h.setpointer(ref, ptr, obj) - h.setpointer(sect(0.99)._ref_v, 'pv', dpp) - if self.celltype.startswith('L2'): - h.setpointer(h._ref_dp_total_L2, 'Qtotal', dpp) - elif self.celltype.startswith('L5'): - h.setpointer(h._ref_dp_total_L5, 'Qtotal', dpp) - # gives INTERNAL segments of the section, non-endpoints - # creating this because need multiple values simultaneously - loc = np.array([seg.x for seg in sect]) - # these are the positions, including 0 but not L - pos = np.array([seg.x for seg in sect.allseg()]) - # diff in yvals, scaled against the pos np.array. y_long as in longitudinal - y_scale = (yscale[sect.name()] * sect.L) * pos - # y_long = (h.y3d(1, sec=sect) - h.y3d(0, sec=sect)) * pos - # diff values calculate length between successive section points - y_diff = np.diff(y_scale) - # y_diff = np.diff(y_long) - # doing range to index multiple values of the same np.array simultaneously - for i in range(len(loc)): - # assign the ri value to the dipole - sect(loc[i]).dipole.ri = h.ri(loc[i], sec=sect) - # range variable 'dipole' - # set pointers to previous segment's voltage, with boundary condition - if i: - h.setpointer(sect(loc[i-1])._ref_v, 'pv', sect(loc[i]).dipole) - else: - h.setpointer(sect(0)._ref_v, 'pv', sect(loc[i]).dipole) - # set aggregate pointers - h.setpointer(dpp._ref_Qsum, 'Qsum', sect(loc[i]).dipole) - if self.celltype.startswith('L2'): - h.setpointer(h._ref_dp_total_L2, 'Qtotal', sect(loc[i]).dipole) - elif self.celltype.startswith('L5'): - h.setpointer(h._ref_dp_total_L5, 'Qtotal', sect(loc[i]).dipole) - # add ztan values - sect(loc[i]).dipole.ztan = y_diff[i] - # set the pp dipole's ztan value to the last value from y_diff - dpp.ztan = y_diff[-1] - - # Add IClamp to a segment - def insert_IClamp (self, sect_name, props_IClamp): - # def insert_iclamp(self, sect_name, seg_loc, tstart, tstop, weight): - # gather list of all sections - seclist = h.SectionList() - seclist.wholetree(sec=self.soma) - # find specified sect in section list, insert IClamp, set props - for sect in seclist: - if sect_name in sect.name(): - stim = h.IClamp(sect(props_IClamp['loc'])) - stim.delay = props_IClamp['delay'] - stim.dur = props_IClamp['dur'] - stim.amp = props_IClamp['amp'] - # stim.dur = tstop - tstart - # stim = h.IClamp(sect(seg_loc)) - # object must exist for NEURON somewhere and needs to be saved - return stim - - # simple function to record current - # for now only at the soma - def record_current_soma (self): - # a soma exists at self.soma - self.rec_i = h.Vector() - try: - # assumes that self.synapses is a dict that exists - list_syn_soma = [key for key in self.synapses.keys() if key.startswith('soma_')] - # matching dict from the list_syn_soma keys - self.dict_currents = dict.fromkeys(list_syn_soma) - # iterate through keys and record currents appropriately - for key in self.dict_currents: - self.dict_currents[key] = h.Vector() - self.dict_currents[key].record(self.synapses[key]._ref_i) - except: - print("Warning in Cell(): record_current_soma() was called, but no self.synapses dict was found") - pass - - # General fn that creates any Exp2Syn synapse type - # requires dictionary of synapse properties - def syn_create (self, secloc, p): - syn = h.Exp2Syn(secloc) - syn.e = p['e'] - syn.tau1 = p['tau1'] - syn.tau2 = p['tau2'] - return syn - - # For all synapses, section location 'secloc' is being explicitly supplied - # for clarity, even though they are (right now) always 0.5. Might change in future - # creates a RECEIVING inhibitory synapse at secloc - def syn_gabaa_create (self, secloc): - syn_gabaa = h.Exp2Syn(secloc) - syn_gabaa.e = -80 - syn_gabaa.tau1 = 0.5 - syn_gabaa.tau2 = 5. - return syn_gabaa - - # creates a RECEIVING slow inhibitory synapse at secloc - # called: self.soma_gabab = syn_gabab_create(self.soma(0.5)) - def syn_gabab_create (self, secloc): - syn_gabab = h.Exp2Syn(secloc) - syn_gabab.e = -80 - syn_gabab.tau1 = 1 - syn_gabab.tau2 = 20. - return syn_gabab - - # creates a RECEIVING excitatory synapse at secloc - # def syn_ampa_create(self, secloc, tau_decay, prng_obj): - def syn_ampa_create (self, secloc): - syn_ampa = h.Exp2Syn(secloc) - syn_ampa.e = 0. - syn_ampa.tau1 = 0.5 - syn_ampa.tau2 = 5. - return syn_ampa - - # creates a RECEIVING nmda synapse at secloc - # this is a pretty fast NMDA, no? - def syn_nmda_create (self, secloc): - syn_nmda = h.Exp2Syn(secloc) - syn_nmda.e = 0. - syn_nmda.tau1 = 1. - syn_nmda.tau2 = 20. - return syn_nmda - - # connect_to_target created for pc, used in Network() - # these are SOURCES of spikes - def connect_to_target (self, target, threshold): - nc = h.NetCon(self.soma(0.5)._ref_v, target, sec=self.soma) - nc.threshold = threshold - return nc - - # parallel receptor-centric connect FROM presyn TO this cell, based on GID - def parconnect_from_src (self, gid_presyn, nc_dict, postsyn): - # nc_dict keys are: {pos_src, A_weight, A_delay, lamtha} - nc = self.pc.gid_connect(gid_presyn, postsyn) - # calculate distance between cell positions with pardistance() - d = self.__pardistance(nc_dict['pos_src']) - # set props here - nc.threshold = nc_dict['threshold'] - nc.weight[0] = nc_dict['A_weight'] * np.exp(-(d**2) / (nc_dict['lamtha']**2)) - nc.delay = nc_dict['A_delay'] / (np.exp(-(d**2) / (nc_dict['lamtha']**2))) - # print("parconnect_from_src in cell.py, weight = ",nc.weight[0]) - #fp = open('delays.txt','a'); fp.write(str(d)+' '+str(nc_dict['A_delay'])+' ' +str(nc.delay)+'\n'); fp.close() - #fp = open('weights.txt','a'); fp.write(str(d)+' '+str(nc_dict['A_weight'])+' ' +str(nc.weight[0])+'\n'); fp.close() - #fp = open('prepostty.txt','a'); fp.write(nc_dict['type_src']+' '+self.celltype+'\n'); fp.close() - - return nc - - # pardistance function requires pre position, since it is calculated on POST cell - def __pardistance (self, pos_pre): - dx = self.pos[0] - pos_pre[0] - dy = self.pos[1] - pos_pre[1] - #dz = self.pos[2] - pos_pre[2] - return np.sqrt(dx**2 + dy**2) - - # Define 3D shape of soma -- is needed for gui representation of cell - # DO NOT need to call h.define_shape() explicitly!! - def shape_soma (self): - h.pt3dclear(sec=self.soma) - # h.ptdadd(x, y, z, diam) -- if this function is run, clobbers - # self.soma.diam set above - h.pt3dadd(0, 0, 0, self.diam, sec=self.soma) - h.pt3dadd(0, self.L, 0, self.diam, sec=self.soma) - -# Inhibitory cell class -class BasketSingle (Cell): - def __init__ (self, gid, pos, cell_name='Basket'): - self.props = self.__set_props(cell_name, pos) - Cell.__init__(self, gid, self.props) - # store cell name for later - self.name = cell_name - # set 3D shape - unused for now but a prototype - self.__shape_change() - - def __set_props (self, cell_name, pos): - return { - 'pos': pos, - 'L': 39., - 'diam': 20., - 'cm': 0.85, - 'Ra': 200., - 'name': cell_name, - } - - # Define 3D shape and position of cell. By default neuron uses xy plane for - # height and xz plane for depth. This is opposite for model as a whole, but - # convention is followed in this function ease use of gui. - def __shape_change (self): - self.shape_soma() - """ - s = self.soma - for i in range(int(s.n3d())): - h.pt3dchange(i, self.pos[0]*100 + s.x3d(i), -self.pos[2] + s.y3d(i), - self.pos[1] * 100 + s.z3d(i), s.diam3d(i), sec=s) - """ - -# General Pyramidal cell class -class Pyr (Cell): - def __init__ (self, gid, soma_props): - Cell.__init__(self, gid, soma_props) - # store cell_name as self variable for later use - self.name = soma_props['name'] - # preallocate dict to store dends - self.dends = {} - # for legacy use with L5Pyr - self.list_dend = [] - - # Create dictionary of section names with entries to scale section lengths to length along z-axis - def get_sectnames (self): - seclist = h.SectionList() - seclist.wholetree(sec=self.soma) - d = dict((sect.name(), 1.) for sect in seclist) - for key in d.keys(): - # basal_2 and basal_3 at 45 degree angle to z-axis. - if 'basal_2' in key: - d[key] = np.sqrt(2) / 2. - elif 'basal_3' in key: - d[key] = np.sqrt(2) / 2. - # apical_oblique at 90 perpendicular to z-axis - elif 'apical_oblique' in key: - d[key] = 0. - # All basalar dendrites extend along negative z-axis - if 'basal' in key: - d[key] = -d[key] - return d - - def create_dends (self, p_dend_props): - for key in p_dend_props: self.dends[key] = h.Section(name=self.name+'_'+key) # create dend - # apical: 0--4; basal: 5--7 - self.list_dend = [self.dends[key] for key in ['apical_trunk', 'apical_oblique', 'apical_1', 'apical_2', 'apical_tuft', 'basal_1', 'basal_2', 'basal_3'] if key in self.dends] - - def set_dend_props (self, p_dend_props): - # iterate over keys in p_dend_props. Create dend for each key. - for key in p_dend_props: - # set dend props - self.dends[key].L = p_dend_props[key]['L'] - self.dends[key].diam = p_dend_props[key]['diam'] - self.dends[key].Ra = p_dend_props[key]['Ra'] - self.dends[key].cm = p_dend_props[key]['cm'] - # set dend nseg - if p_dend_props[key]['L'] > 100.: - self.dends[key].nseg = int(p_dend_props[key]['L'] / 50.) - # make dend.nseg odd for all sections - if not self.dends[key].nseg % 2: - self.dends[key].nseg += 1 - - # Creates dendritic sections based only on dictionary of dendrite props - def create_dends_new (self, p_dend_props): - # iterate over keys in p_dend_props. Create dend for each key. - for key in p_dend_props: - # create dend - self.dends[key] = h.Section(name=self.name+'_'+key) - - # set dend props - self.dends[key].L = p_dend_props[key]['L'] - self.dends[key].diam = p_dend_props[key]['diam'] - self.dends[key].Ra = p_dend_props[key]['Ra'] - self.dends[key].cm = p_dend_props[key]['cm'] - - # set dend nseg - if p_dend_props[key]['L'] > 100.: - self.dends[key].nseg = int(p_dend_props[key]['L'] / 50.) - - # make dend.nseg odd for all sections - if not self.dends[key].nseg % 2: - self.dends[key].nseg += 1 - - # apical: 0--4 - # basal: 5--7 - self.list_dend = [self.dends[key] for key in ['apical_trunk', 'apical_oblique', 'apical_1', 'apical_2', 'apical_tuft', 'basal_1', 'basal_2', 'basal_3'] if key in self.dends] - - - def get_sections (self): - ls = [self.soma] - for key in ['apical_trunk', 'apical_1', 'apical_2', 'apical_tuft', 'apical_oblique', 'basal_1', 'basal_2', 'basal_3']: - if key in self.dends: - ls.append(self.dends[key]) - return ls - - def get_section_names (self): - ls = ['soma'] - for key in ['apical_trunk', 'apical_1', 'apical_2', 'apical_tuft', 'apical_oblique', 'basal_1', 'basal_2', 'basal_3']: - if key in self.dends: - ls.append(key) - return ls diff --git a/cfg.py b/cfg.py deleted file mode 100644 index ea5148d4b..000000000 --- a/cfg.py +++ /dev/null @@ -1,76 +0,0 @@ -# cfg.py - Simulation configuration -from netpyne import specs - -cfg = specs.SimConfig() - -cfg.checkErrors = False # True # leave as False to avoid extra printouts - - -############################################################################### -# -# SIMULATION CONFIGURATION -# -############################################################################### - -############################################################################### -# Run parameters -############################################################################### -cfg.duration = 1.0*1e3 -cfg.dt = 0.05 -cfg.seeds = {'conn': 4321, 'stim': 1234, 'loc': 4321} -cfg.hParams = {'celsius': 34, 'v_init': -80} -cfg.verbose = 0 -cfg.cvode_active = False -cfg.printRunTime = 0.1 -cfg.printPopAvgRates = True - - -############################################################################### -# Recording -############################################################################### -cfg.recordTraces = {'V_soma': {'sec': 'soma', 'loc': 0.5, 'var': 'v'}} -cfg.recordStims = False -cfg.recordStep = 0.1 - - -############################################################################### -# Saving -############################################################################### -cfg.simLabel = 'sim1' -cfg.saveFolder = 'data' -cfg.savePickle = False -cfg.saveJson = True -cfg.saveDataInclude = ['simData', 'simConfig', 'netParams', 'net'] - - -############################################################################### -# Analysis and plotting -############################################################################### -cfg.analysis['plotTraces'] = {'include': ['L2Pyr','L5Pyr'], 'oneFigPer': 'cell', 'saveFig': True, - 'showFig': False, 'figSize': (10,8), 'timeRange': [0,cfg.duration]} - - -############################################################################### -# Parameters -############################################################################### -cfg.dendNa = 0.0345117294903 -cfg.tau1NMDA = 15 - - -############################################################################### -# Current inputs -############################################################################### -cfg.addIClamp = 0 - -cfg.IClamp1 = {'pop': 'PT5B', 'sec': 'soma', 'loc': 0.5, 'start': 1, 'dur': 1000, 'amp': 0.0} - - -############################################################################### -# NetStim inputs -############################################################################### -cfg.addNetStim = 0 - -cfg.NetStim1 = {'pop': 'PT5B', 'sec': 'soma', 'loc': 0.5, 'synMech': 'NMDA', 'start': 500, - 'interval': 1000, 'noise': 0.0, 'number': 1, 'weight': 0.0, 'delay': 1} - - diff --git a/cli.py b/cli.py deleted file mode 100644 index 94dbb2e7f..000000000 --- a/cli.py +++ /dev/null @@ -1,1064 +0,0 @@ -# cli.py - routines for the command line interface console s1sh.py -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: return_data_dir()) -# last major: (SL: reorganized show and pngv) - -from cmd import Cmd -from datetime import datetime -import ast, multiprocessing, os, signal, subprocess, time -import readline as rl -import itertools as it - -import clidefs -import fileio as fio -import paramrw -import specfn -import dipolefn -import ppsth -import praw - -class Console(Cmd): - def __init__(self, file_input=""): - Cmd.__init__(self) - self.prompt = '\033[93m' + "[s1] " + '\033[0m' - self.intro = "\nThis is the SomatoSensory SHell\n" - self.dproj = fio.return_data_dir() - self.server_default = self.__check_server() - self.f_history = '.s1sh_hist_local' - self.ddate = '' - self.dlast = [] - self.dlist = [] - self.dsim = [] - self.expmts = [] - self.sim_list = [] - self.param_list = [] - self.var_list = [] - self.N_sims = 0 - - # check to see if file_input is legit - if os.path.isfile(file_input): - self.file_input = file_input - - else: - # use a default - self.file_input = 'param/debug.param' - - # get initial count of avail processors for subprocess/multiprocessing routines - self.nprocs = multiprocessing.cpu_count() - - # Create the initial datelist - self.datelist = clidefs.get_subdir_list(self.dproj) - - # create the initial paramfile list - self.__get_paramfile_list() - - # set the date, grabs a dlist - self.do_setdate(datetime.now().strftime("%Y-%m-%d")) - - # splits argstring in format of --opt0=val0 --opt1=val1 - def __split_args(self, args): - # split based on leading -- - args_tmp = args.split(' --') - - # only take the args that start with -- and include a = - # drop the leading -- - # args_opt = [arg[2:] for arg in args_tmp if arg.startswith('--')] - args_opt = [arg for arg in args_tmp if '=' in arg] - arg_list = [] - for arg in args_opt: - # getting rid of first case, ugh, hack! - if arg.startswith('--'): - arg_list.append(arg[2:].split('=')) - else: - arg_list.append(arg.split('=')) - - return arg_list - - def __create_dict_from_args(self, args): - # split based on leading -- - args_tmp = args.split(' --') - - # only take the args that start with -- and include a = - # drop the leading -- - # args_opt = [arg[2:] for arg in args_tmp if arg.startswith('--')] - args_opt = [arg for arg in args_tmp if '=' in arg] - arg_dict = {} - for arg in args_opt: - # getting rid of first case, ugh, hack! - if arg.startswith('--'): - args_tmp = arg[2:].split('=') - arg_dict[args_tmp[0]] = args_tmp[1] - - else: - args_tmp = arg.split('=') - arg_dict[args_tmp[0]] = args_tmp[1] - - return arg_dict - - # generalized function for checking and assigning args - def __check_args(self, dict_opts, list_opts): - # assumes list_opts comes from __split_args() - if len(list_opts): - # iterate through possible key vals in list_opts - keys_missing = [] - for key, val in list_opts: - # check to see if the possible keys are in dict_opts - if key in dict_opts.keys(): - # assign the key/val pair in place - # this operation acts IN PLACE on the supplied dict_opts!! - # therefore, no return value necessary - dict_opts[key] = ast.literal_eval(val) - else: - keys_missing.append(key) - - # if there are any keys missing - if keys_missing: - print "Options were not recognized: " - fio.prettyprint(keys_missing) - - # checks to see if a default server file is found, if not, ask for one - def __check_server(self): - f_server = os.path.join(self.dproj, '.server_default') - - if os.path.isfile(f_server): - # read the file and set the default server - lines_f = fio.clean_lines(f_server) - - # there should only be one thing in this file, so assume that's the server name - return lines_f[0] - else: - return '' - - # create a list of parameter files - def __get_paramfile_list(self): - dparam_default = os.path.join(os.getcwd(), 'param') - self.paramfile_list = [f for f in os.listdir(dparam_default) if f.endswith('.param')] - - def do_debug(self, args): - """Qnd function to test other functions - """ - self.do_setdate('2013-12-04') - self.do_load('ftremor-003') - clidefs.exec_pgamma_spec_fig() - # self.do_pgamma_sub_example2('') - # self.do_setdate('pub') - # self.do_spec_current("--runtype='debug' --f_max=250.") - # self.do_load('2013-06-28_gamma_weak_L5-000') - # self.do_pgamma_hf_epochs('') - # self.do_load('2013-08-12_gamma_sub_50Hz-001') - # self.do_pgamma_spikephase('') - # self.do_pgamma_prox_dist_new('') - # self.do_throwaway('--n_trial=-1') - # self.do_load('2013-08-07_gamma_sub_50Hz_stdev-000') - # self.do_pgamma_stdev_new('--f_max_welch=150.') - # self.do_load('2013-06-28_gamma_sub_f-000') - # self.do_pgamma_stdev_new('--f_max_welch=150.') - # self.do_load('2013-07-15_gamma_L5weak_L2weak-000') - # self.do_pgamma_laminar('') - # self.do_pgamma_compare_ping('') - # self.do_show_dpl_max('') - # self.do_pgamma_peaks('') - # self.do_welch_max('') - # self.do_pgamma_sub_examples('') - # self.do_pgamma_distal_phase('--spec0=5 --spec1=9 --spec2=15') - # self.do_specmax('--expmt_group="weak" --f_interval=[50., 75] --t_interval=[50., 550.]') - # self.do_spike_rates('') - # self.do_save('') - # self.do_calc_dpl_regression('') - # self.do_calc_dpl_mean("--t0=100. --tstop=1000. --layer='L2'") - # self.do_praw('') - # self.do_pdipole('grid') - # self.do_pngv('') - # self.do_show('testing in (0, 0)') - # self.do_calc_dipole_avg('') - # self.do_pdipole('evaligned') - # self.do_avgtrials('dpl') - # self.do_replot('') - # self.do_spec_regenerate('--f_max=50.') - # self.do_addalphahist('--xmin=0 --xmax=500') - # self.do_avgtrials('dpl') - # self.do_dipolemin('in (mu, 0, 2) on [400., 410.]') - # self.epscompress('spk') - # self.do_psthgrid() - - def do_throwaway(self, args): - ''' This is a throwaway dipole saving function. Usage: - [s1] throwaway {--n_sim=12} {--n_trial=3} - ''' - dict_opts = self.__create_dict_from_args(args) - - # run the throwaway save function! - clidefs.exec_throwaway(self.ddata, dict_opts) - # clidefs.exec_throwaway(self.ddata, opts['n_sim'], opts['n_trial']) - - def do_spike_rates(self, args): - opts = { - 'expmt_group': 'weak', - 'celltype': 'L5_pyramidal', - } - l_opts = self.__split_args(args) - self.__check_args(opts, l_opts) - - clidefs.exec_spike_rates(self.ddata, opts) - - def do_calc_dpl_mean(self, args): - '''Returns the mean dipole to screen. Usage: - [s1] calc_dpl_mean - ''' - opts = { - 't0': 50., - 'tstop': -1, - 'layer': 'agg', - } - - l_opts = self.__split_args(args) - self.__check_args(opts, l_opts) - - # run the function - clidefs.exec_calc_dpl_mean(self.ddata, opts) - - def do_calc_dpl_regression(self, args): - clidefs.exec_calc_dpl_regression(self.ddata) - - def do_show_dpl_max(self, args): - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_show_dpl_max(self.ddata, dict_opts) - - def do_pgamma_peaks(self, args): - clidefs.exec_pgamma_peaks() - - def do_pgamma_sub_examples(self, args): - clidefs.exec_pgamma_sub_examples() - - def do_pgamma_sub_example2(self, args): - clidefs.exec_pgamma_sub_example2() - - def do_pgamma_hf(self, args): - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_pgamma_hf(self.ddata, dict_opts) - - def do_pgamma_hf_epochs(self, args): - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_pgamma_hf_epochs(self.ddata, dict_opts) - - def do_pgamma_distal_phase(self, args): - '''Generates gamma fig for distal phase. Requires spec data for layers to exist. Usage: - [s1] spec_current - [s1] pgamma_distal_phase {--spec0=0 --spec1=1, --spec2=2} - ''' - - opts = { - 'spec0': 0, - 'spec1': 1, - 'spec2': 1, - } - - l_opts = self.__split_args(args) - self.__check_args(opts, l_opts) - - clidefs.exec_pgamma_distal_phase(self.ddata, opts) - - def do_pgamma_stdev(self, args): - '''Generates gamma fig for standard deviation. Requires spec data for layers to exist. Usage: - [s1] spec_current - [s1] pgamma_stdev - ''' - clidefs.exec_pgamma_stdev(self.ddata) - - def do_pgamma_stdev_new(self, args): - '''Generates gamma fig for standard deviation. Requires spec data for layers to exist. Usage: - [s1] spec_current - [s1] pgamma_stdev_new - ''' - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_pgamma_stdev_new(self.ddata, dict_opts) - - def do_pgamma_prox_dist_new(self, args): - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_pgamma_prox_dist_new(self.ddata, dict_opts) - - def do_pgamma_laminar(self, args): - '''Generates a comparison figure of aggregate, L2 and L5 data. Taken from 1 data set. Usage: - [s1] pgamma_laminar - ''' - clidefs.exec_pgamma_laminar(self.ddata) - - def do_pgamma_compare_ping(self, args): - '''Generates gamma fig for comparison of PING and weak PING. Will need 2 data sets! Usage: - [s1] pgamma_compare_ping - ''' - clidefs.exec_pgamma_compare_ping() - - def do_pgamma_spikephase(self, args): - clidefs.exec_pgamma_spikephase() - - def do_spec_current(self, args): - # parse list of opts - dict_opts = self.__create_dict_from_args(args) - - # actually run the analysis - self.spec_current_tmp = clidefs.exec_spec_current(self.ddata, dict_opts) - - def do_praw(self, args): - '''praw is a fully automated function to replace the dipole plots with aggregate dipole/spec/spikes plots. Usage: - [s1] praw - ''' - praw.praw(self.ddata) - - # update the dlist - def __update_dlist(self): - if os.path.exists(self.ddate): - self.dlist = [d for d in os.listdir(self.ddate) if os.path.isdir(os.path.join(self.ddate, d))] - - def do_setdate(self, args): - """Sets the date string to the specified date - """ - if args: - if args == 'today': - dcheck = os.path.join(self.dproj, datetime.now().strftime("%Y-%m-%d")) - else: - dcheck = os.path.join(self.dproj, args) - - if os.path.exists(dcheck): - self.ddate = dcheck - - else: - self.ddate = os.path.join(self.dproj, 'pub') - - self.__update_dlist() - - print "Date set to", self.ddate - - def complete_setdate(self, text, line, j0, J): - """complete function for setdate - """ - if text: - print text - x = [item for item in self.datelist if item.startswith(text)] - if x: - return x - else: - return self.datelist - - def do_load(self, args): - """Load parameter file and regens all vars - Date needs to be set correctly for this to work. See 'help setdate' - Usage example: - [s1sh] setdate 2013-01-01 - [s1sh] load mucomplex-000 - - Running without arguments will load the last modified directory found in the date dir: - [s1] load - """ - if not args: - # attempt to load the most recent in the dproj/ddate - # find the most recent directory in this folder - list_d = [] - - for dsim_short in os.listdir(self.ddate): - # check to see if dsim_tmp is actually a dir - dsim_tmp = os.path.join(self.ddate, dsim_short) - - # append to list along with its modified time (mtime) - if os.path.isdir(dsim_tmp): - list_d.append((dsim_tmp, time.ctime(os.path.getmtime(dsim_tmp)))) - - # sort by mtime - list_d.sort(key=lambda x: x[1]) - - # grab the directory name of the most recent dir - dcheck = list_d[-1][0] - - else: - # dir_check is the attempt at creating this directory - dcheck = os.path.join(self.dproj, self.ddate, args) - - # check existence of the path - if os.path.exists(dcheck): - # create blank ddata structure from SimPaths - self.ddata = fio.SimulationPaths() - - # set dsim after using ddata's readsim method - self.dsim = self.ddata.read_sim(self.dproj, dcheck) - self.p_exp = paramrw.ExpParams(self.ddata.fparam) - print self.ddata.fparam - self.var_list = paramrw.changed_vars(self.ddata.fparam) - - else: - print dcheck - print "Could not find that dir, maybe check your date?" - - def complete_load(self, text, line, j0, J): - """complete function for load - """ - if text: - return [item for item in self.dlist if item.startswith(text)] - - else: - return self.dlist - - def do_sync(self, args): - """Sync with specified remote server. If 'exclude' is unspecified, by default will use the exclude_eps.txt file in the data dir. If exclude is specified, it will look in the root data dir. Usage examples: - [s1] sync 2013-03-25 - [s1] sync 2013-03-25 --exclude=somefile.txt - """ - try: - fshort_exclude = '' - list_args = args.split('--') - - # expect first arg to be the dsubdir - dsubdir = list_args.pop(0) - - for arg in list_args: - if arg: - opt, val = arg.split('=') - - if opt == 'exclude': - fshort_exclude = val - - if not self.server_default: - server_remote = raw_input("Server address: ") - else: - server_remote = self.server_default - print "Attempting to use default server ..." - - # run the command - if fshort_exclude: - clidefs.exec_sync(self.dproj, server_remote, dsubdir, fshort_exclude) - else: - clidefs.exec_sync(self.dproj, server_remote, dsubdir) - - # path - newdir = os.path.join('from_remote', dsubdir) - self.do_setdate(newdir) - - except: - print "Something went wrong here." - - def do_giddict(self, args): - pass - - def do_welch_max(self, args): - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_welch_max(self.ddata, dict_opts) - - def do_specmax(self, args): - """Find the max spectral power, report value and time. - Usage: specmax {--expmt_group=0 --simrun=0 --trial=0 --t_interval=[0, 1000] --f_interval=[0, 100.]} - """ - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_specmax(self.ddata, dict_opts) - - def do_specmax_dpl_match(self, args): - """Plots dpl around max spectral power over specified time and frequency intervals - usage: specmax_dpl_match --t_interval=[0, 1000] --f_interval=[0, 100] --f_sorted=[0, 100] - """ - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_specmax_dpl_match(self.ddata, dict_opts) - - def do_specmax_dpl_tmpl(self, args): - """Isolates dpl waveforms producing specified spectral frequencies - across trails and averages them to produce a stereotypical waveform - Usage: specmax_dpl_tmpl --expmt_group --n_sim --trials --t_interval - --f_interval --f_sort - """ - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_specmax_dpl_tmpl(self.ddata, dict_opts) - - def do_plot_dpl_tmpl(self, args): - """Plots stereotypical waveforms produced by do_specmax_dpl_tmpl - usage: plot_dpl_tmpl --expmt_group - """ - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_plot_dpl_tmpl(self.ddata, dict_opts) - - def do_dipolemin(self, args): - """Find the minimum of a particular dipole - Usage: dipolemin in (, , ) on [interval] - """ - # look for first keyword - if args.startswith("in"): - try: - # split by 'in' to get the interval - s = args.split(" on ") - - # values are then in first part of s - # yeah, this is gross, sorry. just parsing between parens for params - expmt_group, n_sim_str, n_trial_str = s[0][s[0].find("(")+1:s[0].find(")")].split(", ") - n_sim = int(n_sim_str) - n_trial = int(n_trial_str) - - t_interval = ast.literal_eval(s[-1]) - clidefs.exec_dipolemin(self.ddata, expmt_group, n_sim, n_trial, t_interval) - - except ValueError: - self.do_help('dipolemin') - - else: - self.do_help('dipolemin') - - def do_file(self, args): - """Attempts to open a new file of params - """ - if not args: - print self.file_input - elif os.path.isfile(args): - self.file_input = args - print "New file is:", self.file_input - else: - # try searching specifcally in param dir - f_tmp = os.path.join('param', args) - if os.path.isfile(f_tmp): - self.file_input = f_tmp - else: - print "Does not appear to exist" - return 0 - - # tab complete rules for file - def complete_file(self, text, line, j0, J): - return [item for item in self.paramfile_list if item.startswith(text)] - - def do_diff(self, args): - """Runs a diff on various data types - """ - pass - - def do_testls(self, args): - # file_list = fio.file_match('../param', '*.param') - print "dlist is:", self.dlist - print "datelist is:", self.datelist - print "expmts is:", self.expmts - - def do_expmts(self, args): - """Show list of experiments for active directory. - """ - try: - clidefs.prettyprint(self.ddata.expmt_groups) - except AttributeError: - self.do_help('expmts') - print "No active directory?" - - def do_vars(self, args): - """Show changed variables in loaded simulation and their values. vars comes from p_exp. Usage: - [s1] vars - """ - print "\nVars changed in this simulation:" - - # iterate through params and print them raw - for var in self.var_list: - print " %s: %s" % (var[0], var[1]) - - # also print experimental groups - print "\nExperimental groups:" - self.do_expmts('') - - # cheap newline - print "" - - # this is an old function obsolete for this project - def do_view(self, args): - """Views the changes in the .params file. Use like 'load' - but does not commit variables to workspace - """ - dcheck = os.path.join(self.dproj, self.ddate, args) - - if os.path.exists(dcheck): - # get a list of the .params files - sim_list = fio.gen_sim_list(dcheck) - expmts = gen_expmts(sim_list[0]) - var_list = changed_vars(sim_list) - - clidefs.prettyprint(sim_list) - clidefs.prettyprint(expmts) - for var in var_list: - print var[0]+":", var[1] - - def complete_view(self, text, line, j0, J): - """complete function for view - """ - if text: - x = [item for item in self.dlist if item.startswith(text)] - if x: - return x - else: - return 0 - else: - return self.dlist - - def do_list(self, args): - """Lists simulations on a given date - 'args' is a date - """ - if not args: - dcheck = os.path.join(self.dproj, self.ddate) - - else: - dcheck = os.path.join(self.dproj, args) - - if os.path.exists(dcheck): - self.__update_dlist() - - # dir_list = [name for name in os.listdir(dcheck) if os.path.isdir(os.path.join(dcheck, name))] - clidefs.prettyprint(self.dlist) - - else: - print "Cannot find directory" - return 0 - - def do_pngoptimize(self, args): - """Optimizes png figures based on current directory - """ - fio.pngoptimize(self.simpaths.dsim) - - def do_avgtrials(self, args): - """Averages raw data over all trials for each simulation. - Usage: - [s1] avgtrials - where is either dpl or spec - """ - if not args: - print "You did not specify whether to avgerage dpl or spec data. Try again." - - else: - datatype = args - clidefs.exec_avgtrials(self.ddata, datatype) - - def do_spec_regenerate(self, args): - """Regenerates spec data and saves it to proper expmt directories. Usage: - [s1] spec_regenerate {--f_max=80.} - """ - - # use __split_args() - l_opts = self.__split_args(args) - - # these are the opts for which we are looking - opts = { - 'f_max': None, - } - - # parse the opts - self.__check_args(opts, l_opts) - - # use exec_spec_regenerate to regenerate spec data - clidefs.exec_spec_regenerate(self.ddata, opts['f_max']) - # self.spec_results = clidefs.exec_spec_regenerate(self.ddata, opts['f_max']) - - def do_spec_stationary_avg(self, args): - """Averages spec power over time and plots freq vs power. Fn can act per expmt or over entire simulation. If maxpwr supplied as arg, also plots freq at which max avg pwr occurs v.s input freq - """ - if args == 'maxpwr': - clidefs.exec_spec_stationary_avg(self.ddata, self.dsim, maxpwr=1) - - else: - clidefs.exec_spec_stationary_avg(self.ddata, self.dsim, maxpwr=0) - - def do_spec_avg_stationary_avg(self, args): - """Performs time-averaged stationarity analysis on avg'ed spec data. - Sorry for the terrible name... - """ - # parse args - l_opts = self.__split_args(args) - - # "default" opts - opts = { - 'errorbars': None - } - - # parse opts - self.__check_args(opts, l_opts) - - clidefs.exec_spec_avg_stationary_avg(self.ddata, self.dsim, opts) - - def do_freqpwrwithhist(self, args): - clidefs.freqpwr_with_hist(self.ddata, self.dsim) - - def do_calc_dipole_avg(self, args): - """Calculates average dipole using dipolefn.calc_avgdpl_stimevoked: - Usage: [s1] calc_dipole_avg - """ - dipolefn.calc_avgdpl_stimevoked(self.ddata) - - def do_pdipole(self, args): - """Regenerates plots in given directory. Usage: - To run on current working directory and regenerate each individual plot: 'pdipole' - To run aggregates for all simulations (across all trials/conditions) in a directory: 'pdipole exp' - To run aggregates with lines marking evoked times, run: 'pdipole evoked' - """ - # temporary arg split - arg_tmp = args.split(' ') - - # list of acceptable runtypes - runtype_list = [ - 'exp', - 'exp2', - 'evoked', - 'evaligned', - 'avg', - 'grid', - ] - - # minimal checks in this function - # assume that no ylim argument was specified - if len(arg_tmp) == 1: - runtype = arg_tmp[0] - ylim = [] - - else: - # set the runtype to the first - if arg_tmp[0] in runtype_list: - runtype = arg_tmp[0] - - # get the list of optional args - arg_list = self.__split_args(args) - - # default values for various params - # i_ctrl = 0 - for opt, val in arg_list: - # currently not being used - if opt == 'i_ctrl': - i_ctrl = int(val) - - # assume the first arg is correct, split on that - # arg_ylim_tmp = args.split(runtype) - - # if len(arg_ylim_tmp) == 2: - # ylim_read = ast.literal_eval(arg_ylim_tmp[-1].strip()) - # ylim = ylim_read - - # else: - # ylim = [] - - if runtype == 'exp': - # run the avg dipole per experiment (across all trials/simulations) - # using simpaths (ddata) - dipolefn.pdipole_exp(self.ddata, ylim) - - elif runtype == 'exp2': - dipolefn.pdipole_exp2(self.ddata) - # dipolefn.pdipole_exp2(self.ddata, i_ctrl) - - elif runtype == 'evoked': - # add the evoked lines to the pdipole individual simulations - clidefs.exec_pdipole_evoked(self.ddata, ylim) - - elif runtype == 'evaligned': - dipolefn.pdipole_evoked_aligned(self.ddata) - - elif runtype == 'avg': - # plot average over all TRIALS of a param regime - # requires that avg dipole data exist - clidefs.exec_plotaverages(self.ddata, ylim) - - elif runtype == 'grid': - dipolefn.pdipole_grid(self.ddata) - - def do_replot(self, args): - """Regenerates plots in given directory. Usage: - Usage: replot --xlim=[0, 1000] --ylim=[0, 100] - xlim is a time interval - ylim is a frequency interval - """ - # preallocate variables so they always exist - # xmin = 0. - # xmax = 'tstop' - - # # Parse args if they exist - # if args: - # arg_list = [arg for arg in args.split('--') if arg is not ''] - - # # Assign value to above variables if the value exists as input - # for arg in arg_list: - # if arg.startswith('xmin'): - # xmin = float(arg.split('=')[-1]) - - # elif arg.startswith('xmax'): - # xmax = float(arg.split('=')[-1]) - - # else: - # print "Did not recognize argument %s. Not doing anything with it" % arg - - # # Check to ensure xmin less than xmax - # if xmin and xmax: - # if xmin > xmax: - # print "xmin greater than xmax. Defaulting to sim parameters" - # xmin = 0. - # xmax = 'tstop' - - dict_opts = self.__create_dict_from_args(args) - - # check for spec data, create it if didn't exist, and then run the plots - clidefs.exec_replot(self.ddata, dict_opts) - # clidefs.regenerate_plots(self.ddata, [xmin, xmax]) - - def do_addalphahist(self, args): - """Adds histogram of alpha feed input times to dpl and spec plots. Usage: - [s1] addalphahist {--xlim=[0, 1000] --ylim=[0, 100]} - xlim is a time interval - ylim is a frequency interval - """ - # # preallocate variables so they always exist - # xmin = 0. - # xmax = 'tstop' - - # # Parse args if they exist - # if args: - # arg_list = [arg for arg in args.split('--') if arg is not ''] - - # # Assign value to above variables if the value exists as input - # for arg in arg_list: - # if arg.startswith('xmin'): - # xmin = float(arg.split('=')[-1]) - - # elif arg.startswith('xmax'): - # xmax = float(arg.split('=')[-1]) - - # else: - # print "Did not recognize argument %s. Not doing anything with it" %arg - - # # Check to ensure xmin less than xmax - # if xmin and xmax: - # if xmin > xmax: - # print "xmin greater than xmax. Defaulting to sim parameters" - # xmin = 0. - # xmax = 'tstop' - - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_addalphahist(self.ddata, dict_opts) - # clidefs.exec_addalphahist(self.ddata, [xmin, xmax]) - - def do_aggregatespec(self, args): - """Creates aggregates all spec data with histograms into one massive fig. - Must supply column label and row label as --row_label:param --column_label:param" - row_label should be param that changes only over experiments - column_label should be a param that changes trial to trial - """ - arg_list = [arg for arg in args.split('--') if arg is not ''] - - # Parse args - for arg in arg_list: - if arg.startswith('row'): - row_label = arg.split(':')[-1] - - # See if a list is being passed in - if row_label.startswith('['): - row_label = arg.split('[')[-1].split(']')[0].split(', ') - - else: - row_label = arg.split(':')[-1].split(' ')[0] - - elif arg.startswith('column'): - column_label = arg.split(':')[-1].split(' ')[0] - - else: - print "Did not recongnize argument. Going to break now." - - clidefs.exec_aggregatespec(self.ddata, [row_label, column_label]) - - def do_plotaverages(self, args): - """Creates plots of averaged dipole or spec data. Automatically checks if data exists. Usage: - 'plotaverages' - """ - - clidefs.exec_plotaverages(self.ddata) - - def do_phaselock(self, args): - """Calculates phaselock values between dipole and inputs - """ - args_dict = self.__create_dict_from_args(args) - clidefs.exec_phaselock(self.ddata, args_dict) - - def do_epscompress(self, args): - """Runs the eps compress utils on the specified fig type (currently either spk or spec) - """ - for expmt_group in self.ddata.expmt_groups: - if args == 'figspk': - d_eps = self.ddata.dfig[expmt_group]['figspk'] - elif args == 'figspec': - d_eps = self.ddata.dfig[expmt_group]['figspec'] - - try: - fio.epscompress(d_eps, '.eps') - except UnboundLocalError: - print "oy, this is embarrassing." - - def do_psthgrid(self, args): - """Aggregate plot of psth - """ - ppsth.ppsth_grid(self.simpaths) - - # save currently fails when no dir is loaded - def do_save(self, args): - """Copies the entire current directory over to the cppub directory - """ - clidefs.exec_save(self.dproj, self.ddate, self.dsim) - - # currently doesn't work with mpi interfacing - # def do_runsim(self, args): - # """Run the simulation code - # """ - # try: - # cmd_list = [] - # cmd_list.append('mpiexec -n %i ./s1run.py %s' % (self.nprocs, self.file_input)) - - # for cmd in cmd_list: - # subprocess.call(cmd, shell=True) - - # except (KeyboardInterrupt): - # print "Caught a break" - - def do_hist(self, args): - """Print a list of commands that have been entered""" - print self._hist - - def do_pwd(self, args): - """Displays active dir_data""" - print self.dsim - - def do_ls(self, args): - """Displays active param list""" - clidefs.prettyprint(self.param_list) - - def do_show(self, args): - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_show(self.ddata, dict_opts) - - def complete_show(self, text, line, j0, J): - """Completion function for show - """ - if text: - return [expmt for expmt in self.expmts if expmt.startswith(text)] - else: - return self.expmts - - def do_showf(self, args): - """Show frequency information from rate files - """ - vars = args.split(' in ') - expmt = vars[0] - n = int(vars[1]) - - if n < self.N_sims: - drates = os.path.join(self.dsim, expmt, 'rates') - ratefile_list = fio.file_match(drates, '*.rates') - - with open(ratefile_list[n]) as frates: - lines = (line.rstrip() for line in frates) - lines = [line for line in lines if line] - - clidefs.prettyprint(lines) - else: - print "In do_showf in cli: out of range?" - return 0 - - def complete_showf(self, text, line, j0, J): - """Completion function for showf - """ - if text: - return [expmt for expmt in self.expmts if expmt.startswith(text)] - else: - return self.expmts - - def do_Nsims(self, args): - """Show number of simulations in each 'experiment' - """ - print self.N_sims - - def do_pngv(self, args): - dict_opts = self.__create_dict_from_args(args) - clidefs.exec_pngv(self.ddata, dict_opts) - - def complete_pngv(self, text, line, j0, J): - if text: - return [expmt for expmt in self.expmts if expmt.startswith(text)] - else: - return self.expmts - - ## Command definitions to support Cmd object functionality ## - def do_exit(self, args): - """Exits from the console - """ - return -1 - - def do_EOF(self, args): - """Exit on system end of file character - """ - return self.do_exit(args) - - def do_shell(self, args): - """Pass command to a system shell when line begins with '!' - """ - os.system(args) - - def do_help(self, args): - """Get help on commands - 'help' or '?' with no arguments prints a list of commands for which help is available - 'help ' or '? ' gives help on - """ - ## The only reason to define this method is for the help text in the doc string - Cmd.do_help(self, args) - - ## Override methods in Cmd object ## - def preloop(self): - """Initialization before prompting user for commands. - Despite the claims in the Cmd documentaion, Cmd.preloop() is not a stub. - """ - Cmd.preloop(self) ## sets up command completion - self._hist = self.load_history() - self._locals = {} ## Initialize execution namespace for user - self._globals = {} - - def postloop(self): - """Take care of any unfinished business. - Despite the claims in the Cmd documentaion, Cmd.postloop() is not a stub. - """ - self.write_history() - Cmd.postloop(self) ## Clean up command completion - print "Exiting..." - - def precmd(self, line): - """ This method is called after the line has been input but before - it has been interpreted. If you want to modify the input line - before execution (for example, variable substitution) do it here. - """ - self._hist += [ line.strip() ] - return line - - def postcmd(self, stop, line): - """If you want to stop the console, return something that evaluates to true. - If you want to do some post command processing, do it here. - """ - return stop - - def emptyline(self): - """Do nothing on empty input line""" - pass - - def default(self, line): - """Called on an input line when the command prefix is not recognized. - In that case we execute the line as Python code. - """ - try: - exec(line) in self._locals, self._globals - except Exception, e: - print e.__class__, ":", e - - # Function to read the history file - def load_history(self): - with open(self.f_history) as f_in: - lines = (line.rstrip() for line in f_in) - lines = [line for line in lines if line] - - return lines - - def history_remove_dupes(self): - unique_set = set() - return [x for x in self._hist if x not in unique_set and not unique_set.add(x)] - - # function to write the history file - def write_history(self): - # first we will clean the list of dupes - unique_history = self.history_remove_dupes() - with open(self.f_history, 'w') as f_out: - for line in unique_history[-100:]: - f_out.write(line+'\n') diff --git a/clidefs.py b/clidefs.py deleted file mode 100644 index 71a1c23f9..000000000 --- a/clidefs.py +++ /dev/null @@ -1,1499 +0,0 @@ -# clidefs.py - these are all of the function defs for the cli -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: python3 compatibility) -# last major: (SL: minor) - -# Standard modules -import fnmatch, os, re, sys -import numpy as np -from scipy import stats -from multiprocessing import Pool -from subprocess import call -from glob import iglob -from time import time -import ast -import matplotlib.pyplot as plt -import matplotlib as mpl - -# local modules -import spikefn -import plotfn -import fileio as fio -import paramrw -import specfn -import pspec -import dipolefn -import axes_create as ac -import pmanu_gamma as pgamma -import subprocess - -# Returns length of any list -def number_of_sims(some_list): - return len(some_list) - -# Just a simple thing to print parts of a list -def prettyprint(lines): - for line in lines: - print line - -# gets a subdir list -def get_subdir_list(dcheck): - if os.path.exists(dcheck): - return [name for name in os.listdir(dcheck) if os.path.isdir(os.path.join(dcheck, name))] - - else: - return [] - -# generalized function for checking and assigning args -def args_check(dict_default, dict_check): - if len(dict_check): - keys_missing = [] - - # iterate through possible key vals in dict_check - for key, val in dict_check.items(): - # check to see if the possible keys are in dict_default - if key in dict_default.keys(): - # assign the key/val pair in place - # this operation acts IN PLACE on the supplied dict_default!! - # therefore, no return value necessary - try: - dict_default[key] = ast.literal_eval(val) - - except ValueError: - dict_default[key] = val - - else: - keys_missing.append(key) - - # if there are any keys missing - if keys_missing: - print "Options were not recognized: " - fio.prettyprint(keys_missing) - -def exec_pngv(ddata, dict_opts={}): - """Attempt to find the PNGs and open them - [aushnew] pngv {--run=0 --expmt_group='testing' --type='fig_spec'} - """ - file_viewer(ddata, dict_opts) - -# returns average spike data -def exec_spike_rates(ddata, opts): - # opts should be: - # opts_default = { - # expmt_group: 'something', - # celltype: 'L5_pyramidal', - # } - expmt_group = opts['expmt_group'] - celltype = opts['celltype'] - - list_f_spk = ddata.file_match(expmt_group, 'rawspk') - list_f_param = ddata.file_match(expmt_group, 'param') - - # note! this is NOT ignoring first 50 ms - for fspk, fparam in zip(list_f_spk, list_f_param): - s_all = spikefn.spikes_from_file(fparam, fspk) - _, p_dict = paramrw.read(fparam) - T = p_dict['tstop'] - - # check if the celltype is in s_all - if celltype in s_all.keys(): - s = s_all[celltype].spike_list - n_cells = len(s) - - # grab all the sp_counts - sp_counts = np.array([len(spikes_cell) for spikes_cell in s]) - - # calc mean and stdev - sp_count_mean = np.mean(sp_counts) - sp_count_stdev = np.std(sp_counts) - - # calc rate in Hz, assume T in ms - sp_rates = sp_counts * 1000. / T - sp_rate_mean = np.mean(sp_rates) - sp_rate_stdev = np.std(sp_rates) - - # direct - sp_rate = sp_count_mean * 1000. / T - - print "Sim No. %i, Trial %i, celltype is %s:" % (p_dict['Sim_No'], p_dict['Trial'], celltype) - print " spike count mean is: %4.3f" % sp_count_mean - print " spike count stdev is: %4.3f" % sp_count_stdev - print " spike rate over %4.3f ms is %4.3f Hz +/- %4.3f" % (T, sp_rate_mean, sp_rate_stdev) - print " spike rate over %4.3f ms is %4.3f Hz" % (T, sp_rate) - -def exec_welch_max(ddata, opts): - p = { - 'f_min': 0., - } - - args_check(p, opts) - - # assume first expmt_group for now - expmt_group = ddata.expmt_groups[0] - - # grab list of dipoles - list_dpl = ddata.file_match(expmt_group, 'rawdpl') - list_param = ddata.file_match(expmt_group, 'param') - - # iterate through dipoles - for fdpl, fparam in zip(list_dpl, list_param): - # grab the dt (needed for the Welch) - dt = paramrw.find_param(fparam, 'dt') - - # grab the dipole - dpl = dipolefn.Dipole(fdpl) - dpl.baseline_renormalize(fparam) - dpl.convert_fAm_to_nAm() - - # create empty pgram - pgram = dict.fromkeys(dpl.dpl) - pgram_max = dict.fromkeys(dpl.dpl) - - # perform stationary Welch, since we're not saving this data yet - for key in pgram.keys(): - pgram[key] = specfn.Welch(dpl.t, dpl.dpl[key], dt) - - # create a mask based on f min - fmask = (pgram[key].f > p['f_min']) - P_cut = pgram[key].P[fmask] - f_cut = pgram[key].f[fmask] - - p_max = np.max(P_cut) - f_max = f_cut[P_cut == p_max] - # p_max = np.max(pgram[key].P) - # f_max = pgram[key].f[pgram[key].P == p_max] - - # not clear why saving for now - pgram_max[key] = (f_max, p_max) - print "Max power for %s was %.3e at %4.2f Hz, with f min set to %4.2f" % (key, p_max, f_max, p['f_min']) - -# throwaway save method for now -# trial is currently undefined -# function is broken for N_trials > 1 -def exec_throwaway(ddata, opts): - p = { - 'n_sim': 0, - 'n_trial': 0, - } - args_check(p, opts) - - p_exp = paramrw.ExpParams(ddata.fparam) - N_trials = p_exp.N_trials - print opts, p - - if p['n_sim'] == -1: - for i in range(p_exp.N_sims): - if p['n_trial'] == -1: - for j in range(N_trials): - dipolefn.dpl_convert_and_save(ddata, i, j) - else: - j = p['n_trial'] - dipolefn.dpl_convert_and_save(ddata, i, j) - - else: - i = p['n_sim'] - if p['n_trial'] == -1: - for j in range(N_trials): - dipolefn.dpl_convert_and_save(ddata, i, j) - else: - j = p['n_trial'] - dipolefn.dpl_convert_and_save(ddata, i, j) - - # # take the ith sim, jth trial, do some stuff to it, resave it - # # only uses first expmt_group - # expmt_group = ddata.expmt_groups[0] - - # # need n_trials - # p_exp = paramrw.ExpParams(ddata.fparam) - # if not p_exp.N_trials: - # N_trials = 1 - # else: - # N_trials = p_exp.N_trials - - # # absolute number - # n = i*N_trials + j - - # # grab the correct files - # f_dpl = ddata.file_match(expmt_group, 'rawdpl')[n] - # f_param = ddata.file_match(expmt_group, 'param')[n] - - # # print ddata.sim_prefix, ddata.dsim - # f_name_short = '%s-%03d-T%02d-dpltest.txt' % (ddata.sim_prefix, i, j) - # f_name = os.path.join(ddata.dsim, expmt_group, f_name_short) - # print f_name - - # dpl = dipolefn.Dipole(f_dpl) - # dpl.baseline_renormalize(f_param) - # print "baseline renormalized" - - # dpl.convert_fAm_to_nAm() - # print "converted to nAm" - - # dpl.write(f_name) - -def exec_show(ddata, dict_opts): - dict_opts_default = { - 'run': 0, - 'trial': 0, - 'expmt_group': '', - 'key': 'changed', - 'var_list': [], - } - - # hack for now to get backward compatibility with this original function - var_list = dict_opts_default['var_list'] - - exclude_list = [ - 'sim_prefix', - 'N_trials', - 'Run_Date', - ] - - args_check(dict_opts_default, dict_opts) - if dict_opts_default['expmt_group'] not in ddata.expmt_groups: - # print "Warning: expmt_group %s not found" % dict_opts_default['expmt_group'] - dict_opts_default['expmt_group'] = ddata.expmt_groups[0] - - # output the expmt group used - print "expmt_group: %s" % dict_opts_default['expmt_group'] - - # find the params - p_exp = paramrw.ExpParams(ddata.fparam) - - if dict_opts_default['key'] == 'changed': - print "Showing changed ... \n" - # create a list - var_list = [val[0] for val in paramrw.changed_vars(ddata.fparam)] - - elif dict_opts_default['key'] in p_exp.keys(): - # create a list with just this element - var_list = [dict_opts_default['key']] - - else: - key_part = dict_opts_default['key'] - var_list = [key for key in p_exp.keys() if key_part in key] - - if not var_list: - print "Keys were not found by exec_show()" - return 0 - - # files - fprefix = ddata.trial_prefix_str % (dict_opts_default['run'], dict_opts_default['trial']) - fparam = ddata.create_filename(dict_opts_default['expmt_group'], 'param', fprefix) - - list_param = ddata.file_match(dict_opts_default['expmt_group'], 'param') - - if fparam in list_param: - # this version of read returns the gid dict as well ... - _, p = paramrw.read(fparam) - - # use var_list to print values - for key in var_list: - if key not in exclude_list: - try: - print '%s: %s' % (key, p[key]) - - except KeyError: - print "Value %s not found in file %s!" % (key, fparam) - -def exec_show_dpl_max(ddata, opts={}): - p = { - 'layer': 'L5', - 'n_sim': 0, - 'n_trial': 0, - } - args_check(p, opts) - - expmt_group = ddata.expmt_groups[0] - - n = p['n_sim'] + p['n_sim']*p['n_trial'] - - fdpl = ddata.file_match(expmt_group, 'rawdpl')[n] - fparam = ddata.file_match(expmt_group, 'param')[n] - - T = paramrw.find_param(fparam, 'tstop') - xlim = (50., T) - - dpl = dipolefn.Dipole(fdpl) - dpl.baseline_renormalize(fparam) - dpl.convert_fAm_to_nAm() - - # add this data to the dict for the string output mapping - p['dpl_max'] = dpl.lim(p['layer'], xlim)[1] - p['units'] = dpl.units - - print "The maximal value for the dipole is %(dpl_max)4.3f %(units)s for sim=%(n_sim)i, trial=%(n_trial)i in layer %(layer)s" % (p) - # print "The maximal value for the dipole is %4.3f %s for sim=%i, trial=%i" % (dpl_max, dpl.units, n_sim, n_trial) - -# calculates the mean dipole over a specified range -def exec_calc_dpl_mean(ddata, opts={}): - for expmt_group in ddata.expmt_groups: - list_fdpl = ddata.file_match(expmt_group, 'rawdpl') - - # order of l_dpl is same as list_fdpl - l_dpl = [dipolefn.Dipole(f) for f in list_fdpl] - - for dpl in l_dpl: - print dpl.mean_stationary(opts) - -# calculates the linear regression, shows values of slope (m) and int (b) -# and plots line to dipole fig (in place) -def exec_calc_dpl_regression(ddata, opts={}): - for expmt_group in ddata.expmt_groups: - list_fdpl = ddata.file_match(expmt_group, 'rawdpl') - list_figdpl = ddata.file_match(expmt_group, 'figdpl') - - # this is to overwrite the fig - for f, ffig in zip(list_fdpl, list_figdpl): - dipolefn.plinear_regression(ffig, f) - -def exec_pdipole_evoked(ddata, ylim=[]): - # runtype = 'parallel' - runtype = 'debug' - - expmt_group = ddata.expmt_groups[0] - - # grab just the first element of the dipole list - dpl_list = ddata.file_match(expmt_group, 'rawdpl') - param_list = ddata.file_match(expmt_group, 'param') - spk_list = ddata.file_match(expmt_group, 'rawspk') - - # fig dir will be that of the original dipole - dfig = ddata.dfig[expmt_group]['figdpl'] - - # first file names - f_dpl = dpl_list[0] - f_spk = spk_list[0] - f_param = param_list[0] - - if runtype == 'parallel': - pl = Pool() - for f_dpl, f_spk, f_param in zip(dpl_list, spk_list, param_list): - pl.apply_async(dipolefn.pdipole_evoked, (dfig, f_dpl, f_spk, f_param, ylim)) - - pl.close() - pl.join() - - elif runtype == 'debug': - for f_dpl, f_spk, f_param in zip(dpl_list, spk_list, param_list): - dipolefn.pdipole_evoked(dfig, f_dpl, f_spk, f_param, ylim) - -# timer function wrapper returns WALL CLOCK time (more or less) -def timer(fn, args): - t0 = time() - x = eval(fn + args) - t1 = time() - - print "%s took %4.4f s" % (fn, t1-t0) - - return x - -def exec_pcompare(ddata, cli_args): - vars = cli_args.split(" ") - - # find any expmt and just take the first one. (below) - expmt = [arg.split("=")[1] for arg in vars if arg.startswith("expmt")] - sim0 = int([arg.split("=")[1] for arg in vars if arg.startswith("sim0")][0]) - sim1 = int([arg.split("=")[1] for arg in vars if arg.startswith("sim1")][0]) - - sims = [sim0, sim1] - - labels = ['A. Control E$_g$-I$_s$', 'B. Increased E$_g$-I$_s$'] - - if expmt: - psum.pcompare2(ddata, sims, labels, [expmt[0], expmt[0]]) - else: - psum.pcompare2(ddata, sims, labels) - # print "not found" - -def exec_pcompare3(ddata, cli_args): - # the args will be the 3 sim numbers. - # these will be strings out of the split! - vars = cli_args.split(' ') - sim_no = int(vars[0]) - # expmt_last = int(vars[1]) - - psum.pcompare3(ddata, sim_no) - -# executes the function plotvar in psummary -# At some point, replace 'vars' with a non-standard variable name -def exec_plotvars(cli_args, ddata): - # split the cli args based on options - vars = cli_args.split(' --') - - # first part is always the first 2 options (required, no checks) - vars_to_plot = vars[0].split() - - # grab the experiment handle - # vars_expmt = [arg.split()[1] for arg in vars if arg.startswith('expmt')] - vars_opts = [arg.split()[1:] for arg in vars if arg.startswith('opts')] - - # just pass the first of these - if vars_opts: - psum.plotvars(ddata, vars_to_plot[0], vars_opts[0]) - # psum.plotvars(ddata, vars_to_plot[0], vars_to_plot[1], vars_opts[0]) - # else: - # run the plotvar function with the cli args - # psum.plotvars(ddata, vars_to_plot[0]) - # psum.plotvars(ddata, vars_to_plot[0], vars_to_plot[1]) - -def exec_pphase(ddata, args): - args_split = args.split(" ") - expmt = args_split[0] - N_sim = int(args_split[1]) - - N_bins = 20 - - psum.pphase(ddata, expmt, N_sim, N_bins) - -# do_phist -def exec_phist(ddata, args): - # somehow create these plots - args_split = args.split(" ") - N_sim = args_split[0] - N_bins = int(args_split[1]) - psum.pphase_hist(ddata, N_sim, N_bins) - -# find the spectral max over an interval, for a particular sim -def exec_specmax(ddata, opts): - p = { - 'expmt_group': '', - 'n_sim': 0, - 'n_trial': 0, - 't_interval': None, - 'f_interval': None, - 'f_sort': None, - # 't_interval': [0., -1], - # 'f_interval': [0., -1], - } - - args_check(p, opts) - - p_exp = paramrw.ExpParams(ddata.fparam) - # trial_prefix = p_exp.trial_prefix_str % (p['n_sim'], p['n_trial']) - - if not p['expmt_group']: - p['expmt_group'] = ddata.expmt_groups[0] - - # Get the associated dipole and spec file - fspec = ddata.return_specific_filename(p['expmt_group'], 'rawspec', p['n_sim'], p['n_trial']) - - # Load the spec data - spec = specfn.Spec(fspec) - - # get max data - data_max = spec.max('agg', p['t_interval'], p['f_interval'], p['f_sort']) - - if data_max: - print "Max power of %4.2e at f of %4.2f Hz at %4.3f ms" % (data_max['pwr'], data_max['f_at_max'], data_max['t_at_max']) - - # # data_max = specfn.specmax(fspec, p) - # data = specfn.read(fspec) - # print data.keys() - - # # grab the min and max f - # f_min, f_max = p['f_interval'] - - # # set f_max - # if f_max < 0: - # f_max = data['freq'][-1] - - # # create an f_mask for the bounds of f, inclusive - # f_mask = (data['freq']>=f_min) & (data['freq']<=f_max) - - # # do the same for t - # t_min, t_max = p['t_interval'] - # if t_max < 0: - # t_max = data['time'][-1] - - # t_mask = (data['time']>=t_min) & (data['time']<=t_max) - - # # use the masks truncate these appropriately - # TFR_key = 'TFR' - - # if p['layer'] in ('L2', 'L5'): - # TFR_key += '_%s' % p['layer'] - - # TFR_fcut = data[TFR_key][f_mask, :] - # # TFR_fcut = data['TFR'][f_mask, :] - # TFR_tfcut = TFR_fcut[:, t_mask] - - # f_fcut = data['freq'][f_mask] - # t_tcut = data['time'][t_mask] - - # # find the max power over this new range - # # the max_mask is for the entire TFR - # pwr_max = TFR_tfcut.max() - # max_mask = (TFR_tfcut==pwr_max) - - # # find the t and f at max - # # these are slightly crude and do not allow for the possibility of multiple maxes (rare?) - # t_at_max = t_tcut[max_mask.sum(axis=0)==1] - # f_at_max = f_fcut[max_mask.sum(axis=1)==1] - - # # friendly printout - # print "Max power of %4.2e at f of %4.2f Hz at %4.3f ms" % (pwr_max, f_at_max, t_at_max) - - # pd_at_max = 1000./f_at_max - # t_start = t_at_max - pd_at_max/2. - # t_end = t_at_max + pd_at_max/2. - - # print "Symmetric interval at %4.2f Hz (T=%4.3f ms) about %4.3f ms is (%4.3f, %4.3f)" % (f_at_max, pd_at_max, t_at_max, t_start, t_end) - - # # output structure - # data_max = { - # 'pwr': pwr_max, - # 't': t_at_max, - # 'f': f_at_max, - # } - -def exec_specmax_dpl_match(ddata, opts): - p = { - 'expmt_group': '', - 'n_sim': 0, - 'trials': [0, -1], - 't_interval': None, - 'f_interval': None, - 'f_sort': None, - } - - args_check(p, opts) - - # set expmt group - if not p['expmt_group']: - p['expmt_group'] = ddata.expmt_groups[0] - - # set directory to save fig in and check that it exists - dir_fig = os.path.join(ddata.dsim, p['expmt_group'], 'figint') - fio.dir_create(dir_fig) - - # if p['trials'][1] is -1, assume all trials are wanted - # 1 is subtracted from N_trials to be consistent with manual entry of trial range - if p['trials'][1] == -1: - p_exp = paramrw.ExpParams(ddata.fparam) - p['trials'][1] = p_exp.N_trials - 1 - - # Get spec, dpl, and param files - # Sorry for lack of readability - spec_list = [ddata.return_specific_filename(p['expmt_group'], 'rawspec', p['n_sim'], i) for i in range(p['trials'][0], p['trials'][1]+1)] - dpl_list = [ddata.return_specific_filename(p['expmt_group'], 'rawdpl', p['n_sim'], i) for i in range(p['trials'][0], p['trials'][1]+1)] - param_list = [ddata.return_specific_filename(p['expmt_group'], 'param', p['n_sim'], i) for i in range(p['trials'][0], p['trials'][1]+1)] - - # Get max spectral data - data_max_list = [] - - for fspec in spec_list: - spec = specfn.Spec(fspec) - data_max_list.append(spec.max('agg', p['t_interval'], p['f_interval'], p['f_sort'])) - - # create fig name - if p['f_sort']: - fname_short = "sim-%03i-T%03i-T%03d-sort-%i-%i" %(p['n_sim'], p['trials'][0], p['trials'][1], p['f_sort'][0], p['f_sort'][1]) - - else: - fname_short = "sim-%03i-T%03i-T%03i" %(p['n_sim'], p['trials'][0], p['trials'][1]) - - fname = os.path.join(dir_fig, fname_short) - - # plot time-series over proper intervals - dipolefn.plot_specmax_interval(fname, dpl_list, param_list, data_max_list) - -def exec_specmax_dpl_tmpl(ddata, opts): - p = { - 'expmt_group': '', - 'n_sim': 0, - 'trials': [0, -1], - 't_interval': None, - 'f_interval': None, - 'f_sort': None, - } - - args_check(p, opts) - - # set expmt group - if not p['expmt_group']: - p['expmt_group'] = ddata.expmt_groups[0] - - # set directory to save template in and check that it exists - dir_out = os.path.join(ddata.dsim, p['expmt_group'], 'tmpldpl') - fio.dir_create(dir_out) - - # if p['trials'][1] is -1, assume all trials are wanted - # 1 is subtracted from N_trials to be consistent with manual entry of trial range - if p['trials'][1] == -1: - p_exp = paramrw.ExpParams(ddata.fparam) - p['trials'][1] = p_exp.N_trials - 1 - - # Get spec, dpl, and param files - # Sorry for lack of readability - spec_list = [ddata.return_specific_filename(p['expmt_group'], 'rawspec', p['n_sim'], i) for i in range(p['trials'][0], p['trials'][1]+1)] - dpl_list = [ddata.return_specific_filename(p['expmt_group'], 'rawdpl', p['n_sim'], i) for i in range(p['trials'][0], p['trials'][1]+1)] - param_list = [ddata.return_specific_filename(p['expmt_group'], 'param', p['n_sim'], i) for i in range(p['trials'][0], p['trials'][1]+1)] - - # Get max spectral data - data_max_list = [] - - for fspec in spec_list: - spec = specfn.Spec(fspec) - data_max_list.append(spec.max('agg', p['t_interval'], p['f_interval'], p['f_sort'])) - - # Get time intervals of max spectral pwr - t_interval_list = [dmax['t_int'] for dmax in data_max_list if dmax is not None] - - # truncate dpl_list to include only sorted trials - # kind of crazy that this works. Just sayin'... - dpl_list = [fdpl for fdpl, dmax in zip(dpl_list, data_max_list) if dmax is not None] - - # create file name - if p['f_sort']: - fname_short = "sim-%03i-T%03i-T%03d-sort-%i-%i-tmpldpl.txt" %(p['n_sim'], p['trials'][0], p['trials'][1], p['f_sort'][0], p['f_sort'][1]) - - else: - fname_short = "sim-%03i-T%03i-T%03i-tmpldpl.txt" %(p['n_sim'], p['trials'][0], p['trials'][1]) - - fname = os.path.join(dir_out, fname_short) - - # Create dpl template - dipolefn.create_template(fname, dpl_list, param_list, t_interval_list) - -def exec_plot_dpl_tmpl(ddata, opts): - p = { - 'expmt_group': '', - } - - args_check(p, opts) - - # set expmt group - if not p['expmt_group']: - p['expmt_group'] = ddata.expmt_groups[0] - - # set directory to save template in and check that it exists - dir_out = os.path.join(ddata.dsim, p['expmt_group'], 'figtmpldpl') - fio.dir_create(dir_out) - - # get template dpl data - dpl_list = fio.file_match(os.path.join(ddata.dsim, p['expmt_group']), '-tmpldpl.txt') - - # create file name list - # prefix_list = [fdpl.split('/')[-1].split('-tmpldpl')[0] for fdpl in dpl_list] - # fname_list = [os.path.join(dir_out, prefix+'-tmpldpl.png') for prefix in prefix_list] - - plot_dict = { - 'xlim': None, - 'ylim': None, - } - - for fdpl in dpl_list: - print fdpl - dipolefn.pdipole(fdpl, dir_out, plot_dict) - -# search for the min in a dipole over specified interval -def exec_dipolemin(ddata, expmt_group, n_sim, n_trial, t_interval): - p_exp = paramrw.ExpParams(ddata.fparam) - trial_prefix = p_exp.trial_prefix_str % (n_sim, n_trial) - - # list of all the dipoles - dpl_list = ddata.file_match(expmt_group, 'rawdpl') - - # load the associated dipole file - # find the specific file - # assume just the first file - fdpl = [file for file in dpl_list if trial_prefix in file][0] - - data = np.loadtxt(open(fdpl, 'r')) - t_vec = data[:, 0] - data_dpl = data[:, 1] - - data_dpl_range = data_dpl[(t_vec >= t_interval[0]) & (t_vec <= t_interval[1])] - dpl_min_range = data_dpl_range.min() - t_min_range = t_vec[data_dpl == dpl_min_range] - - print "Minimum value over t range %s was %4.4f at %4.4f." % (str(t_interval), dpl_min_range, t_min_range) - -# averages raw dipole or raw spec over all trials -def exec_avgtrials(ddata, datatype): - # create the relevant key for the data - datakey = 'raw' + datatype - datakey_avg = 'avg' + datatype - - # assumes N_Trials are the same in both - p_exp = paramrw.ExpParams(ddata.fparam) - sim_prefix = p_exp.sim_prefix - N_trials = p_exp.N_trials - - # fix for N_trials=0 - if not N_trials: - N_trials = 1 - - # prefix strings - exp_prefix_str = p_exp.exp_prefix_str - trial_prefix_str = p_exp.trial_prefix_str - - # Averaging must be done per expmt - for expmt_group in ddata.expmt_groups: - ddatatype = ddata.dfig[expmt_group][datakey] - dparam = ddata.dfig[expmt_group]['param'] - - param_list = ddata.file_match(expmt_group, 'param') - rawdata_list = ddata.file_match(expmt_group, datakey) - - # if nothing in the raw data list, then generate it for spec - if datakey == 'rawspec': - if not len(rawdata_list): - # generate the data! - exec_spec_regenerate(ddata) - rawdata_list = ddata.file_match(expmt_group, datakey) - - # simple length check, but will proceed bluntly anyway. - # this will result in truncated lists, per zip function - if len(param_list) != len(rawdata_list): - print "warning, some weirdness detected in list length in exec_avgtrials. Check yo' lengths!" - - # number of unique simulations, per trial - # this had better be equivalent as an integer or a float! - N_unique = len(param_list) / N_trials - - # go through the unique simulations - for i in range(N_unique): - # fills in the correct int for the experimental prefix string formatter 'exp_prefix_str' - prefix_unique = exp_prefix_str % i - fprefix_long = os.path.join(ddatatype, prefix_unique) - fprefix_long_param = os.path.join(dparam, prefix_unique) - - # create the sublist of just these trials - unique_list = [rawdatafile for rawdatafile in rawdata_list if rawdatafile.startswith(fprefix_long)] - unique_param_list = [pfile for pfile in param_list if pfile.startswith(fprefix_long_param)] - - # one filename per unique - # length of the unique list is the number of trials for this sim, should match N_trials - fname_unique = ddata.create_filename(expmt_group, datakey_avg, prefix_unique) - - # Average data for each trial - # average dipole data - if datakey == 'rawdpl': - for f_dpl, f_param in zip(unique_list, unique_param_list): - dpl = dipolefn.Dipole(f_dpl) - # dpl = dipolefn.Dipole(f_dpl, f_param) - - # ah, this is required becaused the dpl *file* still contains the raw, un-normalized data - dpl.baseline_renormalize(f_param) - - # initialize and use x_dpl - if f_dpl is unique_list[0]: - # assume time vec stays the same throughout - t_vec = dpl.t - x_dpl_agg = dpl.dpl['agg'] - x_dpl_L2 = dpl.dpl['L2'] - x_dpl_L5 = dpl.dpl['L5'] - - else: - x_dpl_agg += dpl.dpl['agg'] - x_dpl_L2 += dpl.dpl['L2'] - x_dpl_L5 += dpl.dpl['L5'] - - # poor man's mean - x_dpl_agg /= len(unique_list) - x_dpl_L2 /= len(unique_list) - x_dpl_L5 /= len(unique_list) - - # write this data to the file - # np.savetxt(fname_unique, avg_data, '%5.4f') - with open(fname_unique, 'w') as f: - for t, x_agg, x_L2, x_L5 in zip(t_vec, x_dpl_agg, x_dpl_L2, x_dpl_L5): - f.write("%03.3f\t%5.4f\t%5.4f\t%5.4f\n" % (t, x_agg, x_L2, x_L5)) - - # average spec data - elif datakey == 'rawspec': - specfn.average(fname_unique, unique_list) - # # load TFR data into np array and avg by summing and dividing by n_trials - # data_for_avg = np.array([np.load(file)['TFR'] for file in unique_list]) - # spec_avg = data_for_avg.sum(axis=0)/data_for_avg.shape[0] - - # # load time and freq vectors from the first item on the list, assume all same - # timevec = np.load(unique_list[0])['time'] - # freqvec = np.load(unique_list[0])['freq'] - - # # save the aggregate info - # np.savez_compressed(fname_unique, time=timevec, freq=freqvec, TFR=spec_avg) - -# run the spectral analyses on the somatic current time series -def exec_spec_current(ddata, opts_in=None): - # p_exp = paramrw.ExpParams(ddata.fparam) - - opts = { - 'type': 'dpl_laminar', - 'f_max': 150., - 'save_data': 1, - 'runtype': 'parallel', - } - - if opts_in: - args_check(opts, opts_in) - - specfn.analysis_typespecific(ddata, opts) - -# this function can now use specfn.generate_missing_spec(ddata, f_max) -def exec_spec_regenerate(ddata, f_max=None): - # regenerate and save spec data - opts = { - 'type': 'dpl_laminar', - 'f_max': 60., - 'save_data': 1, - 'runtype': 'parallel', - } - - # set f_max if provided - if f_max: - opts['f_max'] = f_max - - specfn.analysis_typespecific(ddata, opts) - -# Time-averaged stationarity analysis - averages spec power over time and plots it -def exec_spec_stationary_avg(ddata, dsim, maxpwr): - - # Prompt user for type of analysis (per expmt or whole sim) - analysis_type = raw_input('Would you like analysis per expmt or for whole sim? (expmt or sim): ') - - fspec_list = fio.file_match(ddata.dsim, '-spec.npz') - fparam_list = fio.file_match(ddata.dsim, '-param.txt') - # fspec_list = fio.file_match(ddata.dsim, '-spec.npz') - # fparam_list = fio.file_match(ddata.dsim, '-param.txt') - - p_exp = paramrw.ExpParams(ddata.fparam) - key_types = p_exp.get_key_types() - - # If no saved spec results exist, redo spec analysis - if not fspec_list: - print "No saved spec data found. Performing spec analysis...", - exec_spec_regenerate(ddata) - fspec_list = fio.file_match(ddata.dsim, '-spec.npz') - # spec_results = exec_spec_regenerate(ddata) - - print "now doing spec freq-pwr analysis" - - # perform time-averaged stationary analysis - # specpwr_results = [specfn.specpwr_stationary_avg(fspec) for fspec in fspec_list] - specpwr_results = [] - - for fspec in fspec_list: - spec = specfn.Spec(fspec) - specpwr_results.append(spec.stationary_avg()) - - # plot for whole simulation - if analysis_type == 'sim': - - file_name = os.path.join(dsim, 'specpwr.eps') - pspec.pspecpwr(file_name, specpwr_results, fparam_list, key_types) - - # if maxpwr plot indicated - if maxpwr: - f_name = os.path.join(dsim, 'maxpwr.png') - specfn.pmaxpwr(f_name, specpwr_results, fparam_list) - - # plot per expmt - if analysis_type == 'expmt': - for expmt_group in ddata.expmt_groups: - # create name for figure. Figure saved to expmt directory - file_name = os.path.join(dsim, expmt_group, 'specpwr.png') - - # compile list of freqpwr results and param pathways for expmt - partial_results_list = [result for result in specpwr_results if result['expmt']==expmt_group] - partial_fparam_list = [fparam for fparam in fparam_list if expmt_group in fparam] - - # plot results - pspec.pspecpwr(file_name, partial_results_list, partial_fparam_list, key_types) - - # if maxpwr plot indicated - if maxpwr: - f_name = os.path.join(dsim, expmt_group, 'maxpwr.png') - specfn.pmaxpwr(f_name, partial_results_list, partial_fparam_list) - -# Time-averaged Spectral-power analysis/plotting of avg spec data -def exec_spec_avg_stationary_avg(ddata, dsim, opts): - - # Prompt user for type of analysis (per expmt or whole sim) - analysis_type = raw_input('Would you like analysis per expmt or for whole sim? (expmt or sim): ') - - spec_results_avged = fio.file_match(ddata.dsim, '-specavg.npz') - fparam_list = fio.file_match(ddata.dsim, '-param.txt') - - p_exp = paramrw.ExpParams(ddata.fparam) - key_types = p_exp.get_key_types() - - # If no avg spec data found, generate it. - if not spec_results_avged: - exec_avgtrials(ddata, 'spec') - spec_results_avged = fio.file_match(ddata.dsim, '-specavg.npz') - - # perform time-averaged stationarity analysis - # specpwr_results = [specfn.specpwr_stationary_avg(dspec) for dspec in spec_results_avged] - specpwr_results = [] - - for fspec in spec_results_avged: - spec = specfn.Spec(fspec) - specpwr_results.append(spec.stationary_avg()) - - # create fparam list to match avg'ed data - N_trials = p_exp.N_trials - nums = np.arange(0, len(fparam_list), N_trials) - fparam_list = [fparam_list[num] for num in nums] - - # plot for whole simulation - if analysis_type == 'sim': - - # if error bars indicated - if opts['errorbars']: - # get raw (non avg'ed) spec data - raw_spec_data = fio.file_match(ddata.dsim, '-spec.npz') - - # perform freqpwr analysis on raw data - # raw_specpwr = [specfn.specpwr_stationary_avg(dspec)['p_avg'] for dspec in raw_spec_data] - raw_specpwr = [] - - for fspec in raw_spec_data: - spec = specfn.Spec(fspec) - raw_specpwr.append(spec.stationary_avg()['p_avg']) - - # calculate standard error - error_vec = specfn.calc_stderror(raw_specpwr) - - else: - error_vec = [] - - file_name = os.path.join(dsim, 'specpwr-avg.eps') - pspec.pspecpwr(file_name, specpwr_results, fparam_list, key_types, error_vec) - - # # if maxpwr plot indicated - # if maxpwr: - # f_name = os.path.join(dsim, 'maxpwr-avg.png') - # specfn.pmaxpwr(f_name, freqpwr_results_list, fparam_list) - - # plot per expmt - if analysis_type == 'expmt': - for expmt_group in ddata.expmt_groups: - # if error bars indicated - if opts['errorbars']: - # get exmpt group raw spec data - raw_spec_data = ddata.file_match(expmt_group, 'rawspec') - - # perform stationary analysis on raw data - raw_specpwr = [specfn.specpwr_stationary_avg(dspec)['p_avg'] for dspec in raw_spec_data] - - # calculate standard error - error_vec = specfn.calc_stderror(raw_specpwr) - - else: - error_vec = [] - - # create name for figure. Figure saved to expmt directory - file_name = os.path.join(dsim, expmt_group, 'specpwr-avg.png') - - # compile list of specpwr results and param pathways for expmt - partial_results_list = [result for result in specpwr_results if result['expmt']==expmt_group] - partial_fparam_list = [fparam for fparam in fparam_list if expmt_group in fparam] - - # plot results - pspec.pspecpwr(file_name, partial_results_list, partial_fparam_list, key_types, error_vec) - - # # if maxpwr plot indicated - # if maxpwr: - # f_name = os.path.join(dsim, expmt_group, 'maxpwr-avg.png') - # specfn.pmaxpwr(f_name, partial_results_list, partial_fparam_list) - -# Averages spec pwr over time and plots it with histogram of alpha feeds per simulation -# Currently not completed -def freqpwr_with_hist(ddata, dsim): - fspec_list = fio.file_match(ddata.dsim, '-spec.npz') - spk_list = fio.file_match(ddata.dsim, '-spk.txt') - fparam_list = fio.file_match(ddata.dsim, '-param.txt') - - p_exp = paramrw.ExpParams(ddata.fparam) - key_types = p_exp.get_key_types() - - # If no save spec reslts exist, redo spec analysis - if not fspec_list: - print "No saved spec data found. Performing spec analysis...", - exec_spec_regenerate(ddata) - fspec_list = fio.file_match(ddata.dsim, '-spec.npz') - # spec_results = exec_spec_regenerate(ddata) - - print "now doing spec freq-pwr analysis" - - # perform freqpwr analysis - freqpwr_results_list = [specfn.freqpwr_analysis(fspec) for fspec in fspec_list] - - # Plot - for freqpwr_result, f_spk, fparam in zip(freqpwr_results_list, spk_list, fparam_list): - gid_dict, p_dict = paramrw.read(fparam) - file_name = 'freqpwr.png' - - specfn.pfreqpwr_with_hist(file_name, freqpwr_result, f_spk, gid_dict, p_dict, key_types) - -# runs plotfn.pall *but* checks to make sure there are spec data -def exec_replot(ddata, opts): -# def regenerate_plots(ddata, xlim=[0, 'tstop']): - p = { - 'xlim': None, - 'ylim': None, - } - - args_check(p, opts) - - # recreate p_exp ... don't like this - # ** should be guaranteed to be identical ** - p_exp = paramrw.ExpParams(ddata.fparam) - - # grab the list of spec results that exists - # there is a method in SimulationPaths/ddata for this specifically, this should be deprecated - # fspec_list = fio.file_match(ddata.dsim, '-spec.npz') - - # generate data if no spec exists here - if not fio.file_match(ddata.dsim, '-spec.npz'): - # if not fspec_list: - print "No saved spec data found. Performing spec anaylsis ... " - exec_spec_regenerate(ddata) - # spec_results = exec_spec_regenerate(ddata) - - # run our core pall plot - plotfn.pall(ddata, p_exp, p['xlim'], p['ylim']) - -# function to add alpha feed hists -def exec_addalphahist(ddata, opts): -# def exec_addalphahist(ddata, xlim=[0, 'tstop']): - p = { - 'xlim': None, - 'ylim': None, - } - - args_check(p, opts) - - p_exp = paramrw.ExpParams(ddata.fparam) - - # generate data if no spec exists here - if not fio.file_match(ddata.dsim, '-spec.npz'): - print "No saved spec data found. Performing spec anaylsis ... " - exec_spec_regenerate(ddata) - - plotfn.pdpl_pspec_with_hist(ddata, p_exp, p['xlim'], p['ylim']) - # plotfn.pdpl_pspec_with_hist(ddata, p_exp, spec_list, xlim) - -def exec_aggregatespec(ddata, labels): - p_exp = paramrw.ExpParams(ddata.fparam) - - fspec_list = fio.file_match(ddata.dsim, '-spec.npz') - - # generate data if no spec exists here - if not fspec_list: - print "No saved spec data found. Performing spec anaylsis ... " - exec_spec_regenerate(ddata) - - plotfn.aggregate_spec_with_hist(ddata, p_exp, labels) - -def exec_pgamma_spec_fig(): - pgamma.spec_fig() - -def exec_pgamma_spikephase(): - # the directory here is hardcoded for now, inside the function - pgamma.spikephase() - -def exec_pgamma_peaks(): - pgamma.peaks() - -def exec_pgamma_sub_examples(): - pgamma.sub_dist_examples() - -def exec_pgamma_sub_example2(): - pgamma.sub_dist_example2() - -def exec_phaselock(ddata, opts): - p = { - 't_interval': [50, 1000], - 'f_max': 60., - } - args_check(p, opts) - - # Do this per expmt group - for expmt_group in ddata.expmt_groups: - # Get paths to relevant files - list_dpl = ddata.file_match(expmt_group, 'rawdpl') - list_spk = ddata.file_match(expmt_group, 'rawspk') - list_param = ddata.file_match(expmt_group, 'param') - - avg_spec = ddata.file_match(expmt_group, 'avgspec')[0] - - tmp_array_dpl = [] - tmp_array_spk = [] - - for f_dpl, f_spk, f_param in zip(list_dpl, list_spk, list_param): - # load Dpl data, do stuff, and store it - print f_dpl - dpl = dipolefn.Dipole(f_dpl) - dpl.baseline_renormalize(f_param) - dpl.convert_fAm_to_nAm() - t, dp = dpl.truncate_ext(p['t_interval'][0], p['t_interval'][1]) - dp = dp['agg'] - tmp_array_dpl.append(dp) - - # Load extinput data, do stuff, and store it - try: - extinput = spikefn.ExtInputs(f_spk, f_param) - except ValueError: - print("Error: could not load spike timings from %s" % f_spk) - return - - extinput.add_delay_times() - extinput.get_envelope(dpl.t, feed='dist', bins=150) - inputs, t = extinput.truncate_ext('env', p['t_interval']) - tmp_array_spk.append(inputs) - - # Convert tmp arrays (actually lists) to numpy nd arrays - array_dpl = np.array(tmp_array_dpl) - array_spk = np.array(tmp_array_spk) - - # Phase-locking analysis - phase = specfn.PhaseLock(array_dpl, array_spk, list_param[0], p['f_max']) - - fname_d = os.path.join(ddata.dsim, expmt_group, 'phaselock-%iHz.npz' %p['f_max']) - np.savez_compressed(fname_d, t=phase.data['t'], f=phase.data['f'], B=phase.data['B']) - - # Plotting - # Should be moved elsewhere - avg_dpl = np.mean(array_dpl, axis=0) - avg_spk = np.mean(array_spk, axis=0) - - f = ac.FigPhase() - - extent_xy = [t[0], t[-1], phase.data['f'][-1], 0] - pc1 = f.ax['phase'].imshow(phase.data['B'], extent=extent_xy, aspect='auto', origin='upper',cmap=plt.get_cmap('jet')) - pc1.set_clim([0, 1]) - cb1 = f.f.colorbar(pc1, ax=f.ax['phase']) - # cb1.set_clim([0, 1]) - - spec = specfn.Spec(avg_spec) - pc2 = spec.plot_TFR(f.ax['spec'], xlim=[t[0], t[-1]]) - pc2.set_clim([0, 3.8e-7]) - cb2 = f.f.colorbar(pc2, ax=f.ax['spec']) - # cb2.set_clim([0, 3.6e-7]) - - f.ax['dipole'].plot(t, avg_dpl) - f.ax['dipole'].set_xlim([t[0], t[-1]]) - f.ax['dipole'].set_ylim([-0.0015, 0.0015]) - - f.ax['input'].plot(t, avg_spk) - f.ax['input'].set_xlim([t[0], t[-1]]) - f.ax['input'].set_ylim([-1, 5]) - f.ax['input'].invert_yaxis() - - f.ax['phase'].set_xlabel('Time (ms)') - f.ax['phase'].set_ylabel('Frequency (Hz)') - - fname = os.path.join(ddata.dsim, expmt_group, 'phaselock-%iHz.png' %p['f_max']) - print fname - - f.savepng(fname) - -# runs the gamma plot for a comparison of the high frequency -def exec_pgamma_hf(ddata, opts): - p = { - 'xlim_window': [0., -1], - 'n_sim': 0, - 'n_trial': 0, - } - args_check(p, opts) - pgamma.hf(ddata, p['xlim_window'], p['n_sim'], p['n_trial']) - -def exec_pgamma_hf_epochs(ddata, opts): - p = {} - args_check(p, opts) - pgamma.hf_epochs(ddata) - -# comparison of all layers and aggregate data -def exec_pgamma_laminar(ddata): - pgamma.laminar(ddata) - -# comparison between a PING (ddata0) and a weak PING (ddata1) data set -def exec_pgamma_compare_ping(): - # def exec_pgamma_compare_ping(ddata0, ddata1, opts): - pgamma.compare_ping() - -# plot for gamma stdev on a given ddata -def exec_pgamma_stdev(ddata): - pgamma.pgamma_stdev(ddata) - -def exec_pgamma_prox_dist_new(ddata, opts): - p = { - 'f_max_welch': 80., - } - - args_check(p, opts) - pgamma.prox_dist_new(ddata, p) - -def exec_pgamma_stdev_new(ddata, opts): - p = { - 'f_max_welch': 80., - } - - args_check(p, opts) - pgamma.pgamma_stdev_new(ddata, p) - -# plot for gamma distal phase on a given ddata -def exec_pgamma_distal_phase(ddata, opts): - pgamma.pgamma_distal_phase(ddata, opts['spec0'], opts['spec1'], opts['spec2']) - -# plot data averaged over trials -# dipole and spec should be split up at some point (soon) -# ylim specified here is ONLY for the dipole -def exec_plotaverages(ddata, ylim=[]): - # runtype = 'parallel' - runtype = 'debug' - - # this is a qnd check to create the fig dir if it doesn't already exist - # backward compatibility check for sims that didn't auto-create these dirs - for expmt_group in ddata.expmt_groups: - dfig_avgdpl = ddata.dfig[expmt_group]['figavgdpl'] - dfig_avgspec = ddata.dfig[expmt_group]['figavgspec'] - - # create them if they did not previously exist - fio.dir_create(dfig_avgdpl) - fio.dir_create(dfig_avgspec) - - # presumably globally true information - p_exp = paramrw.ExpParams(ddata.fparam) - key_types = p_exp.get_key_types() - - # empty lists to be used/appended - dpl_list = [] - spec_list = [] - dfig_list = [] - dfig_dpl_list = [] - dfig_spec_list = [] - pdict_list = [] - - # by doing all file operations sequentially by expmt_group in this iteration - # trying to guarantee order better than before - for expmt_group in ddata.expmt_groups: - # print expmt_group, ddata.dfig[expmt_group] - - # avgdpl and avgspec data paths - # fio.file_match() returns lists sorted - # dpl_list_expmt is so i can iterate through them in a sec - dpl_list_expmt = fio.file_match(ddata.dfig[expmt_group]['avgdpl'], '-dplavg.txt') - dpl_list += dpl_list_expmt - spec_list += fio.file_match(ddata.dfig[expmt_group]['avgspec'], '-specavg.npz') - - # create redundant list of avg dipole dirs and avg spec dirs - # unique parts are expmt group names - # create one entry for each in dpl_list - dfig_list_expmt = [ddata.dfig[expmt_group] for path in dpl_list_expmt] - dfig_list += dfig_list_expmt - dfig_dpl_list += [dfig['figavgdpl'] for dfig in dfig_list_expmt] - dfig_spec_list += [dfig['figavgspec'] for dfig in dfig_list_expmt] - - # param list to match avg data lists - fparam_list = fio.fparam_match_minimal(ddata.dfig[expmt_group]['param'], p_exp) - pdict_list += [paramrw.read(f_param)[1] for f_param in fparam_list] - - if dpl_list: - # new input to dipolefn - pdipole_dict = { - 'xlim': None, - 'ylim': None, - # 'xmin': 0., - # 'xmax': None, - # 'ymin': None, - # 'ymax': None, - } - - # if there is a length, assume it's 2 (it should be!) - if len(ylim): - pdipole_dict['ymin'] = ylim[0] - pdipole_dict['ymax'] = ylim[1] - - if runtype == 'debug': - for f_dpl, f_param, dfig_dpl in zip(dpl_list, fparam_list, dfig_dpl_list): - dipolefn.pdipole(f_dpl, dfig_dpl, pdipole_dict, f_param, key_types) - - elif runtype == 'parallel': - pl = Pool() - for f_dpl, f_param, dfig_dpl in zip(dpl_list, fparam_list, dfig_dpl_list): - pl.apply_async(dipolefn.pdipole, (f_dpl, f_param, dfig_dpl, key_types, pdipole_dict)) - - pl.close() - pl.join() - - else: - print "No avg dipole data found." - return 0 - - # if avg spec data exists - if spec_list: - if runtype == 'debug': - for f_spec, f_dpl, f_param, dfig_spec, pdict in zip(spec_list, dpl_list, fparam_list, dfig_spec_list, pdict_list): - pspec.pspec_dpl(f_spec, f_dpl, dfig_spec, pdict, key_types, f_param=f_param) - - elif runtype == 'parallel': - pl = Pool() - for f_spec, f_dpl, dfig_spec, pdict in zip(spec_list, dpl_list, dfig_spec_list, pdict_list): - pl.apply_async(pspec.pspec_dpl, (f_spec, f_dpl, dfig_spec, pdict, key_types)) - - pl.close() - pl.join() - - else: - print "No averaged spec data found. Run avgtrials()." - return 0 - -# rsync command with excludetype input -def exec_sync(droot, server_remote, dsubdir, fshort_exclude='exclude_eps.txt'): - # make up the local exclude file name - # f_exclude = os.path.join(droot, 'exclude_eps.txt') - f_exclude = os.path.join(droot, fshort_exclude) - - # create remote and local directories, they should look similar - dremote = os.path.join(droot, dsubdir) - dlocal = os.path.join(droot, 'from_remote') - - # creat the rsync command - cmd_rsync = "rsync -ruv --exclude-from '%s' -e ssh %s:%s %s" % (f_exclude, server_remote, dremote, dlocal) - - call(cmd_rsync, shell=True) - -# save to cppub -def exec_save(dproj, ddate, dsim): - if fio.dir_check(dsim): - dsave_root = os.path.join(dproj, 'pub') - - # check to see if this dir exists or not, and create it if not - fio.dir_create(dsave_root) - - dsave_short = '%s_%s' % (ddate.split('/')[-1], dsim.split('/')[-1]) - dsave = os.path.join(dsave_root, dsave_short) - - # use fileio routine to non-destructively copy dir - fio.dir_copy(dsim, dsave) - - else: - print "Not sure I can find that directory." - return 1 - -# Creates a pdf from a file list and saves it generically to ddata -def pdf_create(ddata, fprefix, flist): - file_out = os.path.join(ddata, fprefix + '-summary.pdf') - - # create the beginning of the call to ghostscript - gscmd = 'gs -dNumRenderingThreads=8 -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=' + file_out + ' -f ' - - for file in flist: - gscmd += file + ' ' - - # print gscmd - call(gscmd, shell=True) - - return file_out - -# PDF Viewer -def view_pdf(pdffile): - if sys.platform.startswith('darwin'): - app_pdf = 'open -a skim ' - elif sys.platform.startswith('linux'): - app_pdf = 'evince ' - - call([app_pdf + pdffile + ' &'], shell=True) - -# PDF finder ... (this is starting to get unwieldy) -def find_pdfs(ddata, expmt): - if expmt == 'all': - # This is recursive - # find the ONE pdf in the root dir - # all refers to the aggregated pdf file - pdf_list = [f for f in iglob(os.path.join(ddata, '*.pdf'))] - - elif expmt == 'each': - # get each and every one of these (syntax matches below) - pdf_list = fio.file_match(ddata, '*.pdf') - else: - # do this non-recursively (i.e. just for this directory) - dexpmt = os.path.join(ddata, expmt, '*.pdf') - pdf_list = [f for f in iglob(dexpmt)] - - # Check the length of pdf_list - if len(pdf_list) > 3: - print "There are", len(pdf_list), "files here." - str_open = raw_input("Do you want to open them all? ") - else: - # just set to open the files if fewer than 3 - str_open = 'y' - - # now check for a yes and go - if str_open == 'y': - for file in pdf_list: - view_pdf(file) - else: - print "Okay, good call. Here's the consolation prize:\n" - prettyprint(pdf_list) - -# Cross-platform file viewing using eog or xee, cmd is pngv in cli.py -def view_img(dir_data, ext): - # platform and extension specific opening - if sys.platform.startswith('darwin'): - ext_img = '/*' + ext - app_img = 'open -a xee ' - - elif sys.platform.startswith('linux'): - if ext == 'png': - app_img = 'eog ' - elif ext == 'eps': - app_img = 'evince ' - ext_img = '/*' + ext + '&' - - call([app_img + os.path.join(dir_data, 'spec') + ext_img], shell=True) - -# Cross platform file viewing over all exmpts -def file_viewer(ddata, dict_opts): - opts_default = { - 'expmt_group': ddata.expmt_groups[0], - 'type': 'figspec', - 'run': 'all', - } - args_check(opts_default, dict_opts) - - # return a list of files by run - if opts_default['run'] == 'all': - flist = ddata.file_match(opts_default['expmt_group'], opts_default['type']) - - else: - flist = ddata.file_match_by_run(**opts_default) - - # sort the list in place - flist.sort() - - # create a list of files for the argument to the program - files_arg = ' '.join(flist) - - if sys.platform.startswith('darwin'): - app_img = 'open -a preview ' - subprocess.call([app_img + files_arg], shell=True) - - elif sys.platform.startswith('linux'): - app_img = 'eog ' - subprocess.call([app_img + files_arg + '&'], shell=True) - -# a really simple image viewer, views images in dimg -def png_viewer_simple(dimg): - list_fpng = fio.file_match(dimg, '*.png') - - # Create an empty file argument - files_arg = '' - for file in list_fpng: - files_arg += file + ' ' - - # uses xee - if sys.platform.startswith('darwin'): - app_img = 'open -a xee ' - call([app_img + files_arg], shell=True) - - # uses eye of gnome (eog) - elif sys.platform.startswith('linux'): - app_img = 'eog ' - call([app_img + files_arg + '&'], shell=True) diff --git a/conf.py b/conf.py index 009468e10..233496465 100644 --- a/conf.py +++ b/conf.py @@ -3,7 +3,6 @@ import pickle import os import sys -from fileio import safemkdir from collections import OrderedDict try: @@ -38,6 +37,18 @@ decay_multiplier = 1.6 """ +# make dir, catch exceptions +def safemkdir (dn): + try: + os.mkdir(dn) + except FileExistsError: + pass + except OSError: + print('ERR: incorrect permissions for creating', dn) + raise + + return True + # parameter used for optimization class param: def __init__ (self, origval, minval, maxval, bounded, var, bestval=None): diff --git a/ctune.py b/ctune.py deleted file mode 100644 index 42ce68c04..000000000 --- a/ctune.py +++ /dev/null @@ -1,361 +0,0 @@ -from math import log, exp - -""" -from neuron import h -# h.load_file("stdrun.hoc") -import numpy -from pylab import * -from time import time, clock -import os -from conf import dconf -import pickle - -dprm = dconf['params'] -sampr = dconf['sampr'] # 10KHz sampling rate in txt,npy file (data/15jun12_BS0284_subthreshandspikes_A0.npy) - -vtime = h.Vector() -vtime.record(h._ref_t) - -tinit = 0.0 -tstop = h.tstop - -gtmp=h.Vector() - -# -def myrun (reconfig=True,inj=0.0,prtime=False): - if reconfig: safereconfig() # makes sure params set within cell - stim.amp = inj - if prtime: clockStart = time() - # h.run() - if prtime: - clockEnd = time() - print('\nsim runtime:',str(round(clockEnd-clockStart,2)),'secs') - -y = h.Vector() -drawOut = False - -# -def readdat (sampr=10e3): - dat = numpy.load(dconf['evolts']) #numpy.load('data/15jun12_BS0284_subthreshandspikes_A0.npy') - etime = numpy.linspace(0,dat.shape[0]*1e3/sampr,dat.shape[0]) - return dat,etime - -dat,etime = readdat(sampr) # dim 1 is voltage - -intert = 3000 # 3000 ms in between clamps -offt = 500 # 500 ms before start of first clamp -durt = 1000 # 1000 ms current clamp -padt = 500 # pad around clamp - -# -def getindices (tdx): - sidx = sampr*(offt/1e3+tdx*(intert+durt)/1e3) - sampr * padt / 1e3 - eidx = sidx + durt * sampr / 1e3 + 2 * sampr * padt / 1e3 - return sidx,eidx - -# -def cuttrace (dat,tdx): - sidx,eidx = getindices(tdx) - if catdat: - return dat[sidx:eidx,1], etime[sidx:eidx] - else: - return dat[:,tdx],etime - -Vector = h.Vector -Iexp = lstimamp = numpy.load(dconf['lstimamp']) # [-0.15+j*0.05 for j in xrange(nstims_fi)] -nstims_fi = len(Iexp) # was 7 -alltrace = [i for i in xrange(nstims_fi)] -targSpikes = numpy.load(dconf['spiket']) # just using this for spike frequency - not timing!! -Fexp = [len(arr) for arr in targSpikes] # assumes 1 s stimulus duration -ltracedxsubth = [i for i in xrange(len(Fexp)) if Fexp[i] <= 0.0 and Iexp[i] != 0.0] -ltrace=ltracedxsubth - -def issubth (tdx): return ltracedxsubth.count(tdx) > 0 -def issuperth (tdx): return not issubth(tdx) - -# simple normalization - with a maximum cap -def getnormval (val,maxval,scale=1.0): - if val > maxval: return scale - return scale * val / maxval - - -# interpolate voltage recorded in simulation to a fixed grid (dt millisecond spacing) -# output time,voltage is returned -def interpvolt (tsrc=vtime,vsrc=vsoma,dt=0.1,tshift=tinit,tend=tstop): - tdest = h.Vector(); tdest.indgen(tshift,tend,dt) - vdest = h.Vector(); vdest.interpolate(tdest,tsrc,vsrc) - tdest.sub(tshift) - return tdest, vdest - -tracedx = 0 # which trace to fit (trace index) - -# -def plotinterp (vtime,vval,clr): - it,ival = interpvolt(vtime,vval,1e3/sampr) - plot(it.as_numpy(),ival.as_numpy(),clr) - -# -def voltcompare (tdx,interponly=True,dcurr=None,xl=None): - if dcurr is not None: subplot(2,1,1) - dd,tt = cuttrace(dat,tdx) - tt = linspace(0,tt[-1]-tt[0],len(tt)) - plot(tt,dd,'b') - if not interponly: plot(vtime.as_numpy(),vsoma.as_numpy(),'r') - it,iv = interpvolt(vtime,vsoma,1e3/sampr) - plot(it.as_numpy(),iv.as_numpy(),'r') - legend(['experiment','simulation'],loc='best') - xlabel('Time (ms)',fontsize=16); ylabel('Vm',fontsize=16); - if xl is not None: xlim(xl) - if dcurr is not None: - subplot(2,1,2) - plotinterp(vtime,dcurr['ina'],'r') - plotinterp(vtime,dcurr['ik'],'b') - plotinterp(vtime,dcurr['ica'],'g') - plotinterp(vtime,dcurr['cai'],'y') - plotinterp(vtime,dcurr['ih'],'k') - legend(['ina','ik','ica','cai','ih'],loc='best') - if xl is not None: xlim(xl) - -prtime = True # print simulation duration? - -lparam = [p for p in dprm.values()]; - -def lparamindex (lp,s): - for i,p in enumerate(lp): - if p.var == s: return i - return -1 - -# -def prmnames (): return [prm.var for prm in lparam] - -# clamps nval (which is between 0,1) to valid param range -def clampval (prm, nval): - if nval < 0.0: return prm.minval - elif nval > 1.0: return prm.maxval - else: return prm.minval + (prm.maxval - prm.minval) * nval - -# -def clampvals (vec,lparam): return [clampval(prm,x) for prm,x in zip(lparam,vec)] -""" - -# exponentiates value -def expval (prm, val): - if prm.minval > 0: return exp(val) - elif prm.maxval < 0: return -exp(val) - else: return val - -# -def expvals (vec,lparam): return [expval(prm,x) for prm,x in zip(lparam,vec)] - -# -def logval (prm, val): - if prm.minval > 0: return log(val) - elif prm.maxval < 0: return log(-val) - else: return val - -# -def logvals (vec,lparam): return [logval(prm,x) for prm,x in zip(lparam,vec)] - - -""" -# -def assignparams (vparam,lparam,useExp=False): - if useExp: - for prm,val in zip(lparam,expvals(vparam,lparam)): # set parameters - exec(prm.assignstr(val)) - else: - for prm,val in zip(lparam,vparam): # set parameters - exec(prm.assignstr(val)) - -# -def assignrow (nqp, row): - if row < 0 or row >= nqp.v[0].size(): return None - nprm = int(nqp.m[0]) - 2 # -2 for idx,err - vprm = [] - for col in xrange(nprm): vprm.append(nqp.v[col].x[row]) - assignparams(vprm,lparam) - safereconfig() - return vprm - -# -def printparams (vparam,lparam,useExp=False): - if useExp: - for prm,val in zip(lparam,expvals(vparam,lparam)): print(prm.var, ' = ' , val) # set parameters - else: - for prm,val in zip(lparam,vparam): print(prm.var, ' = ' , val) # set parameters - -myerrfunc = None # error function - -# create an empty NQS with parameter and error columns -def makeprmnq (): - lp = prmnames() - nqp = h.NQS() - for s in lp: - if type(s) == list: - nqp.resize(s[0]) - else: - nqp.resize(s) - nqp.resize('idx'); nqp.resize('err'); nqp.clear(1e3) - return nqp - -# append parameter values and error to the NQS -def appendprmnq (nqp,vprm,err): - for i,x in enumerate(vprm): nqp.v[i].append(x) - sz = nqp.v[0].size() - nqp.getcol('idx').append(nqp.v[0].size()-1) - nqp.getcol('err').append(err) - -nqp = makeprmnq() - -# -def traceerr (): - toterr = 0.0 # total error across traces - for tracedx in ltrace: - print('stim.amp is ', lstimamp[tracedx]) - myrun(reconfig=False,inj=lstimamp[tracedx]) # - if drawOut: voltcompare(tracedx) - err = myerrfunc(tracedx) - print('err is ' , round(err,6)) - toterr += err - return toterr - -# errwrap - assigns params (xp are param values), evaluates and returns error (uses traceerr) -def errwrap (xp): - assignparams(xp,lparam,useExp=False) - printparams(xp,lparam,useExp=False) - safereconfig() - toterr = traceerr() - print('toterr is ' , toterr) - return toterr - -# optimization run - for an individual set of params specified in vparam -# NB: vparam contains the log of actual param values, & the meaning of params is specified in global lparam -def optrun (vparam): - if prtime: clockStart = time() - global tracedx, ltrace - for prm,val in zip(lparam,expvals(vparam,lparam)): # set parameters - if val >= prm.minval and val <= prm.maxval: - exec(prm.assignstr(val)) - else: - print(val, 'out of bounds for ' , prm.var, prm.minval, prm.maxval) - appendprmnq(nqp,expvals(vparam,lparam),1e9) - return 1e9 - if type(vparam)==list: print('set params:', vparam) - else: print('set params:', vparam.as_numpy()) - safereconfig() # make sure parameters are set in cell - toterr = traceerr() # total error across traces - if prtime: - clockEnd = time() - print('\nsim runtime:',str(round(clockEnd-clockStart,2)),'secs') - print('toterr is ' , round(toterr/len(ltrace),6)) - appendprmnq(nqp,expvals(vparam,lparam),toterr / len(ltrace)) - return toterr / len(ltrace) # average - -# run sims specified in ltrace and plot comparison of voltages -def voltcomprun (ltrace=None,prtime=False): - if ltrace is None: ltrace = ltracedxsubth - for tracedx in ltrace: - myrun(reconfig=False,inj=lstimamp[tracedx],prtime=prtime) - voltcompare(tracedx) - -# mean squared error of voltage -lvoltwin = [] # can use to specify time ranges for volterr -lvoltscale = [] # can use to scale errors (matches to lvoltwin indices) - -# -def volterr (tdx): - it,iv = interpvolt(vtime,vsoma,1e3/sampr) - dd,tt = cuttrace(dat,tdx) - npt = len(dd) - if it.size() > npt: it.resize(npt) - err = 0; ivnp = iv.as_numpy() - if len(lvoltwin) > 0: - if len(lvoltscale) > 0: - npt = 0; - for voltwin,fctr in zip(lvoltwin,lscale): - sidx,eidx = int(voltwin[0]*sampr/1e3),int(voltwin[1]*sampr/1e3) - npt += (eidx-sidx+1) - for idx in xrange(sidx,eidx+1,1): err += fctr * (ivnp[idx] - dd[idx])**2 - else: - npt = 0; - for voltwin in lvoltwin: - sidx,eidx = int(voltwin[0]*sampr/1e3),int(voltwin[1]*sampr/1e3) - npt += (eidx-sidx+1) - for idx in xrange(sidx,eidx+1,1): err += (ivnp[idx] - dd[idx])**2 - else: - for v1,v2 in zip(ivnp,dd): err += (v1-v2)**2 - return sqrt(err/npt) - -# scaled error, scale individual functions, then combine -useV = False; -scaleV = zeros((len(lstimamp),)) - -myerrfunc = volterr - -# randomized optimization - search random points in param space -def randopt (lparam,nstep,errfunc,saveevery=0,fout=None): - global myerrfunc, nqp - myerrfunc = errfunc - for i in xrange(nstep): - print('step ' , i+1 , ' of ' , nstep) - vparam = [p.minval + random.uniform() * (p.maxval-p.minval) for p in lparam] - vplog = [logval(p,x) for p,x in zip(lparam,vparam)] - optrun(vplog) - if fout is not None and saveevery > 0 and i%saveevery==0: nqp.sv(fout) - if fout is not None: nqp.sv(fout) - -# performs praxis optimization using specified params and error function (errfunc) -def praxismatch (vparam,nstep,tol,stepsz,errfunc): - global myerrfunc, nqp - h.nqsdel(nqp) - nqp = makeprmnq() - myerrfunc = errfunc - print('using these traces:', ltrace) - h.attr_praxis(tol, stepsz, 3) - h.stop_praxis(nstep) # - return h.fit_praxis(optrun, vparam) - -# use praxis to match voltage traces -def voltmatch (vparam,nstep=10,tol=0.001,stepsz=0.5): - global tstop - if len(lvoltwin) > 0: - tstop = tinit + amax(lvoltwin) - print('reset tstop to ' , tstop) - return praxismatch(vparam,nstep,tol,stepsz,volterr) - -# get the original param values (stored in lparam) -def getparamorig (): - vparam = h.Vector() - for p in lparam: vparam.append( logval(p,p.origval) ) - return vparam - -# get random param values (from the set stored in lparam) -def getparamrand (seed): - rdm = h.Random() - rdm.ACG(seed) - vparam = h.Vector() - for p in lparam: vparam.append( logval(p, rdm.uniform(p.minval,p.maxval)) ) - return vparam - -# get 'best' param values found (from opt) -def getparambest (): - vparam = h.Vector() - for p in lparam: vparam.append( logval(p,p.bestval) ) - return vparam - -def runsaveopt (): - global ltrace,nqp,vparam - vparam = getparambest(); - assignparams(vparam,lparam,useExp=True); - safereconfig(); - # lvoltwin = [[495.0,750.0]] - ltrace=ltracedxsubth - voltmatch(vparam,nstep=dconf['nstep'],tol=dconf['tol']); - nqp.sv(dconf['nqp']) - dconf['vparam'] = vparam.to_python() # output parameter values - dconf['lparam'] = lparam - pickle.dump(dconf,open(dconf['dout'],'w')) # save everything - -if __name__ == '__main__': - if dconf['runopt']: runsaveopt() -""" diff --git a/dipolefn.py b/dipolefn.py deleted file mode 100644 index c398f7abc..000000000 --- a/dipolefn.py +++ /dev/null @@ -1,1123 +0,0 @@ -# dipolefn.py - dipole-based analysis functions -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: itertools and return data dir) -# last major: (SL: toward python3) -import fileio as fio -import numpy as np -import ast -import os -import paramrw -import spikefn -import specfn -import matplotlib.pyplot as plt -import axes_create as ac -from math import ceil -from filt import boxfilt, hammfilt, emptyfilt - -# class Dipole() is for a single set of f_dpl and f_param -class Dipole(): - def __init__(self, f_dpl): # fix to allow init from data in memory (not disk) - """ some usage: dpl = Dipole(file_dipole, file_param) - this gives dpl.t and dpl.dpl - """ - self.units = None - self.N = None - self.__parse_f(f_dpl) - - # opens the file and sets units - def __parse_f(self, f_dpl): - x = np.loadtxt(open(f_dpl, 'r')) - # better implemented as a dict - self.t = x[:, 0] - self.dpl = { - 'agg': x[:, 1], - 'L2': x[:, 2], - 'L5': x[:, 3], - } - self.N = self.dpl['agg'].shape[-1] - # string that holds the units - self.units = 'fAm' - - # truncate to a length and save here - def truncate(self, t0, T): - """ this is independent of the other stuff - moved to an external function so as to not disturb the delicate genius of this object - """ - self.t, self.dpl = self.truncate_ext(t0, T) - - # just return the values, do not modify the class internally - def truncate_ext(self, t0, T): - # only do this if the limits make sense - if (t0 >= self.t[0]) & (T <= self.t[-1]): - dpl_truncated = dict.fromkeys(self.dpl) - # do this for each dpl - for key in self.dpl.keys(): - dpl_truncated[key] = self.dpl[key][(self.t >= t0) & (self.t <= T)] - t_truncated = self.t[(self.t >= t0) & (self.t <= T)] - return t_truncated, dpl_truncated - - # conversion from fAm to nAm - def convert_fAm_to_nAm (self): - """ must be run after baseline_renormalization() - """ - for key in self.dpl.keys(): self.dpl[key] *= 1e-6 - # change the units string - self.units = 'nAm' - - def scale (self, fctr): - for key in self.dpl.keys(): self.dpl[key] *= fctr - return fctr - - def smooth (self, winsz): - if winsz <= 1: return - #for key in self.dpl.keys(): self.dpl[key] = boxfilt(self.dpl[key],winsz) - for key in self.dpl.keys(): self.dpl[key] = hammfilt(self.dpl[key],winsz) - - # average stationary dipole over a time window - def mean_stationary(self, opts_input={}): - # opts is default AND input to below, can be modified by opts_input - opts = { - 't0': 50., - 'tstop': self.t[-1], - 'layer': 'agg', - } - # attempt to override the keys in opts - for key in opts_input.keys(): - # check for each of the keys in opts - if key in opts.keys(): - # special rule for tstop - if key == 'tstop': - # if value in tstop is -1, then use end to T - if opts_input[key] == -1: - opts[key] = self.t[-1] - else: - opts[key] = opts_input[key] - # check for layer in keys - if opts['layer'] in self.dpl.keys(): - # get the dipole that matches the xlim - x_dpl = self.dpl[opts['layer']][(self.t > opts['t0']) & (self.t < opts['tstop'])] - # directly return the average - return np.mean(x_dpl, axis=0) - else: - print("Layer not found. Try one of %s" % self.dpl.keys()) - - # finds the max value within a specified xlim - # def max(self, layer, xlim): - def lim(self, layer, xlim): - # better implemented as a dict - if layer is None: - dpl_tmp = self.dpl['agg'] - elif layer in self.dpl.keys(): - dpl_tmp = self.dpl[layer] - # set xmin and xmax - if xlim is None: - xmin = self.t[0] - xmax = self.t[-1] - else: - xmin, xmax = xlim - if xmin < 0.: xmin = 0. - if xmax < 0.: xmax = self.f[-1] - dpl_tmp = dpl_tmp[(self.t > xmin) & (self.t < xmax)] - return (np.min(dpl_tmp), np.max(dpl_tmp)) - - # simple layer-specific plot function - def plot(self, ax, xlim, layer='agg'): - # plot the whole thing and just change the xlim and the ylim - # if layer is None: - # ax.plot(self.t, self.dpl['agg']) - # ymax = self.max(None, xlim) - # ylim = (-ymax, ymax) - # ax.set_ylim(ylim) - if layer in self.dpl.keys(): - ax.plot(self.t, self.dpl[layer]) - ylim = self.lim(layer, xlim) - # force ymax to be something sane - # commenting this out for now, but - # we can change if absolutely necessary. - # ax.set_ylim(top=ymax*1.2) - # set the lims here, as a default - ax.set_ylim(ylim) - ax.set_xlim(xlim) - else: - print("raise some error") - return ax.get_xlim() - - # ext function to renormalize - # this function changes in place but does NOT write the new values to the file - def baseline_renormalize(self, f_param): - # only baseline renormalize if the units are fAm - if self.units == 'fAm': - N_pyr_x = paramrw.find_param(f_param, 'N_pyr_x') - N_pyr_y = paramrw.find_param(f_param, 'N_pyr_y') - # N_pyr cells in grid. This is PER LAYER - N_pyr = N_pyr_x * N_pyr_y - # dipole offset calculation: increasing number of pyr cells (L2 and L5, simultaneously) - # with no inputs resulted in an aggregate dipole over the interval [50., 1000.] ms that - # eventually plateaus at -48 fAm. The range over this interval is something like 3 fAm - # so the resultant correction is here, per dipole - # dpl_offset = N_pyr * 50.207 - dpl_offset = { - # these values will be subtracted - 'L2': N_pyr * 0.0443, - 'L5': N_pyr * -49.0502 - # 'L5': N_pyr * -48.3642, - # will be calculated next, this is a placeholder - # 'agg': None, - } - # L2 dipole offset can be roughly baseline shifted over the entire range of t - self.dpl['L2'] -= dpl_offset['L2'] - # L5 dipole offset should be different for interval [50., 500.] and then it can be offset - # slope (m) and intercept (b) params for L5 dipole offset - # uncorrected for N_cells - # these values were fit over the range [37., 750.) - m = 3.4770508e-3 - b = -51.231085 - # these values were fit over the range [750., 5000] - t1 = 750. - m1 = 1.01e-4 - b1 = -48.412078 - # piecewise normalization - self.dpl['L5'][self.t <= 37.] -= dpl_offset['L5'] - self.dpl['L5'][(self.t > 37.) & (self.t < t1)] -= N_pyr * (m * self.t[(self.t > 37.) & (self.t < t1)] + b) - self.dpl['L5'][self.t >= t1] -= N_pyr * (m1 * self.t[self.t >= t1] + b1) - # recalculate the aggregate dipole based on the baseline normalized ones - self.dpl['agg'] = self.dpl['L2'] + self.dpl['L5'] - else: - print("Warning, no dipole renormalization done because units were in %s" % (self.units)) - - # function to write to a file! - # f_dpl must be fully specified - def write(self, f_dpl): - with open(f_dpl, 'w') as f: - for t, x_agg, x_L2, x_L5 in zip(self.t, self.dpl['agg'], self.dpl['L2'], self.dpl['L5']): - f.write("%03.3f\t" % t) - f.write("%9.8f\t" % x_agg) - f.write("%9.8f\t" % x_L2) - f.write("%9.8f\n" % x_L5) - -# throwaway save method for now - see note -def dpl_convert_and_save(ddata, i=0, j=0): - """ trial is currently undefined - function is broken for N_trials > 1 - """ - # take the ith sim, jth trial, do some stuff to it, resave it - # only uses first expmt_group - expmt_group = ddata.expmt_groups[0] - - # need n_trials - p_exp = paramrw.ExpParams(ddata.fparam) - if not p_exp.N_trials: - N_trials = 1 - else: - N_trials = p_exp.N_trials - - # absolute number - n = i*N_trials + j - - # grab the correct files - f_dpl = ddata.file_match(expmt_group, 'rawdpl')[n] - f_param = ddata.file_match(expmt_group, 'param')[n] - - # print ddata.sim_prefix, ddata.dsim - f_name_short = '%s-%03d-T%02d-dpltest.txt' % (ddata.sim_prefix, i, j) - f_name = os.path.join(ddata.dsim, expmt_group, f_name_short) - print(f_name) - - dpl = Dipole(f_dpl) - dpl.baseline_renormalize(f_param) - print("baseline renormalized") - - dpl.convert_fAm_to_nAm() - print("converted to nAm") - - dpl.write(f_name) - -# ddata is a fio.SimulationPaths() object -def calc_aggregate_dipole(ddata): - for expmt_group in ddata.expmt_groups: - # create the filename - dexp = ddata.dexpmt_dict[expmt_group] - fname_short = '%s-%s-dpl' % (ddata.sim_prefix, expmt_group) - fname_data = os.path.join(dexp, fname_short + '.txt') - - # grab the list of raw data dipoles and assoc params in this expmt - dpl_list = ddata.file_match(expmt_group, 'rawdpl') - param_list = ddata.file_match(expmt_group, 'param') - - for f_dpl, f_param in zip(dpl_list, param_list): - dpl = Dipole(f_dpl) - # dpl.baseline_renormalize(f_param) - - # initialize and use x_dpl - if f_dpl is dpl_list[0]: - # assume time vec stays the same throughout - t_vec = dpl.t - x_dpl = dpl.dpl['agg'] - - else: - # guaranteed to exist after dpl_list[0] - x_dpl += dpl.dpl['agg'] - - # poor man's mean - x_dpl /= len(dpl_list) - - # write this data to the file - with open(fname_data, 'w') as f: - for t, x in zip(t_vec, x_dpl): - f.write("%03.3f\t%5.4f\n" % (t, x)) - -# calculate stimulus evoked dipole -def calc_avgdpl_stimevoked(ddata): - for expmt_group in ddata.expmt_groups: - # create the filename - dexp = ddata.dexpmt_dict[expmt_group] - fname_short = '%s-%s-dpl' % (ddata.sim_prefix, expmt_group) - fname_data = os.path.join(dexp, fname_short + '.txt') - - # grab the list of raw data dipoles and assoc params in this expmt - fdpl_list = ddata.file_match(expmt_group, 'rawdpl') - param_list = ddata.file_match(expmt_group, 'param') - spk_list = ddata.file_match(expmt_group, 'rawspk') - - # actual list of Dipole() objects - dpl_list = [Dipole(fdpl) for fdpl in fdpl_list] - t_truncated = [] - - # iterate through the lists, grab the spike time, phase align the signals, - # cut them to length, and then mean the dipoles - for dpl, f_spk, f_param in zip(dpl_list, spk_list, param_list): - _, p = paramrw.read(f_param) - - # grab the corresponding relevant starting spike time - s = spikefn.spikes_from_file(f_param, f_spk) - s = spikefn.alpha_feed_verify(s, p) - s = spikefn.add_delay_times(s, p) - - # t_evoked is the same for all of the cells in these simulations - t_evoked = s['evprox0'].spike_list[0][0] - - # attempt to give a 50 ms buffer - if t_evoked > 50.: - t0 = t_evoked - 50. - else: - t0 = t_evoked - - # truncate the dipole related vectors - dpl.t = dpl.t[dpl.t > t0] - dpl.dpl['agg'] = dpl.dpl['agg'][dpl.t > t0] - t_truncated.append(dpl.t[0]) - - # find the t0_max value to compare on other dipoles - t_truncated -= np.max(t_truncated) - - for dpl, t_adj in zip(dpl_list, t_truncated): - # negative numbers mean that this vector needs to be shortened by that many ms - T_new = dpl.t[-1] + t_adj - dpl.dpl['agg'] = dpl.dpl['agg'][dpl.t < T_new] - dpl.t = dpl.t[dpl.t < T_new] - - if dpl is dpl_list[0]: - dpl_total = dpl.dpl['agg'] - - else: - dpl_total += dpl.dpl['agg'] - - dpl_mean = dpl_total / len(dpl_list) - t_dpl = dpl_list[0].t - - # write this data to the file - with open(fname_data, 'w') as f: - for t, x in zip(t_dpl, dpl_mean): - f.write("%03.3f\t%5.4f\n" % (t, x)) - -# Creates a template of dpl activity by averaging dpl data over specified time intervals -# Assumes t_intervals are all the same length -def create_template(fname, dpl_list, param_list, t_interval_list): - # iterate over lists, load dpl data and average - for fdpl, fparam, t_int in zip(dpl_list, param_list, t_interval_list): - # load ts data - dpl = Dipole(fdpl) - dpl.baseline_renormalize(fparam) - # dpl.convert_fAm_to_nAm() - - # truncate data based on time ranges specified in dmax - t_cut, dpl_tcut = dpl.truncate_ext(t_int[0], t_int[1]) - - if fdpl is dpl_list[0]: - x_dpl_agg = dpl_tcut['agg'] - x_dpl_L2 = dpl_tcut['L2'] - x_dpl_L5 = dpl_tcut['L5'] - - else: - x_dpl_agg += dpl_tcut['agg'] - x_dpl_L2 += dpl_tcut['L2'] - x_dpl_L5 += dpl_tcut['L5'] - - # poor man's mean - x_dpl_agg /= len(dpl_list) - x_dpl_L2 /= len(dpl_list) - x_dpl_L5 /= len(dpl_list) - - # create a tvec that is symmetric about zero and of proper length - # assume time intervals are identical length for all data - t_range = t_interval_list[0][1] - t_interval_list[0][0] - t_start = - t_range / 2. - t_end = t_range / 2. - tvec = np.linspace(t_start, t_end, x_dpl_agg.shape[0]) - # tvec = np.linspace(0, t_range, x_dpl_agg.shape[0]) - - # save to file - with open(fname, 'w') as f: - for t, x_agg, x_L2, x_L5 in zip(tvec, x_dpl_agg, x_dpl_L2, x_dpl_L5): - f.write("%03.3f\t%5.4f\t%5.4f\t%5.4f\n" % (t, x_agg, x_L2, x_L5)) - -# one off function to plot linear regression -def plinear_regression(ffig_dpl, fdpl): - dpl = Dipole(fdpl) - layer = 'L5' - t0 = 750. - - # dipole for the given layer, truncated - # order matters here - x_dpl = dpl.dpl[layer][(dpl.t > t0)] - t = dpl.t[dpl.t > t0] - - # take the transpose (T) of a vector of the times and ones for each element - A = np.vstack([t, np.ones(len(t))]).T - - # find the slope and the y-int of the line fit with least squares method (min. of Euclidean 2-norm) - m, c = np.linalg.lstsq(A, x_dpl)[0] - print(m, c) - - # plot me - f = ac.FigStd() - f.ax0.plot(t, x_dpl) - f.ax0.hold(True) - f.ax0.plot(t, m*t + c, 'r') - - # save over the original - f.savepng(ffig_dpl) - f.close() - -# plot a dipole to an axis from corresponding dipole and param files -def pdipole_ax(a, f_dpl, f_param): - dpl = Dipole(f_dpl) - dpl.baseline_renormalize(f_param) - - a.plot(dpl.t, dpl.dpl['agg']) - - # any further xlim sets can be done by whoever wants to do them later - a.set_xlim((0., dpl.t[-1])) - - # at least make the ylim symmetrical about 0 - ylim = a.get_ylim() - abs_y_max = np.max(np.abs(ylim)) - ylim = (-abs_y_max, abs_y_max) - a.set_ylim(ylim) - - # return the actual time in form of xlim. ain't pretty but works - return a.get_xlim() - -# pdipole is for a single dipole file, should be for a -def pdipole(f_dpl, dfig, plot_dict, f_param=None, key_types={}): - """ single dipole file combination (incl. param file) - this should be done with an axis input too - two separate functions, a pdipole kernel function and a specific function for this simple plot - """ - # dpl is an obj of Dipole() class - dpl = Dipole(f_dpl) - - if f_param: - dpl.baseline_renormalize(f_param) - - dpl.convert_fAm_to_nAm() - - # split to find file prefix - file_prefix = f_dpl.split('/')[-1].split('.')[0] - - - # parse xlim from plot_dict - if plot_dict['xlim'] is None: - xmin = dpl.t[0] - xmax = dpl.t[-1] - - else: - xmin, xmax = plot_dict['xlim'] - - if xmin < 0.: - xmin = 0. - - if xmax < 0.: - xmax = self.f[-1] - - # # get xmin and xmax from the plot_dict - # if plot_dict['xmin'] is None: - # xmin = 0. - # else: - # xmin = plot_dict['xmin'] - - # if plot_dict['xmax'] is None: - # xmax = p_dict['tstop'] - # else: - # xmax = plot_dict['xmax'] - - # truncate them using logical indexing - t_range = dpl.t[(dpl.t >= xmin) & (dpl.t <= xmax)] - dpl_range = dpl.dpl['agg'][(dpl.t >= xmin) & (dpl.t <= xmax)] - - f = ac.FigStd() - f.ax0.plot(t_range, dpl_range) - - # sorry about the parity between vars here and above with xmin/xmax - if plot_dict['ylim'] is None: - # if plot_dict['ymin'] is None or plot_dict['ymax'] is None: - pass - else: - f.ax0.set_ylim(plot_dict['ylim'][0], plot_dict['ylim'][1]) - # f.ax0.set_ylim(plot_dict['ymin'], plot_dict['ymax']) - - # Title creation - if f_param and key_types: - # grabbing the p_dict from the f_param - _, p_dict = paramrw.read(f_param) - - # useful for title strings - title_str = ac.create_title(p_dict, key_types) - f.f.suptitle(title_str) - - # create new fig name - fig_name = os.path.join(dfig, file_prefix+'.png') - - # savefig - plt.savefig(fig_name, dpi=300) - f.close() - -# plot vertical lines corresponding to the evoked input times -def pdipole_evoked(dfig, f_dpl, f_spk, f_param, ylim=[]): - """ for each individual simulation/trial - """ - gid_dict, p_dict = paramrw.read(f_param) - - # get the spike dict from the files - s_dict = spikefn.spikes_from_file(f_param, f_spk) - s = s_dict.keys() - s.sort() - - # create an empty dict 'spk_unique' - spk_unique = dict.fromkeys([key for key in s_dict.keys() if key.startswith(('evprox', 'evdist'))]) - - for key in spk_unique: - spk_unique[key] = s_dict[key].unique_all(0) - - # draw vertical lines for each item in this - - # x_dipole is dipole data - # x_dipole = np.loadtxt(open(f_dpl, 'r')) - dpl = Dipole(f_dpl) - - # split to find file prefix - file_prefix = f_dpl.split('/')[-1].split('.')[0] - - # # set xmin value - # xmin = xlim[0] / p_dict['dt'] - - # # set xmax value - # if xlim[1] == 'tstop': - # xmax = p_dict['tstop'] / p_dict['dt'] - # else: - # xmax = xlim[1] / p_dict['dt'] - - # these are the vectors for now, but this is going to change - t_vec = dpl.t - dp_total = dpl.dpl['agg'] - - f = ac.FigStd() - - # hold on - f.ax0.hold(True) - - f.ax0.plot(t_vec, dp_total) - - lines_spk = dict.fromkeys(spk_unique) - - print(spk_unique) - - # plot the lines - for key in spk_unique: - print(key, spk_unique[key]) - x_val = spk_unique[key][0] - lines_spk[key] = plt.axvline(x=x_val, linewidth=0.5, color='r') - - # title_txt = [key + ': {:.2e}' % p_dict[key] for key in key_types['dynamic_keys']] - title_txt = 'test' - f.ax0.set_title(title_txt) - - if ylim: - f.ax0.set_ylim(ylim) - - fig_name = os.path.join(dfig, file_prefix+'.png') - - plt.savefig(fig_name, dpi=300) - f.close() - -# Plots dipole with histogram of alpha feed inputs - slightly deprecated, see note -def pdipole_with_hist(f_dpl, f_spk, dfig, f_param, key_types, plot_dict): - """ this function has not been converted to use the Dipole() class yet - """ - # dpl is an obj of Dipole() class - dpl = Dipole(f_dpl) - dpl.baseline_renormalize(f_param) - dpl.convert_fAm_to_nAm() - # split to find file prefix - file_prefix = f_dpl.split('/')[-1].split('.')[0] - # grabbing the p_dict from the f_param - _, p_dict = paramrw.read(f_param) - # get xmin and xmax from the plot_dict - if plot_dict['xmin'] is None: - xmin = 0. - else: - xmin = plot_dict['xmin'] - if plot_dict['xmax'] is None: - xmax = p_dict['tstop'] - else: - xmax = plot_dict['xmax'] - # truncate tvec and dpl data using logical indexing - t_range = dpl.t[(dpl.t >= xmin) & (dpl.t <= xmax)] - dpl_range = dpl.dpl['agg'][(dpl.t >= xmin) & (dpl.t <= xmax)] - # Plotting - f = ac.FigDplWithHist() - # dipole - f.ax['dipole'].plot(t_range, dpl_range) - # set new xlim based on dipole plot - xlim_new = f.ax['dipole'].get_xlim() - # Get extinput data and account for delays - try: - extinputs = spikefn.ExtInputs(f_spk, f_param) - except ValueError: - print("Error: could not load spike timings from %s" % f_spk) - f.close() - return - - extinputs.add_delay_times() - # set number of bins (150 bins per 1000ms) - bins = ceil(150. * (xlim_new[1] - xlim_new[0]) / 1000.) # bins needs to be an int - # plot histograms - hist = {} - hist['feed_prox'] = extinputs.plot_hist(f.ax['feed_prox'], 'prox', dpl.t, bins, xlim_new, color='red') - hist['feed_dist'] = extinputs.plot_hist(f.ax['feed_dist'], 'dist', dpl.t, bins, xlim_new, color='green') - # Invert dist histogram - f.ax['feed_dist'].invert_yaxis() - # for now, set the xlim for the other one, force it! - f.ax['dipole'].set_xlim(xlim_new) - f.ax['feed_prox'].set_xlim(xlim_new) - f.ax['feed_dist'].set_xlim(xlim_new) - # set hist axis properties - f.set_hist_props(hist) - # Add legend to histogram - for key in f.ax.keys(): - if 'feed' in key: - f.ax[key].legend() - # force xlim on histograms - f.ax['feed_prox'].set_xlim((xmin, xmax)) - f.ax['feed_dist'].set_xlim((xmin, xmax)) - title_str = ac.create_title(p_dict, key_types) - f.f.suptitle(title_str) - fig_name = os.path.join(dfig, file_prefix+'.png') - plt.savefig(fig_name) - f.close() - -# For a given ddata (SimulationPaths object), find the mean dipole -def pdipole_exp(ddata, ylim=[]): - """ over ALL trials in ALL conditions in EACH experiment - """ - # sim_prefix - fprefix = ddata.sim_prefix - - # create the figure name - fname_exp = '%s_dpl' % (fprefix) - fname_exp_fig = os.path.join(ddata.dsim, fname_exp + '.png') - - # create one figure comparing across all - N_expmt_groups = len(ddata.expmt_groups) - f_exp = ac.FigDipoleExp(ddata.expmt_groups) - - # empty list for the aggregate dipole data - dpl_exp = [] - - # go through each expmt - for expmt_group in ddata.expmt_groups: - # create the filename - dexp = ddata.dexpmt_dict[expmt_group] - fname_short = '%s-%s-dpl' % (fprefix, expmt_group) - fname_data = os.path.join(dexp, fname_short + '.txt') - fname_fig = os.path.join(ddata.dfig[expmt_group]['figdpl'], fname_short + '.png') - - # grab the list of raw data dipoles and assoc params in this expmt - dpl_list = ddata.file_match(expmt_group, 'rawdpl') - param_list = ddata.file_match(expmt_group, 'param') - - for f_dpl, f_param in zip(dpl_list, param_list): - dpl = Dipole(f_dpl) - dpl.baseline_renormalize(f_param) - # x_tmp = np.loadtxt(open(file, 'r')) - - # initialize and use x_dpl - if f_dpl is dpl_list[0]: - - # assume time vec stays the same throughout - t_vec = dpl.t - x_dpl = dpl.dpl['agg'] - - else: - # guaranteed to exist after dpl_list[0] - x_dpl += dpl.dpl['agg'] - - # poor man's mean - x_dpl /= len(dpl_list) - - # save this in a list to do comparison figure - # order is same as ddata.expmt_groups - dpl_exp.append(x_dpl) - - # write this data to the file - with open(fname_data, 'w') as f: - for t, x in zip(t_vec, x_dpl): - f.write("%03.3f\t%5.4f\n" % (t, x)) - - # create the plot I guess? - f = ac.FigStd() - f.ax0.plot(t_vec, x_dpl) - - if len(ylim): - f.ax0.set_ylim(ylim) - - f.savepng(fname_fig) - f.close() - - # plot the aggregate data using methods defined in FigDipoleExp() - f_exp.plot(t_vec, dpl_exp) - - # attempt at setting titles - for ax, expmt_group in zip(f_exp.ax, ddata.expmt_groups): - ax.set_title(expmt_group) - - f_exp.savepng(fname_exp_fig) - f_exp.close() - -# For a given ddata (SimulationPaths object), find the mean dipole -def pdipole_exp2(ddata): - """ over ALL trials in ALL conditions in EACH experiment - appears to be an iteration on pdipole_exp() - """ - # grab the original dipole from a specific dir - dproj = fio.return_data_dir() - - runtype = 'somethingotherthandebug' - # runtype = 'debug' - - # really shoddy testing code! sorry! - if runtype == 'debug': - ddate = '2013-04-08' - dsim = 'mubaseline-15-000' - i_ctrl = 0 - else: - ddate = raw_input('Short date directory? ') - dsim = raw_input('Sim name? ') - i_ctrl = ast.literal_eval(raw_input('Sim number: ')) - dcheck = os.path.join(dproj, ddate, dsim) - - # create a blank ddata structure - ddata_ctrl = fio.SimulationPaths() - dsim = ddata_ctrl.read_sim(dproj, dcheck) - - # find the mu_low and mu_high in the expmtgroup names - # this means the group names must be well formed - for expmt_group in ddata_ctrl.expmt_groups: - if 'mu_low' in expmt_group: - mu_low_group = expmt_group - elif 'mu_high' in expmt_group: - mu_high_group = expmt_group - - # choose the first [0] from the list of the file matches for mu_low - fdpl_mu_low = ddata_ctrl.file_match(mu_low_group, 'rawdpl')[i_ctrl] - fparam_mu_low = ddata_ctrl.file_match(mu_low_group, 'param')[i_ctrl] - fspk_mu_low = ddata_ctrl.file_match(mu_low_group, 'rawspk')[i_ctrl] - fspec_mu_low = ddata_ctrl.file_match(mu_low_group, 'rawspec')[i_ctrl] - - # choose the first [0] from the list of the file matches for mu_high - fdpl_mu_high = ddata_ctrl.file_match(mu_high_group, 'rawdpl')[i_ctrl] - fparam_mu_high = ddata_ctrl.file_match(mu_high_group, 'param')[i_ctrl] - # fspk_mu_high = ddata_ctrl.file_match(mu_high_group, 'rawspk')[i_ctrl] - - # grab the relevant dipole and renormalize it for mu_low - dpl_mu_low = Dipole(fdpl_mu_low) - dpl_mu_low.baseline_renormalize(fparam_mu_low) - - # grab the relevant dipole and renormalize it for mu_high - dpl_mu_high = Dipole(fdpl_mu_high) - dpl_mu_high.baseline_renormalize(fparam_mu_high) - - # input feed information - s = spikefn.spikes_from_file(fparam_mu_low, fspk_mu_low) - _, p_ctrl = paramrw.read(fparam_mu_low) - s = spikefn.alpha_feed_verify(s, p_ctrl) - s = spikefn.add_delay_times(s, p_ctrl) - - # hard coded bin count for now - tstop = paramrw.find_param(fparam_mu_low, 'tstop') - bins = spikefn.bin_count(150., tstop) - - # sim_prefix - fprefix = ddata.sim_prefix - - # create the figure name - fname_exp = '%s_dpl' % (fprefix) - fname_exp_fig = os.path.join(ddata.dsim, fname_exp + '.png') - - # create one figure comparing across all - N_expmt_groups = len(ddata.expmt_groups) - ax_handles = [ - 'spec', - 'input', - 'dpl_mu_low', - 'dpl_mu_high', - ] - f_exp = ac.FigDipoleExp(ax_handles) - - # plot the ctrl dipoles - f_exp.ax['dpl_mu_low'].plot(dpl_mu_low.t, dpl_mu_low.dpl['agg'], color='k') - f_exp.ax['dpl_mu_low'].hold(True) - f_exp.ax['dpl_mu_high'].plot(dpl_mu_high.t, dpl_mu_high.dpl['agg'], color='k') - f_exp.ax['dpl_mu_high'].hold(True) - - # function creates an f_exp.ax_twinx list and returns the index of the new feed - ax_twin_name = f_exp.create_axis_twinx('input') - if not ax_twin_name: - print("You've got bigger problems, I'm afraid") - - # input hist information: predicated on the fact that the input histograms - # should be identical for *all* of the inputs represented in this figure - spikefn.pinput_hist(f_exp.ax['input'], f_exp.ax_twinx['input'], s['alpha_feed_prox'][0].spike_list, s['alpha_feed_dist'][0].spike_list, n_bins) - - # grab the max counts for both hists - # the [0] item of hist are the counts - max_hist = np.max([np.max(hist[key][0]) for key in hist.keys()]) - ymax = 2 * max_hist - - # plot the spec here - pc = specfn.pspec_ax(f_exp.ax['spec'], fspec_mu_low) - print(f_exp.ax[0].get_xlim()) - - # deal with the axes here - f_exp.ax_twinx['input'].set_ylim((ymax, 0)) - f_exp.ax['input'].set_ylim((0, ymax)) - - f_exp.ax['input'].set_xlim((50., tstop)) - f_exp.ax_twinx['input'].set_xlim((50., tstop)) - - # empty list for the aggregate dipole data - dpl_exp = [] - - # go through each expmt - # calculation is extremely redundant - for expmt_group in ddata.expmt_groups: - # a little sloppy, just find the param file - # this param file was for the baseline renormalization and - # assumes it's the same in all for this expmt_group - # also for getting the gid_dict, also assumed to be the same - fparam = ddata.file_match(expmt_group, 'param')[0] - - # general check to see if the aggregate dipole data exists - if 'mu_low' in expmt_group or 'mu_high' in expmt_group: - # check to see if these files exist - flist = ddata.find_aggregate_file(expmt_group, 'dpl') - - # if no file exists, then find one - if not len(flist): - calc_aggregate_dipole(ddata) - flist = ddata.find_aggregate_file(expmt_group, 'dpl') - - # testing the first file - list_spk = ddata.file_match(expmt_group, 'rawspk') - list_s_dict = [spikefn.spikes_from_file(fparam, fspk) for fspk in list_spk] - list_evoked = [s_dict['evprox0'].spike_list[0][0] for s_dict in list_s_dict] - lines_spk = [f_exp.ax[2].axvline(x=x_val, linewidth=0.5, color='r') for x_val in list_evoked] - lines_spk = [f_exp.ax[3].axvline(x=x_val, linewidth=0.5, color='r') for x_val in list_evoked] - - # handle mu_low and mu_high separately - if 'mu_low' in expmt_group: - dpl_mu_low_ev = Dipole(flist[0]) - dpl_mu_low_ev.baseline_renormalize(fparam) - f_exp.ax['dpl_mu_low'].plot(dpl_mu_low_ev.t, dpl_mu_low_ev.dpl['agg']) - - elif 'mu_high' in expmt_group: - dpl_mu_high_ev = Dipole(flist[0]) - dpl_mu_high_ev.baseline_renormalize(fparam) - f_exp.ax['dpl_mu_high'].plot(dpl_mu_high_ev.t, dpl_mu_high_ev.dpl['agg']) - - f_exp.ax['dpl_mu_low'].set_xlim(50., tstop) - f_exp.ax['dpl_mu_high'].set_xlim(50., tstop) - - f_exp.savepng(fname_exp_fig) - f_exp.close() - -# For a given ddata (SimulationPaths object), find the mean dipole -def pdipole_evoked_aligned(ddata): - """ over ALL trials in ALL conditions in EACH experiment - appears to be iteration over pdipole_exp2() - """ - # grab the original dipole from a specific dir - dproj = fio.return_data_dir() - - runtype = 'somethingotherthandebug' - # runtype = 'debug' - - if runtype == 'debug': - ddate = '2013-04-08' - dsim = 'mubaseline-04-000' - i_ctrl = 0 - else: - ddate = raw_input('Short date directory? ') - dsim = raw_input('Sim name? ') - i_ctrl = ast.literal_eval(raw_input('Sim number: ')) - dcheck = os.path.join(dproj, ddate, dsim) - - # create a blank ddata structure - ddata_ctrl = fio.SimulationPaths() - dsim = ddata_ctrl.read_sim(dproj, dcheck) - - # find the mu_low and mu_high in the expmtgroup names - # this means the group names must be well formed - for expmt_group in ddata_ctrl.expmt_groups: - if 'mu_low' in expmt_group: - mu_low_group = expmt_group - elif 'mu_high' in expmt_group: - mu_high_group = expmt_group - - # choose the first [0] from the list of the file matches for mu_low - fdpl_mu_low = ddata_ctrl.file_match(mu_low_group, 'rawdpl')[i_ctrl] - fparam_mu_low = ddata_ctrl.file_match(mu_low_group, 'param')[i_ctrl] - fspk_mu_low = ddata_ctrl.file_match(mu_low_group, 'rawspk')[i_ctrl] - fspec_mu_low = ddata_ctrl.file_match(mu_low_group, 'rawspec')[i_ctrl] - - # choose the first [0] from the list of the file matches for mu_high - fdpl_mu_high = ddata_ctrl.file_match(mu_high_group, 'rawdpl')[i_ctrl] - fparam_mu_high = ddata_ctrl.file_match(mu_high_group, 'param')[i_ctrl] - - # grab the relevant dipole and renormalize it for mu_low - dpl_mu_low = Dipole(fdpl_mu_low) - dpl_mu_low.baseline_renormalize(fparam_mu_low) - - # grab the relevant dipole and renormalize it for mu_high - dpl_mu_high = Dipole(fdpl_mu_high) - dpl_mu_high.baseline_renormalize(fparam_mu_high) - - # input feed information - s = spikefn.spikes_from_file(fparam_mu_low, fspk_mu_low) - _, p_ctrl = paramrw.read(fparam_mu_low) - s = spikefn.alpha_feed_verify(s, p_ctrl) - s = spikefn.add_delay_times(s, p_ctrl) - - # find tstop, assume same over all. grab the first param file, get the tstop - tstop = paramrw.find_param(fparam_mu_low, 'tstop') - - # hard coded bin count for now - n_bins = spikefn.bin_count(150., tstop) - - # sim_prefix - fprefix = ddata.sim_prefix - - # create the figure name - fname_exp = '%s_dpl_align' % (fprefix) - fname_exp_fig = os.path.join(ddata.dsim, fname_exp + '.png') - - # create one figure comparing across all - N_expmt_groups = len(ddata.expmt_groups) - ax_handles = [ - 'spec', - 'input', - 'dpl_mu', - 'spk', - ] - f_exp = ac.FigDipoleExp(ax_handles) - - # plot the ctrl dipoles - f_exp.ax['dpl_mu'].plot(dpl_mu_low.t, dpl_mu_low.dpl, color='k') - f_exp.ax['dpl_mu'].hold(True) - f_exp.ax['dpl_mu'].plot(dpl_mu_high.t, dpl_mu_high.dpl) - - # function creates an f_exp.ax_twinx list and returns the index of the new feed - f_exp.create_axis_twinx('input') - - # input hist information: predicated on the fact that the input histograms - # should be identical for *all* of the inputs represented in this figure - # places 2 histograms on two axes (meant to be one axis flipped) - hists = spikefn.pinput_hist(f_exp.ax['input'], f_exp.ax_twinx['input'], s['alpha_feed_prox'].spike_list, s['alpha_feed_dist'].spike_list, n_bins) - - # grab the max counts for both hists - # the [0] item of hist are the counts - max_hist = np.max([np.max(hists[key][0]) for key in hists.keys()]) - ymax = 2 * max_hist - - # plot the spec here - pc = specfn.pspec_ax(f_exp.ax['spec'], fspec_mu_low) - - # deal with the axes here - f_exp.ax['input'].set_ylim((0, ymax)) - f_exp.ax_twinx['input'].set_ylim((ymax, 0)) - # f_exp.ax[1].set_ylim((0, ymax)) - - # f_exp.ax[1].set_xlim((50., tstop)) - - # turn hold on - f_exp.ax[dpl_mu].hold(True) - - # empty list for the aggregate dipole data - dpl_exp = [] - - # go through each expmt - # calculation is extremely redundant - for expmt_group in ddata.expmt_groups: - # a little sloppy, just find the param file - # this param file was for the baseline renormalization and - # assumes it's the same in all for this expmt_group - # also for getting the gid_dict, also assumed to be the same - fparam = ddata.file_match(expmt_group, 'param')[0] - - # general check to see if the aggregate dipole data exists - if 'mu_low' in expmt_group or 'mu_high' in expmt_group: - # check to see if these files exist - flist = ddata.find_aggregate_file(expmt_group, 'dpl') - - # if no file exists, then find one - if not len(flist): - calc_aggregate_dipole(ddata) - flist = ddata.find_aggregate_file(expmt_group, 'dpl') - - # testing the first file - list_spk = ddata.file_match(expmt_group, 'rawspk') - list_s_dict = [spikefn.spikes_from_file(fparam, fspk) for fspk in list_spk] - list_evoked = [s_dict['evprox0'].spike_list[0][0] for s_dict in list_s_dict] - lines_spk = [f_exp.ax['dpl_mu'].axvline(x=x_val, linewidth=0.5, color='r') for x_val in list_evoked] - lines_spk = [f_exp.ax['spk'].axvline(x=x_val, linewidth=0.5, color='r') for x_val in list_evoked] - - # handle mu_low and mu_high separately - if 'mu_low' in expmt_group: - dpl_mu_low_ev = Dipole(flist[0]) - dpl_mu_low_ev.baseline_renormalize(fparam) - f_exp.ax['spk'].plot(dpl_mu_low_ev.t, dpl_mu_low_ev.dpl, color='k') - - # get xlim stuff - t0 = dpl_mu_low_ev.t[0] - T = dpl_mu_low_ev.t[-1] - - elif 'mu_high' in expmt_group: - dpl_mu_high_ev = Dipole(flist[0]) - dpl_mu_high_ev.baseline_renormalize(fparam) - f_exp.ax['spk'].plot(dpl_mu_high_ev.t, dpl_mu_high_ev.dpl, color='b') - - f_exp.ax['input'].set_xlim(50., tstop) - - for ax_name in f_exp.ax_handles[2:]: - ax.set_xlim((t0, T)) - - f_exp.savepng(fname_exp_fig) - f_exp.close() - -# create a grid of all dipoles in this dir -def pdipole_grid(ddata): - # iterate through expmt_groups - for expmt_group in ddata.expmt_groups: - fname_short = "%s-%s-dpl.png" % (ddata.sim_prefix, expmt_group) - fname = os.path.join(ddata.dsim, expmt_group, fname_short) - - # simple usage, just checks how many dipole files (total in an expmt) - # and then plots dumbly to a grid - dpl_list = ddata.file_match(expmt_group, 'rawdpl') - param_list = ddata.file_match(expmt_group, 'param') - - # assume tstop is the same everywhere - tstop = paramrw.find_param(param_list[0], 'tstop') - - # length of the dpl list - N_dpl = len(dpl_list) - - # make a 5-col figure - N_cols = 5 - - # force int arithmetic - # this is the BASE number of rows, one might be added! - N_rows = int(N_dpl) // int(N_cols) - - # if the mod is not 0, add a row - if (N_dpl % N_cols): - N_rows += 1 - - # print(N_dpl, N_cols, N_rows) - f = ac.FigGrid(N_rows, N_cols, tstop) - - l = [] - r = 0 - for ax_list in f.ax: - l.extend([(r,c) for c in range(len(ax_list))]) - r += 1 - - # automatically truncates the loc list to the size of dpl_list - for loc, fdpl, fparam in zip(l, dpl_list, param_list): - r = loc[0] - c = loc[1] - pdipole_ax(f.ax[r][c], fdpl, fparam) - - f.savepng(fname) - f.close() - -def plot_specmax_interval(fname, dpl_list, param_list, specmax_list): - N_trials = len([d for d in specmax_list if d is not None]) - - # instantiate figure - f = ac.FigInterval(N_trials+1) - - # set spacing between plots - spacers = np.arange(0.5e-4, N_trials*1e-4, 1e-4) - - # invert order of spacers so first trial is at top of plot - spacers = spacers[::-1] - - # iterate over various lists and plot to axis - i = 0 - - for fdpl, fparam, dmax in zip(dpl_list, param_list, specmax_list): - # for fdpl, dmax, space in zip(dpl_list, specmax_list, spacers): - if dmax is not None: - # load ts data - dpl = Dipole(fdpl) - dpl.baseline_renormalize(fparam) - dpl.convert_fAm_to_nAm() - - # truncate data based on time ranges specified in dmax - t_cut, dpl_tcut = dpl.truncate_ext(dmax['t_int'][0], dmax['t_int'][1]) - - # create a tvec that is symmetric about zero and of proper length - t_range = dmax['t_int'][1] - dmax['t_int'][0] - t_start = 0 - t_range / 2. - t_end = 0 + t_range / 2. - tvec = np.linspace(t_start, t_end, dpl_tcut['agg'].shape[0]) - - # plot to proper height - f.ax['ts'].plot(tvec, dpl_tcut['agg']+spacers[i]) - - # add text with pertinent information - x_offset = f.ax['ts'].get_xlim()[1] + 25 - f.ax['ts'].text(x_offset, spacers[i], 'freq: %s Hz\ntime: %s ms\n%s' %(dmax['f_at_max'],dmax['t_at_max'], dmax['fname']), fontsize=12, verticalalignment='center') - - i += 1 - - # force xlim for now - # f.ax['ts'].set_xlim(-100, 100) - - # save fig - f.savepng(fname+'.png') - - # close fig - f.close() diff --git a/example_analysis.py b/example_analysis.py deleted file mode 100644 index b1d6b2ce4..000000000 --- a/example_analysis.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python -# example_analysis.py - Example for an analysis workflow -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: changed it.izip()) -# last major: (RL: added dipole loading) - -import numpy as np -import fileio as fio -import paramrw -import dipolefn -import PT_example - -# example simulation -def example_analysis_for_simulation(): - # from these two directories - droot = fio.return_data_dir() - dsim = os.path.join(droot, '2016-02-03/beta-sweep-000') - - # create the SimulationPaths() object ddata and read the simulation - ddata = fio.SimulationPaths() - ddata.read_sim(droot, dsim) - - # print dir(ddata) - # print type(np.zeros(5)) - - # print ddata.expmt_groups - # print ddata.fparam - # for key, val in ddata.dfig['testing'].items(): - # print key, val - # print dir({}) - - # p_exp = paramrw.ExpParams(ddata.fparam) - # print p_exp.p_all['dt'] - # # print p_exp.p_all - - # iterate through experimental groups and do the analysis on individual files, etc. - for expmt_group in ddata.expmt_groups: - print "experiment group is: %s" % (expmt_group) - # print ddata.dfig[expmt_group] - flist_param = ddata.file_match(expmt_group, 'param') - flist_dpl = ddata.file_match(expmt_group, 'rawdpl') - # flist_spk = ddata.file_match(expmt_group, 'rawspk') - # fio.prettyprint(flist_spk) - - # iterate through files in the lists - for fparam, fdpl in zip(flist_param, flist_dpl): - # print fparam, fdpl - gid_dict, p_tr = paramrw.read(fparam) - - # for key, val in p_tr.items(): - # print key, val - # fio.prettyprint(p_tr.keys()) - - # create and load dipole data structure - d = dipolefn.Dipole(fdpl) - - # more or less analysis goes here. - # generate a filename for a dipole plot - fname_png = ddata.return_filename_example('figdpl', expmt_group, p_tr['Sim_No'], tr=p_tr['Trial']) - # print p_tr['Trial'], p_tr['Sim_No'], fname_png - - # example figure for this pair of files - fig = PT_example.FigExample() - - # plot dipole - fig.ax['dipole'].plot(d.t, d.dpl['agg']) - fig.ax['dipole'].plot(d.t, d.dpl['L2']) - fig.ax['dipole'].plot(d.t, d.dpl['L5']) - fig.savepng(fname_png) - fig.close() - -if __name__ == '__main__': - example_analysis_for_simulation() diff --git a/feed.py b/feed.py deleted file mode 100644 index 75324b960..000000000 --- a/feed.py +++ /dev/null @@ -1,224 +0,0 @@ -# feed.py - establishes FeedExt(), ParFeedAll() -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: updated for python3) -# last major: (SL: toward python3) - -import numpy as np -import itertools as it # this used? -from neuron import h - -class ParFeedAll (): - # p_ext has a different structure for the extinput - # usually, p_ext is a dict of cell types - def __init__ (self, ty, celltype, p_ext, gid): - #print("ParFeedAll __init__") - # VecStim setup - self.eventvec = h.Vector() - self.vs = h.VecStim() - # self.p_unique = p_unique[type] - self.p_ext = p_ext - self.celltype = celltype - self.ty = ty # feed type - self.gid = gid - self.set_prng() # sets seeds for random num generator - self.set_event_times() # sets event times into self.eventvec and plays into self.vs (VecStim) - - # inc random number generator seeds - def inc_prng (self, inc): - self.seed += inc - self.prng = np.random.RandomState(self.seed) - if hasattr(self,'seed2'): - self.seed2 += inc - self.prng2 = np.random.RandomState(self.seed2) - - def set_prng (self, seed = None): - if seed is None: # no seed specified then use p_ext to determine seed - # random generator for this instance - # qnd hack to make the seeds the same across all gids - # for just evoked - if self.ty.startswith(('evprox', 'evdist')): - if self.p_ext['sync_evinput']: - self.seed = self.p_ext['prng_seedcore'] - else: - self.seed = self.p_ext['prng_seedcore'] + self.gid - elif self.ty.startswith('extinput'): - self.seed = self.p_ext['prng_seedcore'] + self.gid # seed for events assuming a given start time - self.seed2 = self.p_ext['prng_seedcore'] # separate seed for start times - else: - self.seed = self.p_ext['prng_seedcore'] + self.gid - else: # if seed explicitly specified use it - self.seed = seed - if hasattr(self,'seed2'): self.seed2 = seed - self.prng = np.random.RandomState(self.seed) - if hasattr(self,'seed2'): self.prng2 = np.random.RandomState(self.seed2) - #print('ty,seed:',self.ty,self.seed) - - def set_event_times (self, inc_evinput = 0.0): - # print('self.p_ext:',self.p_ext) - # each of these methods creates self.eventvec for playback - if self.ty == 'extpois': - self.__create_extpois() - elif self.ty.startswith(('evprox', 'evdist')): - self.__create_evoked(inc_evinput) - elif self.ty == 'extgauss': - self.__create_extgauss() - elif self.ty == 'extinput': - self.__create_extinput() - # load eventvec into VecStim object - self.vs.play(self.eventvec) - - # based on cdf for exp wait time distribution from unif [0, 1) - # returns in ms based on lamtha in Hz - def __t_wait (self, lamtha): - return -1000. * np.log(1. - self.prng.rand()) / lamtha - - # new external pois designation - def __create_extpois (self): - #print("__create_extpois") - if self.p_ext[self.celltype][0] <= 0.0 and \ - self.p_ext[self.celltype][1] <= 0.0: return False # 0 ampa and 0 nmda weight - # check the t interval - t0 = self.p_ext['t_interval'][0] - T = self.p_ext['t_interval'][1] - lamtha = self.p_ext[self.celltype][3] # index 3 is frequency (lamtha) - # values MUST be sorted for VecStim()! - # start the initial value - if lamtha > 0.: - val_pois = np.array([]) - t_gen = t0 - lamtha * 2 # start before t0 to remove artifact of lower event rate at start of period - while t_gen < T: - t_gen += self.__t_wait(lamtha) # move forward by the wait time (so as to not clobber base off of t_gen) - if t_gen >= t0 and t_gen < T: # make sure event time is within the specified interval - # vals are guaranteed to be monotonically increasing, no need to sort - val_pois = np.append(val_pois, t_gen) - else: - val_pois = np.array([]) - # checks the distribution stats - # if len(val_pois): - # xdiff = np.diff(val_pois/1000) - # print(lamtha, np.mean(xdiff), np.var(xdiff), 1/lamtha**2) - # Convert array into nrn vector - # if len(val_pois)>0: print('val_pois:',val_pois) - self.eventvec.from_python(val_pois) - return self.eventvec.size() > 0 - - # mu and sigma vals come from p - def __create_evoked (self, inc=0.0): - #print("__create_evoked", self.p_ext) - if self.celltype in self.p_ext.keys(): - # assign the params - mu = self.p_ext['t0'] + inc - sigma = self.p_ext[self.celltype][3] # index 3 is sigma_t_ (stdev) - numspikes = int(self.p_ext['numspikes']) - # print('mu:',mu,'sigma:',sigma,'inc:',inc) - # if a non-zero sigma is specified - if sigma: - val_evoked = self.prng.normal(mu, sigma, numspikes) - else: - # if sigma is specified at 0 - val_evoked = np.array([mu] * numspikes) - val_evoked = val_evoked[val_evoked > 0] - # vals must be sorted - val_evoked.sort() - # print('__create_evoked val_evoked:',val_evoked) - self.eventvec.from_python(val_evoked) - else: - # return an empty eventvec list - self.eventvec.from_python([]) - return self.eventvec.size() > 0 - - def __create_extgauss (self): - # print("__create_extgauss") - # assign the params - if self.p_ext[self.celltype][0] <= 0.0 and \ - self.p_ext[self.celltype][1] <= 0.0: return False # 0 ampa and 0 nmda weight - # print('gauss params:',self.p_ext[self.celltype]) - mu = self.p_ext[self.celltype][3] - sigma = self.p_ext[self.celltype][4] - # mu and sigma values come from p - # one single value from Gaussian dist. - # values MUST be sorted for VecStim()! - val_gauss = self.prng.normal(mu, sigma, 50) - # val_gauss = np.random.normal(mu, sigma, 50) - # remove non-zero values brute force-ly - val_gauss = val_gauss[val_gauss > 0] - # sort values - critical for nrn - val_gauss.sort() - # if len(val_gauss)>0: print('val_gauss:',val_gauss) - # Convert array into nrn vector - self.eventvec.from_python(val_gauss) - return self.eventvec.size() > 0 - - def __create_extinput (self): # creates the ongoing external inputs (rhythmic) - #print("__create_extinput") - # store f_input as self variable for later use if it exists in p - # t0 is always defined - t0 = self.p_ext['t0'] - # If t0 is -1, randomize start time of inputs - if t0 == -1: - t0 = self.prng.uniform(25., 125.) - #print(self.ty,'t0 was -1; now', t0,'seed:',self.seed) - elif self.p_ext['t0_stdev'] > 0.0: # randomize start time based on t0_stdev - t0 = self.prng2.normal(t0, self.p_ext['t0_stdev']) # start time uses different prng - #print(self.ty,'t0 is', t0, 'seed:',self.seed,'seed2:',self.seed2) - f_input = self.p_ext['f_input'] - stdev = self.p_ext['stdev'] - events_per_cycle = self.p_ext['events_per_cycle'] - distribution = self.p_ext['distribution'] - # events_per_cycle = 1 - if events_per_cycle > 2 or events_per_cycle <= 0: - print("events_per_cycle should be either 1 or 2, trying 2") - events_per_cycle = 2 - # If frequency is 0, create empty vector if input times - if not f_input: - t_input = [] - elif distribution == 'normal': - # array of mean stimulus times, starts at t0 - isi_array = np.arange(t0, self.p_ext['tstop'], 1000. / f_input) - # array of single stimulus times -- no doublets - if stdev: - t_array = self.prng.normal(np.repeat(isi_array, self.p_ext['repeats']), stdev) - else: - t_array = isi_array - if events_per_cycle == 2: # spikes/burst in GUI - # Two arrays store doublet times - t_array_low = t_array - 5 - t_array_high = t_array + 5 - # Array with ALL stimulus times for input - # np.append concatenates two np arrays - t_input = np.append(t_array_low, t_array_high) - elif events_per_cycle == 1: - t_input = t_array - # brute force remove zero times. Might result in fewer vals than desired - t_input = t_input[t_input > 0] - t_input.sort() - # Uniform Distribution - elif distribution == 'uniform': - n_inputs = self.p_ext['repeats'] * f_input * (self.p_ext['tstop'] - t0) / 1000. - t_array = self.prng.uniform(t0, self.p_ext['tstop'], n_inputs) - if events_per_cycle == 2: - # Two arrays store doublet times - t_input_low = t_array - 5 - t_input_high = t_array + 5 - # Array with ALL stimulus times for input - # np.append concatenates two np arrays - t_input = np.append(t_input_low, t_input_high) - elif events_per_cycle == 1: - t_input = t_array - # brute force remove non-zero times. Might result in fewer vals than desired - t_input = t_input[t_input > 0] - t_input.sort() - else: - print("Indicated distribution not recognized. Not making any alpha feeds.") - t_input = [] - # Convert array into nrn vector - self.eventvec.from_python(t_input) - return self.eventvec.size() > 0 - - # for parallel, maybe be that postsyn for this is just nil (None) - def connect_to_target (self, threshold): - #print("connect_to_target") - nc = h.NetCon(self.vs, None) # why is target always nil?? - nc.threshold = threshold - return nc diff --git a/fileio.py b/fileio.py deleted file mode 100644 index 1fd9b337e..000000000 --- a/fileio.py +++ /dev/null @@ -1,368 +0,0 @@ -# fileio.py - general file input/output functions -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: return_data_dir() instead of hardcoded everywhere, etc.) -# last rev: (SL: toward python3) - -import datetime, fnmatch, os, shutil, sys -import subprocess, multiprocessing -import numpy as np -import paramrw - -# creates data dirs and a dictionary of useful types -# self.dfig is a dictionary of experiments, which is each a dictionary of data type -# keys and the specific directories that contain them. -class SimulationPaths (): - def __init__ (self, dbase=None): - # hard coded data types - # fig extensions are not currently being used as well as they could be - # add new directories here to be automatically created for every simulation - self.__datatypes = {'rawspk': 'spk.txt', - 'rawdpl': 'rawdpl.txt', - 'normdpl': 'dpl.txt', # same output name - do not need both raw and normalized dipole - unless debugging - 'rawcurrent': 'i.txt', - 'rawspec': 'spec.npz', - 'rawspeccurrent': 'speci.npz', - 'avgdpl': 'dplavg.txt', - 'avgspec': 'specavg.npz', - 'figavgdpl': 'dplavg.png', - 'figavgspec': 'specavg.png', - 'figdpl': 'dpl.png', - 'figspec': 'spec.png', - 'figspk': 'spk.png', - 'param': 'param.txt', - } - # empty until a sim is created or read - self.fparam = None - self.sim_prefix = None - self.trial_prefix_str = None - self.expmt_groups = [] - self.dproj = None - self.ddate = None - self.dsim = None - self.dexpmt_dict = {} - self.dfig = {} - if dbase is None: - self.dbase = os.path.join(os.path.expanduser('~'),'hnn') - else: - self.dbase = dbase - - # reads sim information based on sim directory and param files - def read_sim (self, dproj, dsim): - self.dproj = dproj - self.dsim = dsim - # match the param from this sim - self.fparam = file_match(dsim, '.param')[0] - self.expmt_groups = paramrw.read_expmt_groups(self.fparam) - self.sim_prefix = paramrw.read_sim_prefix(self.fparam) - # this should somehow be supplied by the ExpParams() class, but doing it here - self.trial_prefix_str = self.sim_prefix + "-%03d-T%02d" - self.dexpmt_dict = self.__create_dexpmt(self.expmt_groups) - # create dfig - self.dfig = self.__read_dirs() - return self.dsim - - # only run for the creation of a new simulation - def create_new_sim (self, dproj, expmt_groups, sim_prefix='test'): - self.dproj = dproj - self.expmt_groups = expmt_groups - # prefix for these simulations in both filenames and directory in ddate - self.sim_prefix = sim_prefix - # create date and sim directories if necessary - self.ddate = self.__datedir() - self.dsim = self.__simdir() - self.dexpmt_dict = self.__create_dexpmt(self.expmt_groups) - # dfig is just a record of all the fig directories, per experiment - # will only be written to at time of creation, by create_dirs - # dfig is a terrible variable name, sorry! - self.dfig = self.__ddata_dict_template() - - # this is a hack - # checks root expmt_group directory for any files i've thrown there - def find_aggregate_file (self, expmt_group, datatype): - # file name is in format: '%s-%s-%s' % (sim_prefix, expmt_group, datatype-ish) - fname = '%s-%s-%s.txt' % (self.sim_prefix, expmt_group, datatype) - # get a list of txt files in the expmt_group - # local=1 forces the search to be local to this directory and not recursive - local = 1 - flist = file_match(self.dexpmt_dict[expmt_group], fname, local) - return flist - - # returns a filename for an example type of data - def return_filename_example (self, datatype, expmt_group, sim_no=None, tr=None, ext='png'): - fname_short = "%s-%s" % (self.sim_prefix, expmt_group) - if sim_no is not None: fname_short += "-%03i" % (sim_no) - if tr is not None: fname_short += "-T%03i" % (tr) - # add the extension - fname_short += ".%s" % (ext) - fname = os.path.join(self.dfig[expmt_group][datatype], fname_short) - return fname - - # creates a dict of dicts for each experiment and all the datatype directories - # this is the empty template that gets filled in later. - def __ddata_dict_template (self): - dfig = dict.fromkeys(self.expmt_groups) - for key in dfig: dfig[key] = dict.fromkeys(self.__datatypes) - return dfig - - # read directories for an already existing sim - def __read_dirs (self): - dfig = self.__ddata_dict_template() - for expmt_group, dexpmt in self.dexpmt_dict.items(): - for key in self.__datatypes.keys(): - ddatatype = os.path.join(dexpmt, key) - dfig[expmt_group][key] = ddatatype - return dfig - - # create the data directory for the sim - def create_datadir (self): - dout = self.__simdir() - print('making dout:',dout) - safemkdir(dout) - - # extern function to create directories - def create_dirs (self): - # create expmt directories - for expmt_group, dexpmt in self.dexpmt_dict.items(): - dir_create(dexpmt) - for key in self.__datatypes.keys(): - ddatatype = os.path.join(dexpmt, key) - self.dfig[expmt_group][key] = ddatatype - dir_create(ddatatype) - - # Returns date directory - # this is NOT safe for midnight - def __datedir (self): - self.str_date = datetime.datetime.now().strftime("%Y-%m-%d") - ddate = os.path.join(self.dproj, self.str_date) - return ddate - - # returns the directory for the sim - def __simdir (self): - return os.path.join(self.dbase,'data',self.sim_prefix) - # return os.path.join(os.path.expanduser('~'),'hnn','data',self.sim_prefix) - - # creates all the experimental directories based on dproj - def __create_dexpmt (self, expmt_groups): - d = dict.fromkeys(expmt_groups) - for expmt_group in d: d[expmt_group] = os.path.join(self.dsim, expmt_group) - return d - - # dictionary creation - # this is specific to a expmt_group - def create_dict (self, expmt_group): - fileinfo = dict.fromkeys(self.__datatypes) - for key in self.__datatypes.keys(): - # join directory name - dtype = os.path.join(self.dexpmt_dict(expmt_group), key) - fileinfo[key] = (self.__datatypes[key], dtype) - return fileinfo - - def return_specific_filename(self, expmt_group, datatype, n_sim, n_trial): - f_list = self.file_match(expmt_group, datatype) - trial_prefix = self.trial_prefix_str % (n_sim, n_trial) - # assume there is only one match (this should be true) - f_datatype = [f for f in f_list if trial_prefix in f][0] - return f_datatype - - # requires dict lookup - def create_filename (self, expmt_group, key): - d = self.__simdir() - # some kind of if key in self.fileinfo.keys() catch - file_name_raw = self.__datatypes[key] - return os.path.join(d,file_name_raw) - # grab the whole experimental directory - dexpmt = self.dexpmt_dict[expmt_group] - # create the full path name for the file - file_path_full = os.path.join(dexpmt, key, file_name_raw) - return file_path_full - - # Get the data files matching file_ext in this directory - # functionally the same as the previous function but with a local scope - def file_match (self, expmt_group, key): - # grab the relevant fext - fext = self.__datatypes[key] - file_list = [] - ddata = self.__simdir() # - # search the sim directory for all relevant files - if os.path.exists(ddata): - for root, dirnames, filenames in os.walk(ddata): - for fname in fnmatch.filter(filenames, '*'+fext): file_list.append(os.path.join(root, fname)) - # sort file list? untested - file_list.sort() - return file_list - - def exp_files_of_type (self, datatype): - # create dict of experiments - d = dict.fromkeys(self.expmt_groups) - # create file lists that match the dict keys for only files for this experiment - # this all would be nicer with a freaking folder - for key in d: d[key] = [file for file in self.filelists[datatype] if key in file.split("/")[-1]] - return d - -# Cleans input files -def clean_lines (file): - with open(file) as f_in: - lines = (line.rstrip() for line in f_in) - lines = [line for line in lines if line] - return lines - -# this make a little more sense in fileio -def prettyprint (iterable_items): - for item in iterable_items: print(item) - -# create gid dict from a file -def gid_dict_from_file (fparam): - l = ['L2_pyramidal', 'L5_pyramidal', 'L2_basket', 'L5_basket', 'extinput'] - d = dict.fromkeys(l) - plist = clean_lines(fparam) - for param in plist: print(param) - -# create file name for temporary spike file -# that every processor is aware of -def file_spike_tmp (dproj): - filename_spikes = 'spikes_tmp.spk' - file_spikes = os.path.join(dproj, filename_spikes) - return file_spikes - -# this is ugly, potentially. sorry, future -# i.e will change when the file name format changes -def strip_extprefix (filename): - f_raw = filename.split("/")[-1] - f = f_raw.split(".")[0].split("-")[:-1] - ext_prefix = f.pop(0) - for part in f: ext_prefix += "-%s" % part - return ext_prefix - -# Get the data files matching file_ext in this directory -# this function traverses ALL directories -# local=1 makes the search local and not recursive -def file_match (dsearch, file_ext, local=0): - file_list = [] - if not local: - if os.path.exists(dsearch): - for root, dirnames, filenames in os.walk(dsearch): - for fname in fnmatch.filter(filenames, '*'+file_ext): - file_list.append(os.path.join(root, fname)) - else: - file_list = [os.path.join(dsearch, file) for file in os.listdir(dsearch) if file.endswith(file_ext)] - # sort file list? untested - file_list.sort() - return file_list - -# Get minimum list of param dicts (i.e. excludes duplicates due to N_trials > 1) -def fparam_match_minimal (dsim, p_exp): - # Complete list of all param dicts used in simulation - fparam_list_complete = file_match(dsim, '-param.txt') - # List of indices from which to pull param dicts from fparam_list_complete - N_trials = p_exp.N_trials - if not N_trials: N_trials = 1 - indexes = np.arange(0, len(fparam_list_complete), N_trials) - # Pull unique param dicts from fparam_list_complete - fparam_list_minimal = [fparam_list_complete[ind] for ind in indexes] - return fparam_list_minimal - -# check any directory -def dir_check (d): - if not os.path.isdir(d): return 0 - else: return os.path.isdir(d) - -# only create if check comes back 0 -def dir_create (d): - if not dir_check(d): os.makedirs(d) - -# non-destructive copy routine -def dir_copy (din, dout): - # this command should work on most posix systems - cmd_cp = 'cp -R %s %s' % (din, dout) - # if the dir doesn't already exist, copy it over - if not dir_check(dout): - # print the actual command when successful - print(cmd_cp) - # use call to run the command - subprocess.call(cmd_cp, shell=True) - return 0 - else: - print("Directory already exists.") - -# Finds and moves files to created subdirectories. -def subdir_move (dir_out, name_dir, file_pattern): - dir_name = os.path.join(dir_out, name_dir) - # create directories that do not exist - if not os.path.isdir(dir_name): os.mkdir(dir_name) - for filename in glob.iglob(os.path.join(dir_out, file_pattern)): shutil.move(filename, dir_name) - -# currently used only minimally in epscompress -# need to figure out how to change argument list in cmd as below -def cmds_runmulti (cmdlist): - n_threads = multiprocessing.cpu_count() - list_runs = [cmdlist[i:i+n_threads] for i in range(0, len(cmdlist), n_threads)] - # open devnull for writing extraneous output - with open(os.devnull, 'w') as devnull: - for sublist in list_runs: - procs = [subprocess.Popen(cmd, stdout=devnull, stderr=devnull) for cmd in sublist] - for proc in procs: proc.wait() - -# small kernel for png optimization based on fig directory -def pngoptimize (dfig): - local = 0 - pnglist = file_match(dfig, '.png', local) - cmds_opti = [('optipng', pngfile) for pngfile in pnglist] - cmds_runmulti(cmds_opti) - -# list spike raster eps files and then rasterize them to HQ png files, lossless compress, -# reencapsulate as eps, and remove backups when successful -def epscompress (dfig_spk, fext_figspk, local=0): - cmds_gs = [] - cmds_opti = [] - cmds_encaps = [] - n_threads = multiprocessing.cpu_count() - # lists of eps files and corresponding png files - # fext_figspk, dfig_spk = fileinfo['figspk'] - epslist = file_match(dfig_spk, fext_figspk, local) - pnglist = [f.replace('.eps', '.png') for f in epslist] - epsbackuplist = [f.replace('.eps', '.bak.eps') for f in epslist] - # create command lists for gs, optipng, and convert - for pngfile, epsfile in zip(pnglist, epslist): - cmds_gs.append(('gs -r300 -dEPSCrop -dTextAlphaBits=4 -sDEVICE=png16m -sOutputFile=%s -dBATCH -dNOPAUSE %s' % (pngfile, epsfile))) - cmds_opti.append(('optipng', pngfile)) - cmds_encaps.append(('convert %s eps3:%s' % (pngfile, epsfile))) - # create procs list of manageable lists and run - runs_gs = [cmds_gs[i:i+n_threads] for i in range(0, len(cmds_gs), n_threads)] - # run each sublist differently - for sublist in runs_gs: - procs_gs = [subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) for cmd in sublist] - for proc in procs_gs: proc.wait() - # create optipng procs list and run - cmds_runmulti(cmds_opti) - # backup original eps files temporarily - for epsfile, epsbakfile in zip(epslist, epsbackuplist): shutil.move(epsfile, epsbakfile) - # recreate original eps files, now encapsulated, optimized rasters - # cmds_runmulti(cmds_encaps) - runs_encaps = [cmds_encaps[i:i+n_threads] for i in range(0, len(cmds_encaps), n_threads)] - for sublist in runs_encaps: - procs_encaps = [subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) for cmd in sublist] - for proc in procs_encaps: proc.wait() - # remove all of the backup files - for epsbakfile in epsbackuplist: os.remove(epsbakfile) - -# make dir, catch exceptions -def safemkdir (dn): - try: - os.mkdir(dn) - except FileExistsError: - pass - except OSError: - print('ERR: incorrect permissions for creating', dn) - raise - - return True - -# returns the data dir -def return_data_dir (): - dfinal = os.path.join('.','data') - if not safemkdir(dfinal): sys.exit(1) - return dfinal - -if __name__ == '__main__': - return_data_dir() diff --git a/filt.py b/filt.py deleted file mode 100644 index e592a20ce..000000000 --- a/filt.py +++ /dev/null @@ -1,444 +0,0 @@ -from pylab import convolve -from numpy import hamming -import numpy as np - -# box filter -def boxfilt (x, winsz): - win = [1.0/winsz for i in range(int(winsz))] - return convolve(x,win,'same') - -# convolve with a hamming window -def hammfilt (x, winsz): - win = hamming(winsz) - win /= sum(win) - return convolve(x,win,'same') - -# returns x -def emptyfilt (x, winsz): - return np.array(x) - -# following from -#------------------------------------------------------------------- -# Purpose: Various Seismogram Filtering Functions -# Author: Tobias Megies, Moritz Beyreuther, Yannik Behr -# Email: tobias.megies@geophysik.uni-muenchen.de -# -# Copyright (C) 2009 Tobias Megies, Moritz Beyreuther, Yannik Behr -#--------------------------------------------------------------------- -""" -Various Seismogram Filtering Functions - -:copyright: - The ObsPy Development Team (devs@obspy.org) -:license: - GNU Lesser General Public License, Version 3 - (http://www.gnu.org/copyleft/lesser.html) -""" - -from numpy import array, where, fft -from scipy.fftpack import hilbert -from scipy.signal import iirfilter, lfilter, remez, convolve, get_window, butter -import math -from numpy import vstack, hstack, eye, ones, zeros, linalg, \ -newaxis, r_, flipud, convolve, matrix, array, vectorize, angle - -def bandpass(data, freqmin, freqmax, df=200, corners=4, zerophase=False): - """ - Butterworth-Bandpass Filter. - - Filter data from freqmin to freqmax using - corners corners. - - :param data: Data to filter, type numpy.ndarray. - :param freqmin: Pass band low corner frequency. - :param freqmax: Pass band high corner frequency. - :param df: Sampling rate in Hz; Default 200. - :param corners: Filter corners. Note: This is twice the value of PITSA's - filter sections - :param zerophase: If True, apply filter once forwards and once backwards. - This results in twice the number of corners but zero phase shift in - the resulting filtered trace. - :return: Filtered data. - """ - fe = 0.5 * df - [b, a] = iirfilter(corners, [freqmin / fe, freqmax / fe], btype='band', - ftype='butter', output='ba') - if zerophase: - firstpass = lfilter(b, a, data) - return lfilter(b, a, firstpass[::-1])[::-1] - else: - return lfilter(b, a, data) - - -def bandpassZPHSH(data, freqmin, freqmax, df=200, corners=2): - """ - DEPRECATED. Use :func:`~obspy.signal.filter.bandpass` instead. - """ - return bandpass(data, freqmin, freqmax, df, corners, zerophase=True) - - -def bandstop(data, freqmin, freqmax, df=200, corners=4, zerophase=False): - """ - Butterworth-Bandstop Filter. - - Filter data removing data between frequencies freqmin and freqmax using - corners corners. - - :param data: Data to filter, type numpy.ndarray. - :param freqmin: Stop band low corner frequency. - :param freqmax: Stop band high corner frequency. - :param df: Sampling rate in Hz; Default 200. - :param corners: Filter corners. Note: This is twice the value of PITSA's - filter sections - :param zerophase: If True, apply filter once forwards and once backwards. - This results in twice the number of corners but zero phase shift in - the resulting filtered trace. - :return: Filtered data. - """ - fe = 0.5 * df - [b, a] = iirfilter(corners, [freqmin / fe, freqmax / fe], - btype='bandstop', ftype='butter', output='ba') - if zerophase: - firstpass = lfilter(b, a, data) - return lfilter(b, a, firstpass[::-1])[::-1] - else: - return lfilter(b, a, data) - - -def bandstopZPHSH(data, freqmin, freqmax, df=200, corners=2): - """ - DEPRECATED. Use :func:`~obspy.signal.filter.bandstop` instead. - """ - return bandstop(data, freqmin, freqmax, df, corners, zerophase=True) - - -def lowpass(data, freq, df=200, corners=4, zerophase=False): - """ - Butterworth-Lowpass Filter. - - Filter data removing data over certain frequency freq using corners - corners. - - :param data: Data to filter, type numpy.ndarray. - :param freq: Filter corner frequency. - :param df: Sampling rate in Hz; Default 200. - :param corners: Filter corners. Note: This is twice the value of PITSA's - filter sections - :param zerophase: If True, apply filter once forwards and once backwards. - This results in twice the number of corners but zero phase shift in - the resulting filtered trace. - :return: Filtered data. - """ - fe = 0.5 * df - [b, a] = iirfilter(corners, freq / fe, btype='lowpass', ftype='butter', - output='ba') - if zerophase: - firstpass = lfilter(b, a, data) - return lfilter(b, a, firstpass[::-1])[::-1] - else: - return lfilter(b, a, data) - - -def lowpassZPHSH(data, freq, df=200, corners=2): - """ - DEPRECATED. Use :func:`~obspy.signal.filter.lowpass` instead. - """ - return lowpass(data, freq, df, corners, zerophase=True) - - -def highpass(data, freq, df=200, corners=4, zerophase=False): - """ - Butterworth-Highpass Filter. - - Filter data removing data below certain frequency freq using corners. - - :param data: Data to filter, type numpy.ndarray. - :param freq: Filter corner frequency. - :param df: Sampling rate in Hz; Default 200. - :param corners: Filter corners. Note: This is twice the value of PITSA's - filter sections - :param zerophase: If True, apply filter once forwards and once backwards. - This results in twice the number of corners but zero phase shift in - the resulting filtered trace. - :return: Filtered data. - """ - fe = 0.5 * df - [b, a] = iirfilter(corners, freq / fe, btype='highpass', ftype='butter', - output='ba') - if zerophase: - firstpass = lfilter(b, a, data) - return lfilter(b, a, firstpass[::-1])[::-1] - else: - return lfilter(b, a, data) - - -def highpassZPHSH(data, freq, df=200, corners=2): - """ - DEPRECATED. Use :func:`~obspy.signal.filter.highpass` instead. - """ - return highpass(data, freq, df, corners, zerophase=True) - - -def envelope(data): - """ - Envelope of a function. - - Computes the envelope of the given function. The envelope is determined by - adding the squared amplitudes of the function and it's Hilbert-Transform - and then taking the squareroot. - (See Kanasewich: Time Sequence Analysis in Geophysics) - The envelope at the start/end should not be taken too seriously. - - :param data: Data to make envelope of, type numpy.ndarray. - :return: Envelope of input data. - """ - hilb = hilbert(data) - data = pow(pow(data, 2) + pow(hilb, 2), 0.5) - return data - - -def remezFIR(data, freqmin, freqmax, samp_rate=200): - """ - The minimax optimal bandpass using Remez algorithm. Zerophase bandpass? - - Finite impulse response (FIR) filter whose transfer function minimizes - the maximum error between the desired gain and the realized gain in the - specified bands using the remez exchange algorithm - """ - # Remez filter description - # ======================== - # - # So, let's go over the inputs that you'll have to worry about. - # First is numtaps. This parameter will basically determine how good your - # filter is and how much processor power it takes up. If you go for some - # obscene number of taps (in the thousands) there's other things to worry - # about, but with sane numbers (probably below 30-50 in your case) that is - # pretty much what it affects (more taps is better, but more expensive - # processing wise). There are other ways to do filters as well - # which require less CPU power if you really need it, but I doubt that you - # will. Filtering signals basically breaks down to convolution, and apple - # has DSP libraries to do lightning fast convolution I'm sure, so don't - # worry about this too much. Numtaps is basically equivalent to the number - # of terms in the convolution, so a power of 2 is a good idea, 32 is - # probably fine. - # - # bands has literally your list of bands, so you'll break it up into your - # low band, your pass band, and your high band. Something like [0, 99, 100, - # 999, 1000, 22049] should work, if you want to pass frequencies between - # 100-999 Hz (assuming you are sampling at 44.1 kHz). - # - # desired will just be [0, 1, 0] as you want to drop the high and low - # bands, and keep the middle one without modifying the amplitude. - # - # Also, specify Hz = 44100 (or whatever). - # - # That should be all you need; run the function and it will spit out a list - # of coefficients [c0, ... c(N-1)] where N is your tap count. When you run - # this filter, your output signal y[t] will be computed from the input x[t] - # like this (t-N means N samples before the current one): - # - # y[t] = c0*x[t] + c1*x[t-1] + ... + c(N-1)*x[t-(N-1)] - # - # After playing around with remez for a bit, it looks like numtaps should - # be above 100 for a solid filter. See what works out for you. Eventually, - # take those coefficients and then move them over and do the convolution - # in C or whatever. Also, note the gaps between the bands in the call to - # remez. You have to leave some space for the transition in frequency - # response to occur, otherwise the call to remez will complain. - # - # SRC: # http://episteme.arstechnica.com/eve/forums/a/tpc/f/6330927813/m/175006289731 - # See also: - # http://aspn.activestate.com/ASPN/Mail/Message/scipy-dev/1592174 - # http://aspn.activestate.com/ASPN/Mail/Message/scipy-dev/1592172 - - #take 10% of freqmin and freqmax as """corners""" - flt = freqmin - 0.1 * freqmin - fut = freqmax + 0.1 * freqmax - #bandpass between freqmin and freqmax - filt = remez(50, array([0, flt, freqmin, freqmax, fut, samp_rate / 2 - 1]), - array([0, 1, 0]), Hz=samp_rate) - return convolve(filt, data) - - -def lowpassFIR(data, freq, samp_rate=200, winlen=2048): - """ - FIR-Lowpass Filter - - Filter data by passing data only below a certain frequency. - - :param data: Data to filter, type numpy.ndarray. - :param freq: Data below this frequency pass. - :param samprate: Sampling rate in Hz; Default 200. - :param winlen: Window length for filter in samples, must be power of 2; - Default 2048 - :return: Filtered data. - """ - # There is not currently an FIR-filter design program in SciPy. One - # should be constructed as it is not hard to implement (of course making - # it generic with all the options you might want would take some time). - # - # What kind of window are you currently using? - # - # For your purposes this is what I would do: - # SRC: Travis Oliphant - # http://aspn.activestate.com/ASPN/Mail/Message/scipy-user/2009409] - # - #winlen = 2**11 #2**10 = 1024; 2**11 = 2048; 2**12 = 4096 - #give frequency bins in Hz and sample spacing - w = fft.fftfreq(winlen, 1 / float(samp_rate)) - #cutoff is low-pass filter - myfilter = where((abs(w) < freq), 1., 0.) - #ideal filter - h = fft.ifft(myfilter) - beta = 11.7 - #beta implies Kaiser - myh = fft.fftshift(h) * get_window(beta, winlen) - return convolve(abs(myh), data)[winlen / 2:-winlen / 2] - -def lfilter_zi (b,a): - #compute the zi state from the filter parameters. see [Gust96]. - - #Based on: - # [Gust96] Fredrik Gustafsson, Determining the initial states in forward-backward - # filtering, IEEE Transactions on Signal Processing, pp. 988--992, April 1996, - # Volume 44, Issue 4 - - n=max(len(a),len(b)) - - zin = ( eye(n-1) - hstack( (-a[1:n,newaxis], - vstack((eye(n-2), zeros(n-2)))))) - - zid= b[1:n] - a[1:n]*b[0] - - zi_matrix=linalg.inv(zin)*(matrix(zid).transpose()) - zi_return=[] - - #convert the result into a regular array (not a matrix) - for i in range(len(zi_matrix)): - zi_return.append(float(zi_matrix[i][0])) - - return array(zi_return) - -# http://www.scipy.org/Cookbook/FiltFilt -def filtfilt (b,a,x): - #For now only accepting 1d arrays - ntaps=max(len(a),len(b)) - edge=ntaps*3 - - if x.ndim != 1: - raise ValueError("Filiflit is only accepting 1 dimension arrays.") - - #x must be bigger than edge - if x.size < edge: - raise ValueError("Input vector needs to be bigger than 3 * max(len(a),len(b).") - - if len(a) < ntaps: - a=r_[a,zeros(len(b)-len(a))] - - if len(b) < ntaps: - b=r_[b,zeros(len(a)-len(b))] - - zi=lfilter_zi(b,a) - - #Grow the signal to have edges for stabilizing - #the filter with inverted replicas of the signal - s=r_[2*x[0]-x[edge:1:-1],x,2*x[-1]-x[-1:-edge:-1]] - #in the case of one go we only need one of the extrems - # both are needed for filtfilt - - (y,zf)=lfilter(b,a,s,-1,zi*s[0]) - - (y,zf)=lfilter(b,a,flipud(y),-1,zi*y[-1]) - - return flipud(y[edge-1:-edge+1]) - - -# bandfilt - bandpass filter signal x using filtfilt -# sampr = sampling rate in Hz -# lohz = low frequency cutoff -# hihz = high frequency cutoff -# bord = order for butterworth filter -def bandfilt (x,sampr,lohz,hihz,bord=2): - fr = [lohz/(sampr/2.0), hihz/(sampr/2.0)] - [b,a] = butter(bord,fr,btype='band') - y=filtfilt(b,a,x) - return y - -# bandfiltlist - apply bandpass filter on x using -# frequencies centered on lfreq +/- fwidths/2 -# return filtered signals as a list -def bandfiltlist (x,sampr,lfreq,fwidths): - lxf = zeros((len(lfreq),len(x))); i=0 - for f in lfreq: - lxf[i,:] = bandfilt(x,sampr,f - fwidths[i]/2.0,f + fwidths[i]/2.0) - i += 1 - return lxf - -#hilb - returns 2 element list with amplitude,phase numpy arrays -# from hilbert transform of x -# before running hilb, should bandpass filter signal with bandfilt -# typical sequence on signal x: -# xf = bandfilt(x,sampr,lohz,hihz) <- bandpass filter x into xf -# xfH = hilb(xf) -> xfH[0] = amplitude, xfH[1] = phase -def hilb (x): - xsz=x.size - lxsz=math.log(xsz)/math.log(2) - if (lxsz!=int(lxsz)): - x=x.copy() # to resize must make x a local copy of the externally referenced x - x.resize(2**(int(lxsz)+1)) - H = hilbert(x) - H.resize(xsz) # only needed if stretched - return [abs(H),angle(H)] - -# hilblist - apply hilbert transform on x using -# frequencies centered on lfreq +/- fwidths/2 -# return amplitude and phase for each frequency as 2 separate lists -# cuts specifies number of seconds to cut off start/end of filtered signal -# before applying the hilbert transform -def hilblist (x,sampr,lfreq,fwidths,cuts): - lhamp,lhang = [], []; i = 0; - cutsamps = int(cuts*sampr); # number of samples to cut off filtered signal - sz = len(x); # length of original signal - lhamp,lhang=zeros((len(lfreq), sz-2*cutsamps)), zeros((len(lfreq), sz-2*cutsamps)) - for f in lfreq: - xf = bandfilt(x,sampr,f - fwidths[i]/2.0, f + fwidths[i]/2.0) # bandpass filter - hamp,hang = hilb(xf) # hilbert transform - del xf # free some memory - lhamp[i,:] = hamp[cutsamps:sz-cutsamps] - lhang[i,:] = hang[cutsamps:sz-cutsamps] - del hamp,hang; - i += 1 - return lhamp,lhang - -#gethilbd - returns a dict with amplitude,phase,filtered signal -def gethilbd (x,sampr,lohz,hihz): - xf = bandfilt(x,sampr,lohz,hihz) # filter the signal - xfh = hilb(xf) # do the hilbert transform - d = {'amp':xfh[0], 'phase':xfh[1], 'filt':xf} - return d - - -if __name__=='__main__': - - from scipy import sin, arange, pi, randn - from pylab import plot, legend, show, hold - - t=arange(-1,1,.01) - x=sin(2*pi*t*.5+2) - #xn=x + sin(2*pi*t*10)*.1 - xn=x+randn(len(t))*0.05 - - [b,a]=butter(3,0.05) - - z=lfilter(b,a,xn) - y=filtfilt(b,a,xn) - - plot(x,'c') - hold(True) - plot(xn,'k') - plot(z,'r') - plot(y,'g') - - legend(('original','noisy signal','lfilter - butter 3 order','filtfilt - butter 3 order')) - hold(False) - show() diff --git a/hnn_nrnui.py b/hnn_nrnui.py deleted file mode 100644 index 22c29ed10..000000000 --- a/hnn_nrnui.py +++ /dev/null @@ -1,52 +0,0 @@ -# DEPRECATE - -import logging -from neuron import h - -from jupyter_geppetto.geppetto_comm import GeppettoCoreAPI as G -from neuron_ui import neuron_utils -from neuron_ui import neuron_geometries_utils - -import params_default -from conf import fcfg,dconf -from simdat import * - -class HNN: - def __init__ (self): - logging.debug('Loading HNN') - neuron_utils.createProject(name='HNN') - import run - - self.t_vec = h.Vector() - self.t_vec.record(h._ref_t) - neuron_utils.createStateVariable(id='time', name='time', - units='ms', python_variable={"record_variable": self.t_vec, - "segment": None}) - - neuron_geometries_utils.extractGeometries() - - logging.debug('HNN loaded') - - self.RunSimButton = neuron_utils.add_button('Run', extraData={'commands':[]}) - self.StopSimButton = neuron_utils.add_button('Stop',extraData={'commands':[]}) - self.SetParams = neuron_utils.add_button('Set Parameters') - self.BasePanel = neuron_utils.add_panel('HNN Control', items=[self.RunSimButton,self.StopSimButton,self.SetParams],widget_id='hnnBasePanel') - self.BasePanel.on_close(self.close) - self.BasePanel.display() - - def close (self): - self.BasePanel.close() - del self.BasePanel - -""" -to start (first 2 commands from console) - -./NEURON-UI/NEURON-UI & -jupyter console --existing -import os -os.chdir('/u/samn/hnn/NEURON-UI/neuron_ui/models/hnn') -import hnn_nrnui -net=hnn_nrnui.HNN() - -""" - diff --git a/init.py b/init.py deleted file mode 100644 index 6e995eb75..000000000 --- a/init.py +++ /dev/null @@ -1,8 +0,0 @@ -# init.py - Starting script to run NetPyNE-based model. -# Usage: python init.py # Run simulation, optionally plot a raster -# MPI usage: mpiexec -n 4 nrniv -python -mpi init.py - -from netpyne import sim -cfg, netParams = sim.readCmdLineArgs('cfg.py','netParams.py') # read cfg and netParams from command line arguments -sim.createSimulateAnalyze(simConfig = cfg, netParams = netParams) - diff --git a/lfp.py b/lfp.py deleted file mode 100644 index c68d31c32..000000000 --- a/lfp.py +++ /dev/null @@ -1,274 +0,0 @@ -""" -LFPsim - Simulation scripts to compute Local Field Potentials (LFP) from cable compartmental -models of neurons and networks implemented in NEURON simulation environment. - -LFPsim works reliably on biophysically detailed multi-compartmental neurons with ion channels in -some or all compartments. - -Last updated 12-March-2016 -Developed by : Harilal Parasuram & Shyam Diwakar -Computational Neuroscience & Neurophysiology Lab, School of Biotechnology, Amrita University, India. -Email: harilalp@am.amrita.edu; shyam@amrita.edu -www.amrita.edu/compneuro - -translated to Python and modified to use use_fast_imem by Sam Neymotin -based on mhines code -""" - -from neuron import h -from math import sqrt, log, pi, exp -from seg3d import * -from pylab import * - -# get all Sections -def getallSections (ty='Pyr'): - ls = h.allsec() - ls = [s for s in ls if s.name().count(ty)>0 or len(ty)==0] - return ls - -def getcoordinf (s): - lcoord = []; ldist = []; lend = []; lsegloc = [] - if s.nseg == 1: - i = 1 - x0, y0, z0 = s.x3d(i-1,sec=s), s.y3d(i-1, sec=s), s.z3d(i-1, sec=s) - x1, y1, z1 = s.x3d(i,sec=s), s.y3d(i, sec=s), s.z3d(i, sec=s) - lcoord.append([(x0+x1)/2.0,(y0+y1)/2.0,(z0+z1)/2.0]) - dist = sqrt((x1-x0)**2 + (y1-y0)**2 + (z1-z0)**2) - ldist.append( dist ) - lend.append([x1, y1, z1]) - lsegloc.append(0.5) - else: - for i in range(1,s.n3d(),1): - x0, y0, z0 = s.x3d(i-1,sec=s), s.y3d(i-1, sec=s), s.z3d(i-1, sec=s) - x1, y1, z1 = s.x3d(i,sec=s), s.y3d(i, sec=s), s.z3d(i, sec=s) - lcoord.append( [(x0+x1)/2.,(y0+y1)/2.(z0+z1)/2.] ) - dist = sqrt((x1-x0)**2 + (y1-y0)**2 + (z1-z0)**2) - ldist.append( dist ) - lend.append([x1, y1, z1]) - lsegloc.append() - return lcoord, ldist, lend, lsegloc - -# this function not used ... yet -def transfer_resistance2 (exyz): - vres = h.Vector() - lsec = getallSections() - sigma = 3.0 # extracellular conductivity in mS/cm - # see http://jn.physiology.org/content/104/6/3388.long shows table of values with conductivity - for s in lsec: - lcoord, ldist, lend = getcoordinf(s) - for i in range(len(lcoord)): - x,y,z = lcoord[i] - dis = sqrt((exyz[0] - x)**2 + (exyz[1] - y)**2 + (exyz[2] - z)**2 ) - # setting radius limit - if(dis<(s.diam/2.0)): dis = (s.diam/2.0) + 0.1 - - dist_comp = ldist[i] # length of the compartment - sum_dist_comp = sqrt(dist_comp[0]**2 + dist_comp[0]**2 + dist_comp[0]**2) - - # print "sum_dist_comp=",sum_dist_comp, secname() - - # setting radius limit - if sum_dist_comp < s.diam/2.0: sum_dist_comp = s.diam/2.0 + 0.1 - - long_dist_x = exyz[0] - lend[i][0] - long_dist_y = exyz[1] - lend[i][1] - long_dist_z = exyz[2] - lend[i][2] - - sum_HH = long_dist_x*dist_comp_x + long_dist_y*dist_comp_y + long_dist_z*dist_comp_z - - final_sum_HH = sum_HH / sum_dist_comp - - sum_temp1 = long_dist_x**2 + long_dist_y**2 + long_dist_z**2 - r_sq = sum_temp1 - (final_sum_HH * final_sum_HH) - - Length_vector = final_sum_HH + sum_dist_comp - - if final_sum_HH < 0 and Length_vector <= 0: - phi=log((sqrt(final_sum_HH**2 + r_sq) - final_sum_HH)/(sqrt(Length_vector**2+r_sq)-Length_vector)) - elif final_sum_HH > 0 and Length_vector > 0: - phi=log((sqrt(Length_vector**2+r_sq) + Length_vector)/(sqrt(final_sum_HH**2+r_sq) + final_sum_HH)) - else: - phi=log(((sqrt(Length_vector**2+r_sq)+Length_vector) * (sqrt(final_sum_HH**2+r_sq)-final_sum_HH))/r_sq) - - line_part1 = 1.0 / (4.0*pi*sum_dist_comp*sigma) * phi - vres.append(line_part1) - - return vres - -# represents a simple LFP electrode -class LFPElectrode (): - - def __init__ (self, coord, sigma = 3.0, pc = None, usePoint = True): - - self.sigma = sigma # extracellular conductivity in mS/cm (uniform for simplicity) - # see http://jn.physiology.org/content/104/6/3388.long shows table of values with conductivity - self.coord = coord - self.vres = None - self.vx = None - - self.imem_ptrvec = self.imem_vec = self.rx = self.vx = self.vres = None - self.bscallback = self.fih = None - - if pc is None: self.pc = h.ParallelContext() - else: self.pc = pc - - def setup (self): - h.cvode.use_fast_imem(1) # enables fast calculation of transmembrane current (nA) at each segment - self.bscallback = h.beforestep_callback(h.cas()(.5)) - self.bscallback.set_callback(self.callback) - fih = h.FInitializeHandler(1, self.LFPinit) - - def transfer_resistance (self, exyz,usePoint=True): - vres = h.Vector() - lsec = getallSections() - for s in lsec: - - x = (h.x3d(0,sec=s) + h.x3d(1,sec=s)) / 2.0 - y = (h.y3d(0,sec=s) + h.y3d(1,sec=s)) / 2.0 - z = (h.z3d(0,sec=s) + h.z3d(1,sec=s)) / 2.0 - - sigma = self.sigma - - dis = sqrt((exyz[0] - x)**2 + (exyz[1] - y)**2 + (exyz[2] - z)**2 ) - - # setting radius limit - if(dis<(s.diam/2.0)): dis = (s.diam/2.0) + 0.1 - - if usePoint: - point_part1 = 10000.0 * (1.0 / (4.0 * pi * dis * sigma)) # x10000 for units of microV : nA/(microm*(mS/cm)) -> microV - vres.append(point_part1) - else: - # calculate length of the compartment - dist_comp = sqrt((h.x3d(1,sec=s) - h.x3d(0,sec=s))**2 + (h.y3d(1,sec=s) - h.y3d(0,sec=s))**2 + (h.z3d(1,sec=s) - h.z3d(0,sec=s))**2) - - dist_comp_x = (h.x3d(1,sec=s) - h.x3d(0,sec=s)) - dist_comp_y = (h.y3d(1,sec=s) - h.y3d(0,sec=s)) - dist_comp_z = (h.z3d(1,sec=s) - h.z3d(0,sec=s)) - - sum_dist_comp = sqrt(dist_comp_x**2 + dist_comp_y**2 + dist_comp_z**2) - - # print "sum_dist_comp=",sum_dist_comp, secname() - - # setting radius limit - if sum_dist_comp < s.diam/2.0: sum_dist_comp = s.diam/2.0 + 0.1 - - long_dist_x = exyz[0] - h.x3d(1,sec=s) - long_dist_y = exyz[1] - h.y3d(1,sec=s) - long_dist_z = exyz[2] - h.z3d(1,sec=s) - - sum_HH = long_dist_x*dist_comp_x + long_dist_y*dist_comp_y + long_dist_z*dist_comp_z - - final_sum_HH = sum_HH / sum_dist_comp - - sum_temp1 = long_dist_x**2 + long_dist_y**2 + long_dist_z**2 - r_sq = sum_temp1 -(final_sum_HH * final_sum_HH) - - Length_vector = final_sum_HH + sum_dist_comp - - if final_sum_HH < 0 and Length_vector <= 0: - phi=log((sqrt(final_sum_HH**2 + r_sq) - final_sum_HH)/(sqrt(Length_vector**2+r_sq)-Length_vector)) - elif final_sum_HH > 0 and Length_vector > 0: - phi=log((sqrt(Length_vector**2+r_sq) + Length_vector)/(sqrt(final_sum_HH**2+r_sq) + final_sum_HH)) - else: - phi=log(((sqrt(Length_vector**2+r_sq)+Length_vector) * (sqrt(final_sum_HH**2+r_sq)-final_sum_HH))/r_sq) - - line_part1 = 10000.0 * (1.0 / (4.0*pi*sum_dist_comp*sigma) * phi) # x10000 for units of microV - vres.append(line_part1) - - return vres - - def LFPinit (self): - lsec = getallSections() - n = len(lsec) - # print('In LFPinit - pc.id = ',self.pc.id(),'len(lsec)=',n) - self.imem_ptrvec = h.PtrVector(n) # - self.imem_vec = h.Vector(n) - for i,s in enumerate(lsec): - seg = s(0.5) - #for seg in s # so do not need to use segments...? more accurate to use segments and their neighbors - self.imem_ptrvec.pset(i, seg._ref_i_membrane_) - - self.vres = self.transfer_resistance(self.coord) - self.lfp_t = h.Vector() - self.lfp_v = h.Vector() - - #for i, cellinfo in enumerate(gidinfo.values()): - # seg = cellinfo.cell.soma(0.5) - # imem_ptrvec.pset(i, seg._ref_i_membrane_) - #rx = h.Matrix(nelectrode, n) - #vx = h.Vector(nelectrode) - #for i in range(nelectrode): - # for j, cellinfo in enumerate(gidinfo.values()): - # rx.setval(i, j, transfer_resistance(cellinfo.cell, e_coord[i])) - # #rx.setval(i,1,1.0) - - def callback (self): - # print('In lfp callback - pc.id = ',self.pc.id(),' t=',self.pc.t(0)) - self.imem_ptrvec.gather(self.imem_vec) - #s = pc.allreduce(imem_vec.sum(), 1) #verify sum i_membrane_ == stimulus - #if rank == 0: print pc.t(0), s - - #sum up the weighted i_membrane_. Result in vx - # rx.mulv(imem_vec, vx) - - val = 0.0 - for j in range(len(self.vres)): val += self.imem_vec.x[j] * self.vres.x[j] - - # append to Vector - self.lfp_t.append(self.pc.t(0)) - self.lfp_v.append(val) - - def lfp_final (self): - self.pc.allreduce(self.lfp_v, 1) - - def lfpout (self,fn = 'LFP.txt', append=False, tvec = None): - fmode = 'w' - if append: fmode = 'a' - if int(self.pc.id()) == 0: - print('len(lfp_t) is %d' % len(self.lfp_t)) - f = open(fn, fmode) - if tvec is None: - for i in range(1, len(self.lfp_t), 1): - line = '%g' % self.lfp_v.x[i] - f.write(line + '\n') - else: - for i in range(1, len(self.lfp_t), 1): - line = '%g'%self.lfp_t.x[i] - line += ' %g' % self.lfp_v.x[i] - f.write(line + '\n') - f.close() - -def test (): - from L5_pyramidal import L5Pyr - cell = L5Pyr() - - h.load_file("stdgui.hoc") - h.cvode_active(1) - - ns = h.NetStim() - ns.number = 10 - ns.start = 100 - ns.interval=50.0 - - nc = h.NetCon(ns,cell.apicaltuft_ampa) - nc.weight[0] = 0.001 - - h.tstop=2000.0 - - elec = LFPElectrode([0, 100.0, 100.0], pc = h.ParallelContext()) - elec.setup() - elec.LFPinit() - h.run() - elec.lfp_final() - ion() - plot(elec.lfp_t, elec.lfp_v) - -if __name__ == '__main__': - test() - """ - for i in range(len(lfp_t)): - print(lfp_t.x[i],) - for j in range(nelectrode): - print(lfp_v[j].x[i],) - print("") - """ diff --git a/loadmodel_nrnui.py b/loadmodel_nrnui.py deleted file mode 100644 index 3dded0af6..000000000 --- a/loadmodel_nrnui.py +++ /dev/null @@ -1,6 +0,0 @@ -# Used by Jupyter/NEURON-UI to import model -import os -os.chdir('NEURON-UI/neuron_ui/models/hnn') -import hnn_nrnui -net=hnn_nrnui.HNN() - diff --git a/morphology.py b/morphology.py deleted file mode 100644 index afe9ddb51..000000000 --- a/morphology.py +++ /dev/null @@ -1,624 +0,0 @@ -# from https://github.com/ahwillia/PyNeuron-Toolbox 4/2017 -# adjusted for python3 -from __future__ import division -import numpy as np -import pylab as plt -from matplotlib.pyplot import cm -import string -from neuron import h -import numbers - -# a helper library, included with NEURON -h.load_file('stdlib.hoc') -h.load_file('import3d.hoc') - -class Cell: - def __init__(self,name='neuron',soma=None,apic=None,dend=None,axon=None): - self.soma = soma if soma is not None else [] - self.apic = apic if apic is not None else [] - self.dend = dend if dend is not None else [] - self.axon = axon if axon is not None else [] - self.all = self.soma + self.apic + self.dend + self.axon - - def delete(self): - self.soma = None - self.apic = None - self.dend = None - self.axon = None - self.all = None - - def __str__(self): - return self.name - -def load(filename, fileformat=None, cell=None, use_axon=True, xshift=0, yshift=0, zshift=0): - """ - Load an SWC from filename and instantiate inside cell. Code kindly provided - by @ramcdougal. - - Args: - filename = .swc file containing morphology - cell = Cell() object. (Default: None, creates new object) - filename = the filename of the SWC file - use_axon = include the axon? Default: True (yes) - xshift, yshift, zshift = use to position the cell - - Returns: - Cell() object with populated soma, axon, dend, & apic fields - - Minimal example: - # pull the morphology for the demo from NeuroMorpho.Org - from PyNeuronToolbox import neuromorphoorg - with open('c91662.swc', 'w') as f: - f.write(neuromorphoorg.morphology('c91662')) - cell = load_swc(filename) - - """ - - if cell is None: - cell = Cell(name=string.join(filename.split('.')[:-1])) - - if fileformat is None: - fileformat = filename.split('.')[-1] - - name_form = {1: 'soma[%d]', 2: 'axon[%d]', 3: 'dend[%d]', 4: 'apic[%d]'} - - # load the data. Use Import3d_SWC_read for swc, Import3d_Neurolucida3 for - # Neurolucida V3, Import3d_MorphML for MorphML (level 1 of NeuroML), or - # Import3d_Eutectic_read for Eutectic. - if fileformat == 'swc': - morph = h.Import3d_SWC_read() - elif fileformat == 'asc': - morph = h.Import3d_Neurolucida3() - else: - raise Exception('file format `%s` not recognized'%(fileformat)) - morph.input(filename) - - # easiest to instantiate by passing the loaded morphology to the Import3d_GUI - # tool; with a second argument of 0, it won't display the GUI, but it will allow - # use of the GUI's features - i3d = h.Import3d_GUI(morph, 0) - - # get a list of the swc section objects - swc_secs = i3d.swc.sections - swc_secs = [swc_secs.object(i) for i in range(int(swc_secs.count()))] - - # initialize the lists of sections - sec_list = {1: cell.soma, 2: cell.axon, 3: cell.dend, 4: cell.apic} - - # name and create the sections - real_secs = {} - for swc_sec in swc_secs: - cell_part = int(swc_sec.type) - - # skip everything else if it's an axon and we're not supposed to - # use it... or if is_subsidiary - if (not(use_axon) and cell_part == 2) or swc_sec.is_subsidiary: - continue - - # figure out the name of the new section - if cell_part not in name_form: - raise Exception('unsupported point type') - name = name_form[cell_part] % len(sec_list[cell_part]) - - # create the section - sec = h.Section(name=name) - - # connect to parent, if any - if swc_sec.parentsec is not None: - sec.connect(real_secs[swc_sec.parentsec.hname()](swc_sec.parentx)) - - # define shape - if swc_sec.first == 1: - h.pt3dstyle(1, swc_sec.raw.getval(0, 0), swc_sec.raw.getval(1, 0), - swc_sec.raw.getval(2, 0), sec=sec) - - j = swc_sec.first - xx, yy, zz = [swc_sec.raw.getrow(i).c(j) for i in range(3)] - dd = swc_sec.d.c(j) - if swc_sec.iscontour_: - # never happens in SWC files, but can happen in other formats supported - # by NEURON's Import3D GUI - raise Exception('Unsupported section style: contour') - - if dd.size() == 1: - # single point soma; treat as sphere - x, y, z, d = [dim.x[0] for dim in [xx, yy, zz, dd]] - for xprime in [x - d / 2., x, x + d / 2.]: - h.pt3dadd(xprime + xshift, y + yshift, z + zshift, d, sec=sec) - else: - for x, y, z, d in zip(xx, yy, zz, dd): - h.pt3dadd(x + xshift, y + yshift, z + zshift, d, sec=sec) - - # store the section in the appropriate list in the cell and lookup table - sec_list[cell_part].append(sec) - real_secs[swc_sec.hname()] = sec - - cell.all = cell.soma + cell.apic + cell.dend + cell.axon - return cell - -def sequential_spherical(xyz): - """ - Converts sequence of cartesian coordinates into a sequence of - line segments defined by spherical coordinates. - - Args: - xyz = 2d numpy array, each row specifies a point in - cartesian coordinates (x,y,z) tracing out a - path in 3D space. - - Returns: - r = lengths of each line segment (1D array) - theta = angles of line segments in XY plane (1D array) - phi = angles of line segments down from Z axis (1D array) - """ - d_xyz = np.diff(xyz,axis=0) - - r = np.linalg.norm(d_xyz,axis=1) - theta = np.arctan2(d_xyz[:,1], d_xyz[:,0]) - hyp = d_xyz[:,0]**2 + d_xyz[:,1]**2 - phi = np.arctan2(np.sqrt(hyp), d_xyz[:,2]) - - return (r,theta,phi) - -def spherical_to_cartesian(r,theta,phi): - """ - Simple conversion of spherical to cartesian coordinates - - Args: - r,theta,phi = scalar spherical coordinates - - Returns: - x,y,z = scalar cartesian coordinates - """ - x = r * np.sin(phi) * np.cos(theta) - y = r * np.sin(phi) * np.sin(theta) - z = r * np.cos(phi) - return (x,y,z) - -def find_coord(targ_length,xyz,rcum,theta,phi): - """ - Find (x,y,z) ending coordinate of segment path along section - path. - - Args: - targ_length = scalar specifying length of segment path, starting - from the begining of the section path - xyz = coordinates specifying the section path - rcum = cumulative sum of section path length at each node in xyz - theta, phi = angles between each coordinate in xyz - """ - # [1] Find spherical coordinates for the line segment containing - # the endpoint. - # [2] Find endpoint in spherical coords and convert to cartesian - i = np.nonzero(rcum <= targ_length)[0][-1] - if i == len(theta): - return xyz[-1,:] - else: - r_lcl = targ_length-rcum[i] # remaining length along line segment - (dx,dy,dz) = spherical_to_cartesian(r_lcl,theta[i],phi[i]) - return xyz[i,:] + [dx,dy,dz] - -def interpolate_jagged(xyz,nseg): - """ - Interpolates along a jagged path in 3D - - Args: - xyz = section path specified in cartesian coordinates - nseg = number of segment paths in section path - - Returns: - interp_xyz = interpolated path - """ - - # Spherical coordinates specifying the angles of all line - # segments that make up the section path - (r,theta,phi) = sequential_spherical(xyz) - - # cumulative length of section path at each coordinate - rcum = np.append(0,np.cumsum(r)) - - # breakpoints for segment paths along section path - breakpoints = np.linspace(0,rcum[-1],nseg+1) - np.delete(breakpoints,0) - - # Find segment paths - seg_paths = [] - for a in range(nseg): - path = [] - - # find (x,y,z) starting coordinate of path - if a == 0: - start_coord = xyz[0,:] - else: - start_coord = end_coord # start at end of last path - path.append(start_coord) - - # find all coordinates between the start and end points - start_length = breakpoints[a] - end_length = breakpoints[a+1] - mid_boolean = (rcum > start_length) & (rcum < end_length) - mid_indices = np.nonzero(mid_boolean)[0] - for mi in mid_indices: - path.append(xyz[mi,:]) - - # find (x,y,z) ending coordinate of path - end_coord = find_coord(end_length,xyz,rcum,theta,phi) - path.append(end_coord) - - # Append path to list of segment paths - seg_paths.append(np.array(path)) - - # Return all segment paths - return seg_paths - -def get_section_path(h,sec): - n3d = int(h.n3d(sec=sec)) - xyz = [] - for i in range(0,n3d): - xyz.append([h.x3d(i,sec=sec),h.y3d(i,sec=sec),h.z3d(i,sec=sec)]) - xyz = np.array(xyz) - return xyz - -def shapeplot(h,ax,sections=None,order='pre',cvals=None,\ - clim=None,cmap=cm.YlOrBr_r,**kwargs): - """ - Plots a 3D shapeplot - - Args: - h = hocObject to interface with neuron - ax = matplotlib axis for plotting - sections = list of h.Section() objects to be plotted - order = { None= use h.allsec() to get sections - 'pre'= pre-order traversal of morphology } - cvals = list/array with values mapped to color by cmap; useful - for displaying voltage, calcium or some other state - variable across the shapeplot. - **kwargs passes on to matplotlib (e.g. color='r' for red lines) - - Returns: - lines = list of line objects making up shapeplot - """ - - # Default is to plot all sections. - if sections is None: - if order == 'pre': - sections = allsec_preorder(h) # Get sections in "pre-order" - else: - sections = list(h.allsec()) - - # Determine color limits - if cvals is not None and clim is None: - cn = [ isinstance(cv, numbers.Number) for cv in cvals ] - if any(cn): - clim = [np.min(cvals[cn]), np.max(cvals[cn])] - - # Plot each segement as a line - lines = [] - i = 0 - for sec in sections: - xyz = get_section_path(h,sec) - seg_paths = interpolate_jagged(xyz,sec.nseg) - - for (j,path) in enumerate(seg_paths): - line, = plt.plot(path[:,0], path[:,1], path[:,2], '-k',**kwargs) - if cvals is not None: - if isinstance(cvals[i], numbers.Number): - # map number to colormap - col = cmap(int((cvals[i]-clim[0])*255/(clim[1]-clim[0]))) - else: - # use input directly. E.g. if user specified color with a string. - col = cvals[i] - line.set_color(col) - lines.append(line) - i += 1 - - return lines - -def getshapecoords (h,sections=None,order='pre',**kwargs): - if sections is None: - if order == 'pre': - sections = allsec_preorder(h) # Get sections in "pre-order" - else: - sections = list(h.allsec()) - i = 0 - lx,ly,lz=[],[],[] - for sec in sections: - xyz = get_section_path(h,sec) - seg_paths = interpolate_jagged(xyz,sec.nseg) - for path in seg_paths: - for i in [0,1]: - lx.append(path[i][0]) - ly.append(path[i][1]) - lz.append(path[i][2]) - return lx,ly,lz - - -def shapeplot_animate(v,lines,nframes=None,tscale='linear',\ - clim=[-80,50],cmap=cm.YlOrBr_r): - """ Returns animate function which updates color of shapeplot """ - if nframes is None: - nframes = v.shape[0] - if tscale == 'linear': - def animate(i): - i_t = int((i/nframes)*v.shape[0]) - for i_seg in range(v.shape[1]): - lines[i_seg].set_color(cmap(int((v[i_t,i_seg]-clim[0])*255/(clim[1]-clim[0])))) - return [] - elif tscale == 'log': - def animate(i): - i_t = int(np.round((v.shape[0] ** (1.0/(nframes-1))) ** i - 1)) - for i_seg in range(v.shape[1]): - lines[i_seg].set_color(cmap(int((v[i_t,i_seg]-clim[0])*255/(clim[1]-clim[0])))) - return [] - else: - raise ValueError("Unrecognized option '%s' for tscale" % tscale) - - return animate - -def mark_locations(h,section,locs,markspec='or',**kwargs): - """ - Marks one or more locations on along a section. Could be used to - mark the location of a recording or electrical stimulation. - - Args: - h = hocObject to interface with neuron - section = reference to section - locs = float between 0 and 1, or array of floats - optional arguments specify details of marker - - Returns: - line = reference to plotted markers - """ - - # get list of cartesian coordinates specifying section path - xyz = get_section_path(h,section) - (r,theta,phi) = sequential_spherical(xyz) - rcum = np.append(0,np.cumsum(r)) - - # convert locs into lengths from the beginning of the path - if type(locs) is float or type(locs) is np.float64: - locs = np.array([locs]) - if type(locs) is list: - locs = np.array(locs) - lengths = locs*rcum[-1] - - # find cartesian coordinates for markers - xyz_marks = [] - for targ_length in lengths: - xyz_marks.append(find_coord(targ_length,xyz,rcum,theta,phi)) - xyz_marks = np.array(xyz_marks) - - # plot markers - line, = plt.plot(xyz_marks[:,0], xyz_marks[:,1], \ - xyz_marks[:,2], markspec, **kwargs) - return line - -def root_sections(h): - """ - Returns a list of all sections that have no parent. - """ - roots = [] - for section in h.allsec(): - sref = h.SectionRef(sec=section) - # has_parent returns a float... cast to bool - if sref.has_parent() < 0.9: - roots.append(section) - return roots - -def leaf_sections(h): - """ - Returns a list of all sections that have no children. - """ - leaves = [] - for section in h.allsec(): - sref = h.SectionRef(sec=section) - # nchild returns a float... cast to bool - if sref.nchild() < 0.9: - leaves.append(section) - return leaves - -def root_indices(sec_list): - """ - Returns the index of all sections without a parent. - """ - roots = [] - for i,section in enumerate(sec_list): - sref = h.SectionRef(sec=section) - # has_parent returns a float... cast to bool - if sref.has_parent() < 0.9: - roots.append(i) - return roots - -def allsec_preorder(h): - """ - Alternative to using h.allsec(). This returns all sections in order from - the root. Traverses the topology each neuron in "pre-order" - """ - #Iterate over all sections, find roots - roots = root_sections(h) - - # Build list of all sections - sec_list = [] - for r in roots: - add_pre(h,sec_list,r) - return sec_list - -def add_pre(h,sec_list,section,order_list=None,branch_order=None): - """ - A helper function that traverses a neuron's morphology (or a sub-tree) - of the morphology in pre-order. This is usually not necessary for the - user to import. - """ - - sec_list.append(section) - sref = h.SectionRef(sec=section) - - if branch_order is not None: - order_list.append(branch_order) - if len(sref.child) > 1: - branch_order += 1 - - for next_node in sref.child: - add_pre(h,sec_list,next_node,order_list,branch_order) - -def dist_between(h,seg1,seg2): - """ - Calculates the distance between two segments. I stole this function from - a post by Michael Hines on the NEURON forum - (www.neuron.yale.edu/phpbb/viewtopic.php?f=2&t=2114) - """ - h.distance(0, seg1.x, sec=seg1.sec) - return h.distance(seg2.x, sec=seg2.sec) - -def all_branch_orders(h): - """ - Produces a list branch orders for each section (following pre-order tree - traversal) - """ - #Iterate over all sections, find roots - roots = [] - for section in h.allsec(): - sref = h.SectionRef(sec=section) - # has_parent returns a float... cast to bool - if sref.has_parent() < 0.9: - roots.append(section) - - # Build list of all sections - order_list = [] - for r in roots: - add_pre(h,[],r,order_list,0) - return order_list - -def branch_order(h,section, path=[]): - """ - Returns the branch order of a section - """ - path.append(section) - sref = h.SectionRef(sec=section) - # has_parent returns a float... cast to bool - if sref.has_parent() < 0.9: - return 0 # section is a root - else: - nchild = len(list(h.SectionRef(sec=sref.parent).child)) - if nchild <= 1.1: - return branch_order(h,sref.parent,path) - else: - return 1+branch_order(h,sref.parent,path) - -def dist_to_mark(h, section, secdict, path=[]): - path.append(section) - sref = h.SectionRef(sec=section) - # print 'current : '+str(section) - # print 'parent : '+str(sref.parent) - if secdict[sref.parent] is None: - # print '-> go to parent' - s = section.L + dist_to_mark(h, sref.parent, secdict, path) - # print 'summing, '+str(s) - return s - else: - # print 'end <- start summing: '+str(section.L) - return section.L # parent is marked - -def branch_precedence(h): - roots = root_sections(h) - leaves = leaf_sections(h) - seclist = allsec_preorder(h) - secdict = { sec:None for sec in seclist } - - for r in roots: - secdict[r] = 0 - - precedence = 1 - while len(leaves)>0: - # build list of distances of all paths to remaining leaves - d = [] - for leaf in leaves: - p = [] - dist = dist_to_mark(h, leaf, secdict, path=p) - d.append((dist,[pp for pp in p])) - - # longest path index - i = np.argmax([ dd[0] for dd in d ]) - leaves.pop(i) # this leaf will be marked - - # mark all sections in longest path - for sec in d[i][1]: - if secdict[sec] is None: - secdict[sec] = precedence - - # increment precedence across iterations - precedence += 1 - - #prec = secdict.values() - #return [0 if p is None else 1 for p in prec], d[i][1] - return [ secdict[sec] for sec in seclist ] - - -from neuron import h -import json - -def parent(sec): - seg = sec.trueparentseg() - if seg is None: - return None - else: - return seg.sec - -def parent_loc(sec, trueparent): - seg = sec.trueparentseg() - if seg is None: - return None - else: - return seg.x - -def morphology_to_dict(sections, outfile=None): - section_map = {sec: i for i, sec in enumerate(sections)} - result = [] - h.define_shape() - - for sec in sections: - my_parent = parent(sec) - my_parent_loc = -1 if my_parent is None else parent_loc(sec, my_parent) - my_parent = -1 if my_parent is None else section_map[my_parent] - n3d = int(h.n3d(sec=sec)) - result.append({ - 'section_orientation': h.section_orientation(sec=sec), - 'parent': my_parent, - 'parent_loc': my_parent_loc, - 'x': [h.x3d(i, sec=sec) for i in range(n3d)], - 'y': [h.y3d(i, sec=sec) for i in range(n3d)], - 'z': [h.z3d(i, sec=sec) for i in range(n3d)], - 'diam': [h.diam3d(i, sec=sec) for i in range(n3d)], - 'name': sec.hname() - }) - - if outfile is not None: - with open(outfile, 'w') as f: - json.dump(result, f) - - return result - - -def load_json(morphfile): - - with open(morphfile, 'r') as f: - secdata = json.load(morphfile) - - seclist = [] - for sd in secdata: - # make section - sec = h.Section(name=sd['name']) - seclist.append(sec) - - - # make 3d morphology - for x,y,z,d in zip(sd['x'], sd['y'], sd['z'], sd('diam')): - h.pt3dadd(x, y, z, d, sec=sec) - - # connect children to parent compartments - for sec,sd in zip(seclist,secdata): - if sd['parent_loc'] >= 0: - parent_sec = sec_list[sd['parent']] - sec.connect(parent_sec(sd['parent_loc']), sd['section_orientation']) - - return seclist diff --git a/netParams.py b/netParams.py deleted file mode 100644 index 54271e666..000000000 --- a/netParams.py +++ /dev/null @@ -1,127 +0,0 @@ -# netParams.py - High-level specifications for network model using NetPyNE -from netpyne import specs - -try: - from __main__ import cfg # import SimConfig object with params from parent module -except: - from cfg import cfg # if no simConfig in parent module, import directly from cfg module - -############################################################################### -# -# NETWORK PARAMETERS -# -############################################################################### - -netParams = specs.NetParams() # object of class NetParams to store the network parameters - -############################################################################### -# Cell parameters -############################################################################### - -# L2Pyr params -cellRule = netParams.importCellParams(label='L2Pyr',conds={'cellType':'L2Pyr','cellModel':'HH_reduced'}, - fileName='L2_pyramidal.py',cellName='L2Pyr') - -cellRule['secLists']['alldend'] = [] -cellRule['secLists']['apicdend'] = [] -cellRule['secLists']['basaldend'] = [] - - -# L2Bas params -cellRule = netParams.importCellParams(label='L2Bas',conds={'cellType':'L2Bas','cellModel':'HH_simple'}, - fileName='L2_basket.py',cellName='L2Basket') - - - -# L5Pyr params -cellRule = netParams.importCellParams(label='L5Pyr',conds={'cellType':'L5Pyr','cellModel':'HH_reduced'}, - fileName='L5_pyramidal.py',cellName='L5Pyr') - - -# L5Bas params -cellRule = netParams.importCellParams(label='L5Bas',conds={'cellType':'L5Bas','cellModel':'HH_simple'}, - fileName='L5_basket.py',cellName='L5Basket') - - -""" -# PT cell params (6-comp) -cellRule = netParams.importCellParams(label='PT_6comp', conds={'cellType': 'PT', 'cellModel': 'HH_reduced'}, - fileName='cells/SPI6.py', cellName='SPI6') - -cellRule['secLists']['alldend'] = ['Bdend', 'Adend1', 'Adend2', 'Adend3'] # define section lists -cellRule['secLists']['apicdend'] = ['Adend1', 'Adend2', 'Adend3'] - -for secName,sec in cellRule['secs'].iteritems(): - sec['vinit'] = -75.0413649414 # set vinit for all secs - if secName in cellRule['secLists']['alldend']: - sec['mechs']['nax']['gbar'] = cfg.dendNa # set dend Na gmax for all dends -""" - -############################################################################### -# Population parameters -############################################################################### -#netParams.popParams['PT5B'] = {'cellModel': 'HH_reduced', 'cellType': 'PT', 'numCells': 1} - -num = { - 'E': 100, - 'I': 35 -} - -p = 1.0 - -netParams.popParams['L2Bas'] = {'cellModel': 'HH_simple', 'cellType': 'L2Bas', 'numCells': int(p*num['E'])} -netParams.popParams['L2Pyr'] = {'cellModel': 'HH_reduced', 'cellType': 'L2Pyr', 'numCells': int(p*num['I'])} -netParams.popParams['L5Bas'] = {'cellModel': 'HH_simple', 'cellType': 'L5Bas', 'numCells': int(p*num['E'])} -netParams.popParams['L5Pyr'] = {'cellModel': 'HH_reduced', 'cellType': 'L5Pyr', 'numCells': int(p*num['I'])} - - - -############################################################################### -# Synaptic mechanism parameters -############################################################################### -# netParams.synMechParams['NMDA'] = {'mod': 'MyExp2SynNMDABB', 'tau1NMDA': cfg.tau1NMDA, 'tau2NMDA': 150, 'e': 0} - -#------------------------------------------------------------------------------ -# Synaptic mechanism parameters -#------------------------------------------------------------------------------ -netParams.synMechParams['NMDA'] = {'mod': 'NMDA'} #, 'tau1NMDA': 15, 'tau2NMDA': 150, 'e': 0} -netParams.synMechParams['AMPA'] = {'mod':'AMPA'}#, 'tau1': 0.05, 'tau2': 5.3, 'e': 0} -netParams.synMechParams['GABAA'] = {'mod':'GABAA'}#, 'tau1': 0.07, 'tau2': 18.2, 'e': -80} - -ESynMech = ['AMPA','NMDA'] - - - -""" -############################################################################### -# Current inputs (IClamp) -############################################################################### -if cfg.addIClamp: - for iclabel in [k for k in dir(cfg) if k.startswith('IClamp')]: - ic = getattr(cfg, iclabel, None) # get dict with params - - # add stim source - netParams.stimSourceParams[iclabel] = {'type': 'IClamp', 'delay': ic['start'], 'dur': ic['dur'], 'amp': ic['amp']} - - # connect stim source to target - netParams.stimTargetParams[iclabel+'_'+ic['pop']] = \ - {'source': iclabel, 'conds': {'pop': ic['pop']}, 'sec': ic['sec'], 'loc': ic['loc']} - - -############################################################################### -# NetStim inputs -############################################################################### -if cfg.addNetStim: - for nslabel in [k for k in dir(cfg) if k.startswith('NetStim')]: - ns = getattr(cfg, nslabel, None) - - # add stim source - netParams.stimSourceParams[nslabel] = {'type': 'NetStim', 'start': ns['start'], 'interval': ns['interval'], - 'noise': ns['noise'], 'number': ns['number']} - - # connect stim source to target - netParams.stimTargetParams[nslabel+'_'+ns['pop']] = \ - {'source': nslabel, 'conds': {'pop': ns['pop']}, 'sec': ns['sec'], 'loc': ns['loc'], - 'synMech': ns['synMech'], 'weight': ns['weight'], 'delay': ns['delay']} -""" - diff --git a/network.py b/network.py deleted file mode 100644 index 0993073dc..000000000 --- a/network.py +++ /dev/null @@ -1,426 +0,0 @@ -# class_net.py - establishes the Network class and related methods -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: removed izip) -# last major: (SL: toward python3) - -import itertools as it -import numpy as np -import sys - -from neuron import h -from feed import ParFeedAll -from L2_pyramidal import L2Pyr -from L5_pyramidal import L5Pyr -from L2_basket import L2Basket -from L5_basket import L5Basket -import paramrw as paramrw - -# create Network class -class NetworkOnNode (): - - def __init__ (self, p): - # set the params internally for this net - # better than passing it around like ... - self.p = p - # Number of time points - # Originally used to create the empty vec for synaptic currents, - # ensuring that they exist on this node irrespective of whether - # or not cells of relevant type actually do - self.N_t = np.arange(0., h.tstop, self.p['dt']).size + 1 - # Create a h.Vector() with size 1xself.N_t, zero'd - self.current = { - 'L5Pyr_soma': h.Vector(self.N_t, 0), - 'L2Pyr_soma': h.Vector(self.N_t, 0), - } - # int variables for grid of pyramidal cells (for now in both L2 and L5) - self.gridpyr = { - 'x': self.p['N_pyr_x'], - 'y': self.p['N_pyr_y'], - } - # Parallel stuff - self.pc = h.ParallelContext() - self.n_hosts = int(self.pc.nhost()) - self.rank = int(self.pc.id()) - self.N_src = 0 - # seed debugging - # for key, val in self.p.items(): - # if key.startswith('prng_seedcore_'): - # print("in net: %i, %s, %i" % (self.rank, key, val)) - self.N = {} # numbers of sources - self.N_cells = 0 # init self.N_cells - # zdiff is expressed as a positive DEPTH of L5 relative to L2 - # this is a deviation from the original, where L5 was defined at 0 - # this should not change interlaminar weight/delay calculations - self.zdiff = 1307.4 - # params of external inputs in p_ext - # Global number of external inputs ... automatic counting makes more sense - # p_unique represent ext inputs that are going to go to each cell - self.p_ext, self.p_unique = paramrw.create_pext(self.p, h.tstop) - self.N_extinput = len(self.p_ext) - # Source list of names - # in particular order (cells, extinput, alpha names of unique inputs) - self.src_list_new = self.__create_src_list() - # cell position lists, also will give counts: must be known by ALL nodes - # extinput positions are all located at origin. - # sort of a hack bc of redundancy - self.pos_dict = dict.fromkeys(self.src_list_new) - # create coords in pos_dict for all cells first - self.__create_coords_pyr() - self.__create_coords_basket() - self.__count_cells() - # create coords for all other sources - self.__create_coords_extinput() - # count external sources - self.__count_extsrcs() - # create dictionary of GIDs according to cell type - # global dictionary of gid and cell type - self.gid_dict = {} - self.__create_gid_dict() - # assign gid to hosts, creates list of gids for this node in __gid_list - # __gid_list length is number of cells assigned to this id() - self.__gid_list = [] - self.__gid_assign() - # create cells (and create self.origin in create_cells_pyr()) - self.cells = [] - self.extinput_list = [] - # external unique input list dictionary - self.ext_list = dict.fromkeys(self.p_unique) - # initialize the lists in the dict - for key in self.ext_list.keys(): self.ext_list[key] = [] - # create sources and init - self.__create_all_src() - self.state_init() - # parallel network connector - self.__parnet_connect() - # set to record spikes - self.spiketimes = h.Vector() - self.spikegids = h.Vector() - self.__record_spikes() - - # creates the immutable source list along with corresponding numbers of cells - def __create_src_list (self): - # base source list of tuples, name and number, in this order - self.cellname_list = [ - 'L2_basket', - 'L2_pyramidal', - 'L5_basket', - 'L5_pyramidal', - ] - # add the legacy extinput here - self.extname_list = [] - self.extname_list.append('extinput') - # grab the keys for the unique set of inputs and sort the names - # append them to the src list along with the number of cells - unique_keys = sorted(self.p_unique.keys()) - self.extname_list += unique_keys - # return one final source list - src_list = self.cellname_list + self.extname_list - return src_list - - # Creates cells and grid - def __create_coords_pyr (self): - """ pyr grid is the immutable grid, origin now calculated in relation to feed - """ - xrange = np.arange(self.gridpyr['x']) - yrange = np.arange(self.gridpyr['y']) - # create list of tuples/coords, (x, y, z) - self.pos_dict['L2_pyramidal'] = [pos for pos in it.product(xrange, yrange, [0])] - self.pos_dict['L5_pyramidal'] = [pos for pos in it.product(xrange, yrange, [self.zdiff])] - - # create basket cell coords based on pyr grid - def __create_coords_basket (self): - # define relevant x spacings for basket cells - xzero = np.arange(0, self.gridpyr['x'], 3) - xone = np.arange(1, self.gridpyr['x'], 3) - # split even and odd y vals - yeven = np.arange(0, self.gridpyr['y'], 2) - yodd = np.arange(1, self.gridpyr['y'], 2) - # create general list of x,y coords and sort it - coords = [pos for pos in it.product(xzero, yeven)] + [pos for pos in it.product(xone, yodd)] - coords_sorted = sorted(coords, key=lambda pos: pos[1]) - # append the z value for position for L2 and L5 - # print(len(coords_sorted)) - self.pos_dict['L2_basket'] = [pos_xy + (0,) for pos_xy in coords_sorted] - self.pos_dict['L5_basket'] = [pos_xy + (self.zdiff,) for pos_xy in coords_sorted] - - # creates origin AND creates external input coords - def __create_coords_extinput (self): - """ (same thing for now but won't fix because could change) - """ - xrange = np.arange(self.gridpyr['x']) - yrange = np.arange(self.gridpyr['y']) - # origin's z component isn't really used in calculating distance functions from origin - # these will be forced as ints! - origin_x = xrange[int((len(xrange)-1)//2)] - origin_y = yrange[int((len(yrange)-1)//2)] - origin_z = np.floor(self.zdiff/2) - self.origin = (origin_x, origin_y, origin_z) - self.pos_dict['extinput'] = [self.origin for i in range(self.N_extinput)] - # at this time, each of the unique inputs is per cell - for key in self.p_unique.keys(): - # create the pos_dict for all the sources - self.pos_dict[key] = [self.origin for i in range(self.N_cells)] - - # cell counting routine - def __count_cells (self): - # cellname list is used *only* for this purpose for now - for src in self.cellname_list: - # if it's a cell, then add the number to total number of cells - self.N[src] = len(self.pos_dict[src]) - self.N_cells += self.N[src] - - # general counting method requires pos_dict is correct for each source - # and that all sources are represented - def __count_extsrcs (self): - # all src numbers are based off of length of pos_dict entry - # generally done here in lieu of upstream changes - for src in self.extname_list: - self.N[src] = len(self.pos_dict[src]) - - # creates gid dicts and pos_lists - def __create_gid_dict (self): - # initialize gid index gid_ind to start at 0 - gid_ind = [0] - # append a new gid_ind based on previous and next cell count - # order is guaranteed by self.src_list_new - for i in range(len(self.src_list_new)): - # N = self.src_list_new[i][1] - # grab the src name in ordered list src_list_new - src = self.src_list_new[i] - # query the N dict for that number and append here to gid_ind, based on previous entry - gid_ind.append(gid_ind[i]+self.N[src]) - # accumulate total source count - self.N_src += self.N[src] - # now actually assign the ranges - for i in range(len(self.src_list_new)): - src = self.src_list_new[i] - self.gid_dict[src] = range(gid_ind[i], gid_ind[i+1]) - - # this happens on EACH node - # creates self.__gid_list for THIS node - def __gid_assign (self): - # round robin assignment of gids - for gid in range(self.rank, self.N_cells, self.n_hosts): - # set the cell gid - self.pc.set_gid2node(gid, self.rank) - self.__gid_list.append(gid) - # now to do the cell-specific external input gids on the same proc - # these are guaranteed to exist because all of these inputs were created - # for each cell - for key in self.p_unique.keys(): - gid_input = gid + self.gid_dict[key][0] - self.pc.set_gid2node(gid_input, self.rank) - self.__gid_list.append(gid_input) - # legacy handling of the external inputs - # NOT perfectly balanced for now - for gid_base in range(self.rank, self.N_extinput, self.n_hosts): - # shift the gid_base to the extinput gid - gid = gid_base + self.gid_dict['extinput'][0] - # set as usual - self.pc.set_gid2node(gid, self.rank) - self.__gid_list.append(gid) - # extremely important to get the gids in the right order - self.__gid_list.sort() - - # reverse lookup of gid to type - def gid_to_type (self, gid): - for gidtype, gids in self.gid_dict.items(): - if gid in gids: - return gidtype - - """ - def checkInputOn (self, type): - if type.startswith('ev'): - if self.p['useEvokedInputs']: - return True - else: - return False - return True - """ - - # reset src (source/external) event times - # evinputinc is an offset for evoked inputs (added to mean start time - e.g. per trial increment) - def reset_src_event_times (self, seed=None,debug=False, inc_evinput = 0.0): - if debug: - print('in reset_src_input_times') - print('self.extinput_list:',self.extinput_list) - print('self.ext_list:',type(self.ext_list),self.ext_list) - - for feed in self.extinput_list: - if seed is None: - feed.inc_prng(1000) - else: - feed.set_prng(seed) - feed.set_event_times(inc_evinput) # uses feed.seed - - for k,lfeed in self.ext_list.items(): # dictionary of lists... - for feed in lfeed: # of feeds - if seed is None: - feed.inc_prng(1) - else: - feed.set_prng(seed) - feed.set_event_times(inc_evinput) # uses feed.seed - - # parallel create cells AND external inputs (feeds) - # these are spike SOURCES but cells are also targets - # external inputs are not targets - def __create_all_src (self): - #print('in __create_all_src') - # loop through gids on this node - for gid in self.__gid_list: - # check existence of gid with Neuron - if self.pc.gid_exists(gid): - # get type of cell and pos via gid - # now should be valid for ext inputs - type = self.gid_to_type(gid) - type_pos_ind = gid - self.gid_dict[type][0] - pos = self.pos_dict[type][type_pos_ind] - # figure out which cell type is assoc with the gid - # create cells based on loc property - # creates a NetCon object internally to Neuron - if type == 'L2_pyramidal': - self.cells.append(L2Pyr(gid, pos, self.p)) - self.pc.cell(gid, self.cells[-1].connect_to_target(None, self.p['threshold'])) - # run the IClamp function here - # create_all_IClamp() is defined in L2Pyr (etc) - self.cells[-1].create_all_IClamp(self.p) - if self.p['save_vsoma']: self.cells[-1].record_volt_soma() - elif type == 'L5_pyramidal': - self.cells.append(L5Pyr(gid, pos, self.p)) - self.pc.cell(gid, self.cells[-1].connect_to_target(None,self.p['threshold'])) - # run the IClamp function here - self.cells[-1].create_all_IClamp(self.p) - if self.p['save_vsoma']: self.cells[-1].record_volt_soma() - elif type == 'L2_basket': - self.cells.append(L2Basket(gid, pos)) - self.pc.cell(gid, self.cells[-1].connect_to_target(None,self.p['threshold'])) - # also run the IClamp for L2_basket - self.cells[-1].create_all_IClamp(self.p) - if self.p['save_vsoma']: self.cells[-1].record_volt_soma() - elif type == 'L5_basket': - self.cells.append(L5Basket(gid, pos)) - self.pc.cell(gid, self.cells[-1].connect_to_target(None,self.p['threshold'])) - # run the IClamp function here - self.cells[-1].create_all_IClamp(self.p) - if self.p['save_vsoma']: self.cells[-1].record_volt_soma() - elif type == 'extinput': - #print('type',type) - # to find param index, take difference between REAL gid - # here and gid start point of the items - p_ind = gid - self.gid_dict['extinput'][0] - # now use the param index in the params and create - # the cell and artificial NetCon - self.extinput_list.append(ParFeedAll(type, None, self.p_ext[p_ind], gid)) - self.pc.cell(gid, self.extinput_list[-1].connect_to_target(self.p['threshold'])) - elif type in self.p_unique.keys(): - gid_post = gid - self.gid_dict[type][0] - cell_type = self.gid_to_type(gid_post) - # create dictionary entry, append to list - self.ext_list[type].append(ParFeedAll(type, cell_type, self.p_unique[type], gid)) - self.pc.cell(gid, self.ext_list[type][-1].connect_to_target(self.p['threshold'])) - else: - print("None of these types in Net()") - exit() - else: - print("GID does not exist. See Cell()") - exit() - - # connections: - # this NODE is aware of its cells as targets - # for each syn, return list of source GIDs. - # for each item in the list, do a: - # nc = pc.gid_connect(source_gid, target_syn), weight,delay - # Both for synapses AND for external inputs - def __parnet_connect (self): - # loop over target zipped gids and cells - # cells has NO extinputs anyway. also no extgausses - for gid, cell in zip(self.__gid_list, self.cells): - # ignore iteration over inputs, since they are NOT targets - if self.pc.gid_exists(gid) and self.gid_to_type(gid) is not 'extinput': - # print("rank:", self.rank, "gid:", gid, cell, self.gid_to_type(gid)) - # for each gid, find all the other cells connected to it, based on gid - # this MUST be defined in EACH class of cell in self.cells - # parconnect receives connections from other cells - # parreceive receives connections from external inputs - cell.parconnect(gid, self.gid_dict, self.pos_dict, self.p) - cell.parreceive(gid, self.gid_dict, self.pos_dict, self.p_ext) - # now do the unique inputs specific to these cells - # parreceive_ext receives connections from UNIQUE external inputs - for type in self.p_unique.keys(): - p_type = self.p_unique[type] - # print('parnet_connect p_type:',p_type) - cell.parreceive_ext(type, gid, self.gid_dict, self.pos_dict, p_type) - - # setup spike recording for this node - def __record_spikes (self): - # iterate through gids on this node and - # set to record spikes in spike time vec and id vec - # agnostic to type of source, will sort that out later - for gid in self.__gid_list: - if self.pc.gid_exists(gid): - self.pc.spike_record(gid, self.spiketimes, self.spikegids) - - def get_vsoma (self): - dsoma = {} - for cell in self.cells: dsoma[cell.gid] = (cell.celltype, np.array(cell.vsoma.to_python())) - return dsoma - - # aggregate recording all the somatic voltages for pyr - def aggregate_currents (self): - """ this method must be run post-integration - """ - # this is quite ugly - for cell in self.cells: - # check for celltype - if cell.celltype == 'L5_pyramidal': - # iterate over somatic currents, assumes this list exists - # is guaranteed in L5Pyr() - for key, I_soma in cell.dict_currents.items(): - # self.current_L5Pyr_soma was created upon - # in parallel, each node has its own Net() - self.current['L5Pyr_soma'].add(I_soma) - elif cell.celltype == 'L2_pyramidal': - for key, I_soma in cell.dict_currents.items(): - # self.current_L5Pyr_soma was created upon - # in parallel, each node has its own Net() - self.current['L2Pyr_soma'].add(I_soma) - - # recording debug function - def rec_debug (self, rank_exec, gid): - # only execute on this rank, make sure called properly - if rank_exec == self.rank: - # only if the gid exists here - # this will break if non-cell source is attempted - if gid in self.__gid_list: - n = self.__gid_list.index(gid) - v = h.Vector() - v.record(self.cells[n].soma(0.5)._ref_v) - return v - - # initializes the state closer to baseline - def state_init (self): - for cell in self.cells: - seclist = h.SectionList() - seclist.wholetree(sec=cell.soma) - for sect in seclist: - for seg in sect: - if cell.celltype == 'L2_pyramidal': - seg.v = -71.46 - elif cell.celltype == 'L5_pyramidal': - if sect.name() == 'L5Pyr_apical_1': - seg.v = -71.32 - elif sect.name() == 'L5Pyr_apical_2': - seg.v = -69.08 - elif sect.name() == 'L5Pyr_apical_tuft': - seg.v = -67.30 - else: - seg.v = -72. - elif cell.celltype == 'L2_basket': - seg.v = -64.9737 - elif cell.celltype == 'L5_basket': - seg.v = -64.9737 - - # move cells 3d positions to positions used for wiring - def movecellstopos (self): - for cell in self.cells: cell.movetopos() diff --git a/nsgr.py b/nsgr.py deleted file mode 100644 index 4d70de488..000000000 --- a/nsgr.py +++ /dev/null @@ -1,270 +0,0 @@ -# based on https://github.com/kenneth59715/nsg-rest-client/blob/master/nsg.nopassword.ipynb -# This works with python 3, with requests module installed -# use port 8443 for production, 8444 for test -# register at https://www.nsgportal.org/reg/reg.php for username and password - -import os -import requests -import xml.etree.ElementTree -import time -import sys -import re -import zipfile -import tarfile -import glob -from conf import dconf - -debug = dconf['debug'] - -def getuserpass (): - f = open('nsgr.txt') - l = f.readlines() - CRA_USER = l[0].strip() - PASSWORD = l[1].strip() # #'changeme' - f.close() - return CRA_USER,PASSWORD - -CRA_USER,PASSWORD = getuserpass() # this will be collected from the HNN GUI later on - -# for production version: -# log in at https://nsgr.sdsc.edu:8443/restusers/login.action -# Tool names can be found at Developer->Documentation (Tools: How to Configure Specific Tools) -# create a new application at Developer->Application Management (Create New Application) -# save the Application Key for use in REST requests - -KEY = 'HNN-418776D750A84FC28A19D5EF1C7B4933' -TOOL = 'SINGULARITY_HNN_TG' -URL = 'https://nsgr.sdsc.edu:8443/cipresrest/v1' # for production version - -def createpayload (paramf, ntrial, tstop): - # returns dictionary of parameters for the NSG job - payload = {'metadata.statusEmail' : 'true'} - payload['vparam.runtime_'] = 0.1 # 0.5 - payload['vparam.filename_'] = 'run.py' - if ntrial == 0: ntrial = 1 - payload['vparam.cmdlineopts_'] = '-nohomeout -paramf ' + os.path.join('param',paramf) + ' ' + str(ntrial) - payload['vparam.number_nodes_'] = 1 - payload['tool'] = TOOL - return payload - -# -def prepinputzip (fout='inputfile.zip'): - """ prepares input zip file for NSGR; file contains all py,mod,param,cfg - files needed to run the simulation """ - try: - if debug: print('Preparing NSGR input zip file...',fout) - fp = zipfile.ZipFile(fout, "w") - lglob = ['*.py','mod/*.mod','*.cfg','param/*.param','res/*.png','Makefile'] - for glb in lglob: - for name in glob.glob(glb): - #if debug: print('adding:',os.path.realpath(name)) - if name.endswith('.mod'): - fp.write(name, 'hnn/mod/'+os.path.basename(name), zipfile.ZIP_DEFLATED) - elif name.endswith('.param'): - fp.write(name,'hnn/param/'+os.path.basename(name),zipfile.ZIP_DEFLATED) - elif name.endswith('.png'): - fp.write(name,'hnn/res/'+os.path.basename(name),zipfile.ZIP_DEFLATED) - else: - fp.write(name, 'hnn/'+os.path.basename(name), zipfile.ZIP_DEFLATED) - fp.close() - return True - except: - print('prepinputzip ERR: could not prepare input zip file',fout,'for NSGR.') - return False - -# -def untar (fname): - # extract contents of tar gz file to current directory - tar = tarfile.open(fname) - tar.extractall() - tar.close() - print("Extracted",fname," in Current Directory.") - -# -def procoutputtar (fname='output.tar.gz'):#,simstr='default'): - """ process HNN NSGR output tar file, saving simulation data - and param file to appropriate directories """ - try: - tar = tarfile.open(fname) - for member in tar.getmembers(): - if member.isreg(): # skip if not a file (e.g. directory) - f = member.name - if f.count('data')>0: - lp = f.split(os.path.sep) - member.name = os.path.basename(member.name) # remove the path by resetting it - tar.extract(member,os.path.join('data',lp[-2])) # extract to data subdir - #tar.extract(member,os.path.join('data',simstr)) # extract to data subdir - if f.endswith('.param'): - tar.extract(member,'param') # extract to param subdir - tar.close() - if debug: print("Extracted",fname) - return True - except: - print('procoutputtar ERR: Could not extract contents of ',fname) - return False - -# -def cleanup (zippath='inputfile.zip'): - # cleanup the temporary NSGR files - try: - l = ['output.tar.gz', zippath, 'STDERR', 'STDOUT', 'scheduler_stdout.txt', 'scheduler_stderr.txt'] - for f in l: os.unlink(f) - except: - print('Could not cleanup temp files.') - -def runjobNSGR (paramf='default.param', ntrial=1, tstop=710.0): - """ run a simulation job on NSG using Restful interface; first prepares input zip - file, then submits job and waits for it to finish, finally downloads simulation output - data and extracts it to appropriate location """ - - try: - - payload = createpayload(paramf,ntrial,tstop) - print('payload:',payload) - headers = {'cipres-appkey' : KEY} # application KEY - zippath = os.path.realpath('inputfile.zip') - - if not prepinputzip(zippath): - print('runjobNSGR ERR: could not prepare NSGR input zip file',zippath) - return False - - files = {'input.infile_' : open(zippath,'rb')} # input zip file with code to run - - r = requests.post('{}/job/{}'.format(URL, CRA_USER), auth=(CRA_USER, PASSWORD), data=payload, headers=headers, files=files) - #if debug: print(r.text) - root = xml.etree.ElementTree.fromstring(r.text) - - #if debug: print(r.text) - print(r.url) - - for child in root: - if child.tag == 'resultsUri': - for urlchild in child: - if urlchild.tag == 'url': - outputuri = urlchild.text - if child.tag == 'selfUri': - for urlchild in child: - if urlchild.tag == 'url': - selfuri = urlchild.text - - if debug: print(outputuri) - if debug: print(selfuri) - - print('Waiting for NSG job to complete. . .') - jobdone = False - while not jobdone: - r = requests.get(selfuri, auth=(CRA_USER, PASSWORD), headers=headers) - #if debug: print(r.text) - root = xml.etree.ElementTree.fromstring(r.text) - for child in root: - if child.tag == 'terminalStage': - jobstatus = child.text - if jobstatus == 'false': - time.sleep(5) - else: - jobdone = True - break - - print('Job completion detected, getting download URIs...') - - r = requests.get(outputuri, - headers= headers, auth=(CRA_USER, PASSWORD)) - #if debug: print(r.text) - globaldownloadurilist = [] - root = xml.etree.ElementTree.fromstring(r.text) - for child in root: - if child.tag == 'jobfiles': - for jobchild in child: - if jobchild.tag == 'jobfile': - for downloadchild in jobchild: - if downloadchild.tag == 'downloadUri': - for attchild in downloadchild: - if attchild.tag == 'url': - print(attchild.text) - globaldownloadurilist.append(attchild.text) - - print('NSG download complete.') - - globaloutputdict = {} - for downloaduri in globaldownloadurilist: - r = requests.get(downloaduri, auth=(CRA_USER, PASSWORD), headers=headers) - #if debug: print(r.text) - globaloutputdict[downloaduri] = r.text - - #http://stackoverflow.com/questions/31804799/how-to-get-pdf-filename-with-python-requests - for downloaduri in globaldownloadurilist: - r = requests.get(downloaduri, auth=(CRA_USER, PASSWORD), headers=headers) - print(r.headers) - d = r.headers['content-disposition'] - fname_list = re.findall("filename=(.+)", d) - for fname in fname_list: - if debug: print(fname) - - # download all output files - for downloaduri in globaldownloadurilist: - r = requests.get(downloaduri, auth=(CRA_USER, PASSWORD), headers=headers) - #if debug: print(r.json()) - #r.content - d = r.headers['content-disposition'] - filename_list = re.findall('filename=(.+)', d) - for filename in filename_list: - #http://docs.python-requests.org/en/master/user/quickstart/#raw-response-content - with open(filename, 'wb') as fd: - for chunk in r.iter_content(): - fd.write(chunk) - - # get a list of jobs for user and app key, and terminalStage status - r = requests.get("%s/job/%s" % (URL,CRA_USER), auth=(CRA_USER, PASSWORD), headers=headers) - #print(r.text) - - ldeluri = [] # list of jobs to delete - root = xml.etree.ElementTree.fromstring(r.text) - for child in root: - if child.tag == 'jobs': - for jobchild in child: - if jobchild.tag == 'jobstatus': - for statuschild in jobchild: - if statuschild.tag == 'selfUri': - for selfchild in statuschild: - if selfchild.tag == 'url': - #print(child) - joburi = selfchild.text - jobr = requests.get(joburi, auth=(CRA_USER, PASSWORD), headers=headers) - jobroot = xml.etree.ElementTree.fromstring(jobr.text) - for jobrchild in jobroot: - if jobrchild.tag == 'terminalStage': - jobstatus = jobrchild.text - if debug: print('job url:',joburi,' status terminalStage:',jobstatus) - ldeluri.append(joburi) - - # get information for a single job, print out raw XML, need to set joburi according to above list - # delete an old job, need to set joburi - for joburi in ldeluri: - if debug: print('deleting old job with joburi = ',joburi) - r = requests.get(joburi, headers= headers, auth=(CRA_USER, PASSWORD)) - #print(r.text) - r = requests.delete(joburi, auth=(CRA_USER, PASSWORD), headers=headers) - if debug: print(r.text) - - #if not procoutputtar('output.tar.gz',paramf.split('.param')[0]): - if not procoutputtar('output.tar.gz'): - print('runjobNSGR ERR: could not extract simulation output data.') - return False - - if not debug: cleanup() - - return True - - except: - print('runjobNSGR: unhandled exception!') - return False - -if __name__ == '__main__': - if debug: print(sys.argv) - if len(sys.argv) < 4: - print('usage: python nsgr.py paramf ntrial tstop') - else: - print(sys.argv) - paramf = sys.argv[1].split(os.path.sep)[-1] - print('paramf:',paramf) - runjobNSGR(paramf=paramf, ntrial=int(sys.argv[2]), tstop=float(sys.argv[3])) diff --git a/paramrw.py b/paramrw.py index 89ae40b7e..b277bd5a9 100644 --- a/paramrw.py +++ b/paramrw.py @@ -5,16 +5,19 @@ # last major: (SL: cleanup of self.p_all) import re -import fileio as fio import numpy as np import itertools as it -# from cartesian import cartesian -from params_default import get_params_default from hnn_core import read_params +# Cleans input files +def clean_lines (file): + with open(file) as f_in: + lines = (line.rstrip() for line in f_in) + lines = [line for line in lines if line] + return lines + def validate_param_file (fn): - # DEPRECATE - should validate file when opening for the first time (hnn-core) try: fp = open(fn, 'r') except OSError: @@ -151,320 +154,8 @@ def usingTonicInputs (d): if t0 < t1 or t1 == -1.0: return True return False -# class controlling multiple simulation files (.param) -class ExpParams(): - # DEPRECATE - # need to add 'spec_cmap' to hnn-core - def __init__ (self, f_psim, debug=False): - - self.debug = debug - self.paramf = f_psim - - self.expmt_group_params = [] - - # self.prng_seedcore = {} - # this list is simply to access these easily - self.prng_seed_list = [] - - # read in params from a file - p_all_input = self.__read_sim(f_psim) - self.p_template = dict.fromkeys(self.expmt_group_params) - - # create non-exp params dict from default dict - self.p_all = self.__create_dict_from_default(p_all_input) - - # pop off fixed known vals and create experimental prefix templates - self.__pop_known_values() - - # make dict of coupled params - self.coupled_params = self.__find_coupled_params() - - # create the list of iterated params - self.list_params = self.__create_paramlist() - self.N_sims = len(self.list_params[0][1]) - - # return pdict based on that one value, PLUS append the p_ext here ... yes, hack-y - def return_pdict(self, expmt_group, i): - # p_template was always updated to include the ones from exp and others - p_sim = dict.fromkeys(self.p_template) - - # go through params in list_params - for param, val_list in self.list_params: - if param.startswith('prng_seedcore_'): - p_sim[param] = int(val_list[i]) - else: - p_sim[param] = val_list[i] - - # go through the expmt group-based params - for param, val in read_params(self.paramf).items(): - p_sim[param] = val - - # add alpha distributions. A bit hack-y - for param, val in self.alpha_distributions.items(): - p_sim[param] = val - - # Add coupled params - for coupled_param, val_param in self.coupled_params.items(): - p_sim[coupled_param] = p_sim[val_param] - - # Add spec_cmap - p_sim['spec_cmap'] = self.spec_cmap - - return p_sim - - # reads .param file and returns p_all_input dict - def __read_sim(self, f_psim): - lines = fio.clean_lines(f_psim) - - # ignore comments - lines = [line for line in lines if line[0] is not '#'] - p = {} - - for line in lines: - # splits line by ':' - param, val = line.split(": ") - - # sim_prefix is not a rotated variable - # not sure why `if param is 'sim_prefix':` does not work here - if param == 'sim_prefix': - p[param] = str(val) - - # expmt_groups must be listed before other vals - elif param == 'expmt_groups': - # this list will be the preservation of the original order - self.expmt_groups = [expmt_group for expmt_group in val[1:-1].split(', ')] - - # this dict here for easy access - # p_group saves each of the changed params per group - self.p_group = dict.fromkeys(self.expmt_groups) - - # create empty dicts in each - for group in self.p_group: - self.p_group[group] = {} - - elif param.startswith('prng_seedcore_'): - p[param] = int(val) - # key = param.split('prng_seedcore_')[-1] - # self.prng_seedcore[key] = val - - # only add values that will change - if p[param] == -1: - self.prng_seed_list.append(param) - - elif param.startswith('distribution_'): - p[param] = str(val) - - elif param == 'Run_Date': - pass - - else: - # assign group params first - if val[0] is '{': - # check for a linspace as a param! - if val[1] is 'L': - # in this case, val_range must be as long as the correct expmt_group length - # everything beyond that will be truncated by the zip operation below - # param passed will strip away the curly braces and just pass the linspace - val_range = self.__expand_linspace(val[1:-1]) - else: - val_range = self.__expand_array(val) - - # add the expmt_group param to the list if it's not already present - if param not in self.expmt_group_params: - self.expmt_group_params.append(param) - - # parcel out vals to exp groups with assigned param names - for expmt_group, val in zip(self.expmt_groups, val_range): - self.p_group[expmt_group][param] = val - - # interpret this as a list of vals - # type floats to a np array - elif val[0] is '[': - p[param] = self.__expand_array(val) - - # interpret as a linspace - elif val[0] is 'L': - p[param] = self.__expand_linspace(val) - - elif val[0] is 'A': - p[param] = self.__expand_arange(val) - - else: - try: - p[param] = float(val) - except ValueError: - p[param] = str(val) - - # hack-y. sorry, future - # tstop_* = 0 is valid now, resets to the actual tstop - # with the added bonus of saving this time to the indiv params - for param, val in p.items(): - if param.startswith('tstop_'): - if isinstance(val, float): - if val == 0: - p[param] = p['tstop'] - elif isinstance(val, np.ndarray): - p[param][p[param] == 0] = p['tstop'] - - return p - - # general function to expand a list of values - def __expand_array(self, str_val): - val_list = str_val[1:-1].split(', ') - val_range = np.array([float(item) for item in val_list]) - - return val_range - - # general function to expand the arange - def __expand_arange(self, str_val): - # strip away the leading character along with the brackets and split the csv values - val_list = str_val[2:-1].split(', ') - - # use the values in val_list as params for np.linspace - val_range = np.arange(float(val_list[0]), float(val_list[1]), float(val_list[2])) - - # return the final linspace expanded - return val_range - - # general function to expand the linspace - def __expand_linspace(self, str_val): - # strip away the leading character along with the brackets and split the csv values - val_list = str_val[2:-1].split(', ') - - # use the values in val_list as params for np.linspace - val_range = np.linspace(float(val_list[0]), float(val_list[1]), int(val_list[2])) - - # return the final linspace expanded - return val_range - - # creates dict of params whose values are to be coupled - def __find_coupled_params(self): - coupled_params = {} - # iterates over all key/value pairs to find vals that are strings - for key, val in self.p_all.items(): - if isinstance(val, str): - # check that string is another param in p_all - if val in self.p_all.keys(): - coupled_params[key] = val - else: - print("Unknown key: %s. Probably going to error." % (val)) - - # Pop coupled params - for key in coupled_params: - self.p_all.pop(key) - - return coupled_params - - # pop known values & strings off of the params list - def __pop_known_values(self): - self.sim_prefix = self.p_all.pop('sim_prefix') - self.spec_cmap = self.p_all.pop('spec_cmap') - - # create an experimental string prefix template - self.exp_prefix_str = self.sim_prefix+"-%03d" - self.trial_prefix_str = self.exp_prefix_str+"-T%02d" - - # self.N_trials = int(self.p_all.pop('N_trials')) - # self.prng_state = self.p_all.pop('prng_state')[1:-1] - - # Save alpha distribution types in dict for later use - self.alpha_distributions = { - 'distribution_prox': self.p_all.pop('distribution_prox'), - 'distribution_dist': self.p_all.pop('distribution_dist'), - } - - # create the dict based on the default param dict - def __create_dict_from_default (self, p_all_input): - nprox, ndist = countEvokedInputs(p_all_input) - # print('found nprox,ndist ev inputs:', nprox, ndist) - - # create a copy of params_default through which to iterate - p_all = get_params_default(nprox, ndist) - - # now find ONLY the values that are present in the supplied p_all_input - # based on the default dict - for key in p_all.keys(): - # automatically expects that keys are either in p_all_input OR will resort - # to default value - if key in p_all_input: - # pop val off so the remaining items in p_all_input are extraneous - p_all[key] = p_all_input.pop(key) - - # now display extraneous keys, if there were any - if len(p_all_input): - if self.debug: print("Invalid keys from param file not found in default params: %s" % str(p_all_input.keys())) - - return p_all - - # creates all combination of non-exp params - def __create_paramlist (self): - # p_all is the dict specifying all of the changing params - plist = [] - - # get all key/val pairs from the all dict - list_sorted = [item for item in self.p_all.items()] - - # sort the list by the key (alpha) - list_sorted.sort(key=lambda x: x[0]) - - # grab just the keys (but now in order) - self.keys_sorted = [item[0] for item in list_sorted] - self.p_template.update(dict.fromkeys(self.keys_sorted)) - - # grab just the values (but now in order) - # plist = [item[1] for item in list_sorted] - for item in list_sorted: - if isinstance(item[1], np.ndarray): - plist.append(item[1]) - else: - plist.append(np.array([item[1]])) - - # print(plist) - # vals_all = cartesian(plist) - vals_new = np.array([np.array(val) for val in it.product(*plist)]) - vals_new = vals_new.transpose() - - return [item for item in zip(self.keys_sorted, vals_new)] - - # Find keys that change anytime during simulation - # (i.e. have more than one associated value) - def get_key_types(self): - key_dict = { - 'expmt_keys': [], - 'dynamic_keys': [], - 'static_keys': [], - } - - # Save exmpt keys - key_dict['expmt_keys'] = self.expmt_group_params - - # Save expmt keys as dynamic keys - key_dict['dynamic_keys'] = self.expmt_group_params - - # Find keys that change run to run within experiments - for key in self.p_all.keys(): - # if key has length associated with it, must change run to run - try: - len(self.p_all[key]) - - # Before storing key, check to make sure it has not already been stored - if key not in key_dict['dynamic_keys']: - key_dict['dynamic_keys'].append(key) - - except TypeError: - key_dict['static_keys'].append(key) - - # Check if coupled params are dynamic - for dep_param, ind_param in self.coupled_params.items(): - if ind_param in key_dict['dynamic_keys']: - key_dict['dynamic_keys'].append(dep_param) - else: - key_dict['static_keys'].append(dep_param) - - return key_dict - def read_gids_param (fparam): - lines = fio.clean_lines(fparam) + lines = clean_lines(fparam) gid_dict = {} for line in lines: if line.startswith('#'): continue @@ -494,295 +185,6 @@ def write_gids_param(fparam, gid_list): f.write('[]') f.write('\n') -# reads the simgroup name from fparam -def read_sim_prefix(fparam): - # DEPRECATE - params = read_params(fparam) - - try: - return params['sim_prefix'] - except KeyError: - print("No sim_prefix found") - return 0 - -# Finds the experiments list from the simulation param file (.param) -def read_expmt_groups(fparam): - # DEPRECATE - lines = fio.clean_lines(fparam) - lines = [line for line in lines if line.split(': ')[0] == 'expmt_groups'] - - try: - return lines[0].split(': ')[1][1:-1].split(', ') - except: - print("Couldn't get a handle on expmts") - return 0 - -# qnd function to add feeds if they are sensible -def feed_validate(p_ext, d, tstop): - # DEPRECATE - """ whips into shape ones that are not - could be properly made into a meaningful class. - """ - # only append if t0 is less than simulation tstop - if tstop > d['t0']: - # # reset tstop if the specified tstop exceeds the - # # simulation runtime - # if d['tstop'] == 0: - # d['tstop'] = tstop - - if d['tstop'] > tstop: - d['tstop'] = tstop - - # if stdev is zero, increase synaptic weights 5 fold to make - # single input equivalent to 5 simultaneous input to prevent spiking <<---- SN: WHAT IS THIS RULE!?!?!? - if not d['stdev'] and d['distribution'] != 'uniform': - for key in d.keys(): - if key.endswith('Pyr'): - d[key] = (d[key][0] * 5., d[key][1]) - elif key.endswith('Basket'): - d[key] = (d[key][0] * 5., d[key][1]) - - # if L5 delay is -1, use same delays as L2 unless L2 delay is 0.1 in which case use 1. <<---- SN: WHAT IS THIS RULE!?!?!? - if d['L5Pyr_ampa'][1] == -1: - for key in d.keys(): - if key.startswith('L5'): - if d['L2Pyr'][1] != 0.1: - d[key] = (d[key][0], d['L2Pyr'][1]) - else: - d[key] = (d[key][0], 1.) - - p_ext.append(d) - - return p_ext - -# -def checkevokedsynkeys (p, nprox, ndist): - # DEPRECATE - # make sure ampa,nmda gbar values are in the param dict for evoked inputs(for backwards compatibility) - lctprox = ['L2Pyr','L5Pyr','L2Basket','L5Basket'] # evoked distal target cell types - lctdist = ['L2Pyr','L5Pyr','L2Basket'] # evoked proximal target cell types - lsy = ['ampa','nmda'] # synapse types used in evoked inputs - for nev,pref,lct in zip([nprox,ndist],['evprox_','evdist_'],[lctprox,lctdist]): - for i in range(nev): - skey = pref + str(i+1) - for sy in lsy: - for ct in lct: - k = 'gbar_'+skey+'_'+ct+'_'+sy - # if the synapse-specific gbar not present, use the existing weight for both ampa,nmda - if k not in p: - p[k] = p['gbar_'+skey+'_'+ct] - -# -def checkpoissynkeys (p): - # DEPRECATE - # make sure ampa,nmda gbar values are in the param dict for Poisson inputs (for backwards compatibility) - lct = ['L2Pyr','L5Pyr','L2Basket','L5Basket'] # target cell types - lsy = ['ampa','nmda'] # synapse types used in Poisson inputs - for ct in lct: - for sy in lsy: - k = ct + '_Pois_A_weight_' + sy - # if the synapse-specific weight not present, set it to 0 in p - if k not in p: - p[k] = 0.0 - -# creates the external feed params based on individual simulation params p -def create_pext (p, tstop): - # DEPRECATE - # indexable py list of param dicts for parallel - # turn off individual feeds by commenting out relevant line here. - # always valid, no matter the length - p_ext = [] - - # p_unique is a dict of input param types that end up going to each cell uniquely - p_unique = {} - - # default params for proximal rhythmic inputs - feed_prox = { - 'f_input': p['f_input_prox'], - 't0': p['t0_input_prox'], - 'tstop': p['tstop_input_prox'], - 'stdev': p['f_stdev_prox'], - 'L2Pyr_ampa': (p['input_prox_A_weight_L2Pyr_ampa'], p['input_prox_A_delay_L2']), - 'L2Pyr_nmda': (p['input_prox_A_weight_L2Pyr_nmda'], p['input_prox_A_delay_L2']), - 'L5Pyr_ampa': (p['input_prox_A_weight_L5Pyr_ampa'], p['input_prox_A_delay_L5']), - 'L5Pyr_nmda': (p['input_prox_A_weight_L5Pyr_nmda'], p['input_prox_A_delay_L5']), - 'L2Basket_ampa': (p['input_prox_A_weight_L2Basket_ampa'], p['input_prox_A_delay_L2']), - 'L2Basket_nmda': (p['input_prox_A_weight_L2Basket_nmda'], p['input_prox_A_delay_L2']), - 'L5Basket_ampa': (p['input_prox_A_weight_L5Basket_ampa'], p['input_prox_A_delay_L5']), - 'L5Basket_nmda': (p['input_prox_A_weight_L5Basket_nmda'], p['input_prox_A_delay_L5']), - 'events_per_cycle': p['events_per_cycle_prox'], - 'prng_seedcore': int(p['prng_seedcore_input_prox']), - 'distribution': p['distribution_prox'], - 'lamtha': 100., - 'loc': 'proximal', - 'repeats': p['repeats_prox'], - 't0_stdev': p['t0_input_stdev_prox'], - 'threshold': p['threshold'] - } - - # ensures time interval makes sense - p_ext = feed_validate(p_ext, feed_prox, tstop) - - # default params for distal rhythmic inputs - feed_dist = { - 'f_input': p['f_input_dist'], - 't0': p['t0_input_dist'], - 'tstop': p['tstop_input_dist'], - 'stdev': p['f_stdev_dist'], - 'L2Pyr_ampa': (p['input_dist_A_weight_L2Pyr_ampa'], p['input_dist_A_delay_L2']), - 'L2Pyr_nmda': (p['input_dist_A_weight_L2Pyr_nmda'], p['input_dist_A_delay_L2']), - 'L5Pyr_ampa': (p['input_dist_A_weight_L5Pyr_ampa'], p['input_dist_A_delay_L5']), - 'L5Pyr_nmda': (p['input_dist_A_weight_L5Pyr_nmda'], p['input_dist_A_delay_L5']), - 'L2Basket_ampa': (p['input_dist_A_weight_L2Basket_ampa'], p['input_dist_A_delay_L2']), - 'L2Basket_nmda': (p['input_dist_A_weight_L2Basket_nmda'], p['input_dist_A_delay_L2']), - 'events_per_cycle': p['events_per_cycle_dist'], - 'prng_seedcore': int(p['prng_seedcore_input_dist']), - 'distribution': p['distribution_dist'], - 'lamtha': 100., - 'loc': 'distal', - 'repeats': p['repeats_dist'], - 't0_stdev': p['t0_input_stdev_dist'], - 'threshold': p['threshold'] - } - - p_ext = feed_validate(p_ext, feed_dist, tstop) - - nprox, ndist = countEvokedInputs(p) - # print('nprox,ndist evoked inputs:', nprox, ndist) - - # NEW: make sure all evoked synaptic weights present (for backwards compatibility) - # could cause differences between output of param files since some nmda weights should - # be 0 while others > 0 - checkevokedsynkeys(p,nprox,ndist) - - # Create proximal evoked response parameters - # f_input needs to be defined as 0 - for i in range(nprox): - skey = 'evprox_' + str(i+1) - p_unique['evprox' + str(i+1)] = { - 't0': p['t_' + skey], - 'L2_pyramidal':(p['gbar_'+skey+'_L2Pyr_ampa'],p['gbar_'+skey+'_L2Pyr_nmda'],0.1,p['sigma_t_'+skey]), - 'L2_basket':(p['gbar_'+skey+'_L2Basket_ampa'],p['gbar_'+skey+'_L2Basket_nmda'],0.1,p['sigma_t_'+skey]), - 'L5_pyramidal':(p['gbar_'+skey+'_L5Pyr_ampa'],p['gbar_'+skey+'_L5Pyr_nmda'],1.,p['sigma_t_'+skey]), - 'L5_basket':(p['gbar_'+skey+'_L5Basket_ampa'],p['gbar_'+skey+'_L5Basket_nmda'],1.,p['sigma_t_'+skey]), - 'prng_seedcore': int(p['prng_seedcore_' + skey]), - 'lamtha_space': 3., - 'loc': 'proximal', - 'sync_evinput': p['sync_evinput'], - 'threshold': p['threshold'], - 'numspikes': p['numspikes_' + skey] - } - - # Create distal evoked response parameters - # f_input needs to be defined as 0 - for i in range(ndist): - skey = 'evdist_' + str(i+1) - p_unique['evdist' + str(i+1)] = { - 't0': p['t_' + skey], - 'L2_pyramidal':(p['gbar_'+skey+'_L2Pyr_ampa'],p['gbar_'+skey+'_L2Pyr_nmda'],0.1,p['sigma_t_'+skey]), - 'L5_pyramidal':(p['gbar_'+skey+'_L5Pyr_ampa'],p['gbar_'+skey+'_L5Pyr_nmda'],0.1,p['sigma_t_'+skey]), - 'L2_basket':(p['gbar_'+skey+'_L2Basket_ampa'],p['gbar_'+skey+'_L2Basket_nmda'],0.1,p['sigma_t_' + skey]), - 'prng_seedcore': int(p['prng_seedcore_' + skey]), - 'lamtha_space': 3., - 'loc': 'distal', - 'sync_evinput': p['sync_evinput'], - 'threshold': p['threshold'], - 'numspikes': p['numspikes_' + skey] - } - - # this needs to create many feeds - # (amplitude, delay, mu, sigma). ordered this way to preserve compatibility - p_unique['extgauss'] = { # NEW: note double weight specification since only use ampa for gauss inputs - 'stim': 'gaussian', - 'L2_basket':(p['L2Basket_Gauss_A_weight'],p['L2Basket_Gauss_A_weight'],1.,p['L2Basket_Gauss_mu'],p['L2Basket_Gauss_sigma']), - 'L2_pyramidal':(p['L2Pyr_Gauss_A_weight'],p['L2Pyr_Gauss_A_weight'],0.1,p['L2Pyr_Gauss_mu'],p['L2Pyr_Gauss_sigma']), - 'L5_basket':(p['L5Basket_Gauss_A_weight'],p['L5Basket_Gauss_A_weight'],1.,p['L5Basket_Gauss_mu'],p['L5Basket_Gauss_sigma']), - 'L5_pyramidal':(p['L5Pyr_Gauss_A_weight'],p['L5Pyr_Gauss_A_weight'],1.,p['L5Pyr_Gauss_mu'],p['L5Pyr_Gauss_sigma']), - 'lamtha': 100., - 'prng_seedcore': int(p['prng_seedcore_extgauss']), - 'loc': 'proximal', - 'threshold': p['threshold'] - } - - checkpoissynkeys(p) - - # define T_pois as 0 or -1 to reset automatically to tstop - if p['T_pois'] in (0, -1): p['T_pois'] = tstop - - # Poisson distributed inputs to proximal - p_unique['extpois'] = {# NEW: setting up AMPA and NMDA for Poisson inputs; why delays differ? - 'stim': 'poisson', - 'L2_basket': (p['L2Basket_Pois_A_weight_ampa'],p['L2Basket_Pois_A_weight_nmda'],1.,p['L2Basket_Pois_lamtha']), - 'L2_pyramidal': (p['L2Pyr_Pois_A_weight_ampa'],p['L2Pyr_Pois_A_weight_nmda'], 0.1,p['L2Pyr_Pois_lamtha']), - 'L5_basket': (p['L5Basket_Pois_A_weight_ampa'],p['L5Basket_Pois_A_weight_nmda'],1.,p['L5Basket_Pois_lamtha']), - 'L5_pyramidal': (p['L5Pyr_Pois_A_weight_ampa'],p['L5Pyr_Pois_A_weight_nmda'],1.,p['L5Pyr_Pois_lamtha']), - 'lamtha_space': 100., - 'prng_seedcore': int(p['prng_seedcore_extpois']), - 't_interval': (p['t0_pois'], p['T_pois']), - 'loc': 'proximal', - 'threshold': p['threshold'] - } - - return p_ext, p_unique - -# Finds the changed variables -# sort of inefficient, probably should be part of something else -# not worried about all that right now, as it appears to work -# brittle in that the match string needs to be correct to find all the changed params -# is redundant with(?) get_key_types() dynamic keys information -def changed_vars(fparam): - # DEPRECATE - # Strip empty lines and comments - lines = fio.clean_lines(fparam) - lines = [line for line in lines if line[0] != '#'] - - # grab the keys and vals in a list of lists - # each item of keyvals is a pair [key, val] - keyvals = [line.split(": ") for line in lines] - - # match the list for changed items starting with "AKL[(" on the 1st char of the val - var_list = [line for line in keyvals if re.match('[AKL[\(]', line[1][0])] - - # additional default info to add always - list_meta = [ - 'N_trials', - 'N_sims', - 'Run_Date' - ] - - # list concatenate these lists - var_list += [line for line in keyvals if line[0] in list_meta] - - # return the list of "changed" or "default" vars - return var_list - -# Takes two dictionaries (d1 and d2) and compares the keys in d1 to those in d2 -# if any match, updates the (key, value) pair of d1 to match that of d2 -# not real happy with variable names, but will have to do for now -def compare_dictionaries(d1, d2): - # DEPRECATE - # iterate over intersection of key sets (i.e. any common keys) - for key in d1.keys() and d2.keys(): - # update d1 to have same (key, value) pair as d2 - d1[key] = d2[key] - - return d1 - -# get diff on 2 dictionaries -def diffdict (d1, d2, verbose=True): - # DEPRECATE - print('d1,d2 num keys - ', len(d1.keys()), len(d2.keys())) - for k in d1.keys(): - if not k in d2: - if verbose: print(k, ' in d1, not in d2') - for k in d2.keys(): - if not k in d1: - if verbose: print(k, ' in d2, not in d1') - for k in d1.keys(): - if k in d2: - if d1[k] != d2[k]: - print('d1[',k,']=',d1[k],' d2[',k,']=',d2[k]) - def consolidate_chunks(input_dict): # MOVE to hnn-core # get a list of sorted chunks diff --git a/params_default.py b/params_default.py deleted file mode 100644 index b573f7399..000000000 --- a/params_default.py +++ /dev/null @@ -1,365 +0,0 @@ -from collections import OrderedDict - -# params_default.py - master list of changeable params. most set to default val of inactive -# -# v 1.9.01 -# rev 2015-12-08 (RL: added t0_pois) -# last major: (SL: Added default params for L2Basket and L5Basket cells) - -# returns default params - see note -def get_params_default (nprox = 2, ndist = 1): - """ Note that nearly all start times are set BEYOND tstop for this file - Most values here are set to whatever default value inactivates them, such as 0 for conductance - prng seed values are also set to 0 (non-random) - flat file of default values - will most often be overwritten - """ - # set default params - p = { - 'sim_prefix': 'default', - - # simulation end time (ms) - 'tstop': 250., - - # numbers of cells making up the pyramidal grids - 'N_pyr_x': 1, - 'N_pyr_y': 1, - - # amplitudes of individual Gaussian random inputs to L2Pyr and L5Pyr - # L2 Basket params - 'L2Basket_Gauss_A_weight': 0., - 'L2Basket_Gauss_mu': 2000., - 'L2Basket_Gauss_sigma': 3.6, - 'L2Basket_Pois_A_weight_ampa': 0., - 'L2Basket_Pois_A_weight_nmda': 0., - 'L2Basket_Pois_lamtha': 0., - - # L2 Pyr params - 'L2Pyr_Gauss_A_weight': 0., - 'L2Pyr_Gauss_mu': 2000., - 'L2Pyr_Gauss_sigma': 3.6, - 'L2Pyr_Pois_A_weight_ampa': 0., - 'L2Pyr_Pois_A_weight_nmda': 0., - 'L2Pyr_Pois_lamtha': 0., - - # L5 Pyr params - 'L5Pyr_Gauss_A_weight': 0., - 'L5Pyr_Gauss_mu': 2000., - 'L5Pyr_Gauss_sigma': 4.8, - 'L5Pyr_Pois_A_weight_ampa': 0., - 'L5Pyr_Pois_A_weight_nmda': 0., - 'L5Pyr_Pois_lamtha': 0., - - # L5 Basket params - 'L5Basket_Gauss_A_weight': 0., - 'L5Basket_Gauss_mu': 2000., - 'L5Basket_Gauss_sigma': 2., - 'L5Basket_Pois_A_weight_ampa': 0., - 'L5Basket_Pois_A_weight_nmda': 0., - 'L5Basket_Pois_lamtha': 0., - - # maximal conductances for all synapses - # max conductances TO L2Pyrs - 'gbar_L2Pyr_L2Pyr_ampa': 0., - 'gbar_L2Pyr_L2Pyr_nmda': 0., - 'gbar_L2Basket_L2Pyr_gabaa': 0., - 'gbar_L2Basket_L2Pyr_gabab': 0., - - # max conductances TO L2Baskets - 'gbar_L2Pyr_L2Basket': 0., - 'gbar_L2Basket_L2Basket': 0., - - # max conductances TO L5Pyr - 'gbar_L5Pyr_L5Pyr_ampa': 0., - 'gbar_L5Pyr_L5Pyr_nmda': 0., - 'gbar_L2Pyr_L5Pyr': 0., - 'gbar_L2Basket_L5Pyr': 0., - 'gbar_L5Basket_L5Pyr_gabaa': 0., - 'gbar_L5Basket_L5Pyr_gabab': 0., - - # max conductances TO L5Baskets - 'gbar_L5Basket_L5Basket': 0., - 'gbar_L5Pyr_L5Basket': 0., - 'gbar_L2Pyr_L5Basket': 0., - - # Ongoing proximal alpha rhythm - 'distribution_prox': 'normal', - 't0_input_prox': 1000., - 'tstop_input_prox': 250., - 'f_input_prox': 10., - 'f_stdev_prox': 20., - 'events_per_cycle_prox': 2, - 'repeats_prox': 10, - 't0_input_stdev_prox': 0.0, - - # Ongoing distal alpha rhythm - 'distribution_dist': 'normal', - 't0_input_dist': 1000., - 'tstop_input_dist': 250., - 'f_input_dist': 10., - 'f_stdev_dist': 20., - 'events_per_cycle_dist': 2, - 'repeats_dist': 10, - 't0_input_stdev_dist': 0.0, - - # thalamic input amplitudes and delays - 'input_prox_A_weight_L2Pyr_ampa': 0., - 'input_prox_A_weight_L2Pyr_nmda': 0., - 'input_prox_A_weight_L5Pyr_ampa': 0., - 'input_prox_A_weight_L5Pyr_nmda': 0., - 'input_prox_A_weight_L2Basket_ampa': 0., - 'input_prox_A_weight_L2Basket_nmda': 0., - 'input_prox_A_weight_L5Basket_ampa': 0., - 'input_prox_A_weight_L5Basket_nmda': 0., - 'input_prox_A_delay_L2': 0.1, - 'input_prox_A_delay_L5': 1.0, - - # current values, not sure where these distal values come from, need to check - 'input_dist_A_weight_L2Pyr_ampa': 0., - 'input_dist_A_weight_L2Pyr_nmda': 0., - 'input_dist_A_weight_L5Pyr_ampa': 0., - 'input_dist_A_weight_L5Pyr_nmda': 0., - 'input_dist_A_weight_L2Basket_ampa': 0., - 'input_dist_A_weight_L2Basket_nmda': 0., - 'input_dist_A_delay_L2': 5., - 'input_dist_A_delay_L5': 5., - - # times and stdevs for evoked responses - 'dt_evprox0_evdist': -1, # not used in GUI - 'dt_evprox0_evprox1': -1, # not used in GUI - 'sync_evinput': 1, # whether evoked inputs arrive at same time to all cells - 'inc_evinput': 0.0, # increment (ms) for avg evoked input start (for trial n, avg start time is n * evinputinc - - # analysis - 'save_spec_data': 0, - 'f_max_spec': 40., - 'spec_cmap': 'jet', # default colormap for consistency with previous versions - 'dipole_scalefctr': 30e3, # scale factor for dipole - default at 30e3 - #based on scaling needed to match model ongoing rhythms from jones 2009 - for ERPs can use 300 - # for ongoing rhythms + ERPs ... use ... ? - 'dipole_smooth_win': 15.0, # window for smoothing (box filter) - 15 ms from jones 2009; shorten - # in case want to look at higher frequency activity - 'save_figs': 0, - 'save_vsoma': 0, # whether to record/save somatic voltage - - # IClamp params for L2Pyr - 'Itonic_A_L2Pyr_soma': 0., - 'Itonic_t0_L2Pyr_soma': 0., - 'Itonic_T_L2Pyr_soma': -1., - - # IClamp param for L2Basket - 'Itonic_A_L2Basket': 0., - 'Itonic_t0_L2Basket': 0., - 'Itonic_T_L2Basket': -1., - - # IClamp params for L5Pyr - 'Itonic_A_L5Pyr_soma': 0., - 'Itonic_t0_L5Pyr_soma': 0., - 'Itonic_T_L5Pyr_soma': -1., - - # IClamp param for L5Basket - 'Itonic_A_L5Basket': 0., - 'Itonic_t0_L5Basket': 0., - 'Itonic_T_L5Basket': -1., - - # numerics - # N_trials of 1 means that seed is set by rank - 'N_trials': 1, - - # prng_state is a string for a filename containing the random state one wants to use - # prng seed cores are the base integer seed for the specific - # prng object for a specific random number stream - # 'prng_state': None, - 'prng_seedcore_opt': 1, - 'prng_seedcore_input_prox': 0, - 'prng_seedcore_input_dist': 0, - 'prng_seedcore_extpois': 0, - 'prng_seedcore_extgauss': 0, - - # default end time for pois inputs - 't0_pois': 0., - 'T_pois': -1, - 'dt': 0.025, - 'celsius': 37.0, - 'threshold': 0.0 # firing threshold - } - - # grab cell-specific params and update p accordingly - p_L2Pyr = get_L2Pyr_params_default() - p_L5Pyr = get_L5Pyr_params_default() - p.update(p_L2Pyr) - p.update(p_L5Pyr) - - # get evoked params and update p accordingly - p_ev_prox = get_ev_params_default(nprox,True) - p_ev_dist = get_ev_params_default(ndist,False) - p.update(p_ev_prox) - p.update(p_ev_dist) - - return p - -# return dict with default params (empty) for evoked inputs; n is number of evoked inputs -# isprox == True iff proximal (otherwise distal) -def get_ev_params_default (n,isprox): - dout = {}#OrderedDict() - if isprox: pref = 'evprox' - else: pref = 'evdist' - # print('isprox:',isprox,'n:',n) - lty = ['L2Pyr', 'L5Pyr', 'L2Basket'] - if isprox: lty.append('L5Basket') - lsy = ['ampa', 'nmda'] # allow changing both ampa and nmda weights - for i in range(n): - tystr = pref + '_' + str(i+1) # this string includes input number - for ty in lty: - for sy in lsy: - dout['gbar_' + tystr + '_' + ty + '_' + sy] = 0. # feed strength - dout['t_' + tystr] = 0. # times and stdevs for evoked responses - dout['sigma_t_' + tystr] = 0. - dout['prng_seedcore_' + tystr] = 0 # random number generator seed for this input - dout['numspikes_' + tystr] = 1 # number of presynaptic spikes (postsynaptic inputs) - return dout - -# returns default params for L2 pyramidal cell -def get_L2Pyr_params_default(): - return { - # Soma - 'L2Pyr_soma_L': 22.1, - 'L2Pyr_soma_diam': 23.4, - 'L2Pyr_soma_cm': 0.6195, - 'L2Pyr_soma_Ra': 200., - - # Dendrites - 'L2Pyr_dend_cm': 0.6195, - 'L2Pyr_dend_Ra': 200., - - 'L2Pyr_apicaltrunk_L': 59.5, - 'L2Pyr_apicaltrunk_diam': 4.25, - - 'L2Pyr_apical1_L': 306., - 'L2Pyr_apical1_diam': 4.08, - - 'L2Pyr_apicaltuft_L': 238., - 'L2Pyr_apicaltuft_diam': 3.4, - - 'L2Pyr_apicaloblique_L': 340., - 'L2Pyr_apicaloblique_diam': 3.91, - - 'L2Pyr_basal1_L': 85., - 'L2Pyr_basal1_diam': 4.25, - - 'L2Pyr_basal2_L': 255., - 'L2Pyr_basal2_diam': 2.72, - - 'L2Pyr_basal3_L': 255., - 'L2Pyr_basal3_diam': 2.72, - - # Synapses - 'L2Pyr_ampa_e': 0., - 'L2Pyr_ampa_tau1': 0.5, - 'L2Pyr_ampa_tau2': 5., - - 'L2Pyr_nmda_e': 0., - 'L2Pyr_nmda_tau1': 1., - 'L2Pyr_nmda_tau2': 20., - - 'L2Pyr_gabaa_e': -80., - 'L2Pyr_gabaa_tau1': 0.5, - 'L2Pyr_gabaa_tau2': 5., - - 'L2Pyr_gabab_e': -80., - 'L2Pyr_gabab_tau1': 1., - 'L2Pyr_gabab_tau2': 20., - - # Biophysics soma - 'L2Pyr_soma_gkbar_hh2': 0.01, - 'L2Pyr_soma_gnabar_hh2': 0.18, - 'L2Pyr_soma_el_hh2': -65., - 'L2Pyr_soma_gl_hh2': 4.26e-5, - 'L2Pyr_soma_gbar_km': 250., - - # Biophysics dends - 'L2Pyr_dend_gkbar_hh2': 0.01, - 'L2Pyr_dend_gnabar_hh2': 0.15, - 'L2Pyr_dend_el_hh2': -65., - 'L2Pyr_dend_gl_hh2': 4.26e-5, - 'L2Pyr_dend_gbar_km': 250., - } - -# returns default params for L5 pyramidal cell -def get_L5Pyr_params_default(): - return { - # Soma - 'L5Pyr_soma_L': 39., - 'L5Pyr_soma_diam': 28.9, - 'L5Pyr_soma_cm': 0.85, - 'L5Pyr_soma_Ra': 200., - - # Dendrites - 'L5Pyr_dend_cm': 0.85, - 'L5Pyr_dend_Ra': 200., - - 'L5Pyr_apicaltrunk_L': 102., - 'L5Pyr_apicaltrunk_diam': 10.2, - - 'L5Pyr_apical1_L': 680., - 'L5Pyr_apical1_diam': 7.48, - - 'L5Pyr_apical2_L': 680., - 'L5Pyr_apical2_diam': 4.93, - - 'L5Pyr_apicaltuft_L': 425., - 'L5Pyr_apicaltuft_diam': 3.4, - - 'L5Pyr_apicaloblique_L': 255., - 'L5Pyr_apicaloblique_diam': 5.1, - - 'L5Pyr_basal1_L': 85., - 'L5Pyr_basal1_diam': 6.8, - - 'L5Pyr_basal2_L': 255., - 'L5Pyr_basal2_diam': 8.5, - - 'L5Pyr_basal3_L': 255., - 'L5Pyr_basal3_diam': 8.5, - - # Synapses - 'L5Pyr_ampa_e': 0., - 'L5Pyr_ampa_tau1': 0.5, - 'L5Pyr_ampa_tau2': 5., - - 'L5Pyr_nmda_e': 0., - 'L5Pyr_nmda_tau1': 1., - 'L5Pyr_nmda_tau2': 20., - - 'L5Pyr_gabaa_e': -80., - 'L5Pyr_gabaa_tau1': 0.5, - 'L5Pyr_gabaa_tau2': 5., - - 'L5Pyr_gabab_e': -80., - 'L5Pyr_gabab_tau1': 1., - 'L5Pyr_gabab_tau2': 20., - - # Biophysics soma - 'L5Pyr_soma_gkbar_hh2': 0.01, - 'L5Pyr_soma_gnabar_hh2': 0.16, - 'L5Pyr_soma_el_hh2': -65., - 'L5Pyr_soma_gl_hh2': 4.26e-5, - 'L5Pyr_soma_gbar_ca': 60., - 'L5Pyr_soma_taur_cad': 20., - 'L5Pyr_soma_gbar_kca': 2e-4, - 'L5Pyr_soma_gbar_km': 200., - 'L5Pyr_soma_gbar_cat': 2e-4, - 'L5Pyr_soma_gbar_ar': 1e-6, - - # Biophysics dends - 'L5Pyr_dend_gkbar_hh2': 0.01, - 'L5Pyr_dend_gnabar_hh2': 0.14, - 'L5Pyr_dend_el_hh2': -71., - 'L5Pyr_dend_gl_hh2': 4.26e-5, - 'L5Pyr_dend_gbar_ca': 60., - 'L5Pyr_dend_taur_cad': 20., - 'L5Pyr_dend_gbar_kca': 2e-4, - 'L5Pyr_dend_gbar_km': 200., - 'L5Pyr_dend_gbar_cat': 2e-4, - 'L5Pyr_dend_gbar_ar': 1e-6, - } diff --git a/plotfn.py b/plotfn.py deleted file mode 100644 index 406490057..000000000 --- a/plotfn.py +++ /dev/null @@ -1,196 +0,0 @@ -# plotfn.py - pall and possibly other plot routines -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: removed it.izip() dependence) -# last major: (SL: toward python3) - -from praster import praster -import axes_create as ac -import dipolefn -import paramrw -import pspec -import specfn -import os -import fileio as fio -from multiprocessing import Pool - -# terrible handling of variables -def pkernel(dfig, f_param, f_spk, f_dpl, f_spec, key_types, xlim=None, ylim=None): - gid_dict, p_dict = paramrw.read(f_param) - tstop = p_dict['tstop'] - # fig dirs - dfig_dpl = dfig['figdpl'] - dfig_spec = dfig['figspec'] - dfig_spk = dfig['figspk'] - pdipole_dict = { - 'xlim': xlim, - 'ylim': ylim, - # 'xmin': xlim[0], - # 'xmax': xlim[1], - # 'ymin': None, - # 'ymax': None, - } - # plot kernels - praster(f_param, tstop, f_spk, dfig_spk) - dipolefn.pdipole(f_dpl, dfig_dpl, pdipole_dict, f_param, key_types) - # dipolefn.pdipole(f_dpl, f_param, dfig_dpl, key_types, pdipole_dict) - # usage of xlim to pspec is temporarily disabled. pspec_dpl() will use internal states for plotting - pspec.pspec_dpl(f_spec, f_dpl, dfig_spec, p_dict, key_types, xlim, ylim, f_param) - # pspec.pspec_dpl(f_spec, f_dpl, dfig_spec, p_dict, key_types) - # pspec.pspec_dpl(data_spec, f_dpl, dfig_spec, p_dict, key_types, xlim) - return 0 - -# Kernel for plotting dipole and spec with alpha feed histograms -def pkernel_with_hist(datdir, dfig, f_param, f_spk, f_dpl, f_spec, key_types, xlim=None, ylim=None): - # gid_dict, p_dict = paramrw.read(f_param) - # tstop = p_dict['tstop'] - # fig dirs - dfig_dpl = datdir - dfig_spec = datdir - dfig_spk = datdir - pdipole_dict = { - 'xmin': None, - 'xmax': None, - 'ymin': None, - 'ymax': None, - } - # plot kernels - dipolefn.pdipole_with_hist(f_dpl, f_spk, dfig_dpl, f_param, key_types, pdipole_dict) - pspec.pspec_with_hist(f_spec, f_dpl, f_spk, dfig_spec, f_param, key_types, xlim, ylim) - return 0 - -# r is the value returned by pkernel -# this is sort of a dummy function -def cb(r): pass - -# plot function - this is sort of a stop-gap and shouldn't live here, really -# reads all data except spec and gid_dict from files -def pallsimp (datdir, p_exp, doutf, xlim=None, ylim=None): - key_types = p_exp.get_key_types() - param_list = [doutf['file_param']] - dpl_list = [doutf['file_dpl']] - spec_list = [doutf['file_spec']] - spk_list = [doutf['file_spikes']] - dfig_list = [{'figavgdpl': None, 'avgspec': None, 'param': None, 'normdpl': None, 'rawspk': None, 'rawspec': None, 'figavgspec': None, 'rawdpl': None, 'figdpl': None, 'rawcurrent': None, 'avgdpl': None, 'figspk': None, 'rawspeccurrent': None, 'figspec': None}] - # print('dfig_list:',dfig_list) - for dfig, f_param, f_spk, f_dpl, f_spec in zip(dfig_list, param_list, spk_list, dpl_list, spec_list): - pkernel_with_hist(datdir, dfig, f_param, f_spk, f_dpl, f_spec, key_types, xlim, ylim) - -# plot function - this is sort of a stop-gap and shouldn't live here, really -# reads all data except spec and gid_dict from files -def pall(datdir, ddir, p_exp, xlim=None, ylim=None): - # def pall(ddir, p_exp, spec_results, xlim=[0., 'tstop']): - # runtype allows easy (hard coded switching between two modes) - # either 'parallel' or 'debug' - # runtype = 'parallel' - runtype = 'debug' - dsim = ddir.dsim - key_types = p_exp.get_key_types() - # preallocate lists for use below - param_list = [] - dpl_list = [] - spec_list = [] - spk_list = [] - dfig_list = [] - # aggregate all file types from individual expmts into lists - # NB The only reason this works is because the analysis results are returned - # IDENTICALLY! - for expmt_group in ddir.expmt_groups: - # these should be equivalent lengths - param_list.extend(ddir.file_match(expmt_group, 'param')) - dpl_list.extend(ddir.file_match(expmt_group, 'rawdpl')) - spec_list.extend(ddir.file_match(expmt_group, 'rawspec')) - spk_list.extend(ddir.file_match(expmt_group, 'rawspk')) - # append as many copies of expmt dfig dict as there were runs in expmt - # this must be done because we're iterating over ALL expmts at the same time - for i in range(len(ddir.file_match(expmt_group, 'param'))): - dfig_list.append(ddir.dfig[expmt_group]) - # create giant list of appropriate files and run them all at the same time - if runtype is 'parallel': - # apply async to compiled lists - pl = Pool() - for dfig, f_param, f_spk, f_dpl, f_spec in zip(dfig_list, param_list, spk_list, dpl_list, spec_list): - pl.apply_async(pkernel, (dfig, f_param, f_spk, f_dpl, f_spec, key_types, xlim, ylim), callback=cb) - pl.close() - pl.join() - elif runtype is 'debug': - # run serially - for dfig, f_param, f_spk, f_dpl, f_spec in zip(dfig_list, param_list, spk_list, dpl_list, spec_list): - pkernel_with_hist(dfig, f_param, f_spk, f_dpl, f_spec, key_types, xlim, ylim) - # pkernel(dfig, f_param, f_spk, f_dpl, f_spec, key_types, xlim, ylim) - -# Plots dipole and spec with alpha feed histograms -def pdpl_pspec_with_hist(ddir, p_exp, xlim=None, ylim=None): - # def pdpl_pspec_with_hist(ddir, p_exp, spec_results, xlim=[0., 'tstop']): - # runtype = 'debug' - runtype = 'parallel' - # preallocate lists for use below - param_list = [] - dpl_list = [] - spec_list = [] - spk_list = [] - dfig_list = [] - # Grab all necessary data in aggregated lists - for expmt_group in ddir.expmt_groups: - # these should be equivalent lengths - param_list.extend(ddir.file_match(expmt_group, 'param')) - dpl_list.extend(ddir.file_match(expmt_group, 'rawdpl')) - spec_list.extend(ddir.file_match(expmt_group, 'rawspec')) - spk_list.extend(ddir.file_match(expmt_group, 'rawspk')) - # append as many copies of expmt dfig dict as there were runs in expmt - for i in range(len(ddir.file_match(expmt_group, 'param'))): - dfig_list.append(ddir.dfig[expmt_group]) - # grab the key types - key_types = p_exp.get_key_types() - print(spec_list) - if runtype is 'parallel': - # apply async to compiled lists - pl = Pool() - for dfig, f_param, f_spk, f_dpl, f_spec in zip(dfig_list, param_list, spk_list, dpl_list, spec_list): - pl.apply_async(pkernel_with_hist, (dfig, f_param, f_spk, f_dpl, f_spec, key_types, xlim, ylim), callback=cb) - pl.close() - pl.join() - elif runtype is 'debug': - for dfig, f_param, f_spk, f_dpl, f_spec in zip(dfig_list, param_list, spk_list, dpl_list, spec_list): - pkernel_with_hist(dfig, f_param, f_spk, f_dpl, f_spec, key_types, xlim, ylim) - -def aggregate_spec_with_hist(ddir, p_exp, labels): - untype = 'debug' - # preallocate lists for use below - param_list = [] - dpl_list = [] - spec_list = [] - spk_list = [] - dfig_list = [] - spec_list = [] - # Get dimensions for aggregate fig - N_rows = len(ddir.expmt_groups) - N_cols = len(ddir.file_match(ddir.expmt_groups[0], 'param')) - # Create figure - f = ac.FigAggregateSpecWithHist(N_rows, N_cols) - # Grab all necessary data in aggregated lists - for expmt_group in ddir.expmt_groups: - # these should be equivalent lengths - param_list.extend(ddir.file_match(expmt_group, 'param')) - dpl_list.extend(ddir.file_match(expmt_group, 'rawdpl')) - spec_list.extend(ddir.file_match(expmt_group, 'rawspec')) - spk_list.extend(ddir.file_match(expmt_group, 'rawspk')) - # apply async to compiled lists - if runtype is 'parallel': - pl = Pool() - for f_param, f_spk, f_dpl, fspec, ax in zip(param_list, spk_list, dpl_list, spec_list, f.ax_list): - _, p_dict = paramrw.read(f_param) - pl.apply_async(specfn.aggregate_with_hist, (f, ax, fspec, f_dpl, f_spk, fparam, p_dict)) - pl.close() - pl.join() - elif runtype is 'debug': - for f_param, f_spk, f_dpl, fspec, ax in zip(param_list, spk_list, dpl_list, spec_list, f.ax_list): - # _, p_dict = paramrw.read(f_param) - pspec.aggregate_with_hist(f, ax, fspec, f_dpl, f_spk, f_param) - # add row labels - f.add_row_labels(param_list, labels[0]) - # add column labels - f.add_column_labels(param_list, labels[1]) - fig_name = os.path.join(ddir.dsim, 'aggregate_hist.png') - f.save(fig_name) - f.close() diff --git a/pmanu_gamma.py b/pmanu_gamma.py deleted file mode 100644 index 9f6aacceb..000000000 --- a/pmanu_gamma.py +++ /dev/null @@ -1,1713 +0,0 @@ -# pmanu_gamma.py - plot functions for gamma manuscript -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: return_data_dir() and it.izip) -# last major: (SL: plot updates) - -import numpy as np -import os -import fileio as fio -import currentfn -import dipolefn -import specfn -import spikefn -import paramrw -import ac_manu_gamma as acg -import axes_create as ac - -def spec_fig(): - f = acg.FigSimpleSpec() - dproj = fio.return_data_dir() - d = os.path.join(dproj, '2013-12-04/ftremor-003') - - ddata = fio.SimulationPaths() - ddata.read_sim(dproj, d) - - expmt_group = ddata.expmt_groups[0] - f_dpl = ddata.file_match(expmt_group, 'rawdpl')[0] - fspec = ddata.file_match(expmt_group, 'rawspec')[0] - fparam = ddata.file_match(expmt_group, 'param')[0] - - dpl = dipolefn.Dipole(f_dpl) - dpl.baseline_renormalize(fparam) - dpl.convert_fAm_to_nAm() - - xlim = (200., 700.) - - dpl.plot(f.ax['dipole'], xlim, layer='L5') - pc = { - 'spec': specfn.pspec_ax(f.ax['spec'], fspec, (50, 1050), layer='L5'), - } - cb = f.f.colorbar(pc['spec'], ax=f.ax['spec'], format='%.1e') - - f.ax['spec'].set_xlim(xlim) - f.ax['spec'].set_ylabel('Frequency (Hz)') - f.ax['spec'].set_xlabel('Time (ms)') - - f.set_fontsize(14) - - # fname = os.path.join(d, 'testing.eps') - - f.saveeps(d, 'testing') - f.close() - -# all the data comes from one sim -def hf_epochs(ddata): - # runtype = 'debug' - runtype = 'pub' - - # create the figure from the ac template - f = acg.FigHFEpochs(runtype) - - # hard coded for now - n_sim = 0 - n_trial = 0 - - # and assume just the first expmt for now - expmt = ddata.expmt_groups[0] - - # hard code the 50 ms epochs that will be used here. - # centers for the data in tuples (t, f) - tf_specmax = [ - (79.525, 115.), - (136.925, 114.), - (324.350, 109.), - (418.400, 106.), - ] - - # these are approximate centers on which to draw sines - tf_centers = [ - [83.], - [137.], - [330.], - [411., 429.], - ] - - # these all come from one filename - f_spec = ddata.return_specific_filename(expmt, 'rawspec', n_sim, n_trial) - f_dpl = ddata.return_specific_filename(expmt, 'rawdpl', n_sim, n_trial) - f_spk = ddata.return_specific_filename(expmt, 'rawspk', n_sim, n_trial) - f_param = ddata.return_specific_filename(expmt, 'param', n_sim, n_trial) - f_current = ddata.return_specific_filename(expmt, 'rawcurrent', n_sim, n_trial) - - # p_dict is needed for the spike thing. - _, p_dict = paramrw.read(f_param) - - # figure out the tstop and xlim - tstop = paramrw.find_param(f_param, 'tstop') - dt = paramrw.find_param(f_param, 'dt') - xlim = (50., tstop) - - # grab the dipole data - dpl = dipolefn.Dipole(f_dpl) - dpl.baseline_renormalize(f_param) - dpl.convert_fAm_to_nAm() - - # current data - I_soma = currentfn.SynapticCurrent(f_current) - I_soma.convert_nA_to_uA() - - # grab the spike data for histogram - s = spikefn.spikes_from_file(f_param, f_spk) - n_bins = 500 - s_list = np.concatenate(s['L5_pyramidal'].spike_list) - - # spikes - spikes = { - 'L5': spikefn.filter_spike_dict(s, 'L5_'), - } - - # xrange is just the length of the window, dx is the distance from the center - xrange = 50. - dx = xrange / 2. - - # pc will be a list of the colorbar props for each spec (length len(f.gspec)) - pc = [] - - # plot the aggregate data too - specfn.pspec_ax(f.ax['L_spec'], f_spec, xlim, layer='L5') - dpl.plot(f.ax['L_dpl'], xlim, layer='L5') - spikefn.spike_png(f.ax['L_spk'], spikes['L5']) - - # change the color - color_dpl_L5 = '#1e90ff' - f.set_linecolor('L_dpl', color_dpl_L5) - - # grab a list of the ax handles for the leftmost plot - ax_L_keys = [ax for ax in f.ax.keys() if ax.startswith('L_')] - - # set all these xlim correctly - for ax_h in ax_L_keys: - f.ax[ax_h].set_xlim(xlim) - - list_ylim_dpl = [] - - # now plot the individual epochs - for i in range(len(f.gspec_ex)): - # for each (t, f) pair, find the xlim_window - t_center = tf_specmax[i][0] - f_center = tf_specmax[i][1] - xlim_window = (t_center - dx, t_center + dx) - print xlim_window - - # crude setting of vertical lines to denote roi - for ax_h in ax_L_keys: - f.ax[ax_h].axvline(x=xlim_window[0], color='b') - f.ax[ax_h].axvline(x=xlim_window[1], color='k') - - # this is the highlight portion, one fixed - dx_hl = 0.5 * (1000. / f_center) - dx_hl_fixed = 10. - - xlim_hl = (t_center - dx_hl, t_center + dx_hl) - xlim_fixed = (t_center - dx_hl_fixed, t_center + dx_hl_fixed) - - # fix xlim_window in case - if xlim_window[0] < xlim[0]: - xlim_window[0] = xlim[0] - - if xlim_window[1] == -1: - xlim_window[1] = tstop - - I_soma.plot_to_axis(f.ax_twinx['dpl'][i], 'L5') - - # truncate and then plot the dpl - # dpl_short must be a dict of all the different dipoles - # so only need here the L5 key - t_short, dpl_short = dpl.truncate_ext(xlim_hl[0], xlim_hl[1]) - t_fixed, dpl_fixed = dpl.truncate_ext(xlim_fixed[0], xlim_fixed[1]) - - # create a sine waveform for this interval - f_max = tf_specmax[i][1] - t_half = 0.5 * (1000. / f_max) - - # plot the dipole for the xlim window - # plot the dipole and the current, either over the appropriate range or the whole window for now - dpl.plot(f.ax['dpl'][i], xlim_window, layer='L5') - f.ax['dpl'][i].hold(True) - - for j in range(len(tf_centers[i])): - t0 = tf_centers[i][j] - t_half - T = tf_centers[i][j] + t_half - - ylim_dpl = dpl.lim('L5', (t0, T)) - - props_dict = { - 't': (t0, T), - 'dt': dt, - 'f': f_max, - 'A': ylim_dpl[1], - } - - f.add_sine(f.ax['dpl'][i], props_dict) - - f.ax['dpl'][i].set_ylim((-0.025, 0.025)) - - # f.ax['dpl'][i].plot(t_fixed, dpl_fixed['L5'], 'g') - # f.ax['dpl'][i].plot(t_short, dpl_short['L5'], 'r') - - # spec - i think must be plotted as xlim first and then truncated? - pc.append(specfn.pspec_ax(f.ax['spec'][i], f_spec, xlim, layer='L5')) - - # plot the data - spikefn.pinput_hist_onesided(f.ax['hist'][i], s_list, n_bins) - spikefn.spike_png(f.ax['spk'][i], spikes['L5']) - - # set xlim_windows accordingly - f.ax['hist'][i].set_xlim(xlim_window) - f.ax['spk'][i].set_xlim(xlim_window) - f.ax_twinx['dpl'][i].set_xlim(xlim_window) - f.ax['spec'][i].set_xlim(xlim_window) - - # hist - f.ax['hist'][i].yaxis.set_ticks(np.arange(0, 9, 4)) - - # set the color of the I_soma line - f.ax['dpl'][i].lines[0].set_color(color_dpl_L5) - f.ax_twinx['dpl'][i].lines[0].set_color('k') - f.ysymmetry(f.ax_twinx['dpl'][i]) - - # no need for outputs - if runtype == 'debug': - f.f.colorbar(pc[i], ax=f.ax['spec'][i], format='%.3e') - - if runtype == 'pub': - p_ticks = np.arange(0, 9e-5, 2e-5) - pctest = f.f.colorbar(pc[-1], ax=f.ax['spec'][-1], format='%.2e', ticks=p_ticks) - pctest.ax.set_yticklabels(p_ticks) - # pctest.ax.set_ytick(np.arange(0, 8e-5, 2e-5)) - # pctest.ax.locator_params(axis='y', nbins=5) - - # some fig naming stuff - dfig = os.path.join(ddata.dsim, expmt) - trial_prefix = ddata.trial_prefix_str % (n_sim, n_trial) - fprefix_short = trial_prefix + '-hf_epochs' - - # use methods to save figs - f.savepng_new(dfig, fprefix_short) - f.saveeps(dfig, fprefix_short) - f.close() - -def hf(ddata, xlim_window, n_sim, n_trial): - # data directories (made up for now) - # the resultant figure is saved in d0 - # d = os.path.join(dproj, 'pub', '2013-06-28_gamma_weak_L5-000') - - # for now grab the first experiment - # ddata = fio.SimulationPaths() - # ddata.read_sim(dproj, d) - expmt = ddata.expmt_groups[0] - - runtype = 'debug' - # runtype = 'pub' - - # prints the fig in ddata0 - f = acg.FigHF(runtype) - - # grab the relevant files - f_spec = ddata.return_specific_filename(expmt, 'rawspec', n_sim, n_trial) - f_dpl = ddata.return_specific_filename(expmt, 'rawdpl', n_sim, n_trial) - f_spk = ddata.return_specific_filename(expmt, 'rawspk', n_sim, n_trial) - f_param = ddata.return_specific_filename(expmt, 'param', n_sim, n_trial) - f_current = ddata.return_specific_filename(expmt, 'rawcurrent', n_sim, n_trial) - - # p_dict is needed for the spike thing. - _, p_dict = paramrw.read(f_param) - - # figure out the tstop and xlim - tstop = paramrw.find_param(f_param, 'tstop') - dt = paramrw.find_param(f_param, 'dt') - xlim = (50., tstop) - - # fix xlim_window - if xlim_window[0] < xlim[0]: - xlim_window[0] = xlim[0] - - if xlim_window[1] == -1: - xlim_window[1] = tstop - - # grab the dipole data - dpl = dipolefn.Dipole(f_dpl) - dpl.baseline_renormalize(f_param) - dpl.convert_fAm_to_nAm() - dpl.plot(f.ax['dpl_L'], xlim, layer='agg') - - # plot currents - I_soma = currentfn.SynapticCurrent(f_current) - I_soma.plot_to_axis(f.ax_twinx['dpl_L'], 'L5') - - # spec - pc = { - 'L': specfn.pspec_ax(f.ax['spec_L'], f_spec, xlim, layer='L5'), - } - - # no need for outputs - if runtype == 'debug': - f.f.colorbar(pc['L'], ax=f.ax['spec_L'], format='%.3e') - - # grab the spike data for histogram - s = spikefn.spikes_from_file(f_param, f_spk) - n_bins = 500 - s_list = np.concatenate(s['L5_pyramidal'].spike_list) - spikefn.pinput_hist_onesided(f.ax['hist_L'], s_list, n_bins) - - # spikes - spikes = { - 'L5': spikefn.filter_spike_dict(s, 'L5_'), - } - - # plot the data - spikefn.spike_png(f.ax['spk'], spikes['L5']) - - # xlim_window - # xlim_window = (400., 450.) - f.ax['hist_L'].set_xlim(xlim_window) - f.ax['dpl_L'].set_xlim(xlim_window) - f.ax['spec_L'].set_xlim(xlim_window) - f.ax_twinx['dpl_L'].set_xlim(xlim_window) - - f.ax_twinx['dpl_L'].lines[0].set_color('k') - f.ysymmetry(f.ax_twinx['dpl_L']) - f.ax['spk'].set_xlim(xlim_window) - - # # save the fig in ddata0 (arbitrary) - trial_prefix = ddata.trial_prefix_str % (n_sim, n_trial) - f_prefix = '%s_hf' % trial_prefix - dfig = os.path.join(ddata.dsim, expmt) - - f.savepng_new(dfig, f_prefix) - f.saveeps(dfig, f_prefix) - f.close() - -def laminar(ddata): - # for now grab the first experiment - expmt = ddata.expmt_groups[0] - - # runtype = 'debug' - runtype = 'pub' - - # for now hard code the simulation run - n_run = 0 - - # prints the fig in ddata0 - f = acg.FigLaminarComparison(runtype) - - # grab the relevant files - f_spec = ddata.file_match(expmt, 'rawspec')[n_run] - f_dpl = ddata.file_match(expmt, 'rawdpl')[n_run] - f_spk = ddata.file_match(expmt, 'rawspk')[n_run] - f_param = ddata.file_match(expmt, 'param')[n_run] - f_current = ddata.file_match(expmt, 'rawcurrent')[n_run] - - # figure out the tstop and xlim - tstop = paramrw.find_param(f_param, 'tstop') - dt = paramrw.find_param(f_param, 'dt') - xlim = (50., tstop) - - # grab the dipole data - dpl = dipolefn.Dipole(f_dpl) - dpl.baseline_renormalize(f_param) - dpl.convert_fAm_to_nAm() - - # calculate the Welch periodogram - pgram = { - 'agg': specfn.Welch(dpl.t, dpl.dpl['agg'], dt), - 'L2': specfn.Welch(dpl.t, dpl.dpl['L2'], dt), - 'L5': specfn.Welch(dpl.t, dpl.dpl['L5'], dt), - } - - # plot periodograms - pgram['agg'].plot_to_ax(f.ax['pgram_L']) - pgram['L2'].plot_to_ax(f.ax['pgram_M']) - pgram['L5'].plot_to_ax(f.ax['pgram_R']) - - # plot currents - I_soma = currentfn.SynapticCurrent(f_current) - I_soma.convert_nA_to_uA() - I_soma.plot_to_axis(f.ax['current_M'], 'L2') - I_soma.plot_to_axis(f.ax['current_R'], 'L5') - f.set_linecolor('current_M', 'k') - f.set_linecolor('current_R', 'k') - # f.set_axes_pingping() - - # cols have same suffix - list_cols = ['L', 'M', 'R'] - - # create handles list - list_h_pgram = ['pgram_'+col for col in list_cols] - list_h_dpl = ['dpl_'+col for col in list_cols] - - # spec - pc = { - 'L': specfn.pspec_ax(f.ax['spec_L'], f_spec, xlim, layer='agg'), - 'R': specfn.pspec_ax(f.ax['spec_R'], f_spec, xlim, layer='L5'), - } - - pc2 = { - 'M': specfn.pspec_ax(f.ax['spec_M'], f_spec, xlim, layer='L2'), - } - - # create a list of spec color handles - # list_h_spec_cb = ['pc_'+col for col in list_cols] - - # get the vmin, vmax and add them to the master list - # f.equalize_speclim(pc) - # list_lim_spec = [] - - # no need for outputs - if runtype == 'debug': - f.f.colorbar(pc['L'], ax=f.ax['spec_L'], format='%.1e') - f.f.colorbar(pc2['M'], ax=f.ax['spec_M'], format='%.1e') - f.f.colorbar(pc['R'], ax=f.ax['spec_R'], format='%.1e') - # list_spec_handles = [ax for ax in f.ax.keys() if ax.startswith('spec')] - list_spec_handles = ['spec_M', 'spec_R'] - f.remove_tick_labels(list_spec_handles, ax_xy='y') - - elif runtype == 'pub': - f.f.colorbar(pc['L'], ax=f.ax['spec_L'], format='%.1e') - f.f.colorbar(pc2['M'], ax=f.ax['spec_M'], format='%.1e') - f.f.colorbar(pc['R'], ax=f.ax['spec_R'], format='%.1e') - list_spec_handles = ['spec_L', 'spec_R'] - f.remove_tick_labels(list_spec_handles, ax_xy='y') - - # grab the spike data - s = spikefn.spikes_from_file(f_param, f_spk) - - # dipoles - dpl.plot(f.ax['dpl_L'], xlim, layer='agg') - # f.set_linecolor('dpl_L', 'k') - f.ax['dpl_L'].hold(True) - dpl.plot(f.ax['dpl_L'], xlim, layer='L5') - dpl.plot(f.ax['dpl_L'], xlim, layer='L2') - - color_dpl_L5 = '#1e90ff' - - # these colors mirror below, should be vars - f.ax['dpl_L'].lines[0].set_color('k') - f.ax['dpl_L'].lines[1].set_color(color_dpl_L5) - f.ax['dpl_L'].lines[2].set_color('#b22222') - - # plot and color - dpl.plot(f.ax['dpl_M'], xlim, layer='L2') - f.set_linecolor('dpl_M', '#b22222') - - # plot and color - dpl.plot(f.ax['dpl_R'], xlim, layer='L5') - f.set_linecolor('dpl_R', color_dpl_L5) - - # equalize the ylim - # f.equalize_ylim(list_h_pgram) - f.equalize_speclim(pc) - f.equalize_ylim(['dpl_L', 'dpl_R']) - ylim_dpl_M = dpl.lim('L2', xlim) - # f.ysymmetry(f.ax['dpl_M']) - # f.ax['dpl_M'].set_ylim(ylim_dpl_M) - f.ax['dpl_M'].set_ylim((-0.01, 0.01)) - for ax in f.ax.keys(): - if ax.startswith('dpl'): - f.ax[ax].locator_params(axis='y', nbins=7) - - # spikes - spikes = { - 'L2': spikefn.filter_spike_dict(s, 'L2_'), - 'L5': spikefn.filter_spike_dict(s, 'L5_'), - } - - # plot the data - spikefn.spike_png(f.ax['spk_M'], spikes['L2']) - spikefn.spike_png(f.ax['spk_R'], spikes['L5']) - f.ax['spk_M'].set_xlim(xlim) - f.ax['spk_R'].set_xlim(xlim) - - # thin the yaxis - # function defined in FigBase() - # f.thin_yaxis(f.ax['current_M'], 5) - f.ax['current_M'].locator_params(axis='y', nbins=5) - f.ax['current_M'].set_ylim((-0.20, 0)) - f.ax['current_R'].locator_params(axis='y', nbins=5) - f.ax['current_R'].set_ylim((-0.8, 0)) - - # Welch number of labels - f.ax['pgram_M'].locator_params(axis='y', nbins=5) - f.ax['pgram_R'].locator_params(axis='y', nbins=5) - f.ax['pgram_L'].locator_params(axis='y', nbins=5) - - # set the colors - f.set_linecolor('pgram_L', 'k') - f.set_linecolor('pgram_R', 'k') - f.set_linecolor('pgram_M', 'k') - - # save the fig in ddata0 (arbitrary) - f_prefix = '%s_laminar' % ddata.sim_prefix - dfig = os.path.join(ddata.dsim, expmt) - - f.savepng_new(dfig, f_prefix) - f.saveeps(dfig, f_prefix) - f.close() - -# compares PING regimes for two different trial runs -def compare_ping(): - dproj = fio.return_data_dir() - runtype = 'pub2' - # runtype = 'debug' - - # data directories (made up for now) - # the resultant figure is saved in d0 - d0 = os.path.join(dproj, 'pub', '2013-06-28_gamma_ping_L5-000') - d1 = os.path.join(dproj, 'pub', '2013-07-31_gamma_weak_L5-000') - # d1 = os.path.join(dproj, 'pub', '2013-06-28_gamma_weak_L5-000') - - # hard code the data for now - ddata0 = fio.SimulationPaths() - ddata1 = fio.SimulationPaths() - - # use read_sim() to read the simulations - ddata0.read_sim(dproj, d0) - ddata1.read_sim(dproj, d1) - - # for now grab the first experiment in each - expmt0 = ddata0.expmt_groups[0] - expmt1 = ddata1.expmt_groups[0] - - # for now hard code the simulation run - run0 = 0 - run1 = 0 - - # prints the fig in ddata0 - f = acg.FigL5PingExample(runtype) - - # first panel data - f_spec0 = ddata0.file_match(expmt0, 'rawspec')[run0] - f_dpl0 = ddata0.file_match(expmt0, 'rawdpl')[run0] - f_spk0 = ddata0.file_match(expmt0, 'rawspk')[run0] - f_param0 = ddata0.file_match(expmt0, 'param')[run0] - f_current0 = ddata0.file_match(expmt0, 'rawcurrent')[run0] - - # figure out the tstop and xlim - tstop0 = paramrw.find_param(f_param0, 'tstop') - dt = paramrw.find_param(f_param0, 'dt') - xlim0 = (50., tstop0) - - # grab the dipole data - dpl0 = dipolefn.Dipole(f_dpl0) - dpl0.baseline_renormalize(f_param0) - dpl0.convert_fAm_to_nAm() - - # calculate the Welch periodogram - f_max = 150. - pgram0 = specfn.Welch(dpl0.t, dpl0.dpl['L5'], dt) - pgram0.plot_to_ax(f.ax['pgram_L'], f_max) - - # grab the spike data - s0 = spikefn.spikes_from_file(f_param0, f_spk0) - s0_L5 = spikefn.filter_spike_dict(s0, 'L5_') - - # plot the spike histogram data - icell0_spikes = np.concatenate(s0_L5['L5_basket'].spike_list) - ecell0_spikes = np.concatenate(s0_L5['L5_pyramidal'].spike_list) - - # 1 ms bins - n_bins = int(tstop0) - - f.ax['hist_L'].hist(icell0_spikes, n_bins, facecolor='r', histtype='stepfilled', alpha=0.75, edgecolor='none') - f.ax_twinx['hist_L'].hist(ecell0_spikes, n_bins, facecolor='k') - - # based on number of cells - f.ax['hist_L'].set_ylim((0, 20)) - f.ax_twinx['hist_L'].set_ylim((0, 100)) - - f.ax_twinx['hist_L'].set_xlim(xlim0) - f.ax['hist_L'].set_xlim(xlim0) - - # hack - labels = f.ax['hist_L'].yaxis.get_ticklocs() - labels_text = [str(label) for label in labels[:-1]] - for i in range(len(labels_text)): - labels_text[i] = '' - - labels_text.append('20') - f.ax['hist_L'].set_yticklabels(labels_text) - - labels_twinx = f.ax_twinx['hist_L'].yaxis.get_ticklocs() - labels_text = [str(label) for label in labels_twinx[:-1]] - for i in range(len(labels_text)): - labels_text[i] = '' - - labels_text.append('100') - f.ax_twinx['hist_L'].set_yticklabels(labels_text) - - # grab the current data - I_soma0 = currentfn.SynapticCurrent(f_current0) - I_soma0.convert_nA_to_uA() - - # plot the data - dpl0.plot(f.ax['dpl_L'], xlim0, layer='L5') - spikefn.spike_png(f.ax['raster_L'], s0_L5) - f.ax['raster_L'].set_xlim(xlim0) - - # second panel data - f_spec1 = ddata1.file_match(expmt1, 'rawspec')[run1] - f_dpl1 = ddata1.file_match(expmt1, 'rawdpl')[run1] - f_spk1 = ddata1.file_match(expmt1, 'rawspk')[run1] - f_param1 = ddata1.file_match(expmt1, 'param')[run1] - f_current1 = ddata1.file_match(expmt1, 'rawcurrent')[run1] - - # figure out the tstop and xlim - tstop1 = paramrw.find_param(f_param1, 'tstop') - xlim1 = (50., tstop1) - - # grab the dipole data - dpl1 = dipolefn.Dipole(f_dpl1) - dpl1.baseline_renormalize(f_param1) - dpl1.convert_fAm_to_nAm() - - # calculate the Welch periodogram - pgram1 = specfn.Welch(dpl1.t, dpl1.dpl['L5'], dt) - pgram1.plot_to_ax(f.ax['pgram_R'], f_max) - - # grab the spike data - s1 = spikefn.spikes_from_file(f_param1, f_spk1) - s1_L5 = spikefn.filter_spike_dict(s1, 'L5_') - # s1_L2 = spikefn.filter_spike_dict(s1, 'L2_') - - # plot the spike histogram data - icell1_spikes = np.concatenate(s1_L5['L5_basket'].spike_list) - ecell1_spikes = np.concatenate(s1_L5['L5_pyramidal'].spike_list) - - # 1 ms bins - n_bins = int(tstop1) - - f.ax['hist_R'].hist(icell1_spikes, n_bins, facecolor='r', histtype='stepfilled', alpha=0.75, edgecolor='none') - f.ax_twinx['hist_R'].hist(ecell1_spikes, n_bins, facecolor='k') - - # based on number of cells - f.ax['hist_R'].set_ylim((0, 12)) - f.ax_twinx['hist_R'].set_ylim((0, 12)) - - f.ax_twinx['hist_R'].set_xlim(xlim0) - f.ax['hist_R'].set_xlim(xlim0) - - # hack - labels = f.ax['hist_R'].yaxis.get_ticklocs() - labels_text = [str(label) for label in labels[:-1]] - for i in range(len(labels_text)): - labels_text[i] = '' - - labels_text.append('12') - f.ax['hist_R'].set_yticklabels(labels_text) - - labels_twinx = f.ax_twinx['hist_R'].yaxis.get_ticklocs() - labels_text = [str(label) for label in labels_twinx[:-1]] - for i in range(len(labels_text)): - labels_text[i] = '' - - labels_text.append('12') - f.ax_twinx['hist_R'].set_yticklabels(labels_text) - - # grab the current data - I_soma1 = currentfn.SynapticCurrent(f_current1) - I_soma1.convert_nA_to_uA() - - # plot the data - dpl1.plot(f.ax['dpl_R'], xlim1, layer='L5') - f.ysymmetry(f.ax['dpl_R']) - spikefn.spike_png(f.ax['raster_R'], s1_L5) - f.ax['raster_R'].set_xlim(xlim1) - - # plot the spec data - pc = { - 'L': specfn.pspec_ax(f.ax['spec_L'], f_spec0, xlim0, layer='L5'), - 'R': specfn.pspec_ax(f.ax['spec_R'], f_spec1, xlim1, layer='L5'), - } - - # f.equalize_speclim(pc) - - # grab the dipole figure handles - list_h_dpl = [h for h in f.ax.keys() if h.startswith('dpl')] - for ax_h in list_h_dpl: - f.ax[ax_h].locator_params(axis='y', nbins=5) - # f.equalize_ylim(list_h_dpl) - - # and the pgrams - # list_h_pgram = [h for h in f.ax.keys() if h.startswith('pgram')] - # test = f.equalize_ylim(list_h_pgram) - - # plot current and do lims - I_soma0.plot_to_axis(f.ax['current_L'], 'L5') - I_soma1.plot_to_axis(f.ax['current_R'], 'L5') - list_h_current = [ax_h for ax_h in f.ax.keys() if ax_h.startswith('current')] - f.equalize_ylim(list_h_current) - - # this is a hack - # now in uA instead of nA - for ax_handle in f.ax.keys(): - if ax_handle.startswith('current_'): - f.ax[ax_handle].set_ylim((-2, 0.)) - - # testing something - # f.ax['pgram_L'].set_yscale('log') - # f.ax['pgram_R'].set_yscale('log') - # f.ax['pgram_L'].set_ylim((1e-12, 1e-3)) - # f.ax['pgram_R'].set_ylim((1e-12, 1e-3)) - - # save the fig in ddata0 (arbitrary) - f_prefix = 'gamma_L5ping_L5weak' - dfig = os.path.join(ddata0.dsim, expmt0) - - # create the colorbars - cb = dict.fromkeys(pc) - - if runtype in ('debug', 'pub2'): - for key in pc.keys(): - key_ax = 'spec_' + key - cb[key] = f.f.colorbar(pc[key], ax=f.ax[key_ax], format='%.1e') - - elif runtype == 'pub': - cb['R'] = f.f.colorbar(pc['R'], ax=f.ax['spec_R'], format='%.1e') - - f.savepng_new(dfig, f_prefix) - f.saveeps(dfig, f_prefix) - f.close() - -def sub_dist_examples(): - dproj = fio.return_data_dir() - # runtype = 'pub2' - runtype = 'debug' - - # data directories (made up for now) - # the resultant figure is saved in d0 - d0 = os.path.join(dproj, 'pub', '2013-07-01_gamma_sub_50Hz-002') - d1 = os.path.join(dproj, 'pub', '2013-07-18_gamma_sub_100Hz-000') - - # hard code the data for now - ddata0 = fio.SimulationPaths() - ddata1 = fio.SimulationPaths() - - # use read_sim() to read the simulations - ddata0.read_sim(dproj, d0) - ddata1.read_sim(dproj, d1) - - # for now grab the first experiment in each - expmt0 = ddata0.expmt_groups[0] - expmt1 = ddata1.expmt_groups[0] - - # for now hard code the simulation run - run0 = 0 - run1 = 0 - - # number of bins for the spike histograms - n_bins = 500 - - # prints the fig in ddata0 - f = acg.FigSubDistExample(runtype) - - # first panel data - f_spec0 = ddata0.file_match(expmt0, 'rawspec')[run0] - f_dpl0 = ddata0.file_match(expmt0, 'rawdpl')[run0] - f_spk0 = ddata0.file_match(expmt0, 'rawspk')[run0] - f_param0 = ddata0.file_match(expmt0, 'param')[run0] - # f_current0 = ddata0.file_match(expmt0, 'rawcurrent')[run0] - - # figure out the tstop and xlim - tstop0 = paramrw.find_param(f_param0, 'tstop') - dt = paramrw.find_param(f_param0, 'dt') - xlim0 = (50., tstop0) - - # grab the dipole data - dpl0 = dipolefn.Dipole(f_dpl0) - dpl0.baseline_renormalize(f_param0) - dpl0.convert_fAm_to_nAm() - - # grab the current data - # I_soma0 = currentfn.SynapticCurrent(f_current0) - - # grab the spike data - _, p_dict0 = paramrw.read(f_param0) - s0 = spikefn.spikes_from_file(f_param0, f_spk0) - s0 = spikefn.alpha_feed_verify(s0, p_dict0) - sp_list = s0['alpha_feed_prox'].spike_list[0] - sd_list = s0['alpha_feed_dist'].spike_list[0] - spikefn.pinput_hist(f.ax['hist_L'], f.ax_twinx['hist_L'], sp_list, sd_list, n_bins, xlim0) - - # plot the data - dpl0.plot(f.ax['dpl_L'], xlim0, layer='L5') - - # second panel data - f_spec1 = ddata1.file_match(expmt1, 'rawspec')[run1] - f_dpl1 = ddata1.file_match(expmt1, 'rawdpl')[run1] - f_spk1 = ddata1.file_match(expmt1, 'rawspk')[run1] - f_param1 = ddata1.file_match(expmt1, 'param')[run1] - # f_current1 = ddata1.file_match(expmt1, 'rawcurrent')[run1] - - # figure out the tstop and xlim - tstop1 = paramrw.find_param(f_param1, 'tstop') - xlim1 = (50., tstop1) - - # grab the dipole data - dpl1 = dipolefn.Dipole(f_dpl1) - dpl1.baseline_renormalize(f_param1) - dpl1.convert_fAm_to_nAm() - - # # calculate the Welch periodogram - # pgram1 = specfn.Welch(dpl1.t, dpl1.dpl['L5'], dt) - # pgram1.plot_to_ax(f.ax['pgram_R'], f_max) - - # grab the spike data - _, p_dict1 = paramrw.read(f_param1) - s1 = spikefn.spikes_from_file(f_param1, f_spk1) - s1 = spikefn.alpha_feed_verify(s1, p_dict1) - sp_list = s1['alpha_feed_prox'].spike_list[0] - sd_list = s1['alpha_feed_dist'].spike_list[0] - spikefn.pinput_hist(f.ax['hist_R'], f.ax_twinx['hist_R'], sp_list, sd_list, n_bins, xlim1) - - # grab the current data - # I_soma1 = currentfn.SynapticCurrent(f_current1) - - # plot the data - dpl1.plot(f.ax['dpl_R'], xlim1, layer='L5') - # spikefn.spike_png(f.ax['raster_R'], s1_L5) - # f.ax['raster_R'].set_xlim(xlim1) - - # plot the spec data - pc = { - 'L': specfn.pspec_ax(f.ax['spec_L'], f_spec0, xlim0, layer='L5'), - 'R': specfn.pspec_ax(f.ax['spec_R'], f_spec1, xlim1, layer='L5'), - } - - # f.equalize_speclim(pc) - - # # grab the dipole figure handles - # # list_h_dpl = [h for h in f.ax.keys() if h.startswith('dpl')] - # # f.equalize_ylim(list_h_dpl) - - # # and the pgrams - # # list_h_pgram = [h for h in f.ax.keys() if h.startswith('pgram')] - # # test = f.equalize_ylim(list_h_pgram) - - # # plot current and do lims - # I_soma0.plot_to_axis(f.ax['current_L'], 'L5') - # I_soma1.plot_to_axis(f.ax['current_R'], 'L5') - # for ax_handle in f.ax.keys(): - # if ax_handle.startswith('current_'): - # f.ax[ax_handle].set_ylim((-2000, 0.)) - - # # testing something - # # f.ax['pgram_L'].set_yscale('log') - # # f.ax['pgram_R'].set_yscale('log') - # # f.ax['pgram_L'].set_ylim((1e-12, 1e-3)) - # # f.ax['pgram_R'].set_ylim((1e-12, 1e-3)) - - # save the fig in ddata0 (arbitrary) - f_prefix = 'gamma_sub_examples' - dfig = os.path.join(ddata0.dsim, expmt0) - - # create the colorbars - cb = dict.fromkeys(pc) - - if runtype == 'debug': - for key in pc.keys(): - key_ax = 'spec_' + key - cb[key] = f.f.colorbar(pc[key], ax=f.ax[key_ax]) - - elif runtype == 'pub': - cb['R'] = f.f.colorbar(pc['R'], ax=f.ax['spec_R']) - - f.savepng_new(dfig, f_prefix) - f.saveeps(dfig, f_prefix) - f.close() - -def sub_dist_example2(): - dproj = fio.return_data_dir() - runtype = 'pub2' - # runtype = 'debug' - - # data directories (made up for now) - # the resultant figure is saved in d0 - d0 = os.path.join(dproj, 'pub', '2013-08-07_gamma_sub_50Hz-000') - - # hard code the data for now - ddata0 = fio.SimulationPaths() - - # use read_sim() to read the simulations - ddata0.read_sim(dproj, d0) - - # for now grab the first experiment in each - expmt0 = ddata0.expmt_groups[0] - - # for now hard code the simulation run - run0 = 0 - run1 = 1 - - # number of bins for the spike histograms - n_bins = 500 - - # prints the fig in ddata0 - f = acg.FigSubDistExample(runtype) - - # first panel data - f_spec0 = ddata0.file_match(expmt0, 'rawspec')[run0] - f_dpl0 = ddata0.file_match(expmt0, 'rawdpl')[run0] - f_spk0 = ddata0.file_match(expmt0, 'rawspk')[run0] - f_param0 = ddata0.file_match(expmt0, 'param')[run0] - # f_current0 = ddata0.file_match(expmt0, 'rawcurrent')[run0] - - # figure out the tstop and xlim - tstop0 = paramrw.find_param(f_param0, 'tstop') - dt = paramrw.find_param(f_param0, 'dt') - xlim0 = (50., tstop0) - - # grab the dipole data - dpl0 = dipolefn.Dipole(f_dpl0) - dpl0.baseline_renormalize(f_param0) - dpl0.convert_fAm_to_nAm() - - # grab the current data - # I_soma0 = currentfn.SynapticCurrent(f_current0) - - # grab the spike data - _, p_dict0 = paramrw.read(f_param0) - s0 = spikefn.spikes_from_file(f_param0, f_spk0) - s0 = spikefn.alpha_feed_verify(s0, p_dict0) - sp_list = s0['alpha_feed_prox'].spike_list[0] - sd_list = s0['alpha_feed_dist'].spike_list[0] - spikefn.pinput_hist(f.ax['hist_L'], f.ax_twinx['hist_L'], sp_list, sd_list, n_bins, xlim0) - - # plot the data - dpl0.plot(f.ax['dpl_L'], xlim0, layer='L5') - - # second panel data - f_spec1 = ddata0.file_match(expmt0, 'rawspec')[run1] - f_dpl1 = ddata0.file_match(expmt0, 'rawdpl')[run1] - f_spk1 = ddata0.file_match(expmt0, 'rawspk')[run1] - f_param1 = ddata0.file_match(expmt0, 'param')[run1] - # f_current1 = ddata1.file_match(expmt1, 'rawcurrent')[run1] - - # figure out the tstop and xlim - tstop1 = paramrw.find_param(f_param1, 'tstop') - xlim1 = (50., tstop1) - - # grab the dipole data - dpl1 = dipolefn.Dipole(f_dpl1) - dpl1.baseline_renormalize(f_param1) - dpl1.convert_fAm_to_nAm() - - # # calculate the Welch periodogram - # pgram1 = specfn.Welch(dpl1.t, dpl1.dpl['L5'], dt) - # pgram1.plot_to_ax(f.ax['pgram_R'], f_max) - - # grab the spike data - _, p_dict1 = paramrw.read(f_param1) - s1 = spikefn.spikes_from_file(f_param1, f_spk1) - s1 = spikefn.alpha_feed_verify(s1, p_dict1) - sp_list = s1['alpha_feed_prox'].spike_list[0] - sd_list = s1['alpha_feed_dist'].spike_list[0] - spikefn.pinput_hist(f.ax['hist_R'], f.ax_twinx['hist_R'], sp_list, sd_list, n_bins, xlim1) - f.ax['hist_R'].set_ylim((0., 20.)) - f.ax_twinx['hist_R'].set_ylim((20., 0.)) - - # grab the current data - # I_soma1 = currentfn.SynapticCurrent(f_current1) - - # plot the data - dpl1.plot(f.ax['dpl_R'], xlim1, layer='L5') - # spikefn.spike_png(f.ax['raster_R'], s1_L5) - # f.ax['raster_R'].set_xlim(xlim1) - - # plot the spec data - pc = { - 'L': specfn.pspec_ax(f.ax['spec_L'], f_spec0, xlim0, layer='L5'), - 'R': specfn.pspec_ax(f.ax['spec_R'], f_spec1, xlim1, layer='L5'), - } - - # change the xlim format - f.set_notation_scientific([ax for ax in f.ax.keys() if ax.startswith('dpl')], n=2) - - # f.equalize_speclim(pc) - - # # grab the dipole figure handles - list_h_dpl = [h for h in f.ax.keys() if h.startswith('dpl')] - f.equalize_ylim(list_h_dpl) - - # hack. - f.ax['dpl_R'].set_yticklabels('') - - # and the pgrams - # list_h_pgram = [h for h in f.ax.keys() if h.startswith('pgram')] - # test = f.equalize_ylim(list_h_pgram) - - # # plot current and do lims - # I_soma0.plot_to_axis(f.ax['current_L'], 'L5') - # I_soma1.plot_to_axis(f.ax['current_R'], 'L5') - # for ax_handle in f.ax.keys(): - # if ax_handle.startswith('current_'): - # f.ax[ax_handle].set_ylim((-2000, 0.)) - - # # testing something - # # f.ax['pgram_L'].set_yscale('log') - # # f.ax['pgram_R'].set_yscale('log') - # # f.ax['pgram_L'].set_ylim((1e-12, 1e-3)) - # # f.ax['pgram_R'].set_ylim((1e-12, 1e-3)) - - # save the fig in ddata0 (arbitrary) - f_prefix = 'gamma_sub_examples' - dfig = os.path.join(ddata0.dsim, expmt0) - - # create the colorbars - cb = dict.fromkeys(pc) - - if runtype in ('debug', 'pub2'): - for key in pc.keys(): - key_ax = 'spec_' + key - cb[key] = f.f.colorbar(pc[key], ax=f.ax[key_ax]) - - f.remove_twinx_labels() - - elif runtype == 'pub': - cb['R'] = f.f.colorbar(pc['R'], ax=f.ax['spec_R']) - - f.savepng_new(dfig, f_prefix) - f.saveeps(dfig, f_prefix) - f.close() - -# plots a histogram of e cell spikes relative to I cell spikes -def spikephase(): - dproj = fio.return_data_dir() - # runtype = 'pub2' - runtype = 'debug' - - # data directories (made up for now) - # the resultant figure is saved in d0 - d0 = os.path.join(dproj, 'pub', '2013-06-28_gamma_weak_L5-000') - - # hard code the data for now - ddata0 = fio.SimulationPaths() - - # use read_sim() to read the simulations - ddata0.read_sim(dproj, d0) - - # for now grab the first experiment in each - expmt0 = ddata0.expmt_groups[0] - - # for now hard code the simulation run - run0 = 0 - - # prints the fig in ddata0 - f = ac.FigStd() - - # create a twin axis - f.create_axis_twinx('ax0') - - # first panel data - f_spec0 = ddata0.file_match(expmt0, 'rawspec')[run0] - f_dpl0 = ddata0.file_match(expmt0, 'rawdpl')[run0] - f_spk0 = ddata0.file_match(expmt0, 'rawspk')[run0] - f_param0 = ddata0.file_match(expmt0, 'param')[run0] - # f_current0 = ddata0.file_match(expmt0, 'rawcurrent')[run0] - - # figure out the tstop and xlim - tstop0 = paramrw.find_param(f_param0, 'tstop') - dt = paramrw.find_param(f_param0, 'dt') - xlim0 = (0., tstop0) - - # grab the spike data - _, p_dict0 = paramrw.read(f_param0) - s0 = spikefn.spikes_from_file(f_param0, f_spk0) - icell_spikes = s0['L5_basket'].spike_list - ecell_spikes = s0['L5_pyramidal'].spike_list - - ispike_counts = [len(slist) for slist in icell_spikes] - espike_counts = [len(slist) for slist in ecell_spikes] - - # let's try a sort ... - icell_spikes_agg = np.concatenate(icell_spikes) - ecell_spikes_agg = np.concatenate(ecell_spikes) - - # lop off the first 50 ms - icell_spikes_agg = icell_spikes_agg[icell_spikes_agg >= 50] - icell_spikes_agg_sorted = np.sort(icell_spikes_agg) - - n_bins = int(tstop0 - 50) - - f.ax['ax0'].hist(icell_spikes_agg, n_bins, facecolor='r', histtype='stepfilled', alpha=0.75, edgecolor='none') - f.ax_twinx['ax0'].hist(ecell_spikes_agg, n_bins, facecolor='k') - # f.ax_twinx['ax0'].hist(ecell_spikes_agg, n_bins, facecolor='k', alpha=0.75) - - # sets these lims to the MAX number of possible events per bin (n_celltype limited) - f.ax['ax0'].set_ylim((0, 35)) - f.ax_twinx['ax0'].set_ylim((0, 100)) - - f.ax_twinx['ax0'].set_xlim((50, tstop0)) - f.ax['ax0'].set_xlim((50, tstop0)) - - f.savepng_new(d0, 'testing') - f.close() - - # save the fig in ddata0 (arbitrary) - f_prefix = 'gamma_spikephase' - dfig = os.path.join(ddata0.dsim, expmt0) - -def peaks(): - dproj = fio.return_data_dir() - # runtype = 'pub2' - runtype = 'debug' - - # data directories (made up for now) - # the resultant figure is saved in d0 - d0 = os.path.join(dproj, 'pub', '2013-07-01_gamma_sub_50Hz-002') - # d1 = os.path.join(dproj, 'pub', '2013-07-18_gamma_sub_100Hz-000') - - # hard code the data for now - ddata0 = fio.SimulationPaths() - - # use read_sim() to read the simulations - ddata0.read_sim(dproj, d0) - - # for now grab the first experiment in each - expmt0 = ddata0.expmt_groups[0] - - # for now hard code the simulation run - run0 = 0 - - # prints the fig in ddata0 - f = acg.FigPeaks(runtype) - - # first panel data - f_spec0 = ddata0.file_match(expmt0, 'rawspec')[run0] - f_dpl0 = ddata0.file_match(expmt0, 'rawdpl')[run0] - f_spk0 = ddata0.file_match(expmt0, 'rawspk')[run0] - f_param0 = ddata0.file_match(expmt0, 'param')[run0] - # f_current0 = ddata0.file_match(expmt0, 'rawcurrent')[run0] - - # figure out the tstop and xlim - tstop0 = paramrw.find_param(f_param0, 'tstop') - dt = paramrw.find_param(f_param0, 'dt') - xlim0 = (0., tstop0) - - # grab the dipole data - dpl0 = dipolefn.Dipole(f_dpl0) - dpl0.baseline_renormalize(f_param0) - dpl0.convert_fAm_to_nAm() - - # grab the current data - # I_soma0 = currentfn.SynapticCurrent(f_current0) - - # grab the spike data - _, p_dict0 = paramrw.read(f_param0) - s0 = spikefn.spikes_from_file(f_param0, f_spk0) - s0 = spikefn.alpha_feed_verify(s0, p_dict0) - sp_list = s0['alpha_feed_prox'].spike_list[0] - sd_list = s0['alpha_feed_dist'].spike_list[0] - # spikefn.pinput_hist(f.ax['hist_L'], f.ax_twinx['hist_L'], sp_list, sd_list, n_bins, xlim0) - - # plot the data - dpl0.plot(f.ax['dpl_L'], xlim0, layer='L5') - - # plot the spec data - pc = { - 'L': specfn.pspec_ax(f.ax['spec_L'], f_spec0, xlim0, layer='L5'), - } - - # save the fig in ddata0 (arbitrary) - f_prefix = 'gamma_peaks' - dfig = os.path.join(ddata0.dsim, expmt0) - - # create the colorbars - cb = dict.fromkeys(pc) - - if runtype == 'debug': - for key in pc.keys(): - key_ax = 'spec_' + key - cb[key] = f.f.colorbar(pc[key], ax=f.ax[key_ax]) - - elif runtype == 'pub': - cb['R'] = f.f.colorbar(pc['L'], ax=f.ax['spec_L']) - - f.savepng_new(dfig, f_prefix) - f.saveeps(dfig, f_prefix) - f.close() - -# needs spec for multiple experiments, will plot 2 examples and aggregate -def pgamma_distal_phase(ddata, data_L=0, data_M=1, data_R=2): - layer_specific = 'agg' - - for expmt in ddata.expmt_groups: - f = acg.FigDistalPhase() - - # grab file lists - list_spec = ddata.file_match(expmt, 'rawspec') - list_dpl = ddata.file_match(expmt, 'rawdpl') - list_spk = ddata.file_match(expmt, 'rawspk') - list_param = ddata.file_match(expmt, 'param') - - # grab the tstop and make an xlim - T = paramrw.find_param(list_param[0], 'tstop') - xlim = (50., T) - - # grab the input frequency, try prox before dist - f_max = paramrw.find_param(list_param[0], 'f_input_prox') - - # only try dist if prox is 0, otherwise, use prox - if not f_max: - f_max = paramrw.find_param(list_param[0], 'f_input_dist') - - # dealing with the left panel - dpl_L = dipolefn.Dipole(list_dpl[data_L]) - dpl_L.baseline_renormalize(list_param[data_L]) - dpl_L.convert_fAm_to_nAm() - dpl_L.plot(f.ax['dpl_L'], xlim, layer='agg') - - # middle data panel - dpl_M = dipolefn.Dipole(list_dpl[data_M]) - dpl_M.baseline_renormalize(list_param[data_M]) - dpl_M.convert_fAm_to_nAm() - dpl_M.plot(f.ax['dpl_M'], xlim, layer='agg') - - # dealing with right panel - dpl_R = dipolefn.Dipole(list_dpl[data_R]) - dpl_R.baseline_renormalize(list_param[data_R]) - dpl_R.convert_fAm_to_nAm() - dpl_R.plot(f.ax['dpl_R'], xlim, layer='agg') - - # get the vmin, vmax and add them to the master list - pc = { - 'L': specfn.pspec_ax(f.ax['spec_L'], list_spec[data_L], xlim, layer=layer_specific), - 'M': specfn.pspec_ax(f.ax['spec_M'], list_spec[data_M], xlim, layer=layer_specific), - 'R': specfn.pspec_ax(f.ax['spec_R'], list_spec[data_R], xlim, layer=layer_specific), - } - - # use the equalize function - f.equalize_speclim(pc) - - # create colorbars - f.f.colorbar(pc['L'], ax=f.ax['spec_L']) - f.f.colorbar(pc['M'], ax=f.ax['spec_M']) - f.f.colorbar(pc['R'], ax=f.ax['spec_R']) - - # hist data - xlim_hist = (50., 100.) - - # get the data for the left panel - _, p_dict = paramrw.read(list_param[data_L]) - s_L = spikefn.spikes_from_file(list_param[data_L], list_spk[data_L]) - s_L = spikefn.alpha_feed_verify(s_L, p_dict) - # n_bins = spikefn.hist_bin_opt(s_L['alpha_feed_prox'].spike_list, 10) - n_bins = 500 - - # prox and dist spike lists - sp_list = spike_list_truncate(s_L['alpha_feed_prox'].spike_list[0]) - sd_list = spike_list_truncate(s_L['alpha_feed_dist'].spike_list[0]) - spikefn.pinput_hist(f.ax['hist_L'], f.ax_twinx['hist_L'], sp_list, sd_list, n_bins, xlim_hist) - - # same motif as previous lines, I'm tired. - _, p_dict = paramrw.read(list_param[data_M]) - s_M = spikefn.spikes_from_file(list_param[data_M], list_spk[data_M]) - s_M = spikefn.alpha_feed_verify(s_M, p_dict) - sp_list = spike_list_truncate(s_M['alpha_feed_prox'].spike_list[0]) - sd_list = spike_list_truncate(s_M['alpha_feed_dist'].spike_list[0]) - spikefn.pinput_hist(f.ax['hist_M'], f.ax_twinx['hist_M'], sp_list, sd_list, n_bins, xlim_hist) - - # same motif as previous lines, I'm tired. - _, p_dict = paramrw.read(list_param[data_R]) - s_R = spikefn.spikes_from_file(list_param[data_R], list_spk[data_R]) - s_R = spikefn.alpha_feed_verify(s_R, p_dict) - sp_list = spike_list_truncate(s_R['alpha_feed_prox'].spike_list[0]) - sd_list = spike_list_truncate(s_R['alpha_feed_dist'].spike_list[0]) - spikefn.pinput_hist(f.ax['hist_R'], f.ax_twinx['hist_R'], sp_list, sd_list, n_bins, xlim_hist) - - # now do the aggregate data - # theta is the normalized phase - list_spec_max = np.zeros(len(list_spec)) - list_theta = np.zeros(len(list_spec)) - list_delay = np.zeros(len(list_spec)) - - i = 0 - for fspec, fparam in zip(list_spec, list_param): - # f_max comes from the input f - # f_max = 50. - t_pd = 1000. / f_max - - # read the data - data_spec = specfn.read(fspec) - - # use specpwr_stationary() to get an aggregate measure of power over the entire time - p_stat = specfn.specpwr_stationary(data_spec['time'], data_spec['freq'], data_spec['TFR']) - - # this is ONLY for aggregate and NOT for individual layers right now - # here, f_max is the hard coded one and NOT the calculated one from specpwr_stationary() - list_spec_max[i] = p_stat['p'][p_stat['f']==f_max] - - # get the relevant param's value - t0_prox = paramrw.find_param(fparam, 't0_input_prox') - t0_dist = paramrw.find_param(fparam, 't0_input_dist') - - # calculating these two together BUT don't need to. Cleanness beats efficiency here - list_delay[i] = t0_dist - t0_prox - list_theta[i] = list_delay[i] / t_pd - - i += 1 - - f.ax['aggregate'].plot(list_delay, list_spec_max, marker='o') - - # deal with names - f_prefix = 'gamma_%s_distal_phase' % expmt - dfig = os.path.join(ddata.dsim, expmt) - - f.savepng_new(dfig, f_prefix) - f.saveeps(dfig, f_prefix) - f.close() - -def spike_list_truncate(s_list): - return s_list[(s_list > 55.) & (s_list < 100.)] - -# needs spec for 3 experiments -# really a generic comparison of the top 3 sims in a given exp -# the list is naturally truncated by the length of ax_suffices -def pgamma_stdev(ddata): - for expmt in ddata.expmt_groups: - # runtype = 'debug' - # runtype = 'pub2' - runtype = 'pub' - - f = acg.Fig3PanelPlusAgg(runtype) - - # data types - list_spec = ddata.file_match(expmt, 'rawspec') - list_dpl = ddata.file_match(expmt, 'rawdpl') - list_param = ddata.file_match(expmt, 'param') - list_spk = ddata.file_match(expmt, 'rawspk') - - # time info - T = paramrw.find_param(list_param[0], 'tstop') - xlim = (50., T) - - # assume only the first 3 files are the ones we care about - ax_suffices = [ - '_L', - '_M', - '_R', - '_FR', - ] - - # dpl handles list - list_handles_dpl = [] - - # spec handles - pc = {} - - # lists in zip are naturally truncated by the shortest list - for ax_end, fdpl, fspec, fparam, fspk in zip(ax_suffices, list_dpl, list_spec, list_param, list_spk): - # create axis handle names - ax_dpl = 'dpl%s' % ax_end - ax_spec = 'spec%s' % ax_end - ax_hist = 'hist%s' % ax_end - - # add to my terrible list - list_handles_dpl.append(ax_dpl) - - # grab the dipole and convert - dpl = dipolefn.Dipole(fdpl) - dpl.baseline_renormalize(fparam) - dpl.convert_fAm_to_nAm() - - # plot relevant data - dpl.plot(f.ax[ax_dpl], xlim, layer='L5') - pc[ax_spec] = specfn.pspec_ax(f.ax[ax_spec], fspec, xlim, layer='L5') - - # only set the colorbar for all axes in debug mode - # otherwise set only for the rightmost spec axis - if runtype in ('debug', 'pub2'): - f.f.colorbar(pc[ax_spec], ax=f.ax[ax_spec]) - - elif runtype == 'pub': - if ax_end == '_FR': - f.f.colorbar(pc[ax_spec], ax=f.ax[ax_spec]) - - # histogram stuff - _, p_dict = paramrw.read(fparam) - s = spikefn.spikes_from_file(fparam, fspk) - s = spikefn.alpha_feed_verify(s, p_dict) - - # result of the optimization function, for the right 2 panels. 100 - # was the value returned for the L panel for f plot - # result for stdev plot was 290, 80, 110 - # n_bins = spikefn.hist_bin_opt(s['alpha_feed_prox'][0].spike_list, 10) - # print n_bins - n_bins = 110 - - # plot the hist - spikefn.pinput_hist_onesided(f.ax[ax_hist], s['alpha_feed_prox'][0].spike_list, n_bins) - f.ax[ax_hist].set_xlim(xlim) - - # equalize ylim on hists - list_ax_hist = [ax for ax in f.ax.keys() if ax.startswith('hist')] - f.equalize_ylim(list_ax_hist) - - # normalize the spec - f.equalize_speclim(pc) - f.remove_twinx_labels() - - # normalize the dpl with that hack - # centers c and lim l - # c = [1e-3, 1.2e-3, 1.8e-3] - # l = 2e-3 - # for h in list_handles_dpl: - # f.ax[h].set_ylim((-3e-3, 3e-3)) - f.ysymmetry(f.ax['dpl_L']) - f.set_notation_scientific(['dpl_L']) - list_ax_dpl = [ax for ax in f.ax.keys() if ax.startswith('dpl')] - f.equalize_ylim(list_ax_dpl) - - # some fig naming stuff - fprefix_short = 'gamma_%s_compare3' % expmt - dfig = os.path.join(ddata.dsim, expmt) - - # use methods to save figs - f.savepng_new(dfig, fprefix_short) - f.saveeps(dfig, fprefix_short) - f.close() - -def pgamma_stdev_new(ddata, p): - for expmt in ddata.expmt_groups: - # runtype = 'debug' - runtype = 'pub2' - # runtype = 'pub' - - f = acg.Fig3PanelPlusAgg(runtype) - - # data types - list_spec = ddata.file_match(expmt, 'rawspec') - list_dpl = ddata.file_match(expmt, 'rawdpl') - list_param = ddata.file_match(expmt, 'param') - list_spk = ddata.file_match(expmt, 'rawspk') - - # time info - T = paramrw.find_param(list_param[0], 'tstop') - xlim = (50., T) - - # assume only the first 3 files are the ones we care about - ax_suffices = [ - '_L', - '_M', - '_R', - '_FR', - ] - - # dpl handles list - list_handles_dpl = [] - - # spec handles - pc = {} - - # pgram_list - list_pgram = [] - - # lists in zip are naturally truncated by the shortest list - for ax_end, fdpl, fspec, fparam, fspk in zip(ax_suffices, list_dpl, list_spec, list_param, list_spk): - # create axis handle names - ax_dpl = 'dpl%s' % ax_end - ax_spec = 'spec%s' % ax_end - ax_hist = 'hist%s' % ax_end - - # add to my terrible list - list_handles_dpl.append(ax_dpl) - - # grab the dipole and convert - dpl = dipolefn.Dipole(fdpl) - dpl.baseline_renormalize(fparam) - dpl.convert_fAm_to_nAm() - - # find the dpl lim - ylim_dpl = dpl.lim('L5', xlim) - - # plot relevant data - dpl.plot(f.ax[ax_dpl], xlim, layer='L5') - f.ax[ax_dpl].set_ylim(ylim_dpl) - pc[ax_spec] = specfn.pspec_ax(f.ax[ax_spec], fspec, xlim, layer='L5') - - # only set the colorbar for all axes in debug mode - # otherwise set only for the rightmost spec axis - if runtype in ('debug', 'pub2'): - f.f.colorbar(pc[ax_spec], ax=f.ax[ax_spec]) - - elif runtype == 'pub': - if ax_end == '_FR': - f.f.colorbar(pc[ax_spec], ax=f.ax[ax_spec]) - - # histogram stuff - _, p_dict = paramrw.read(fparam) - s = spikefn.spikes_from_file(fparam, fspk) - s = spikefn.alpha_feed_verify(s, p_dict) - - # result of the optimization function, for the right 2 panels. 100 - # was the value returned for the L panel for f plot - # result for stdev plot was 290, 80, 110 - # n_bins = spikefn.hist_bin_opt(s['alpha_feed_prox'][0].spike_list, 10) - # print n_bins - n_bins = 110 - - # plot the hist - spikefn.pinput_hist_onesided(f.ax[ax_hist], s['alpha_feed_prox'][0].spike_list, n_bins) - # print s['alpha_feed_prox'] - f.ax[ax_hist].set_xlim(xlim) - - # run the Welch and plot it - # get the dt, for the welch - dt = paramrw.find_param(fparam, 'dt') - list_pgram.append(specfn.Welch(dpl.t, dpl.dpl['L5'], dt)) - list_pgram[-1].scale(1e7) - # f.ax['pgram'].plot(list_pgram[-1].f, list_pgram[-1].P) - list_pgram[-1].plot_to_ax(f.ax['pgram'], p['f_max_welch']) - print list_pgram[-1].units - - # equalize ylim on hists - list_ax_hist = [ax for ax in f.ax.keys() if ax.startswith('hist')] - f.equalize_ylim(list_ax_hist) - for ax_h in list_ax_hist: - f.ax[ax_h].set_ylim((0, 10)) - f.ax[ax_h].locator_params(axis='y', nbins=3) - - # f.ax['pgram'].yaxis.tick_right() - - # normalize the spec - # f.equalize_speclim(pc) - f.remove_twinx_labels() - - # normalize the dpl with that hack - # centers c and lim l - # c = [1e-3, 1.2e-3, 1.8e-3] - # l = 2e-3 - # for h in list_handles_dpl: - # f.ax[h].set_ylim((-3e-3, 3e-3)) - # f.ysymmetry(f.ax['dpl_L']) - # f.set_notation_scientific(['dpl_L']) - list_ax_dpl = [ax for ax in f.ax.keys() if ax.startswith('dpl')] - f.equalize_ylim(list_ax_dpl) - for ax_h in list_ax_dpl: - f.ax[ax_h].locator_params(axis='y', nbins=5) - - # some fig naming stuff - fprefix_short = 'gamma_%s_compare3' % expmt - dfig = os.path.join(ddata.dsim, expmt) - - # use methods to save figs - f.savepng_new(dfig, fprefix_short) - f.saveeps(dfig, fprefix_short) - f.close() - -def prox_dist_new(ddata, p): - for expmt in ddata.expmt_groups: - # runtype = 'debug' - runtype = 'pub2' - # runtype = 'pub' - - f = acg.Fig3PanelPlusAgg(runtype) - - # data types - list_spec = ddata.file_match(expmt, 'rawspec') - list_dpl = ddata.file_match(expmt, 'rawdpl') - list_param = ddata.file_match(expmt, 'param') - list_spk = ddata.file_match(expmt, 'rawspk') - - # time info - T = paramrw.find_param(list_param[0], 'tstop') - xlim = (50., T) - - # assume only the first 3 files are the ones we care about - ax_suffices = [ - '_L', - '_M', - '_R', - '_FR', - ] - - # dpl handles list - list_handles_dpl = [] - - # spec handles - pc = {} - - # pgram_list - list_pgram = [] - - # lists in zip are naturally truncated by the shortest list - for ax_end, fdpl, fspec, fparam, fspk in zip(ax_suffices, list_dpl, list_spec, list_param, list_spk): - # create axis handle names - ax_dpl = 'dpl%s' % ax_end - ax_spec = 'spec%s' % ax_end - ax_hist = 'hist%s' % ax_end - - # add to my terrible list - list_handles_dpl.append(ax_dpl) - - # grab the dipole and convert - dpl = dipolefn.Dipole(fdpl) - dpl.baseline_renormalize(fparam) - dpl.convert_fAm_to_nAm() - - # find the dpl lim - ylim_dpl = dpl.lim('L5', xlim) - - # plot relevant data - dpl.plot(f.ax[ax_dpl], xlim, layer='L5') - f.ax[ax_dpl].set_ylim(ylim_dpl) - pc[ax_spec] = specfn.pspec_ax(f.ax[ax_spec], fspec, xlim, layer='L5') - - # only set the colorbar for all axes in debug mode - # otherwise set only for the rightmost spec axis - if runtype in ('debug', 'pub2'): - f.f.colorbar(pc[ax_spec], ax=f.ax[ax_spec]) - - elif runtype == 'pub': - if ax_end == '_FR': - f.f.colorbar(pc[ax_spec], ax=f.ax[ax_spec]) - - # histogram stuff - n_bins = 110 - _, p_dict = paramrw.read(fparam) - s = spikefn.spikes_from_file(fparam, fspk) - s = spikefn.alpha_feed_verify(s, p_dict) - sp_list = s['alpha_feed_prox'].spike_list[0] - sd_list = s['alpha_feed_dist'].spike_list[0] - spikefn.pinput_hist(f.ax[ax_hist], f.ax_twinx[ax_hist], sp_list, sd_list, n_bins, xlim) - - # result of the optimization function, for the right 2 panels. 100 - # was the value returned for the L panel for f plot - # result for stdev plot was 290, 80, 110 - # n_bins = spikefn.hist_bin_opt(s['alpha_feed_prox'][0].spike_list, 10) - # print n_bins - - # plot the hist - # spikefn.pinput_hist_onesided(f.ax[ax_hist], s['alpha_feed_prox'][0].spike_list, n_bins) - # print s['alpha_feed_prox'] - f.ax[ax_hist].set_xlim(xlim) - - # run the Welch and plot it - # get the dt, for the welch - dt = paramrw.find_param(fparam, 'dt') - list_pgram.append(specfn.Welch(dpl.t, dpl.dpl['L5'], dt)) - list_pgram[-1].scale(1e7) - # f.ax['pgram'].plot(list_pgram[-1].f, list_pgram[-1].P) - list_pgram[-1].plot_to_ax(f.ax['pgram'], p['f_max_welch']) - print list_pgram[-1].units - - # equalize ylim on hists - list_ax_hist = [ax for ax in f.ax.keys() if ax.startswith('hist')] - f.equalize_ylim(list_ax_hist) - - for ax_h in list_ax_hist: - f.ax[ax_h].set_ylim((0, 20)) - f.ax_twinx[ax_h].set_ylim((20, 0)) - - f.ax[ax_h].locator_params(axis='y', nbins=3) - f.ax_twinx[ax_h].locator_params(axis='y', nbins=3) - - f.ax_twinx['hist_M'].set_yticklabels('') - f.ax_twinx['hist_R'].set_yticklabels('') - # f.ax['pgram'].yaxis.tick_right() - - # normalize the spec - # f.equalize_speclim(pc) - # f.remove_twinx_labels() - - # normalize the dpl with that hack - # centers c and lim l - # c = [1e-3, 1.2e-3, 1.8e-3] - # l = 2e-3 - # for h in list_handles_dpl: - # f.ax[h].set_ylim((-3e-3, 3e-3)) - # f.ysymmetry(f.ax['dpl_L']) - # f.set_notation_scientific(['dpl_L']) - list_ax_dpl = [ax for ax in f.ax.keys() if ax.startswith('dpl')] - f.equalize_ylim(list_ax_dpl) - for ax_h in list_ax_dpl: - f.ax[ax_h].locator_params(axis='y', nbins=5) - - # some fig naming stuff - fprefix_short = 'gamma_%s_compare3' % expmt - dfig = os.path.join(ddata.dsim, expmt) - - # use methods to save figs - f.savepng_new(dfig, fprefix_short) - f.saveeps(dfig, fprefix_short) - f.close() - -# manual setting of ylims -def ylim_hack(f, list_handles, ylim_centers, ylim_limit): - # ylim_centers = [1.5e-5, 2e-5, 2.5e-5] - # ylim_limit = 1.5e-5 - - # gross - for h, c in zip(list_handles, ylim_centers): - f.ax[h].grid(True, which='minor') - ylim = (c - ylim_limit, c + ylim_limit) - f.ax[h].set_ylim(ylim) - f.f.canvas.draw() - # labels = [tick.get_text() for tick in f.ax[list_handles[1]].get_yticklabels()] - labels = f.ax[h].yaxis.get_ticklocs() - labels_text = [str(label) for label in labels[:-1]] - labels_text[0] = '' - f.ax[h].set_yticklabels(labels_text) - # print labels_text - -if __name__ == '__main__': - hf_epochs() diff --git a/ppsth.py b/ppsth.py deleted file mode 100644 index 735309f7b..000000000 --- a/ppsth.py +++ /dev/null @@ -1,230 +0,0 @@ -# ppsth.py - Plots aggregate psth of all trials in an "experiment" -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: removed it.izip()) -# last rev: (SL: changed class names from axes_create.py) - -import numpy as np -import itertools as it -import matplotlib.pyplot as plt -import os -import paramrw, spikefn -import fileio as fio -import axes_create as ac -# from axes_create import ac.FigPSTH, ac.FigPSTHGrid - -def ppsth_grid(simpaths): - # get filename lists in dictionaries of experiments - dict_exp_param = simpaths.exp_files_of_type('param') - dict_exp_spk = simpaths.exp_files_of_type('rawspk') - - # recreate the ExpParams object used in the simulation - p_exp = paramrw.ExpParams(simpaths.fparam[0]) - - # need number of lambda vals (cols) and number of sigma vals (rows) - try: - N_rows = len(p_exp.p_all['L2Pyr_Gauss_A_weight']) - except TypeError: - N_rows = 1 - - try: - N_cols = len(p_exp.p_all['L2Basket_Pois_lamtha']) - except TypeError: - N_cols = 1 - - tstop = p_exp.p_all['tstop'] - - print N_rows, N_cols, tstop - - # ugly but slightly less ugly than the index arithmetic i had planned. muahaha - f = ac.FigGrid(N_rows, N_cols, tstop) - - # create coordinates for axes - # this is backward-looking for a reason! - axes_coords = [(j, i) for i, j in it.product(np.arange(N_cols), np.arange(N_rows))] - - if len(simpaths.expnames) != len(axes_coords): - print "um ... see ppsth.py" - - # assumes a match between expnames and the keys of the previous dicts - for expname, axis_coord in zip(simpaths.expnames, axes_coords): - # get the tstop - exp_param_list = dict_exp_param[expname] - exp_spk_list = dict_exp_spk[expname] - gid_dict, p = paramrw.read(exp_param_list[0]) - tstop = p['tstop'] - lamtha = p['L2Basket_Pois_lamtha'] - sigma = p['L2Pyr_Gauss_A_weight'] - - # these are total spike dicts for the experiments - s_L2Pyr_list = [] - # s_L5Pyr_list = [] - - # iterate through params and spikes for a given experiment - for fparam, fspk in zip(dict_exp_param[expname], dict_exp_spk[expname]): - # get gid dict - gid_dict, p = paramrw.read(fparam) - - # get spike dict - s_dict = spikefn.spikes_from_file(gid_dict, fspk) - - # add a new entry to list for each different file assoc with an experiment - s_L2Pyr_list.append(np.array(list(it.chain.from_iterable(s_dict['L2_pyramidal'].spike_list)))) - # s_L5Pyr_list.append(np.array(list(it.chain.from_iterable(s_dict['L5_pyramidal'].spike_list)))) - - # now aggregate over all spikes - s_L2Pyr = np.array(list(it.chain.from_iterable(s_L2Pyr_list))) - # s_L5Pyr = np.array(list(it.chain.from_iterable(s_L5Pyr_list))) - - # optimize bins, currently unused for comparison reasons! - N_trials = len(fparam) - bin_L2 = 250 - # bin_L5 = 120 - # bin_L2 = spikefn.hist_bin_opt(s_L2Pyr, N_trials) - # bin_L5 = spikefn.hist_bin_opt(s_L5Pyr, N_trials) - - r = axis_coord[0] - c = axis_coord[1] - # create standard fig and axes - f.ax[r][c].hist(s_L2Pyr, bin_L2, facecolor='g', alpha=0.75) - - if r == 0: - f.ax[r][c].set_title(r'$\lambda_i$ = %d' % lamtha) - - if c == 0: - f.ax[r][c].set_ylabel(r'$A_{gauss}$ = %.3e' % sigma) - # f.ax[r][c].set_ylabel(r'$\sigma_{gauss}$ = %d' % sigma) - - # normalize these axes - y_L2 = f.ax[r][c].get_ylim() - # y_L2 = f.ax['L2_psth'].get_ylim() - - print expname, lamtha, sigma, r, c, y_L2[1] - - f.ax[r][c].set_ylim((0, 250.)) - # f.ax['L2_psth'].set_ylim((0, 450.)) - # f.ax['L5_psth'].set_ylim((0, 450.)) - - # spikefn.spike_png(f.ax['L2'], s_dict_L2) - # spikefn.spike_png(f.ax['L5'], s_dict_L5) - # spikefn.spike_png(f.ax['L2_extpois'], s_dict_L2_extpois) - # spikefn.spike_png(f.ax['L2_extgauss'], s_dict_L2_extgauss) - # spikefn.spike_png(f.ax['L5_extpois'], s_dict_L5_extpois) - # spikefn.spike_png(f.ax['L5_extgauss'], s_dict_L5_extgauss) - - # testfig.ax0.plot(t_vec, dp_total) - fig_name = os.path.join(simpaths.dsim, 'aggregate.eps') - - plt.savefig(fig_name) - f.close() - - # run the compression - fio.epscompress(simpaths.dsim, '.eps', 1) - -# will take a directory, find the files bin all the psth's, plot a representative spike raster -def ppsth(simpaths): - # get filename lists in dictionaries of experiments - dict_exp_param = simpaths.exp_files_of_type('param') - dict_exp_spk = simpaths.exp_files_of_type('rawspk') - - # assumes a match between expnames and the keys of the previous dicts - for expname in simpaths.expnames: - # get the tstop - exp_param_list = dict_exp_param[expname] - exp_spk_list = dict_exp_spk[expname] - gid_dict, p = paramrw.read(exp_param_list[0]) - # gid_dict, p = paramrw.read(dict_exp_param[expname][0]) - tstop = p['tstop'] - - # get representative spikes - s_dict = spikefn.spikes_from_file(gid_dict, exp_spk_list[0]) - - s_dict_L2 = {} - s_dict_L5 = {} - s_dict_L2_extgauss = {} - s_dict_L2_extpois = {} - s_dict_L5_extgauss = {} - s_dict_L5_extpois = {} - - # clean out s_dict destructively - # borrowed from praster - for key in s_dict.keys(): - # do this first to remove all extgauss feeds - if 'extgauss' in key: - if 'L2_' in key: - s_dict_L2_extgauss[key] = s_dict.pop(key) - - elif 'L5_' in key: - s_dict_L5_extgauss[key] = s_dict.pop(key) - - elif 'extpois' in key: - # s_dict_extpois[key] = s_dict.pop(key) - if 'L2_' in key: - s_dict_L2_extpois[key] = s_dict.pop(key) - - elif 'L5_' in key: - s_dict_L5_extpois[key] = s_dict.pop(key) - - # L2 next - elif 'L2_' in key: - s_dict_L2[key] = s_dict.pop(key) - - elif 'L5_' in key: - s_dict_L5[key] = s_dict.pop(key) - - # these are total spike dicts for the experiments - s_L2Pyr_list = [] - s_L5Pyr_list = [] - - # iterate through params and spikes for a given experiment - for fparam, fspk in zip(dict_exp_param[expname], dict_exp_spk[expname]): - # get gid dict - gid_dict, p = paramrw.read(fparam) - - # get spike dict - s_dict = spikefn.spikes_from_file(gid_dict, fspk) - - # add a new entry to list for each different file assoc with an experiment - s_L2Pyr_list.append(np.array(list(it.chain.from_iterable(s_dict['L2_pyramidal'].spike_list)))) - s_L5Pyr_list.append(np.array(list(it.chain.from_iterable(s_dict['L5_pyramidal'].spike_list)))) - - # now aggregate over all spikes - s_L2Pyr = np.array(list(it.chain.from_iterable(s_L2Pyr_list))) - s_L5Pyr = np.array(list(it.chain.from_iterable(s_L5Pyr_list))) - - # optimize bins, currently unused for comparison reasons! - N_trials = len(fparam) - # bin_L2 = 120 - # bin_L5 = 120 - bin_L2 = spikefn.hist_bin_opt(s_L2Pyr, N_trials) - bin_L5 = spikefn.hist_bin_opt(s_L5Pyr, N_trials) - - # create standard fig and axes - f = ac.FigPSTH(400.) - f.ax['L2_psth'].hist(s_L2Pyr, bin_L2, facecolor='g', alpha=0.75) - f.ax['L5_psth'].hist(s_L5Pyr, bin_L5, facecolor='g', alpha=0.75) - - # normalize these axes - y_L2 = f.ax['L2_psth'].get_ylim() - y_L5 = f.ax['L5_psth'].get_ylim() - - print y_L2, y_L5 - - # f.ax['L2_psth'].set_ylim((0, 450.)) - # f.ax['L5_psth'].set_ylim((0, 450.)) - - spikefn.spike_png(f.ax['L2'], s_dict_L2) - spikefn.spike_png(f.ax['L5'], s_dict_L5) - spikefn.spike_png(f.ax['L2_extpois'], s_dict_L2_extpois) - spikefn.spike_png(f.ax['L2_extgauss'], s_dict_L2_extgauss) - spikefn.spike_png(f.ax['L5_extpois'], s_dict_L5_extpois) - spikefn.spike_png(f.ax['L5_extgauss'], s_dict_L5_extgauss) - - # # testfig.ax0.plot(t_vec, dp_total) - fig_name = os.path.join(simpaths.dsim, expname+'.eps') - - plt.savefig(fig_name) - f.close() - - # run the compression - fio.epscompress(simpaths.dsim, '.eps', 1) diff --git a/praster.py b/praster.py deleted file mode 100644 index 0ad9c021b..000000000 --- a/praster.py +++ /dev/null @@ -1,67 +0,0 @@ -# praster.py - plot dipole function -# -# v 1.9.2a -# rev 2013-04-08 (SL: changed spikes_from_file) -# last major: (SL: minor changes to FigRaster) - -import os -import numpy as np -import matplotlib.pyplot as plt -from neuron import h as nrn -from axes_create import FigRaster -import spikefn as spikefn - -# file_info is (rootdir, subdir, -def praster(f_param, tstop, file_spk, dfig): - # ddipole is dipole data - s_dict = spikefn.spikes_from_file(f_param, file_spk) - - s_dict_L2 = {} - s_dict_L5 = {} - s_dict_L2_extgauss = {} - s_dict_L2_extpois = {} - s_dict_L5_extgauss = {} - s_dict_L5_extpois = {} - - # clean out s_dict destructively - for key in s_dict.keys(): - # do this first to remove all extgauss feeds - if 'extgauss' in key: - if 'L2_' in key: - s_dict_L2_extgauss[key] = s_dict.pop(key) - - elif 'L5_' in key: - s_dict_L5_extgauss[key] = s_dict.pop(key) - - elif 'extpois' in key: - # s_dict_extpois[key] = s_dict.pop(key) - if 'L2_' in key: - s_dict_L2_extpois[key] = s_dict.pop(key) - - elif 'L5_' in key: - s_dict_L5_extpois[key] = s_dict.pop(key) - - # L2 next - elif 'L2_' in key: - s_dict_L2[key] = s_dict.pop(key) - - elif 'L5_' in key: - s_dict_L5[key] = s_dict.pop(key) - - # split to find file prefix - file_prefix = file_spk.split('/')[-1].split('.')[0] - - # create standard fig and axes - f = FigRaster(tstop) - spikefn.spike_png(f.ax['L2'], s_dict_L2) - spikefn.spike_png(f.ax['L5'], s_dict_L5) - spikefn.spike_png(f.ax['L2_extpois'], s_dict_L2_extpois) - spikefn.spike_png(f.ax['L2_extgauss'], s_dict_L2_extgauss) - spikefn.spike_png(f.ax['L5_extpois'], s_dict_L5_extpois) - spikefn.spike_png(f.ax['L5_extgauss'], s_dict_L5_extgauss) - - # testfig.ax0.plot(t_vec, dp_total) - fig_name = os.path.join(dfig, file_prefix+'.png') - - plt.savefig(fig_name, dpi=300) - f.close() diff --git a/praw.py b/praw.py deleted file mode 100644 index 4be678e5b..000000000 --- a/praw.py +++ /dev/null @@ -1,154 +0,0 @@ -# praw.py - all of the raw data types on one fig -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: updated for it.izip() and return_data_dir()) -# last major: (SL: minor) - -import fileio as fio -import numpy as np -import multiprocessing as mp -import ast -import os -import paramrw -import dipolefn -import spikefn -import specfn -import currentfn -import matplotlib.pyplot as plt -from neuron import h as nrn -import axes_create as ac - -def pkernel(dfig_dpl, f_dpl, f_spk, f_spec, f_current, f_spec_current, f_param, ax_handles, spec_cmap='jet'): - T = paramrw.find_param(f_param, 'tstop') - xlim = (50., T) - - # into the pdipole directory, this will plot dipole, spec, and spikes - # create the axis handle - f = ac.FigDipoleExp(ax_handles) - - # create the figure name - fprefix = fio.strip_extprefix(f_dpl) + '-dpl' - fname = os.path.join(dfig_dpl, fprefix + '.png') - - # grab the dipole - dpl = dipolefn.Dipole(f_dpl) - dpl.convert_fAm_to_nAm() - - # plot the dipole to the agg axes - dpl.plot(f.ax['dpl_agg'], xlim) - dpl.plot(f.ax['dpl_agg_L5'], xlim) - # f.ax['dpl_agg_L5'].hold(True) - # dpl.plot(f.ax['dpl_agg_L5'], xlim, 'L5') - - # plot individual dipoles - dpl.plot(f.ax['dpl'], xlim, 'L2') - dpl.plot(f.ax['dpl_L5'], xlim, 'L5') - - # f.ysymmetry(f.ax['dpl']) - # print dpl.max('L5', (0., -1)), dpl.max('L5', (50., -1)) - # print f.ax['dpl_L5'].get_ylim() - # f.ax['dpl_L5'].set_ylim((-1e5, 1e5)) - # f.ysymmetry(f.ax['dpl_L5']) - - # plot the current - I_soma = currentfn.SynapticCurrent(f_current) - I_soma.plot_to_axis(f.ax['I_soma'], 'L2') - I_soma.plot_to_axis(f.ax['I_soma_L5'], 'L5') - - # plot the dipole-based spec data - pc = specfn.pspec_ax(f.ax['spec_dpl'], f_spec, xlim, 'L2') - f.f.colorbar(pc, ax=f.ax['spec_dpl']) - - pc = specfn.pspec_ax(f.ax['spec_dpl_L5'], f_spec, xlim, 'L5') - f.f.colorbar(pc, ax=f.ax['spec_dpl_L5']) - - # grab the current spec and plot them - spec_L2, spec_L5 = data_spec_current = specfn.read(f_spec_current, type='current') - pc_L2 = f.ax['spec_I'].imshow(spec_L2['TFR'], aspect='auto', origin='upper',cmap=plt.get_cmap(spec_cmap)) - pc_L5 = f.ax['spec_I_L5'].imshow(spec_L5['TFR'], aspect='auto', origin='upper',cmap=plt.get_cmap(spec_cmap)) - - # plot the current-based spec data - # pci = specfn.pspec_ax(f.ax['spec_I'], f_spec_current, type='current') - f.f.colorbar(pc_L2, ax=f.ax['spec_I']) - f.f.colorbar(pc_L5, ax=f.ax['spec_I_L5']) - - # get all spikes - s = spikefn.spikes_from_file(f_param, f_spk) - - # these work primarily because of how the keys are done - # in the spike dict s (consequence of spikefn.spikes_from_file()) - s_L2 = spikefn.filter_spike_dict(s, 'L2_') - s_L5 = spikefn.filter_spike_dict(s, 'L5_') - - # resize xlim based on our 50 ms cutoff thingy - xlim = (50., xlim[1]) - - # plot the spikes - spikefn.spike_png(f.ax['spk'], s_L2) - spikefn.spike_png(f.ax['spk_L5'], s_L5) - - f.ax['dpl'].set_xlim(xlim) - # f.ax['dpl_L5'].set_xlim(xlim) - # f.ax['spec_dpl'].set_xlim(xlim) - f.ax['spk'].set_xlim(xlim) - f.ax['spk_L5'].set_xlim(xlim) - - f.savepng(fname) - f.close() - - return 0 - -# dummy function for callback -def cb(r): - pass - -# For a given ddata (SimulationPaths object), find the mean dipole -# over ALL trials in ALL conditions in EACH experiment -def praw(ddata): - # grab the original dipole from a specific dir - dproj = fio.return_data_dir() - - runtype = 'parallel' - # runtype = 'debug' - - # check on spec data - # generates both spec because both are needed here - specfn.generate_missing_spec(ddata) - - # test experiment - # expmt_group = ddata.expmt_groups[0] - - ax_handles = [ - 'dpl_agg', - 'dpl', - 'spec_dpl', - 'spk', - 'I_soma', - 'spec_I', - ] - - # iterate over exmpt groups - for expmt_group in ddata.expmt_groups: - dfig_dpl = ddata.dfig[expmt_group]['figdpl'] - - # grab lists of files (l_) - l_dpl = ddata.file_match(expmt_group, 'rawdpl') - l_spk = ddata.file_match(expmt_group, 'rawspk') - l_param = ddata.file_match(expmt_group, 'param') - l_spec = ddata.file_match(expmt_group, 'rawspec') - l_current = ddata.file_match(expmt_group, 'rawcurrent') - l_spec_current = ddata.file_match(expmt_group, 'rawspeccurrent') - - if runtype == 'parallel': - pl = mp.Pool() - - for f_dpl, f_spk, f_spec, f_current, f_spec_current, f_param \ - in zip(l_dpl, l_spk, l_spec, l_current, l_spec_current, l_param): - pl.apply_async(pkernel, (dfig_dpl, f_dpl, f_spk, f_spec, f_current, f_spec_current, f_param, ax_handles), callback=cb) - pl.close() - pl.join() - - elif runtype == 'debug': - for f_dpl, f_spk, f_spec, f_current, f_spec_current, f_param \ - in zip(l_dpl, l_spk, l_spec, l_current, l_spec_current, l_param): - pkernel(dfig_dpl, f_dpl, f_spk, f_spec, f_current, f_spec_current, f_param, ax_handles) diff --git a/pspec.py b/pspec.py deleted file mode 100644 index 72c0b9f87..000000000 --- a/pspec.py +++ /dev/null @@ -1,289 +0,0 @@ -# DEPRECATE - -# pspec.py - Very long plotting methods having to do with spec. -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: removed it.izip()) -# last major: (MS: Updated calls to extinput.plot_hist()) - -import os -import sys -import numpy as np -import scipy.signal as sps -import matplotlib.pyplot as plt -import paramrw -import fileio as fio -import multiprocessing as mp -from neuron import h as nrn -from math import ceil - -import fileio as fio -import currentfn -import dipolefn -import specfn -import spikefn -import axes_create as ac - -# this is actually a plot kernel for one sim that does dipole, etc. -# needs f_param not p_dict -def pspec_dpl(f_spec, f_dpl, dfig, p_dict, key_types, xlim=None, ylim=None, f_param=None): - # Generate file prefix - fprefix = f_spec.split('/')[-1].split('.')[0] - - # using png for now - # fig_name = os.path.join(dfig, fprefix+'.eps') - fig_name = os.path.join(dfig, fprefix+'.png') - - # f.f is the figure handle! - f = ac.FigSpec() - - # load spec data - spec = specfn.Spec(f_spec) - - # Plot TFR data and add colorbar - pc = spec.plot_TFR(f.ax['spec'], 'agg', xlim, ylim) - f.f.colorbar(pc, ax=f.ax['spec']) - - # grab the dipole data - # data_dipole = np.loadtxt(open(f_dpl, 'r')) - dpl = dipolefn.Dipole(f_dpl) - - # If f_param supplied, renormalize dipole data - if f_param: - dpl.baseline_renormalize(f_param) - dpl.convert_fAm_to_nAm() - - # plot routine - dpl.plot(f.ax['dipole'], xlim, 'agg') - - # Plot Welch data - # Use try/except for backwards compatibility - try: - spec.plot_pgram(f.ax['pgram']) - - except KeyError: - pgram = specfn.Welch(dpl.t, dpl.dpl['agg'], p_dict['dt']) - pgram.plot_to_ax(f.ax['pgram'], spec.spec['agg']['f'][-1]) - - # plot and create an xlim - xlim_new = f.ax['spec'].get_xlim() - xticks = f.ax['spec'].get_xticks() - xticks[0] = xlim_new[0] - - # for now, set the xlim for the other one, force it! - f.ax['dipole'].set_xlim(xlim_new) - f.ax['dipole'].set_xticks(xticks) - f.ax['spec'].set_xticks(xticks) - - # axis labels - f.ax['spec'].set_xlabel('Time (ms)') - f.ax['spec'].set_ylabel('Frequency (Hz)') - - # create title - title_str = ac.create_title(p_dict, key_types) - f.f.suptitle(title_str) - - # use our fig classes to save and close - f.savepng(fig_name) - # f.saveeps(dfig, fprefix) - f.close() - -# Spectral plotting kernel with alpha feed histogram for ONE simulation run -def pspec_with_hist(f_spec, f_dpl, f_spk, dfig, f_param, key_types, xlim=None, ylim=None): - # Generate file prefix - # print('f_spec:',f_spec) - fprefix = f_spec.split('/')[-1].split('.')[0] - # Create the fig name - fig_name = os.path.join(dfig, fprefix+'.png') - # print('fig_name:',fig_name) - # load param dict - _, p_dict = paramrw.read(f_param) - f = ac.FigSpecWithHist() - # load spec data - spec = specfn.Spec(f_spec) - # Plot TFR data and add colorbar - pc = spec.plot_TFR(f.ax['spec'], 'agg', xlim, ylim) - f.f.colorbar(pc, ax=f.ax['spec']) - # set xlim based on TFR plot - xlim_new = f.ax['spec'].get_xlim() - # grab the dipole data - dpl = dipolefn.Dipole(f_dpl) - dpl.baseline_renormalize(f_param) - dpl.convert_fAm_to_nAm() - # plot routine - dpl.plot(f.ax['dipole'], xlim_new, 'agg') - # data_dipole = np.loadtxt(open(f_dpl, 'r')) - # t_dpl = data_dipole[xmin_ind:xmax_ind+1, 0] - # dp_total = data_dipole[xmin_ind:xmax_ind+1, 1] - # f.ax['dipole'].plot(t_dpl, dp_total) - # x = (xmin, xmax) - # # grab alpha feed data. spikes_from_file() from spikefn.py - # s_dict = spikefn.spikes_from_file(f_param, f_spk) - # # check for existance of alpha feed keys in s_dict. - # s_dict = spikefn.alpha_feed_verify(s_dict, p_dict) - # # Account for possible delays - # s_dict = spikefn.add_delay_times(s_dict, p_dict) - # Get extinput data and account for delays - try: - extinputs = spikefn.ExtInputs(f_spk, f_param) - except ValueError: - print("Error: could not load spike timings from %s" % f_spk) - f.close() - return - - extinputs.add_delay_times() - extinputs.get_envelope(dpl.t, feed='dist') - # set number of bins (150 bins per 1000ms) - bins = ceil(150. * (xlim_new[1] - xlim_new[0]) / 1000.) # bins should be int - # plot histograms - hist = {} - hist['feed_prox'] = extinputs.plot_hist(f.ax['feed_prox'], 'prox', dpl.t, bins=bins, xlim=xlim_new, color='red') - hist['feed_dist'] = extinputs.plot_hist(f.ax['feed_dist'], 'dist', dpl.t, bins=bins, xlim=xlim_new, color='green') - f.ax['feed_dist'].invert_yaxis() - # for now, set the xlim for the other one, force it! - f.ax['dipole'].set_xlim(xlim_new) - f.ax['spec'].set_xlim(xlim_new) - f.ax['feed_prox'].set_xlim(xlim_new) - f.ax['feed_dist'].set_xlim(xlim_new) - # set hist axis props - f.set_hist_props(hist) - # axis labels - f.ax['spec'].set_xlabel('Time (ms)') - f.ax['spec'].set_ylabel('Frequency (Hz)') - # Add legend to histogram - for key in f.ax.keys(): - if 'feed' in key: - f.ax[key].legend() - # create title - title_str = ac.create_title(p_dict, key_types) - f.f.suptitle(title_str) - f.savepng(fig_name) - f.close() - -def pspecpwr(file_name, results_list, fparam_list, key_types, error_vec=[]): - # instantiate fig - f = ac.FigStd() - f.set_fontsize(18) - - # pspecpwr_ax is a plot kernel for specpwr plotting - legend_list = pspecpwr_ax(f.ax0, results_list, fparam_list, key_types) - - # Add error bars if necessary - if len(error_vec): - # errors are only used with avg'ed data. There will be only one entry in results_list - pyerrorbars_ax(f.ax0, results_list[0]['freq'], results_list[0]['p_avg'], error_vec) - - # insert legend - f.ax0.legend(legend_list, loc='upper right', prop={'size': 8}) - - # axes labels - f.ax0.set_xlabel('Freq (Hz)') - f.ax0.set_ylabel('Avgerage Power (nAm^2)') - - # add title - # f.set_title(fparam_list[0], key_types) - - f.savepng(file_name) - # f.save(file_name) - # f.saveeps(file_name) - f.close() - -# frequency-power analysis plotting kernel -def pspecpwr_ax(ax_specpwr, specpwr_list, fparam_list, key_types): - ax_specpwr.hold(True) - - # Preallocate legend list - legend_list = [] - - # iterate over freqpwr results and param list to plot and construct legend - for result, fparam in zip(specpwr_list, fparam_list): - # Plot to axis - ax_specpwr.plot(result['freq'], result['p_avg']) - - # Build legend - p = paramrw.read(fparam)[1] - lgd_temp = [key + ': %2.1f' %p[key] for key in key_types['dynamic_keys']] - legend_list.append(reduce(lambda x, y: x+', '+y, lgd_temp[:])) - - # Do not need to return axis, apparently - return legend_list - -# Plot vertical error bars -def pyerrorbars_ax(ax, x, y, yerr_vec): - ax.errorbar(x, y, xerr=None, yerr=yerr_vec, fmt=None, ecolor='blue') - -def aggregate_with_hist(f, ax, f_spec, f_dpl, f_spk, f_param, spec_cmap='jet'): - # load param dict - _, p_dict = paramrw.read(f_param) - - # load spec data from file - spec = specfn.Spec(f_spec) - # data_spec = np.load(f_spec) - - # timevec = data_spec['time'] - # freqvec = data_spec['freq'] - # TFR = data_spec['TFR'] - - xmin = timevec[0] - xmax = p_dict['tstop'] - x = (xmin, xmax) - - pc = spec.plot_TFR(ax['spec'], layer='agg', xlim=x) - # pc = ax['spec'].imshow(TFR, extent=[timevec[0], timevec[-1], freqvec[-1], freqvec[0]], aspect='auto', origin='upper') - f.f.colorbar(pc, ax=ax['spec'],cmap=plt.get_cmap(spec_cmap)) - - # grab the dipole data - dpl = dipolefn.Dipole(f_dpl) - dpl.plot(ax['dipole'], x, layer='agg') - # data_dipole = np.loadtxt(open(f_dpl, 'r')) - - # t_dpl = data_dipole[xmin/p_dict['dt']:, 0] - # dp_total = data_dipole[xmin/p_dict['dt']:, 1] - - # ax['dipole'].plot(t_dpl, dp_total) - - # grab alpha feed data. spikes_from_file() from spikefn.py - s_dict = spikefn.spikes_from_file(f_param, f_spk) - - # check for existance of alpha feed keys in s_dict. - s_dict = spikefn.alpha_feed_verify(s_dict, p_dict) - - # Account for possible delays - s_dict = spikefn.add_delay_times(s_dict, p_dict) - - # set number of bins (150 bins/1000ms) - bins = 150. * (xmax - xmin) / 1000. - - hist = {} - - # Proximal feed - hist['feed_prox'] = ax['feed_prox'].hist(s_dict['alpha_feed_prox'].spike_list, bins, range=[xmin, xmax], color='red', label='Proximal feed') - - # Distal feed - hist['feed_dist'] = ax['feed_dist'].hist(s_dict['alpha_feed_dist'].spike_list, bins, range=[xmin, xmax], color='green', label='Distal feed') - - # for now, set the xlim for the other one, force it! - ax['dipole'].set_xlim(x) - ax['spec'].set_xlim(x) - ax['feed_prox'].set_xlim(x) - ax['feed_dist'].set_xlim(x) - - # set hist axis props - f.set_hist_props(ax, hist) - - # axis labels - ax['spec'].set_xlabel('Time (ms)') - ax['spec'].set_ylabel('Frequency (Hz)') - - # Add legend to histogram - for key in ax.keys(): - if 'feed' in key: - ax[key].legend() - - # create title - # title_str = ac.create_title(p_dict, key_types) - # f.f.suptitle(title_str) - # title_str = [key + ': %2.1f' % p_dict[key] for key in key_types['dynamic_keys']] - - # plt.savefig(fig_name) - # f.close() diff --git a/ptest.py b/ptest.py deleted file mode 100644 index e0e600e6f..000000000 --- a/ptest.py +++ /dev/null @@ -1,22 +0,0 @@ -# ptest.py - plot test function -# -# v 1.7.11a -# rev 2012-08-23 (SL: created) -# last major: - -import matplotlib as mpl -mpl.use("Agg") - -import matplotlib.pyplot as plt -from neuron import h as nrn -from axes_create import FigStd - -def ptest(t_vec, v_e, v_i): - testfig = FigStd() - testfig.ax0.hold(True) - - testfig.ax0.plot(t_vec, v_e) - testfig.ax0.plot(t_vec, v_i) - - plt.savefig('outputspikes.png') - testfig.close() diff --git a/run.py b/run.py index c6bd52c19..6a086e16e 100755 --- a/run.py +++ b/run.py @@ -15,12 +15,7 @@ from paramrw import usingOngoingInputs, write_gids_param import specfn as specfn #import pickle -import datetime from conf import readconf -from L5_pyramidal import L5Pyr -from L2_pyramidal import L2Pyr -from L2_basket import L2Basket -from L5_basket import L5Basket import os.path as op diff --git a/seg3d.py b/seg3d.py deleted file mode 100644 index 9a2549db9..000000000 --- a/seg3d.py +++ /dev/null @@ -1,91 +0,0 @@ -# from pt3d of section, calculate the 3d location of segment centers, or -# a list of 3d points for each segment where the first and last 3-d points -# are the segment boundaries -# written by mh - -from neuron import h - -def segment_centers_3d (sec): - ''' return list of x, y, z tuples for centers of non-zero area segments ''' - # vector of arc values of segment centers - arcseg = h.Vector([seg.x for seg in sec]) - return interpolate(pt3d(sec), arcseg) - -def segment_points_3d (sec): - ''' return list of nseg length list of x,y,z tuples for segments - first and last xyz tuple are the ends of the segment ''' - # vector of arc values of segment edges (nseg+1) - arcseg = h.Vector(sec.nseg + 1).indgen().div(sec.nseg) - n = int(sec.n3d()) - axyz = pt3d(sec) - xyz = interpolate(axyz, arcseg) - # organize into [[(x,y,z)]*nseg] but remove end identical points in [(x,y,z)] - ret = [] - j = 0 # index into axyz vectors; - for iseg in range(int(sec.nseg)): - segitem = [] - a1 = arcseg[iseg] # proximal edge - a2 = arcseg[iseg + 1] # distal edge - segitem.append(xyz[iseg]) - while j < n and axyz[0][j] < a2: # insert the ones > a1 and less than a2 - a, pt = axyz[0][j], (axyz[1][j], axyz[2][j], axyz[3][j]) - if a > a1: - segitem.append(pt) - j += 1 - segitem.append(xyz[iseg + 1]) - ret.append(segitem) - return ret - -def pt3d (sec): - ''' return list of arc, x, y, z vectors from pt3d info of sec ''' - n = int(sec.n3d()) - #list of 3d point vectors - sec.push() - axyz = [h.Vector([f(i) for i in range(n)]) for f in [h.arc3d, h.x3d, h.y3d, h.z3d]] - h.pop_section() - axyz[0].div(sec.L) - return axyz - -def interpolate (axyz, arcvec): - ''' return list of x, y, z tuples at the arcvec locations ''' - #interpolate onto arcvec - xyz = [v.c().interpolate(arcvec, axyz[0]) for v in axyz[1:]] - return [(xyz[0][i],xyz[1][i],xyz[2][i]) for i in range(len(xyz[0]))] - -def drawsec (sec): - # draw original 3d points (x,y values). Not using Shape because of origin issues - g = h.Graph(0) - g.view(2) - n = int(sec.n3d()) - g.beginline(1, 4) - for i in range(n): - g.line(sec.x3d(i), sec.y3d(i)) - return g - -def test_segment_centers (sec, g): - xyz = segment_centers_3d(sec) - for x in xyz: - #print (x) - g.mark(x[0], x[1], 'O', 10, 2, 1) - g.exec_menu("View = plot") - -def test_segment_points (sec, g): - xyzsegs = segment_points_3d(sec) - for iseg, xyzseg in enumerate(xyzsegs): - color = 4 + iseg%2 #blue, green - g.beginline(color, 1) - for x,y,z in xyzseg: - g.line(x, y) - -if __name__ == '__main__': - # load pyramidal of neurondemo - from neuron import gui - h.load_file(h.neuronhome() + '/demo/pyramid.nrn') - s = h.dendrite_1[8] #proximal apical - s.nseg = 5 - #h.load_file(h.neuronhome() + '/demo/pyramid.ses') - - g = drawsec(s) - test_segment_points(s, g) - test_segment_centers(s, g) - diff --git a/specfn.py b/specfn.py index 6e6b04469..3d4e307b0 100644 --- a/specfn.py +++ b/specfn.py @@ -10,14 +10,6 @@ import scipy.signal as sps import matplotlib.pyplot as plt import paramrw -import fileio as fio -import multiprocessing as mp -from neuron import h as nrn - -import fileio as fio -import currentfn -import spikefn -import axes_create as ac from conf import dconf # MorletSpec class based on a time vec tvec and a time series vec tsvec @@ -209,136 +201,6 @@ def __energyvec(self, f, s): return y -# calculates a phase locking value between 2 time series via morlet wavelets -class PhaseLock(): - """ Based on 4Dtools (deprecated) MATLAB code - Might be a newer version in fieldtrip - """ - def __init__(self, tsarray1, tsarray2, fparam, f_max=60.): - raise DeprecationWarning - - # Save time-series arrays as self variables - # ohhhh. Do not use 1-indexed keys of a dict! - self.ts = { - 1: tsarray1, - 2: tsarray2, - } - - # Get param dict - self.p = paramrw.read(fparam)[1] - - # Set frequecies over which to sort - self.f = 1. + np.arange(0., f_max, 1.) - - # Set width of Morlet wavelet (>= 5 suggested) - self.width = 7. - - # Calculate sampling frequency - self.fs = 1000. / self.p['dt'] - - self.data = self.__traces2PLS() - - def __traces2PLS(self): - # Not sure what's going on here... - # nshuffle = 200; - nshuffle = 1; - - # Construct timevec - tvec = np.arange(1, self.ts[1].shape[1]) / self.fs - - # Prellocated arrays - # Check sizes - B = np.zeros((self.f.size, self.ts[1].shape[1])) - Bstat = np.zeros((self.f.size, self.ts[1].shape[1])) - Bplf = np.zeros((self.f.size, self.ts[1].shape[1])) - - # Do the analysis - for i, freq in enumerate(self.f): - print('%i Hz' % freq) - - # Get phase of signals for given freq - # Check sizes - B1 = self.__phasevec(freq, num_ts=1) - B2 = self.__phasevec(freq, num_ts=2) - - # Potential conflict here - # Check size - B[i, :] = np.mean(B1 / B2, axis=0) - B[i, :] = abs(B[i, :]) - - # Randomly shuffle B2 - for j in range(0, nshuffle): - # Check size - idxShuffle = np.random.permutation(B2.shape[0]) - B2shuffle = B2[idxShuffle, :] - - Bshuffle = np.mean(B1 / B2shuffle, axis=0) - Bplf[i, :] += Bshuffle - - idxSign = (abs(B[i, :]) > abs(Bshuffle)) - Bstat[i, idxSign] += 1 - - # Final calculation of Bstat, Bplf - Bstat = 1. - Bstat / nshuffle - Bplf /= nshuffle - - # Store data - return { - 't': tvec, - 'f': self.f, - 'B': B, - 'Bstat': Bstat, - 'Bplf': Bplf, - } - - def __phasevec(self, f, num_ts=1): - """ should num_ts here be 0, as an index? - """ - dt = 1. / self.fs - sf = f / self.width - st = 1. / (2. * np.pi * sf) - - # create a time vector for the morlet wavelet - t = np.arange(-3.5*st, 3.5*st+dt, dt) - m = self.__morlet(f, t) - - y = np.array([]) - - for k in range(0, self.ts[num_ts].shape[0]): - if k == 0: - s = sps.detrend(self.ts[num_ts][k, :]) - y = np.array([sps.fftconvolve(s, m)]) - - else: - # convolve kth time series with morlet wavelet - # might as well let return valid length (not implemented) - y_tmp = sps.fftconvolve(self.ts[num_ts][k, :], m) - y = np.vstack((y, y_tmp)) - - # Change 0s to 1s to avoid division by 0 - # l is an index - # y is now complex, so abs(y) is the complex absolute value - l = (abs(y) == 0) - y[l] = 1. - - # normalize phase values and return 1s to zeros - y = y / abs(y) - y[l] = 0 - y = y[:, np.ceil(len(m)/2.)-1:y.shape[1]-np.floor(len(m)/2.)] - - return y - - def __morlet(self, f, t): - """ Calculate the morlet wavelet - """ - sf = f / self.width - st = 1. / (2. * np.pi * sf) - A = 1. / np.sqrt(st*np.sqrt(np.pi)) - - y = A * np.exp(-t**2./(2.*st**2.)) * np.exp(1.j*2.*np.pi*f*t) - - return y - # functions on the aggregate spec data class Spec(): def __init__(self, fspec, spec_cmap='jet', dtype='dpl'): @@ -665,223 +527,6 @@ def read(fdata_spec, type='dpl'): return spec_L2, spec_L5 -# average spec data for a given set of files -def average(fname, fspec_list, spec_cmap='jet'): - for fspec in fspec_list: - print(fspec) - # load spec data - spec = Spec(fspec, spec_cmap) - - # if this is first file, copy spec data structure wholesale to x - if fspec is fspec_list[0]: - x = spec.spec - - # else, iterate through spec data and add to x_agg - # there might be a more 'pythonic' way of doing this... - else: - for subdict in x: - for key in x[subdict]: - x[subdict][key] += spec.spec[subdict][key] - - # poor man's mean - for subdict in x: - for key in x[subdict]: - x[subdict][key] /= len(fspec_list) - - # save data - # if max_agg is a key in x, assume all keys are present - # else, assume only aggregate data is present - # Terrible way to save due to how np.savez_compressed works (i.e. must specify key=value) - if 'max_agg' in x.keys(): - max_agg = (x['max_agg']['p'], x['max_agg']['t'], x['max_agg']['f']) - # max_agg = (x['max_agg']['p_at_max'], x['max_agg']['t_at_max'], x['max_agg']['f_at_max']) - - np.savez_compressed(fname, t_agg=x['agg']['t'], f_agg=x['agg']['f'], TFR_agg=x['agg']['TFR'], t_L2=x['L2']['t'], f_L2=x['L2']['f'], TFR_L2=x['L2']['TFR'], t_L5=x['L5']['t'], f_L5=x['L5']['f'], TFR_L5=x['L5']['TFR'], max_agg=max_agg, pgram_p=x['pgram']['p'], pgram_f=x['pgram']['f']) - - else: - np.savez_compressed(fname, t_agg=x['agg']['t'], f_agg=x['agg']['f'], TFR_agg=x['agg']['TFR']) - -# spectral plotting kernel should be simpler and take just a file name and an axis handle -def pspec_ax(ax_spec, fspec, xlim, spec_cmap='jet', layer=None): - """ Spectral plotting kernel for ONE simulation run - ax_spec is the axis handle. fspec is the file name - """ - # read is a function in this file to read the fspec - data_spec = read(fspec) - - if layer in (None, 'agg'): - TFR = data_spec['TFR'] - - if 'f' in data_spec.keys(): - f = data_spec['f'] - else: - f = data_spec['freq'] - - else: - TFR_layer = 'TFR_%s' % layer - f_layer = 'f_%s' % layer - - if TFR_layer in data_spec.keys(): - TFR = data_spec[TFR_layer] - f = data_spec[f_layer] - - else: - print(data_spec.keys()) - - extent_xy = [xlim[0], xlim[1], f[-1], 0.] - pc = ax_spec.imshow(TFR, extent=extent_xy, aspect='auto', origin='upper', cmap=plt.get_cmap(spec_cmap)) - [vmin, vmax] = pc.get_clim() - # print(np.min(TFR), np.max(TFR)) - # print(vmin, vmax) - # ax_spec.colorbar(pc, ax=ax_spec) - - # return (vmin, vmax) - return pc - -# find max spectral power and associated time/freq for individual file -def specmax(fspec, opts): - print("Warning: you are using specmax(). It should be changed from == to np.isclose()") - # opts is a dict that includes t_interval and f_interval - # grab name of file - fname = fspec.split('/')[-1].split('-spec')[0] - - # load spec data - data = read(fspec) - - # grab the min and max f - f_min, f_max = opts['f_interval'] - - # set f_max and f_min - if f_max < 0: - f_max = data['freq'][-1] - - if f_min < 0: - f_min = data['freq'][0] - - # create an f_mask for the bounds of f, inclusive - f_mask = (data['freq']>=f_min) & (data['freq']<=f_max) - - # do the same for t - t_min, t_max = opts['t_interval'] - if t_max < 0: - t_max = data['time'][-1] - - if t_min < 0: - t_min = data['time'][0] - - t_mask = (data['time']>=t_min) & (data['time']<=t_max) - - # use the masks truncate these appropriately - TFR_fcut = data['TFR'][f_mask, :] - TFR_tfcut = TFR_fcut[:, t_mask] - - f_fcut = data['freq'][f_mask] - t_tcut = data['time'][t_mask] - - # find the max power over this new range - # the max_mask is for the entire TFR - pwr_max = TFR_tfcut.max() - max_mask = (TFR_tfcut == pwr_max) - - # find the t and f at max - # these are slightly crude and do not allow for the possibility of multiple maxes (rare?) - t_at_max = t_tcut[max_mask.sum(axis=0) == 1] - f_at_max = f_fcut[max_mask.sum(axis=1) == 1] - - pd_at_max = 1000. / f_at_max - t_start = t_at_max - pd_at_max - t_end = t_at_max + pd_at_max - - # output structure - data_max = { - 'fname': fname, - 'pwr': pwr_max, - 't_int': [t_start, t_end], - 't_at_max': t_at_max, - 'f_at_max': f_at_max, - } - - return data_max - -# return the max spectral power (simple) for a series of files -def spec_max(ddata, expmt_group, layer='agg'): - # grab the spec list, assumes it exists - list_spec = ddata.file_match(expmt_group, 'rawspec') - - # really only perform these actions if there are items in the list - if len(list_spec): - # simple prealloc - val_max = np.zeros(len(list_spec)) - - # iterate through list_spec - i = 0 - for fspec in list_spec: - data_spec = read(fspec) - - # for now only do the TFR for the aggregate data - val_max[i] = np.max(data_spec['TFR']) - i += 1 - - return spec_max - -# common function to generate spec if it appears to be missing -def generate_missing_spec(ddata, f_max=40): - raise DeprecationWarning - - # just check first expmt_group - expmt_group = ddata.expmt_groups[0] - - # list of spec data - l_spec = ddata.file_match(expmt_group, 'rawspec') - - # if this list is empty, assume it is everywhere and run the analysis function - if not l_spec: - opts = { - 'type': 'dpl_laminar', - 'f_max': f_max, - 'save_data': 1, - 'runtype': 'parallel', - } - analysis_typespecific(ddata, opts) - - else: - # this is currently incorrect, it should actually return the data that has been referred to - # as spec_results. such a function to properly get this without analysis (eg. reader to this data) - # should exist - spec = [] - - # do the one for current, too. Might as well at this point - l_speccurrent = ddata.file_match(expmt_group, 'rawspeccurrent') - - if not l_speccurrent: - p_exp = paramrw.ExpParams(ddata.fparam) - opts = { - 'type': 'current', - 'f_max': 90., - 'save_data': 1, - 'runtype': 'parallel', - } - analysis_typespecific(ddata, opts) - else: - spec_current = [] - -# Kernel for spec analysis of current data -# necessary for parallelization -def spec_current_kernel(fparam, fts, fspec, f_max): - raise DeprecationWarning - - I_syn = currentfn.SynapticCurrent(fts) - - # TODO: replace with function that reads justs params - params = paramrw.read(fparam)[1] - - # Generate spec results - spec_L2 = MorletSpec(I_syn.t, I_syn.I_soma_L2Pyr, f_max, p_dict=params) - spec_L5 = MorletSpec(I_syn.t, I_syn.I_soma_L5Pyr, f_max, p_dict=params) - - # Save spec data - np.savez_compressed(fspec, t_L2=spec_L2.t, f_L2=spec_L2.f, TFR_L2=spec_L2.TFR, t_L5=spec_L5.t, f_L5=spec_L5.f, TFR_L5=spec_L5.TFR) - # Kernel for spec analysis of dipole data # necessary for parallelization def spec_dpl_kernel(params, dpl, fspec, f_max): @@ -914,220 +559,3 @@ def analysis_simp (opts, params, fdpl, fspec): for key, val in opts.items(): if key in opts_run.keys(): opts_run[key] = val spec_dpl_kernel(params, fdpl, fspec, opts_run['f_max']) - -# Does spec analysis for all files in simulation directory -# ddata comes from fileio -def analysis_typespecific(ddata, opts=None): - # def analysis_typespecific(ddata, p_exp, opts=None): - # 'opts' input are the options in a dictionary - # if opts is defined, then make it well formed - # the valid keys of opts are in list_opts - opts_run = { - 'type': 'dpl_laminar', - 'f_max': 100., - 'save_data': 0, - 'runtype': 'parallel', - } - # check if opts is supplied - if opts: - # assume opts is a dict - # iterate through provided opts and assign if the key is present - # otherwise, ignore - for key, val in opts.items(): - if key in opts_run.keys(): - opts_run[key] = val - # preallocate lists for use below - list_param, list_ts, list_spec = [], [], [] - - # aggregrate all files from individual expmts into lists - expmt_group = ddata.expmt_groups[0] - # get the list of params - # returns an alpha SORTED list - # add to list of all param files - param_tmp = ddata.file_match(expmt_group, 'param') - print('param_tmp:',param_tmp) - list_param.extend(param_tmp) - # get exp prefix for each trial in this expmt group - list_exp_prefix = [fio.strip_extprefix(fparam) for fparam in param_tmp] - # get the list of dipoles and create spec output filenames - if opts_run['type'] in ('dpl', 'dpl_laminar'): - list_ts.extend(ddata.file_match(expmt_group, 'rawdpl')) - list_spec.extend([ddata.create_filename(expmt_group, 'rawspec', exp_prefix) for exp_prefix in list_exp_prefix]) - elif opts_run['type'] == 'current': - list_ts.extend(ddata.file_match(expmt_group, 'rawcurrent')) - list_spec.extend(ddata.create_filename(expmt_group, 'rawspeccurrent', list_exp_prefix[-1])) - # create list of spec output names - # this is sorted because of file_match - # exp_prefix_list = [fio.strip_extprefix(fparam) for fparam in list_param] - - # perform analysis on all runs from all exmpts at same time - if opts_run['type'] == 'current': - # list_spec.extend([ddata.create_filename(expmt_group, 'rawspeccurrent', exp_prefix) for exp_prefix in exp_prefix_list]) - if opts_run['runtype'] == 'parallel': - pl = mp.Pool() - for fparam, fts, fspec in zip(list_param, list_ts, list_spec): - pl.apply_async(spec_current_kernel, (fparam, fts, fspec, opts_run['f_max'])) - pl.close() - pl.join() - elif opts_run['runtype'] == 'debug': - for fparam, fts, fspec in zip(list_param, list_ts, list_spec): - spec_current_kernel(fparam, fts, fspec, opts_run['f_max']) - elif opts_run['type'] == 'dpl_laminar': - # these should be OUTPUT filenames that are being generated - # list_spec.extend([ddata.create_filename(expmt_group, 'rawspec', exp_prefix) for exp_prefix in exp_prefix_list]) - # also in this case, the original spec results will be overwritten - # and replaced by laminar specific ones and aggregate ones - # in this case, list_ts is a list of dipole - if opts_run['runtype'] == 'parallel': - pl = mp.Pool() - for fparam, fts, fspec in zip(list_param, list_ts, list_spec): - pl.apply_async(spec_dpl_kernel, (fparam, fts, fspec, opts_run['f_max'])) - pl.close() - pl.join() - elif opts_run['runtype'] == 'debug': - # spec_results_L2 and _L5 - for fparam, fts, fspec in zip(list_param, list_ts, list_spec): - spec_dpl_kernel(fparam, fts, fspec, opts_run['f_max']) - # else: - # print('Type %s not recognized. Try again later.' %(opts_run['type'])) - -# returns spec results *only* for a given experimental group -def from_expmt(spec_result_list, expmt_group): - return [spec_result for spec_result in spec_result_list if expmt_group in spec_result.name] - -# Averages spec power over time, returning an array of average pwr per frequency -def specpwr_stationary_avg(fspec): - print("Warning: you are using specpwr_stationary_avg(). It should be changed from == to np.isclose()") - - # Load data from file - data_spec = np.load(fspec) - - timevec = data_spec['time'] - freqvec = data_spec['freq'] - TFR = data_spec['TFR'] - - # get experiment name - expmt = fspec.split('/')[6].split('.')[0] - - # axis = 1 sums over columns - pwr_avg = TFR.sum(axis=1) / len(timevec) - pwr_max = pwr_avg.max() - f_at_max = freqvec[pwr_avg == pwr_max] - - return { - 'p_avg': pwr_avg, - 'p_max': pwr_max, - 'f_max': f_at_max, - 'freq': freqvec, - 'expmt': expmt, - } - -def specpwr_stationary(t, f, TFR): - print("Warning: you are using specpwr_stationary(). It should be changed from == to np.isclose()") - - # aggregate sum of power of all calculated frequencies - p = TFR.sum(axis=1) - - # calculate max power - p_max = p.max() - - # calculate max f - f_max = f[p == p_max] - - return { - 'p': p, - 'f': f, - 'p_max': p_max, - 'f_max': f_max, - } - -def calc_stderror(data_list): - # np.std returns standard deviation - # axis=0 performs standard deviation over rows - error_vec = np.std(data_list, axis=0) - - return error_vec - -def pfreqpwr_with_hist(file_name, freqpwr_result, f_spk, gid_dict, p_dict, key_types): - f = ac.FigFreqpwrWithHist() - f.ax['hist'].hold(True) - - xmin = 50. - xmax = p_dict['tstop'] - - f.ax['freqpwr'].plot(freqpwr_result['freq'], freqpwr_result['avgpwr']) - - # grab alpha feed data. spikes_from_file() from spikefn.py - s_dict = spikefn.spikes_from_file(gid_dict, f_spk) - - # check for existance of alpha feed keys in s_dict. - s_dict = spikefn.alpha_feed_verify(s_dict, p_dict) - - # Account for possible delays - s_dict = spikefn.add_delay_times(s_dict, p_dict) - - # set number of bins (150 bins/1000ms) - bins = 150. * (xmax - xmin) / 1000. - hist_data = [] - - # Proximal feed - hist_data.extend(f.ax['hist'].hist(s_dict['alpha_feed_prox'].spike_list, bins, range=[xmin, xmax], color='red', label='Proximal feed')[0]) - - # Distal feed - hist_data.extend(f.ax['hist'].hist(s_dict['alpha_feed_dist'].spike_list, bins, range=[xmin, xmax], color='green', label='Distal feed')[0]) - - # set hist axis props - f.set_hist_props(hist_data) - - # axis labels - f.ax['freqpwr'].set_xlabel('freq (Hz)') - f.ax['freqpwr'].set_ylabel('power') - f.ax['hist'].set_xlabel('time (ms)') - f.ax['hist'].set_ylabel('# spikes') - - # create title - title_str = ac.create_title(p_dict, key_types) - f.f.suptitle(title_str) - # title_str = [key + ': %2.1f' % p_dict[key] for key in key_types['dynamic_keys']] - - f.savepng(file_name) - f.close() - -def pmaxpwr(file_name, results_list, fparam_list): - raise DeprecationWarning - - f = ac.FigStd() - f.ax0.hold(True) - - # instantiate lists for storing x and y data - x_data = [] - y_data = [] - - # plot points - for result, fparam in zip(results_list, fparam_list): - p = paramrw.read(fparam)[1] - - x_data.append(p['f_input_prox']) - y_data.extend(result['freq_at_max']) - - f.ax0.plot(x_data[-1], y_data[-1], 'kx') - - # add trendline - fit = np.polyfit(x_data, y_data, 1) - fit_fn = np.poly1d(fit) - - f.ax0.plot(x_data, fit_fn(x_data), 'k-') - - # Axis stuff - f.ax0.set_xlabel('Proximal/Distal Input Freq (Hz)') - f.ax0.set_ylabel('Freq at which max avg power occurs (Hz)') - - f.save(file_name) - -if __name__ == '__main__': - x = np.arange(0, 10.1, 0.1) - s1 = np.array([np.sin(x)]) - s2 = np.array([np.sin(2*x)]) - dt = 0.1 - - p = PhaseLock(s1, s2, dt) diff --git a/spikefn.py b/spikefn.py index ef29f2faf..18108f745 100644 --- a/spikefn.py +++ b/spikefn.py @@ -4,7 +4,6 @@ # rev 2016-05-01 (SL: minor) # last major: (SL: toward python3) -import fileio as fio import numpy as np import scipy.signal as sps import matplotlib.pyplot as plt @@ -232,15 +231,6 @@ def add_delay_times (self): if self.p_dict['input_dist_A_delay_L2'] == self.p_dict['input_dist_A_delay_L5']: self.inputs['dist'] += self.p_dict['input_dist_A_delay_L2'] - def get_envelope (self, tvec, feed='dist', bins=150): - h_range = (tvec[0], tvec[-1]) - hist, edges = np.histogram(self.inputs[feed], bins=bins, range=h_range) - centers = edges[0:bins] + np.diff(edges) / 2. - num = len(tvec) - env, t = sps.resample(hist, num, t=centers) - self.inputs['env'] = env - self.inputs['t'] = t - # extinput is either 'dist' or 'prox' def plot_hist (self, ax, extinput, tvec, bins='auto', xlim=None, color='green', hty='bar',lw=4): if bins is 'auto': @@ -256,17 +246,6 @@ def plot_hist (self, ax, extinput, tvec, bins='auto', xlim=None, color='green', hist = None return hist -# filters spike dict s_dict for keys that start with str_startswith -def filter_spike_dict (s_dict, str_startswith): - """ easy enough to modify for future conditions - just fix associated functions - """ - s_filt = {} - for key, val in s_dict.items(): - if key.startswith(str_startswith): - s_filt[key] = val - return s_filt - # weird bin counting function def bin_count(bins_per_second, tinterval): return bins_per_second * tinterval / 1000. @@ -372,43 +351,6 @@ def get_markerstyle(key): markerstyle += 'r|' return markerstyle -# spike_png plots spikes based on input dict -def spike_png(a, s_dict): - # new spikepng function: - # receive lists of cell spikes and the gid dict for now - # parse spikes file by cell type - # output all cell spikes - # get the length of s - new way - N_total = 0 - for key in s_dict.keys(): N_total += s_dict[key].N_cells - # 2 added to this in order to pad the y_ticks off the x axis and top - # e_ticks starts at 1 for padding - # i_ticks ends at -1 for padding - y_ticks = np.linspace(0, 1, N_total + 2) - # Turn the hold on - a.hold(True) - # define start point - tick_start = 1 - # sort the keys by alpha: consistency in names will lead to consistent behavior here - # reverse=True because _basket comes before _pyramidal, and the spikes plot bottom up - key_list = [key for key in s_dict.keys()] - key_list.sort(reverse=True) - # for key in s_dict.keys(): - for key in key_list: - # print key, s_dict[key].spike_list - s_dict[key].tick_marks = y_ticks[tick_start:tick_start+s_dict[key].N_cells] - tick_start += s_dict[key].N_cells - markerstyle = get_markerstyle(key) - # There must be congruency between lines in spike_list and the number of ticks - i = 0 - for spk_cell in s_dict[key].spike_list: - # a.plot(np.array([451.6]), e_ticks[i] * np.ones(1), 'k.', markersize=2.5) - # print len(s_dict[key].tick_marks), len(spk_cell) - a.plot(spk_cell, s_dict[key].tick_marks[i] * np.ones(len(spk_cell)), markerstyle, markeredgewidth=1, markersize=1.5) - i += 1 - a.set_ylim([0, 1]) - a.grid() - # Add synaptic delays to alpha input times if applicable: def add_delay_times(s_dict, p_dict): # Only add delays if delay is same for L2 and L5 diff --git a/visdipole.py b/visdipole.py index 7df47117b..ca6f6948c 100644 --- a/visdipole.py +++ b/visdipole.py @@ -16,9 +16,7 @@ import matplotlib.gridspec as gridspec from DataViewGUI import DataViewGUI import paramrw -from filt import boxfilt, hammfilt import spikefn -from math import ceil from simdat import readdpltrials from conf import dconf diff --git a/vislfp.py b/vislfp.py deleted file mode 100644 index b04e9a321..000000000 --- a/vislfp.py +++ /dev/null @@ -1,301 +0,0 @@ -import sys, os -from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout -from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QDialog, QGridLayout, QLineEdit, QLabel -from PyQt5.QtWidgets import QCheckBox -from PyQt5.QtGui import QIcon, QFont, QPixmap -from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, pyqtSlot -from PyQt5 import QtCore -import numpy as np -import matplotlib.pyplot as plt -import matplotlib.patches as mpatches -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar -from matplotlib.figure import Figure -import pylab as plt -import matplotlib.gridspec as gridspec -from DataViewGUI import DataViewGUI -import paramrw -from filt import boxfilt, hammfilt, lowpass -import spikefn -from math import ceil -from conf import dconf -from specfn import MorletSpec - -from hnn_core import read_params - -if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] - -debug = True - -tstop = -1; ntrial = 1; maxlfp = 0; scalefctr = 30e3; lfppath = ''; paramf = ''; laminar = False -for i in range(len(sys.argv)): - if sys.argv[i].endswith('.txt'): - lfppath = sys.argv[i] - elif sys.argv[i].endswith('.param'): - paramf = sys.argv[i] - params = read_params(paramf) - scalefctr = params['dipole_scalefctr'] - if type(scalefctr)!=float and type(scalefctr)!=int: scalefctr=30e3 - tstop = params['tstop'] - ntrial = params['N_trials'] - -basedir = os.path.join(dconf['datdir'],params['sim_prefix']) - -ddat = {}; tvec = None; dspec = None - -def readLFPs (basedir, ntrial): - if debug: print('readLFPs') - ddat = {'lfp':{}} - lfile = os.listdir(basedir) - maxlfp = 0; tvec = None - if debug: print('readLFPs:',lfile) - for f in lfile: - if f.count('lfp_') > 0 and f.endswith('.txt'): - lf = f.split('.txt')[0].split('_') - if debug: print('readLFPs: lf=',lf,'ntrial=',ntrial) - if ntrial > 1: - trial = int(lf[1]) - nlfp = int(lf[2]) - else: - trial = 0 - nlfp = int(lf[1]) - maxlfp = max(nlfp,maxlfp) - if debug: print('readLFPs:',trial,nlfp,maxlfp) - fullpath = os.path.join(basedir,f) - if debug: print('readLFPs: fullpath=',fullpath) - try: - k2 = (trial,nlfp) - #print('k2:',k2) - ddat['lfp'][k2] = np.loadtxt(fullpath) - if tvec is None: tvec = ddat['lfp'][k2][:,0] - except: - print('exception!') - print('readLFPs:',ddat['lfp'].keys()) - #print('ddat:',ddat,maxlfp) - return ddat, maxlfp, tvec - -# lowpass filter the items in lfps. lfps is a list or numpy array of LFPs arranged spatially by row -def getlowpass (lfps,sampr,maxf): - return np.array([lowpass(lfp,maxf,df=sampr,zerophase=True) for lfp in lfps]) - -# gets 2nd spatial derivative of voltage as approximation of CSD. -# performs lowpass filter on voltages before taking spatial derivative -# input dlfp is dictionary of LFP voltage time-series keyed by (trial, electrode) -# output dCSD is keyed by trial -def getCSD (dlfp,sampr,minf=0.1,maxf=300.0): - if debug: print('getCSD:','sampr=',sampr,'ntrial=',ntrial,'maxlfp=',maxlfp) - dCSD = {} - for trial in range(ntrial): - if debug: print('trial:',trial) - lfps = [dlfp[(trial,i)][:,1] for i in range(maxlfp+1)] - datband = getlowpass(lfps,sampr,maxf) - dCSD[trial] = -np.diff(datband,n=2,axis=0) # now each row is an electrode -- CSD along electrodes - return dCSD - -try: - ddat, maxlfp, tvec = readLFPs(basedir,ntrial) - if maxlfp > 1: laminar = True - ddat['spec'] = {} - waveprm = {'f_max_spec':40.0,'dt':tvec[1]-tvec[0],'tstop':tvec[-1]} - minwavet = 50.0 - sampr = 1e3 / (tvec[1]-tvec[0]) - - if laminar: - print('getting CSD') - ddat['CSD'] = getCSD(ddat['lfp'],sampr) - if ntrial > 1: - ddat['avgCSD'] = np.zeros(ddat['CSD'][1].shape) - for i in range(ntrial): ddat['avgCSD'] += ddat['CSD'][i] - ddat['avgCSD']/=float(ntrial) - - print('Extracting Wavelet spectrogram(s).') - for i in range(maxlfp+1): - for trial in range(ntrial): - ddat['spec'][(trial,i)] = MorletSpec(tvec, ddat['lfp'][(trial,i)][:,1], None, - p_dict=waveprm, tmin=minwavet) - if ntrial > 1: - if debug: print('here') - davglfp = {}; davgspec = {} - for i in range(maxlfp+1): - if debug: print(i,maxlfp,list(ddat['lfp'].keys())[0]) - davglfp[i] = np.zeros(len(ddat['lfp'][list(ddat['lfp'].keys())[0]]),) - try: - ms = ddat['spec'][(0,0)] - if debug: print('shape',ms.TFR.shape,ms.tmin,ms.f[0],ms.f[-1]) - davgspec[i] = [np.zeros(ms.TFR.shape), ms.tmin, ms.f] - except: - print('err in davgspec[i]=') - for trial in range(ntrial): - davglfp[i] += ddat['lfp'][(trial,i)][:,1] - davgspec[i][0] += ddat['spec'][(trial,i)].TFR - davglfp[i] /= float(ntrial) - davgspec[i][0] /= float(ntrial) - ddat['avglfp'] = davglfp - ddat['avgspec'] = davgspec -except: - print('Could not load LFPs') - quit() - -def getnorm (yin): - yout = yin - min(yin) - return yout / max(yout) - -def getrngfctroff (dat): - yrng = [max(dat[i,:])-min(dat[i,:]) for i in range(dat.shape[0])] - mxrng = np.amax(yrng) - yfctr = [yrng[i]/mxrng for i in range(len(yrng))] - yoff = [maxlfp - 1 - (i + 1) for i in range(len(yrng))] - return yrng,yfctr,yoff - -class LFPCanvas (FigureCanvas): - - def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, title='LFP Viewer'): - FigureCanvas.__init__(self, Figure(figsize=(width, height), dpi=dpi)) - self.title = title - self.setParent(parent) - self.index = index - FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding) - FigureCanvas.updateGeometry(self) - self.paramf = paramf - self.drawwavelet = True - - # get spec_cmap - p_exp = paramrw.ExpParams(self.paramf, 0) - if len(p_exp.expmt_groups) > 0: - expmt_group = p_exp.expmt_groups[0] - else: - expmt_group = None - p = p_exp.return_pdict(expmt_group, 0) - self.spec_cmap = p['spec_cmap'] - - self.plot() - - def clearaxes (self): - try: - for ax in self.lax: - ax.set_yticks([]) - ax.cla() - except: - pass - - def drawCSD (self, fig, G): - ax = fig.add_subplot(G[:,2]) - ax.set_yticks([]) - lw = 2; clr = 'k' - if ntrial > 1: - if self.index == 0: - cax = ax.imshow(ddat['avgCSD'],extent=[0, tstop, 0, maxlfp-1], aspect='auto', origin='upper',cmap=plt.get_cmap(self.spec_cmap),interpolation='None') - # overlay the time-series - yrng,yfctr,yoff = getrngfctroff(ddat['avgCSD']) - for i in range(ddat['avgCSD'].shape[0]): - y = yfctr[i] * getnorm(ddat['avgCSD'][i,:]) + yoff[i] - ax.plot(tvec,y,clr,linewidth=lw) - else: - cax = ax.imshow(ddat['CSD'][self.index-1],extent=[0, tstop, 0, maxlfp-1], aspect='auto', origin='upper',cmap=plt.get_cmap(self.spec_cmap),interpolation='None') - # overlay the time-series - yrng,yfctr,yoff = getrngfctroff(ddat['CSD'][self.index-1]) - for i in range(ddat['CSD'][self.index-1].shape[0]): - y = yfctr[i] * getnorm(ddat['CSD'][self.index-1][i,:]) + yoff[i] - ax.plot(tvec,y,clr,linewidth=lw) - else: - # draw CSD as image; blue/red corresponds to excit/inhib - cax = ax.imshow(ddat['CSD'][0],extent=[0, tstop, 0, 15], aspect='auto', origin='upper',cmap=plt.get_cmap(self.spec_cmap),interpolation='None') - # overlay the time-series - yrng,yfctr,yoff = getrngfctroff(ddat['CSD'][0]) - for i in range(ddat['CSD'][0].shape[0]): - y = yfctr[i] * getnorm(ddat['CSD'][0][i,:]) + yoff[i] - ax.plot(tvec,y,clr,linewidth=lw) - cbaxes = fig.add_axes([0.69, 0.88, 0.005, 0.1]) - fig.colorbar(cax, cax=cbaxes, orientation='vertical') - ax.set_xlim((minwavet,tstop)); ax.set_ylim((0,maxlfp-1)) - - def drawLFP (self, fig): - - if laminar: - nrow = maxlfp+1 - ncol = 3 - ltitle = ['' for x in range(nrow*ncol)] - else: - nrow = (maxlfp+1) * 2 - ncol = 1 - ltitle = ['LFP'+str(x) for x in range(nrow)] - - G = gridspec.GridSpec(nrow,ncol) - - white_patch = mpatches.Patch(color='white', label='Average') - gray_patch = mpatches.Patch(color='gray', label='Individual') - lpatch = [] - - if debug: print('ntrial:',ntrial) - - if ntrial > 1: lpatch = [white_patch,gray_patch] - - yl = [1e9,-1e9] - - minx = 100 - - for i in [1]: # this gets min,max LFP values - # print('ddat[lfp].keys():',ddat['lfp'].keys()) - for k in ddat['lfp'].keys(): - yl[0] = min(yl[0],ddat['lfp'][k][minx:-1,i].min()) - yl[1] = max(yl[1],ddat['lfp'][k][minx:-1,i].max()) - - yl = tuple(yl) # y-axis range - - self.lax = [] - - for nlfp in range(maxlfp+1): - title = ltitle[nlfp] - - if laminar: ax = fig.add_subplot(G[nlfp, 0]) - else: ax = fig.add_subplot(G[nlfp*2]) - - self.lax.append(ax) - - if self.index == 0: # draw all along with average - if ntrial > 1: clr = 'gray' - else: clr = 'white' - for i in range(ntrial): ax.plot(tvec,ddat['lfp'][(i,nlfp)][:,1],color=clr,linewidth=2) - if ntrial > 1: - ax.plot(tvec,ddat['avglfp'][nlfp],'w',linewidth=3) - if nlfp == 0: ax.legend(handles=lpatch) - else: # draw individual trial - ax.plot(tvec,ddat['lfp'][(self.index-1,nlfp)][:,1],color='white',linewidth=2) - - if not laminar: ax.set_ylabel(r'$\mu V$') - if tstop != -1: ax.set_xlim((minwavet,tstop)) - ax.set_ylim(yl) - - ax.set_facecolor('k'); ax.grid(True); ax.set_title(title) - - # plot wavelet spectrogram - if laminar: ax = fig.add_subplot(G[nlfp, 1]) - else: ax = fig.add_subplot(G[nlfp*2+1]) - self.lax.append(ax) - if self.index == 0: - if ntrial > 1: - TFR,tmin,F = ddat['avgspec'][nlfp] - ax.imshow(TFR, extent=[tmin, tvec[-1], F[-1], F[0]], aspect='auto', origin='upper',cmap=plt.get_cmap(self.spec_cmap)) - else: - ms = ddat['spec'][(0,nlfp)] - ax.imshow(ms.TFR, extent=[ms.tmin, tvec[-1], ms.f[-1], ms.f[0]], aspect='auto', origin='upper',cmap=plt.get_cmap(self.spec_cmap)) - else: - ms = ddat['spec'][(self.index-1,nlfp)] - ax.imshow(ms.TFR, extent=[ms.tmin, tvec[-1], ms.f[-1], ms.f[0]], aspect='auto', origin='upper',cmap=plt.get_cmap(self.spec_cmap)) - ax.set_xlim(minwavet,tvec[-1]) - if nlfp == maxlfp: ax.set_xlabel('Time (ms)') - if not laminar: ax.set_ylabel('Frequency (Hz)'); - - if laminar: self.drawCSD(fig, G) - - self.figure.subplots_adjust(bottom=0.04, left=0.04, right=1.0, top=0.99, wspace=0.1, hspace=0.01) - - def plot (self): - self.drawLFP(self.figure) - self.draw() - -if __name__ == '__main__': - app = QApplication(sys.argv) - ex = DataViewGUI(LFPCanvas,paramf,ntrial,'LFP Viewer') - sys.exit(app.exec_()) diff --git a/visnet.py b/visnet.py deleted file mode 100644 index 3a01a9d6d..000000000 --- a/visnet.py +++ /dev/null @@ -1,231 +0,0 @@ -import sys, os -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -#from pyqtgraph.graphicsItems.AxisItem import * -import pyqtgraph.opengl as gl -import pyqtgraph as pg -import numpy as np - -from morphology import shapeplot, getshapecoords -from mpl_toolkits.mplot3d import Axes3D -import pylab as plt -from neuron import h -from L5_pyramidal import L5Pyr -from L2_pyramidal import L2Pyr -from L2_basket import L2Basket -from L5_basket import L5Basket -from run import net - -drawallcells = True # False -cell = net.cells[-1] - -# colors for the different cell types -dclr = {'L2_pyramidal' : 'g', L2Pyr: (0.,1.,0.,0.6), - 'L5_pyramidal' : 'r', L5Pyr: (1.,0.,0.,0.6), - 'L2_basket' : 'k', L2Basket: (1.,1.,1.,0.6), - 'L5_basket' : 'b', L5Basket: (0.,0.,1.,0.6)} - -def getcellpos (net,ty): - lx,ly = [],[] - for cell in net.cells: - if type(cell) == ty: - lx.append(cell.pos[0]) - ly.append(cell.pos[1]) - return lx,ly - -def cellsecbytype (ty): - lss = [] - for cell in net.cells: - if type(cell) == ty: - ls = cell.get_sections() - for s in ls: lss.append(s) - return lss - -def getdrawsec (ncells=1,ct=L2Pyr): - global cell - if drawallcells: return list(h.allsec()) - ls = [] - nfound = 0 - for c in net.cells: - if type(c) == ct: - cell = c - lss = c.get_sections() - for s in lss: ls.append(s) - nfound += 1 - if nfound >= ncells: break - return ls - -dsec = {} -for ty in [L2Pyr, L5Pyr, L2Basket, L5Basket]: dsec[ty] = cellsecbytype(ty) -dlw = {L2Pyr:1, L5Pyr:1,L2Basket:4,L5Basket:4} -whichdraw = [L2Pyr, L2Basket, L5Pyr, L5Basket] - -lsecnames = cell.get_section_names() - -def get3dinfo (sidx,eidx): - llx,lly,llz,lldiam = [],[],[],[] - for i in range(sidx,eidx,1): - lx,ly,lz,ldiam = net.cells[i].get3dinfo() - llx.append(lx); lly.append(ly); llz.append(lz); lldiam.append(ldiam) - return llx,lly,llz,lldiam - -llx,lly,llz,lldiam = get3dinfo(0,len(net.cells)) - -def countseg (ls): return sum([s.nseg for s in ls]) - -defclr = 'k'; selclr = 'r' -useGL = True -fig = None - -def drawcellspylab3d (): - global shapeax,fig - plt.ion(); fig = plt.figure() - shapeax = plt.subplot(111, projection='3d') - #shapeax.set_xlabel('X',fontsize=24); shapeax.set_ylabel('Y',fontsize=24); shapeax.set_zlabel('Z',fontsize=24) - shapeax.set_xticks([]); shapeax.set_yticks([]); shapeax.set_zticks([]) - shapeax.view_init(elev=105,azim=-71) - shapeax.grid(False) - lshapelines = [] - for ty in whichdraw: - ls = dsec[ty] - lshapelines.append(shapeplot(h,shapeax,sections=ls,cvals=[dclr[ty] for i in range(countseg(ls))],lw=dlw[ty])) - return lshapelines - -if not useGL: drawcellspylab3d() - -def onclick(event): - try: - print('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % - (event.button, event.x, event.y, event.xdata, event.ydata)) - except: - pass - -def setcolor (ls,clr): - for l in ls: l.set_color(clr) - -# click on section event handler - not used for network -def onpick (event): - print('onpick') - thisline = event.artist - c = thisline.get_color() - idx = -1 - setcolor(shapelines,defclr) - for idx,l in enumerate(shapelines): - if l == thisline: - break - try: - print('idx is ', idx, 'selected',lsecnames[idx]) - xdata = thisline.get_xdata() - ydata = thisline.get_ydata() - ind = event.ind - points = tuple(zip(xdata[ind], ydata[ind])) - print('onpick points:', points) - if c == defclr: - thisline.set_color(selclr) - else: - thisline.set_color(defclr) - print(ind) - #print(dir(thisline)) - except: - pass - -def setcallbacks (): - if useGL: return [] - lcid = [] - if False: lcid.append(fig.canvas.mpl_connect('button_press_event', onclick)) - if not drawallcells: lcid.append(fig.canvas.mpl_connect('pick_event', onpick)) - return lcid - -lcid = setcallbacks() - -# -def drawinputs2d (cell,clr,ax): - for lsrc in [cell.ncfrom_L2Pyr, cell.ncfrom_L2Basket, cell.ncfrom_L5Pyr, cell.ncfrom_L5Basket]: - for src in lsrc: - precell = src.precell() - ax.plot([precell.pos[0],cell.pos[0]],[precell.pos[1],cell.pos[1]],clr) - -# -def drawconn2d (): - plt.figure() - ax = plt.gca() - """ - loc = np.array(net.pos_dict['L2_basket']) - plot(loc[:,0],loc[:,1],'ko',markersize=14) - loc = np.array(net.pos_dict['L2_pyramidal']) - plot(loc[:,0],loc[:,1],'ro',markersize=14) - loc = np.array(net.pos_dict['L2_basket']) - plot(loc[:,0],loc[:,1],'bo',markersize=10) - """ - lx = [cell.pos[0] for cell in net.cells] - ly = [cell.pos[1] for cell in net.cells] - ax.plot(lx,ly,'ko',markersize=14) - """ - self.ncfrom_L2Pyr = [] - self.ncfrom_L2Basket = [] - self.ncfrom_L5Pyr = [] - self.ncfrom_L5Basket = [] - """ - for cell in net.cells: - drawinputs2d(cell,'r',ax) - break - -# -def drawinputs3d (cell,clr,widg,width=2.0): - for lsrc in [cell.ncfrom_L2Pyr, cell.ncfrom_L2Basket, cell.ncfrom_L5Pyr, cell.ncfrom_L5Basket]: - for src in lsrc: - precell = src.precell() - pts = np.vstack([[precell.pos[0]*100,cell.pos[0]*100],[precell.pos[2],cell.pos[2]],[precell.pos[1]*100,cell.pos[1]*100]]).transpose() - plt = gl.GLLinePlotItem(pos=pts, color=clr, width=width, antialias=True, mode='lines') - widg.addItem(plt) - -# -def drawconn3d (widg,width=2.0,clr=(1.0,0.0,0.0,0.5)): - i = 0 - for cell in net.cells: - drawinputs3d(cell,clr,widg,width) - i += 1 - #if i > 20: break - -def drawcells3dgl (ty,widget,width=2.2): - for cell in net.cells: - if type(cell) != ty: continue - lx,ly,lz = getshapecoords(h,cell.get_sections()) - pts = np.vstack([lx,ly,lz]).transpose() - plt = gl.GLLinePlotItem(pos=pts, color=dclr[type(cell)], width=width, antialias=True, mode='lines') - #plt.showGrid(x=True,y=True) - widget.addItem(plt) - #axis = pg.AxisItem(orientation='bottom') - #print(dir(axis)) - #print(dir(widget)) - #print(widget.getViewport()) - #axis.linkToView(axis.getViewBox())#widget.getViewport()) - #widget.addItem(pg.AxisItem(orientation='bottom')) - -def drawallcells3dgl (wcells): - drawcells3dgl(L5Pyr,wcells,width=15.0) - drawcells3dgl(L2Pyr,wcells,width=15.0) - drawcells3dgl(L5Basket,wcells,width=40.0) - drawcells3dgl(L2Basket,wcells,width=40.0) - wcells.opts['distance'] = 4320.9087386478195 - wcells.opts['elevation']=105 - wcells.opts['azimuth']=-71 - wcells.opts['fov'] = 90 - wcells.setWindowTitle('Network Visualization') - -if __name__ == '__main__': - app = QtGui.QApplication([]) - widg = gl.GLViewWidget() - for s in sys.argv: - if s == 'cells': - drawallcells3dgl(widg) - if s == 'Econn': - drawconn3d(widg,clr=(1.0,0.0,0.0,0.25)) - if s == 'Iconn': - drawconn3d(widg,clr=(0.0,0.0,1.0,0.25)) - #app.axis = axis = pg.AxisItem(orientation='bottom') - #app.pqg_plot_item.showAxis('bottom',True) - widg.show() - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() - diff --git a/vispsd.py b/vispsd.py index 3abee51eb..e8345c137 100644 --- a/vispsd.py +++ b/vispsd.py @@ -15,10 +15,7 @@ import pylab as plt import matplotlib.gridspec as gridspec from DataViewGUI import DataViewGUI -import paramrw -from filt import boxfilt, hammfilt -import spikefn -from math import ceil, sqrt +from math import sqrt from specfn import MorletSpec from conf import dconf diff --git a/visrast.py b/visrast.py index a7e347cfd..3f5e7acd7 100644 --- a/visrast.py +++ b/visrast.py @@ -12,10 +12,10 @@ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure -import pylab as plt +import pylab as plt, convolve +from numpy import hamming import matplotlib.gridspec as gridspec import paramrw -from filt import boxfilt, hammfilt import spikefn from math import ceil from conf import dconf @@ -60,6 +60,18 @@ bDrawHist = True # whether to draw histograms (spike counts per time) +# box filter +def boxfilt (x, winsz): + win = [1.0/winsz for i in range(int(winsz))] + return convolve(x,win,'same') + +# convolve with a hamming window +def hammfilt (x, winsz): + win = hamming(winsz) + win /= sum(win) + return convolve(x,win,'same') + + # adjust input gids for display purposes def adjustinputgid (extinputs, gid): if gid == extinputs.gid_prox: diff --git a/visvolt.py b/visvolt.py index 27c6cd4a5..76d926dec 100644 --- a/visvolt.py +++ b/visvolt.py @@ -14,7 +14,6 @@ from matplotlib.figure import Figure import pylab as plt import matplotlib.gridspec as gridspec -import paramrw import pickle from conf import dconf from gutils import getmplDPI From 08ece1259c77e50150e1536e04d240df4f66a009 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Fri, 18 Sep 2020 13:28:34 -0400 Subject: [PATCH 012/107] BUG: fix ordering of saving files --- run.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/run.py b/run.py index 6a086e16e..5ddbab77e 100755 --- a/run.py +++ b/run.py @@ -122,7 +122,6 @@ def simulate (params, n_core): ddir = setupsimdir(params) # now write the files - params.write(os.path.join(ddir, params['sim_prefix'] + '.json')) # TODO: Can below be removed if spk.txt is new hnn-core format with 3 columns (including spike type)? write_gids_param(getfname(ddir,'param'), net.gid_dict) @@ -140,19 +139,6 @@ def simulate (params, n_core): int(net.spikes.gids[trial_idx][spike_idx]), net.spikes.types[trial_idx][spike_idx])) - # save average spectrogram from individual trials in a single file - lf = [os.path.join(ddir,'rawspec_'+str(i)+'.npz') for i in range(ntrial)] - dspecin = {} - dout = {} - try: - for f in lf: dspecin[f] = np.load(f) - except: - return None - for k in ['t_L5', 'f_L5', 't_L2', 'f_L2', 'time', 'freq']: dout[k] = dspecin[lf[0]][k] - for k in ['TFR', 'TFR_L5', 'TFR_L2']: dout[k] = np.mean(np.array([dspecin[f][k] for f in lf]),axis=0) - with open(os.path.join(ddir,'rawspec.npz'), 'wb') as fdpl: - np.savez_compressed(fdpl,t_L5=dout['t_L5'],f_L5=dout['f_L5'],t_L2=dout['t_L2'],f_L2=dout['f_L2'],time=dout['time'],freq=dout['freq'],TFR=dout['TFR'],TFR_L5=dout['TFR_L5'],TFR_L2=dout['TFR_L2']) - # save dipole for each trial and perform spectral analysis for trial_idx, dpl in enumerate(dpls): file_dipole = getfname(ddir,'normdpl', trial_idx, params['N_trials']) @@ -174,7 +160,7 @@ def simulate (params, n_core): if params['save_spec_data'] or usingOngoingInputs(params): spec_opts = {'type': 'dpl_laminar', 'f_max': params['f_max_spec'], - 'save_data': 0, + 'save_data': 1, 'runtype': 'parallel', } @@ -185,4 +171,17 @@ def simulate (params, n_core): # NOTE: the savefigs functionality is quite complicated and rewriting from scratch in hnn-core is probably # a much better option that allows deprecating the large amount of legacy code # if params['save_figs']: - # savefigs(params) # save output figures \ No newline at end of file + # savefigs(params) # save output figures + + if params['save_spec_data'] or usingOngoingInputs(params): + # save average spectrogram from individual trials in a single file + lf = [os.path.join(ddir,'rawspec_'+str(i)+'.npz') for i in range(ntrial)] + dspecin = {} + dout = {} + for f in lf: + dspecin[f] = np.load(f) + + for k in ['t_L5', 'f_L5', 't_L2', 'f_L2', 'time', 'freq']: dout[k] = dspecin[lf[0]][k] + for k in ['TFR', 'TFR_L5', 'TFR_L2']: dout[k] = np.mean(np.array([dspecin[f][k] for f in lf]),axis=0) + with open(os.path.join(ddir,'rawspec.npz'), 'wb') as fdpl: + np.savez_compressed(fdpl,t_L5=dout['t_L5'],f_L5=dout['f_L5'],t_L2=dout['t_L2'],f_L2=dout['f_L2'],time=dout['time'],freq=dout['freq'],TFR=dout['TFR'],TFR_L5=dout['TFR_L5'],TFR_L2=dout['TFR_L2']) From 41821b264b9714025e8f0172ad3c7fd2e60079c0 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Fri, 18 Sep 2020 13:28:40 -0400 Subject: [PATCH 013/107] MAINT: cleanup external viz windows For windows that are launched as a separate process, only read the param file once with hnn-core and pass params as necessary. This avoids the need for globals in most cases. Globals still exist in visrast.py -- more work needed there Also this completes using 'spec_cmap' with hnn core param files --- DataViewGUI.py | 12 ++-- hnn_qt5.py | 59 +++++++-------- simdat.py | 11 +-- specfn.py | 14 ++-- visdipole.py | 2 +- vispsd.py | 97 ++++++++++--------------- visrast.py | 3 +- visspec.py | 190 ++++++++++++++++++++++--------------------------- 8 files changed, 168 insertions(+), 220 deletions(-) diff --git a/DataViewGUI.py b/DataViewGUI.py index b01c16f15..8611afae6 100644 --- a/DataViewGUI.py +++ b/DataViewGUI.py @@ -16,14 +16,14 @@ # GUI for viewing data from individual/all trials class DataViewGUI (QMainWindow): - def __init__ (self, CanvasType, paramf, ntrial,title): + def __init__ (self, CanvasType, params, title): super().__init__() self.fontsize = dconf['fontsize'] self.linewidth = plt.rcParams['lines.linewidth'] = 1 self.markersize = plt.rcParams['lines.markersize'] = 5 self.CanvasType = CanvasType - self.paramf = paramf - self.ntrial = ntrial + self.ntrial = params['N_trials'] + self.params = params self.title = title self.initUI() @@ -86,7 +86,7 @@ def initCanvas (self): self.m = self.toolbar = None except: pass - self.m = self.CanvasType(self.paramf, self.index, parent = self, width=12, height=10, dpi=getmplDPI()) + self.m = self.CanvasType(self.params, self.index, parent = self, width=12, height=10, dpi=getmplDPI()) # this is the Navigation widget # it takes the Canvas widget and a parent self.toolbar = NavigationToolbar(self.m, self) @@ -107,7 +107,7 @@ def initUI (self): self.initMenu() self.statusBar() self.setGeometry(300, 300, 1300, 1100) - self.setWindowTitle(self.title + ' - ' + self.paramf) + self.setWindowTitle(self.title + ' - ' + os.path.join(dconf['datdir'], self.params['sim_prefix'] + '.param')) self.grid = grid = QGridLayout() self.index = 0 self.initCanvas() @@ -119,7 +119,7 @@ def initUI (self): # need a separate widget to put grid on widget = QWidget(self) widget.setLayout(grid) - self.setCentralWidget(widget); + self.setCentralWidget(widget) try: self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) except: pass diff --git a/hnn_qt5.py b/hnn_qt5.py index 35438e7af..3a87c9ac3 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -23,8 +23,7 @@ import numpy as np from math import ceil, isclose import spikefn -import params_default -from paramrw import usingOngoingInputs, countEvokedInputs, usingEvokedInputs, ExpParams +from paramrw import usingOngoingInputs, countEvokedInputs, usingEvokedInputs from paramrw import chunk_evinputs, get_inputs, trans_input, validate_param_file from simdat import SIMCanvas, getinputfiles, updatedat from gutils import setscalegeom, lowresdisplay, setscalegeomcenter, getmplDPI, getscreengeom @@ -2649,7 +2648,7 @@ def initExtra (self): self.addtransvar('NumCores','Number Cores') self.ltabs[0].layout.addRow('NumCores',self.dqextra['NumCores']) - self.spec_map_cb = None + self.spec_cmap_cb = None self.cmaps = ['jet', 'viridis', @@ -2658,20 +2657,11 @@ def initExtra (self): 'magma', 'cividis'] - # get default spec_cmap - p_exp = ExpParams(paramf, 0) - if len(p_exp.expmt_groups) > 0: - expmt_group = p_exp.expmt_groups[0] - else: - expmt_group = None - p = p_exp.return_pdict(expmt_group, 0) - self.spec_cmap = p['spec_cmap'] - - self.spec_map_cb = QComboBox() + self.spec_cmap_cb = QComboBox() for cmap in self.cmaps: - self.spec_map_cb.addItem(cmap) - self.spec_map_cb.currentIndexChanged.connect(self.selectionchange) - self.ltabs[1].layout.addRow(self.transvar('spec_cmap'),self.spec_map_cb) + self.spec_cmap_cb.addItem(cmap) + self.spec_cmap_cb.currentIndexChanged.connect(self.selectionchange) + self.ltabs[1].layout.addRow(self.transvar('spec_cmap'),self.spec_cmap_cb) def getntrial (self): ntrial = int(self.dqline['N_trials'].text().strip()) @@ -2694,12 +2684,21 @@ def setfromdin (self,din): # number of cores may have changed if the configured number failed self.dqextra['NumCores'].setText(str(defncore)) + + # update ordered dict of QLineEdit objects with new parameters for k,v in din.items(): if k in self.dqline: self.dqline[k].setText(str(v).strip()) elif k == 'spec_cmap': self.spec_cmap = v - self.spec_map_cb.setCurrentIndex(self.cmaps.index(self.spec_cmap)) + + # for spec_cmap we want the user to be able to change (e.g. 'viridis'), but the + # default is 'jet' to be consistent with prior publications on HNN + if 'spec_cmap' not in din: + self.spec_cmap = 'jet' + + # update the spec_cmap dropdown menu + self.spec_cmap_cb.setCurrentIndex(self.cmaps.index(self.spec_cmap)) def __str__ (self): s = '' @@ -3126,7 +3125,6 @@ def setpoisparam (self): bringwintotop(self.poisparamwin) def settonicparam (self): bringwintotop(self.tonicparamwin) def initUI (self): - global params grid = QGridLayout() grid.setSpacing(10) @@ -3516,7 +3514,6 @@ def showHelpDialog (self): def showSomaVPlot (self): # start the somatic voltage visualization process (separate window) - global basedir if not float(self.baseparamwin.runparamwin.getval('save_vsoma')): smsg='In order to view somatic voltages you must first rerun the simulation with saving somatic voltages. To do so from the main GUI, click on Set Parameters -> Run -> Analysis -> Save Somatic Voltages, enter a 1 and then rerun the simulation.' msg = QMessageBox() @@ -3526,28 +3523,22 @@ def showSomaVPlot (self): msg.setStandardButtons(QMessageBox.Ok) msg.exec_() else: - lcmd = [getPyComm(), 'visvolt.py',paramf] + outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') + lcmd = [getPyComm(), 'visvolt.py',outparamf] if debug: print('visvolt cmd:',lcmd) Popen(lcmd) # nonblocking def showPSDPlot (self): # start the PSD visualization process (separate window) - global basedir - lcmd = [getPyComm(), 'vispsd.py',paramf] + outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') + lcmd = [getPyComm(), 'vispsd.py',outparamf] if debug: print('vispsd cmd:',lcmd) Popen(lcmd) # nonblocking - def showLFPPlot (self): - # start the LFP visualization process (separate window) - global basedir - lcmd = [getPyComm(), 'vislfp.py',paramf] - if debug: print('vislfp cmd:',lcmd) - Popen(lcmd) # nonblocking - def showSpecPlot (self): # start the spectrogram visualization process (separate window) - global basedir - lcmd = [getPyComm(), 'visspec.py',paramf] + outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') + lcmd = [getPyComm(), 'visspec.py',outparamf] if debug: print('visspec cmd:',lcmd) Popen(lcmd) # nonblocking @@ -3557,7 +3548,8 @@ def showRasterPlot (self): spikefile = os.path.join(basedir,'spk.txt') if os.path.isfile(spikefile): - lcmd = [getPyComm(), 'visrast.py',paramf,spikefile] + outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') + lcmd = [getPyComm(), 'visrast.py',outparamf,spikefile] else: QMessageBox.information(self, "HNN", "WARNING: no spiking data at %s" % spikefile) return @@ -3572,7 +3564,8 @@ def showDipolePlot (self): dipole_file = os.path.join(basedir,'dpl.txt') if os.path.isfile(dipole_file): - lcmd = [getPyComm(), 'visdipole.py',paramf,dipole_file] + outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') + lcmd = [getPyComm(), 'visdipole.py',outparamf,dipole_file] else: QMessageBox.information(self, "HNN", "WARNING: no dipole data at %s" % dipole_file) return diff --git a/simdat.py b/simdat.py index 62c8c0a4a..53d2fc6d0 100644 --- a/simdat.py +++ b/simdat.py @@ -10,7 +10,7 @@ from conf import dconf import conf import spikefn -from paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs, usingTonicInputs, countEvokedInputs, ExpParams +from paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs, usingTonicInputs, countEvokedInputs from scipy import signal from gutils import getscreengeom import traceback @@ -696,16 +696,9 @@ def plotsimdat (self): if DrawSpec: # if debug: print('ylim is : ', np.amin(ddat['dpl'][sidx:eidx,1]),np.amax(ddat['dpl'][sidx:eidx,1])) - p_exp = ExpParams(self.paramf, debug=debug) - if len(p_exp.expmt_groups) > 0: - expmt_group = p_exp.expmt_groups[0] - else: - expmt_group = None - p = p_exp.return_pdict(expmt_group, 0) - gRow = 6 self.axspec = self.figure.add_subplot(self.G[gRow:10,0]); # specgram - cax = self.axspec.imshow(ds['TFR'],extent=(ds['time'][0],ds['time'][-1],ds['freq'][-1],ds['freq'][0]),aspect='auto',origin='upper',cmap=plt.get_cmap(p['spec_cmap'])) + cax = self.axspec.imshow(ds['TFR'],extent=(ds['time'][0],ds['time'][-1],ds['freq'][-1],ds['freq'][0]),aspect='auto',origin='upper',cmap=plt.get_cmap(self.params['spec_cmap'])) self.axspec.set_ylabel('Frequency (Hz)',fontsize=dconf['fontsize']) self.axspec.set_xlabel('Time (ms)',fontsize=dconf['fontsize']) self.axspec.set_xlim(xl) diff --git a/specfn.py b/specfn.py index 3d4e307b0..9fedfd8c7 100644 --- a/specfn.py +++ b/specfn.py @@ -64,6 +64,7 @@ def __init__(self, tvec, tsvec, f_max=None, p_dict=None, tmin = 50.0, f_min = 1. # externally callable save function def save(self, fdata_spec): + raise DeprecationWarning write(fdata_spec, self.timevec, self.freqvec, self.TFR) # plots spec to axis @@ -146,6 +147,7 @@ def __lnr50(self, s): A sinusoid with these characterisitics is then subtracted from the signal. s: signal """ + raise DeprecationWarning fNoise = 50. tv = np.arange(0,len(s)) / self.fs @@ -204,6 +206,8 @@ def __energyvec(self, f, s): # functions on the aggregate spec data class Spec(): def __init__(self, fspec, spec_cmap='jet', dtype='dpl'): + raise DeprecationWarning + # save dtype self.dtype = dtype @@ -529,15 +533,15 @@ def read(fdata_spec, type='dpl'): # Kernel for spec analysis of dipole data # necessary for parallelization -def spec_dpl_kernel(params, dpl, fspec, f_max): +def spec_dpl_kernel(params, dpl, fspec, opts): # Do the conversion prior to generating these spec # dpl.convert_fAm_to_nAm() # Generate various spec results - spec_agg = MorletSpec(dpl.times, dpl.data['agg'], f_max, p_dict=params) - spec_L2 = MorletSpec(dpl.times, dpl.data['L2'], f_max, p_dict=params) - spec_L5 = MorletSpec(dpl.times, dpl.data['L5'], f_max, p_dict=params) + spec_agg = MorletSpec(dpl.times, dpl.data['agg'], opts['f_max'], p_dict=params) + spec_L2 = MorletSpec(dpl.times, dpl.data['L2'], opts['f_max'], p_dict=params) + spec_L5 = MorletSpec(dpl.times, dpl.data['L5'], opts['f_max'], p_dict=params) # Get max spectral power data # for now, only doing this for agg @@ -558,4 +562,4 @@ def analysis_simp (opts, params, fdpl, fspec): if opts: for key, val in opts.items(): if key in opts_run.keys(): opts_run[key] = val - spec_dpl_kernel(params, fdpl, fspec, opts_run['f_max']) + spec_dpl_kernel(params, fdpl, fspec, opts_run) diff --git a/visdipole.py b/visdipole.py index ca6f6948c..198448cc6 100644 --- a/visdipole.py +++ b/visdipole.py @@ -136,5 +136,5 @@ def plot (self): if __name__ == '__main__': app = QApplication(sys.argv) - ex = DataViewGUI(DipoleCanvas,paramf,ntrial,'Dipole Viewer') + ex = DataViewGUI(DipoleCanvas,params,'Dipole Viewer') sys.exit(app.exec_()) diff --git a/vispsd.py b/vispsd.py index e8345c137..e943ba24f 100644 --- a/vispsd.py +++ b/vispsd.py @@ -24,26 +24,7 @@ if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] else: dconf['fontsize'] = 10 -ntrial = 1; specpath = ''; paramf = '' -for i in range(len(sys.argv)): - if sys.argv[i].endswith('.txt'): - specpath = sys.argv[i] - elif sys.argv[i].endswith('.param'): - paramf = sys.argv[i] - params = read_params(paramf) - ntrial = params['N_trials'] - -basedir = os.path.join(dconf['datdir'],params['sim_prefix']) -print('basedir:',basedir) - ddat = {} -try: - specpath = os.path.join(basedir,'rawspec.npz') - print('specpath',specpath) - ddat['spec'] = np.load(specpath) -except: - print('Could not load',specpath) - quit() # assumes column 0 is time, rest of columns are time-series def extractpsd (dat, fmax=120.0): @@ -100,7 +81,7 @@ def drawpsd (self, dspec, fig, G, ltextra=''): ax = fig.add_subplot(gdx) lax.append(ax) - if i == 2: ax.set_xlabel('Frequency (Hz)'); + if i == 2: ax.set_xlabel('Frequency (Hz)') ax.plot(dspec[lkF[i]],np.mean(dspec[lkS[i]],axis=1),color='w',linewidth=self.gui.linewidth+2) avg = ddat['avg'+str(i)] @@ -131,10 +112,10 @@ def clearaxes (self): def clearlextdatobj (self): if hasattr(self,'lextdatobj'): for o in self.lextdatobj: - try: - o.set_visible(False) - except: - o[0].set_visible(False) + # try: + o.set_visible(False) + # except: + # o[0].set_visible(False) del self.lextdatobj def plotextdat (self, lF, lextpsd, lextfiles): # plot 'external' data (e.g. from experiment/other simulation) @@ -150,7 +131,7 @@ def plotextdat (self, lF, lextpsd, lextfiles): # plot 'external' data (e.g. from yl = ax.get_ylim() cmap=plt.get_cmap('nipy_spectral') - csm = plt.cm.ScalarMappable(cmap=cmap); + csm = plt.cm.ScalarMappable(cmap=cmap) csm.set_clim((0,100)) for f,lpsd,fname in zip(lF,lextpsd,lextfiles): @@ -169,25 +150,20 @@ def plotextdat (self, lF, lextpsd, lextfiles): # plot 'external' data (e.g. from self.lextdatobj.append(ax.legend(handles=self.lpatch)) def plot (self): - global params - - #self.clearaxes() - #plt.close(self.figure) if self.index == 0: self.lax = self.drawpsd(ddat['spec'],self.figure, self.G, ltextra='All Trials') else: - specpathtrial = os.path.join(dconf['datdir'], params['sim_prefix'],'rawspec_'+str(self.index)+'.npz') if 'spec'+str(self.index) not in ddat: ddat['spec'+str(self.index)] = np.load(specpath) - self.lax=self.drawpsd(ddat['spec'+str(self.index)],self.figure, self.G, ltextra='Trial '+str(self.index)); + self.lax=self.drawpsd(ddat['spec'+str(self.index)],self.figure, self.G, ltextra='Trial '+str(self.index)) self.figure.subplots_adjust(bottom=0.06, left=0.06, right=0.98, top=0.97, wspace=0.1, hspace=0.09) self.draw() class PSDViewGUI (DataViewGUI): - def __init__ (self,CanvasType,paramf,ntrial,title): - super(PSDViewGUI,self).__init__(CanvasType,paramf,ntrial,title) + def __init__ (self,CanvasType,params,title): + super(PSDViewGUI,self).__init__(CanvasType,params,title) self.addLoadDataActions() self.lF = [] # frequencies associated with external data psd self.lextpsd = [] # external data psd @@ -215,35 +191,26 @@ def addLoadDataActions (self): def loadDisplayData (self): extdataf,dat = self.loadDataFileDialog() if not extdataf: return - try: - f, lpsd = extractpsd(dat) - self.printStat('Extracted PSDs from ' + extdataf) - self.lextpsd.append(lpsd) - self.lextfiles.append(extdataf) - self.lF.append(f) - except: - self.printStat('Could not extract PSDs from ' + extdataf) - - try: - if len(self.lextpsd) > 0: - self.printStat('Plotting ext data PSDs.') - self.m.plotextdat(self.lF,self.lextpsd,self.lextfiles) - self.m.draw() # make sure new lines show up in plot - self.printStat('') - except: - self.printStat('Could not plot data from ' + extdataf) + f, lpsd = extractpsd(dat) + self.printStat('Extracted PSDs from ' + extdataf) + self.lextpsd.append(lpsd) + self.lextfiles.append(extdataf) + self.lF.append(f) + + if len(self.lextpsd) > 0: + self.printStat('Plotting ext data PSDs.') + self.m.plotextdat(self.lF,self.lextpsd,self.lextfiles) + self.m.draw() # make sure new lines show up in plot + self.printStat('') def loadDataFileDialog (self): fn = QFileDialog.getOpenFileName(self, 'Open file', 'data') if fn[0]: - try: - extdataf = os.path.abspath(fn[0]) # data file - dat = np.loadtxt(extdataf) - self.printStat('Loaded data in ' + extdataf + '. Extracting PSDs.') - return extdataf,dat - except: - self.printStat('Could not load data in ' + fn[0]) - return None,None + extdataf = os.path.abspath(fn[0]) # data file + dat = np.loadtxt(extdataf) + self.printStat('Loaded data in ' + extdataf + '. Extracting PSDs.') + return extdataf,dat + return None,None def clearDataFile (self): @@ -255,7 +222,17 @@ def clearDataFile (self): if __name__ == '__main__': + for i in range(len(sys.argv)): + if sys.argv[i].endswith('.param'): + paramf = sys.argv[i] + params = read_params(paramf) + ntrial = params['N_trials'] + + basedir = os.path.join(dconf['datdir'],params['sim_prefix']) + specpath = os.path.join(basedir,'rawspec.npz') + print('specpath',specpath) + ddat['spec'] = np.load(specpath) + app = QApplication(sys.argv) - ex = PSDViewGUI(PSDCanvas,paramf,ntrial,'PSD Viewer') + ex = PSDViewGUI(PSDCanvas,params,'PSD Viewer') sys.exit(app.exec_()) - diff --git a/visrast.py b/visrast.py index 3f5e7acd7..295d0cda0 100644 --- a/visrast.py +++ b/visrast.py @@ -12,7 +12,8 @@ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure -import pylab as plt, convolve +import pylab as plt +from pylab import convolve from numpy import hamming import matplotlib.gridspec as gridspec import paramrw diff --git a/visspec.py b/visspec.py index 653613eb7..541ab7bb4 100644 --- a/visspec.py +++ b/visspec.py @@ -25,18 +25,6 @@ if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] -ntrial = 1; paramf = '' -for i in range(len(sys.argv)): - if sys.argv[i].endswith('.txt'): - specpath = sys.argv[i] - elif sys.argv[i].endswith('.param'): - paramf = sys.argv[i] - params = read_params(paramf) - ntrial = params['N_trials'] - -basedir = os.path.join(dconf['datdir'],params['sim_prefix']) -#print('basedir:',basedir,'paramf:',paramf,'ntrial:',ntrial) - # assumes column 0 is time, rest of columns are time-series def extractspec (dat, fmax=40.0): global ntrial @@ -71,40 +59,38 @@ def extractspec (dat, fmax=40.0): return ms.f, lspec, avgdipole, avgspec -def loaddat (fname): - try: - if fname.endswith('.txt'): - dat = np.loadtxt(fname) - print('Loaded data in ' + fname + '. Extracting Spectrograms.') - return dat - elif fname.endswith('.param'): - ntrial = params['N_trials'] - basedir = os.path.join(dconf['datdir'],params['sim_prefix']) - - if ntrial > 1: - ddat = readdpltrials(basedir) - #print('read dpl trials',ddat[0].shape) - dout = np.zeros((ddat[0].shape[0],1+ntrial)) - #print('set dout shape',dout.shape) - dout[:,0] = ddat[0][:,0] - for i in range(ntrial): - dout[:,i+1] = ddat[i][:,1] - return dout - else: - ddat = np.loadtxt(os.path.join(basedir,'dpl.txt')) - #print('ddat.shape:',ddat.shape) - dout = np.zeros((ddat.shape[0],2)) - #print('dout.shape:',dout.shape) - dout[:,0] = ddat[:,0] - dout[:,1] = ddat[:,1] - return dout - except: - print('Could not load data in ' + fname) - return None +def loaddat_txt (fname): + dat = np.loadtxt(fname) + print('Loaded data in ' + fname + '. Extracting Spectrograms.') + return dat + +def loaddat (sim_prefix, ntrial): + basedir = os.path.join(dconf['datdir'], sim_prefix) + if ntrial > 1: + ddat = readdpltrials(basedir) + #print('read dpl trials',ddat[0].shape) + dout = np.zeros((ddat[0].shape[0],1+ntrial)) + #print('set dout shape',dout.shape) + dout[:,0] = ddat[0][:,0] + for i in range(ntrial): + dout[:,i+1] = ddat[i][:,1] + return dout + else: + ddat = np.loadtxt(os.path.join(basedir,'dpl.txt')) + #print('ddat.shape:',ddat.shape) + dout = np.zeros((ddat.shape[0],2)) + #print('dout.shape:',dout.shape) + dout[:,0] = ddat[:,0] + dout[:,1] = ddat[:,1] + return dout + # except: + # print('Could not load data in ' + fname) + # return None return None class SpecCanvas (FigureCanvas): - def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, title='Spectrogram Viewer'): + def __init__ (self, parama, index, parent=None, width=12, height=10, dpi=120, title='Spectrogram Viewer'): + FigureCanvas.__init__(self, Figure(figsize=(width, height), dpi=dpi)) self.title = title self.setParent(parent) @@ -112,7 +98,7 @@ def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, ti self.index = index FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) - self.paramf = paramf + self.params = params self.invertedhistax = False self.G = gridspec.GridSpec(10,1) self.dat = [] @@ -121,32 +107,29 @@ def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, ti self.avgdipole = [] self.avgspec = [] - # get spec_cmap - p_exp = paramrw.ExpParams(self.paramf, 0) - if len(p_exp.expmt_groups) > 0: - expmt_group = p_exp.expmt_groups[0] + if 'spec_cmap' in self.params: + self.spec_cmap = self.params['spec_cmap'] else: - expmt_group = None - p = p_exp.return_pdict(expmt_group, 0) - self.spec_cmap = p['spec_cmap'] + # default to jet, but allow user to change in param file + self.spec_cmap = 'jet' self.plot() def clearaxes (self): - try: - for ax in self.lax: - ax.set_yticks([]) - ax.cla() - except: - pass + # try: + for ax in self.lax: + ax.set_yticks([]) + ax.cla() + # except: + # pass def clearlextdatobj (self): if hasattr(self,'lextdatobj'): for o in self.lextdatobj: - try: - o.set_visible(False) - except: - o[0].set_visible(False) + # try: + o.set_visible(False) + # except: + # o[0].set_visible(False) del self.lextdatobj def drawspec (self, dat, lspec, sdx, avgdipole, avgspec, fig, G, ltextra=''): @@ -159,8 +142,8 @@ def drawspec (self, dat, lspec, sdx, avgdipole, avgspec, fig, G, ltextra=''): ax = fig.add_subplot(gdx) lax = [ax] tvec = dat[:,0] - dt = tvec[1] - tvec[0] - tstop = tvec[-1] + # dt = tvec[1] - tvec[0] + # tstop = tvec[-1] if sdx == 0: for i in range(1,dat.shape[1],1): @@ -187,7 +170,7 @@ def drawspec (self, dat, lspec, sdx, avgdipole, avgspec, fig, G, ltextra=''): ax.set_xlim(tvec[0],tvec[-1]) ax.set_xlabel('Time (ms)') - ax.set_ylabel('Frequency (Hz)'); + ax.set_ylabel('Frequency (Hz)') lax.append(ax) @@ -201,18 +184,17 @@ def plot (self): self.draw() class SpecViewGUI (DataViewGUI): - def __init__ (self,CanvasType,paramf,ntrial,title): + def __init__ (self,CanvasType,params,title): self.lF = [] # frequencies associated with external data spec self.lextspec = [] # external data spec - self.lextfiles = [] # external data files + # self.lextfiles = [] # external data files self.dat = None self.avgdipole = [] self.avgspec = [] - super(SpecViewGUI,self).__init__(CanvasType,paramf,ntrial,title) + self.params = params + super(SpecViewGUI,self).__init__(CanvasType,self.params,title) self.addLoadDataActions() - #print('paramf:',paramf) - if len(paramf): - self.loadDisplayData(paramf) + self.loadDisplayData(params) if "TRAVIS_TESTING" in os.environ and os.environ["TRAVIS_TESTING"] == "1": print("Exiting gracefully with TRAVIS_TESTING=1") @@ -240,53 +222,51 @@ def addLoadDataActions (self): self.fileMenu.addAction(loadDataFile) self.fileMenu.addAction(clearDataFileAct) - def loadDisplayData (self, fname=None): - if fname is None or fname is False: + def loadDisplayData (self, params=None): + if params is None or params is False: fname = QFileDialog.getOpenFileName(self, 'Open .param or .txt file', 'data') fname = os.path.abspath(fname[0]) - if not fname: return - dat = loaddat(fname) + dat = loaddat_txt(fname) + else: + dat = loaddat(self.params['sim_prefix'], self.params['N_trials']) self.dat = dat - try: - try: - fmax = params['f_max_spec'] - except: - fmax = 40. - f, lspec, avgdipole, avgspec = extractspec(dat,fmax=fmax) - self.ntrial = len(lspec) - self.updateCB() - self.printStat('Extracted ' + str(len(lspec)) + ' spectrograms from ' + fname) - self.lextspec = lspec - self.lextfiles.append(fname) - self.avgdipole = avgdipole - self.avgspec = avgspec - self.lF.append(f) - except: - self.printStat('Could not extract Spectrograms from ' + fname) - - try: - if len(self.lextspec) > 0: - self.printStat('Plotting Spectrograms.') - self.m.lextspec = self.lextspec - self.m.dat = self.dat - self.m.avgspec = self.avgspec - self.m.avgdipole = self.avgdipole - self.m.plot() - self.m.draw() # make sure new lines show up in plot - self.printStat('') - except: - self.printStat('Could not plot data from ' + fname) + + fmax = self.params['f_max_spec'] + + f, lspec, avgdipole, avgspec = extractspec(dat,fmax=fmax) + self.ntrial = len(lspec) + self.updateCB() + self.printStat('Extracted ' + str(len(lspec)) + ' spectrograms for ' + self.params['sim_prefix']) + self.lextspec = lspec + # self.lextfiles.append(fname) + self.avgdipole = avgdipole + self.avgspec = avgspec + self.lF.append(f) + + if len(self.lextspec) > 0: + self.printStat('Plotting Spectrograms.') + self.m.lextspec = self.lextspec + self.m.dat = self.dat + self.m.avgspec = self.avgspec + self.m.avgdipole = self.avgdipole + self.m.plot() + self.m.draw() # make sure new lines show up in plot + self.printStat('') def clearDataFile (self): self.m.clearlextdatobj() self.lextspec = [] - self.lextfiles = [] self.lF = [] self.m.draw() if __name__ == '__main__': + for i in range(len(sys.argv)): + if sys.argv[i].endswith('.param'): + paramf = sys.argv[i] + params = read_params(paramf) + app = QApplication(sys.argv) - ex = SpecViewGUI(SpecCanvas,paramf,ntrial,'Spectrogram Viewer') + ex = SpecViewGUI(SpecCanvas,params,'Spectrogram Viewer') sys.exit(app.exec_()) From e6eeef6a4e6f391f0ba537c6a0f2b49a6e9ce14a Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Fri, 18 Sep 2020 23:08:42 -0400 Subject: [PATCH 014/107] MAINT: add back morphology.py --- morphology.py | 624 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 624 insertions(+) create mode 100644 morphology.py diff --git a/morphology.py b/morphology.py new file mode 100644 index 000000000..afe9ddb51 --- /dev/null +++ b/morphology.py @@ -0,0 +1,624 @@ +# from https://github.com/ahwillia/PyNeuron-Toolbox 4/2017 +# adjusted for python3 +from __future__ import division +import numpy as np +import pylab as plt +from matplotlib.pyplot import cm +import string +from neuron import h +import numbers + +# a helper library, included with NEURON +h.load_file('stdlib.hoc') +h.load_file('import3d.hoc') + +class Cell: + def __init__(self,name='neuron',soma=None,apic=None,dend=None,axon=None): + self.soma = soma if soma is not None else [] + self.apic = apic if apic is not None else [] + self.dend = dend if dend is not None else [] + self.axon = axon if axon is not None else [] + self.all = self.soma + self.apic + self.dend + self.axon + + def delete(self): + self.soma = None + self.apic = None + self.dend = None + self.axon = None + self.all = None + + def __str__(self): + return self.name + +def load(filename, fileformat=None, cell=None, use_axon=True, xshift=0, yshift=0, zshift=0): + """ + Load an SWC from filename and instantiate inside cell. Code kindly provided + by @ramcdougal. + + Args: + filename = .swc file containing morphology + cell = Cell() object. (Default: None, creates new object) + filename = the filename of the SWC file + use_axon = include the axon? Default: True (yes) + xshift, yshift, zshift = use to position the cell + + Returns: + Cell() object with populated soma, axon, dend, & apic fields + + Minimal example: + # pull the morphology for the demo from NeuroMorpho.Org + from PyNeuronToolbox import neuromorphoorg + with open('c91662.swc', 'w') as f: + f.write(neuromorphoorg.morphology('c91662')) + cell = load_swc(filename) + + """ + + if cell is None: + cell = Cell(name=string.join(filename.split('.')[:-1])) + + if fileformat is None: + fileformat = filename.split('.')[-1] + + name_form = {1: 'soma[%d]', 2: 'axon[%d]', 3: 'dend[%d]', 4: 'apic[%d]'} + + # load the data. Use Import3d_SWC_read for swc, Import3d_Neurolucida3 for + # Neurolucida V3, Import3d_MorphML for MorphML (level 1 of NeuroML), or + # Import3d_Eutectic_read for Eutectic. + if fileformat == 'swc': + morph = h.Import3d_SWC_read() + elif fileformat == 'asc': + morph = h.Import3d_Neurolucida3() + else: + raise Exception('file format `%s` not recognized'%(fileformat)) + morph.input(filename) + + # easiest to instantiate by passing the loaded morphology to the Import3d_GUI + # tool; with a second argument of 0, it won't display the GUI, but it will allow + # use of the GUI's features + i3d = h.Import3d_GUI(morph, 0) + + # get a list of the swc section objects + swc_secs = i3d.swc.sections + swc_secs = [swc_secs.object(i) for i in range(int(swc_secs.count()))] + + # initialize the lists of sections + sec_list = {1: cell.soma, 2: cell.axon, 3: cell.dend, 4: cell.apic} + + # name and create the sections + real_secs = {} + for swc_sec in swc_secs: + cell_part = int(swc_sec.type) + + # skip everything else if it's an axon and we're not supposed to + # use it... or if is_subsidiary + if (not(use_axon) and cell_part == 2) or swc_sec.is_subsidiary: + continue + + # figure out the name of the new section + if cell_part not in name_form: + raise Exception('unsupported point type') + name = name_form[cell_part] % len(sec_list[cell_part]) + + # create the section + sec = h.Section(name=name) + + # connect to parent, if any + if swc_sec.parentsec is not None: + sec.connect(real_secs[swc_sec.parentsec.hname()](swc_sec.parentx)) + + # define shape + if swc_sec.first == 1: + h.pt3dstyle(1, swc_sec.raw.getval(0, 0), swc_sec.raw.getval(1, 0), + swc_sec.raw.getval(2, 0), sec=sec) + + j = swc_sec.first + xx, yy, zz = [swc_sec.raw.getrow(i).c(j) for i in range(3)] + dd = swc_sec.d.c(j) + if swc_sec.iscontour_: + # never happens in SWC files, but can happen in other formats supported + # by NEURON's Import3D GUI + raise Exception('Unsupported section style: contour') + + if dd.size() == 1: + # single point soma; treat as sphere + x, y, z, d = [dim.x[0] for dim in [xx, yy, zz, dd]] + for xprime in [x - d / 2., x, x + d / 2.]: + h.pt3dadd(xprime + xshift, y + yshift, z + zshift, d, sec=sec) + else: + for x, y, z, d in zip(xx, yy, zz, dd): + h.pt3dadd(x + xshift, y + yshift, z + zshift, d, sec=sec) + + # store the section in the appropriate list in the cell and lookup table + sec_list[cell_part].append(sec) + real_secs[swc_sec.hname()] = sec + + cell.all = cell.soma + cell.apic + cell.dend + cell.axon + return cell + +def sequential_spherical(xyz): + """ + Converts sequence of cartesian coordinates into a sequence of + line segments defined by spherical coordinates. + + Args: + xyz = 2d numpy array, each row specifies a point in + cartesian coordinates (x,y,z) tracing out a + path in 3D space. + + Returns: + r = lengths of each line segment (1D array) + theta = angles of line segments in XY plane (1D array) + phi = angles of line segments down from Z axis (1D array) + """ + d_xyz = np.diff(xyz,axis=0) + + r = np.linalg.norm(d_xyz,axis=1) + theta = np.arctan2(d_xyz[:,1], d_xyz[:,0]) + hyp = d_xyz[:,0]**2 + d_xyz[:,1]**2 + phi = np.arctan2(np.sqrt(hyp), d_xyz[:,2]) + + return (r,theta,phi) + +def spherical_to_cartesian(r,theta,phi): + """ + Simple conversion of spherical to cartesian coordinates + + Args: + r,theta,phi = scalar spherical coordinates + + Returns: + x,y,z = scalar cartesian coordinates + """ + x = r * np.sin(phi) * np.cos(theta) + y = r * np.sin(phi) * np.sin(theta) + z = r * np.cos(phi) + return (x,y,z) + +def find_coord(targ_length,xyz,rcum,theta,phi): + """ + Find (x,y,z) ending coordinate of segment path along section + path. + + Args: + targ_length = scalar specifying length of segment path, starting + from the begining of the section path + xyz = coordinates specifying the section path + rcum = cumulative sum of section path length at each node in xyz + theta, phi = angles between each coordinate in xyz + """ + # [1] Find spherical coordinates for the line segment containing + # the endpoint. + # [2] Find endpoint in spherical coords and convert to cartesian + i = np.nonzero(rcum <= targ_length)[0][-1] + if i == len(theta): + return xyz[-1,:] + else: + r_lcl = targ_length-rcum[i] # remaining length along line segment + (dx,dy,dz) = spherical_to_cartesian(r_lcl,theta[i],phi[i]) + return xyz[i,:] + [dx,dy,dz] + +def interpolate_jagged(xyz,nseg): + """ + Interpolates along a jagged path in 3D + + Args: + xyz = section path specified in cartesian coordinates + nseg = number of segment paths in section path + + Returns: + interp_xyz = interpolated path + """ + + # Spherical coordinates specifying the angles of all line + # segments that make up the section path + (r,theta,phi) = sequential_spherical(xyz) + + # cumulative length of section path at each coordinate + rcum = np.append(0,np.cumsum(r)) + + # breakpoints for segment paths along section path + breakpoints = np.linspace(0,rcum[-1],nseg+1) + np.delete(breakpoints,0) + + # Find segment paths + seg_paths = [] + for a in range(nseg): + path = [] + + # find (x,y,z) starting coordinate of path + if a == 0: + start_coord = xyz[0,:] + else: + start_coord = end_coord # start at end of last path + path.append(start_coord) + + # find all coordinates between the start and end points + start_length = breakpoints[a] + end_length = breakpoints[a+1] + mid_boolean = (rcum > start_length) & (rcum < end_length) + mid_indices = np.nonzero(mid_boolean)[0] + for mi in mid_indices: + path.append(xyz[mi,:]) + + # find (x,y,z) ending coordinate of path + end_coord = find_coord(end_length,xyz,rcum,theta,phi) + path.append(end_coord) + + # Append path to list of segment paths + seg_paths.append(np.array(path)) + + # Return all segment paths + return seg_paths + +def get_section_path(h,sec): + n3d = int(h.n3d(sec=sec)) + xyz = [] + for i in range(0,n3d): + xyz.append([h.x3d(i,sec=sec),h.y3d(i,sec=sec),h.z3d(i,sec=sec)]) + xyz = np.array(xyz) + return xyz + +def shapeplot(h,ax,sections=None,order='pre',cvals=None,\ + clim=None,cmap=cm.YlOrBr_r,**kwargs): + """ + Plots a 3D shapeplot + + Args: + h = hocObject to interface with neuron + ax = matplotlib axis for plotting + sections = list of h.Section() objects to be plotted + order = { None= use h.allsec() to get sections + 'pre'= pre-order traversal of morphology } + cvals = list/array with values mapped to color by cmap; useful + for displaying voltage, calcium or some other state + variable across the shapeplot. + **kwargs passes on to matplotlib (e.g. color='r' for red lines) + + Returns: + lines = list of line objects making up shapeplot + """ + + # Default is to plot all sections. + if sections is None: + if order == 'pre': + sections = allsec_preorder(h) # Get sections in "pre-order" + else: + sections = list(h.allsec()) + + # Determine color limits + if cvals is not None and clim is None: + cn = [ isinstance(cv, numbers.Number) for cv in cvals ] + if any(cn): + clim = [np.min(cvals[cn]), np.max(cvals[cn])] + + # Plot each segement as a line + lines = [] + i = 0 + for sec in sections: + xyz = get_section_path(h,sec) + seg_paths = interpolate_jagged(xyz,sec.nseg) + + for (j,path) in enumerate(seg_paths): + line, = plt.plot(path[:,0], path[:,1], path[:,2], '-k',**kwargs) + if cvals is not None: + if isinstance(cvals[i], numbers.Number): + # map number to colormap + col = cmap(int((cvals[i]-clim[0])*255/(clim[1]-clim[0]))) + else: + # use input directly. E.g. if user specified color with a string. + col = cvals[i] + line.set_color(col) + lines.append(line) + i += 1 + + return lines + +def getshapecoords (h,sections=None,order='pre',**kwargs): + if sections is None: + if order == 'pre': + sections = allsec_preorder(h) # Get sections in "pre-order" + else: + sections = list(h.allsec()) + i = 0 + lx,ly,lz=[],[],[] + for sec in sections: + xyz = get_section_path(h,sec) + seg_paths = interpolate_jagged(xyz,sec.nseg) + for path in seg_paths: + for i in [0,1]: + lx.append(path[i][0]) + ly.append(path[i][1]) + lz.append(path[i][2]) + return lx,ly,lz + + +def shapeplot_animate(v,lines,nframes=None,tscale='linear',\ + clim=[-80,50],cmap=cm.YlOrBr_r): + """ Returns animate function which updates color of shapeplot """ + if nframes is None: + nframes = v.shape[0] + if tscale == 'linear': + def animate(i): + i_t = int((i/nframes)*v.shape[0]) + for i_seg in range(v.shape[1]): + lines[i_seg].set_color(cmap(int((v[i_t,i_seg]-clim[0])*255/(clim[1]-clim[0])))) + return [] + elif tscale == 'log': + def animate(i): + i_t = int(np.round((v.shape[0] ** (1.0/(nframes-1))) ** i - 1)) + for i_seg in range(v.shape[1]): + lines[i_seg].set_color(cmap(int((v[i_t,i_seg]-clim[0])*255/(clim[1]-clim[0])))) + return [] + else: + raise ValueError("Unrecognized option '%s' for tscale" % tscale) + + return animate + +def mark_locations(h,section,locs,markspec='or',**kwargs): + """ + Marks one or more locations on along a section. Could be used to + mark the location of a recording or electrical stimulation. + + Args: + h = hocObject to interface with neuron + section = reference to section + locs = float between 0 and 1, or array of floats + optional arguments specify details of marker + + Returns: + line = reference to plotted markers + """ + + # get list of cartesian coordinates specifying section path + xyz = get_section_path(h,section) + (r,theta,phi) = sequential_spherical(xyz) + rcum = np.append(0,np.cumsum(r)) + + # convert locs into lengths from the beginning of the path + if type(locs) is float or type(locs) is np.float64: + locs = np.array([locs]) + if type(locs) is list: + locs = np.array(locs) + lengths = locs*rcum[-1] + + # find cartesian coordinates for markers + xyz_marks = [] + for targ_length in lengths: + xyz_marks.append(find_coord(targ_length,xyz,rcum,theta,phi)) + xyz_marks = np.array(xyz_marks) + + # plot markers + line, = plt.plot(xyz_marks[:,0], xyz_marks[:,1], \ + xyz_marks[:,2], markspec, **kwargs) + return line + +def root_sections(h): + """ + Returns a list of all sections that have no parent. + """ + roots = [] + for section in h.allsec(): + sref = h.SectionRef(sec=section) + # has_parent returns a float... cast to bool + if sref.has_parent() < 0.9: + roots.append(section) + return roots + +def leaf_sections(h): + """ + Returns a list of all sections that have no children. + """ + leaves = [] + for section in h.allsec(): + sref = h.SectionRef(sec=section) + # nchild returns a float... cast to bool + if sref.nchild() < 0.9: + leaves.append(section) + return leaves + +def root_indices(sec_list): + """ + Returns the index of all sections without a parent. + """ + roots = [] + for i,section in enumerate(sec_list): + sref = h.SectionRef(sec=section) + # has_parent returns a float... cast to bool + if sref.has_parent() < 0.9: + roots.append(i) + return roots + +def allsec_preorder(h): + """ + Alternative to using h.allsec(). This returns all sections in order from + the root. Traverses the topology each neuron in "pre-order" + """ + #Iterate over all sections, find roots + roots = root_sections(h) + + # Build list of all sections + sec_list = [] + for r in roots: + add_pre(h,sec_list,r) + return sec_list + +def add_pre(h,sec_list,section,order_list=None,branch_order=None): + """ + A helper function that traverses a neuron's morphology (or a sub-tree) + of the morphology in pre-order. This is usually not necessary for the + user to import. + """ + + sec_list.append(section) + sref = h.SectionRef(sec=section) + + if branch_order is not None: + order_list.append(branch_order) + if len(sref.child) > 1: + branch_order += 1 + + for next_node in sref.child: + add_pre(h,sec_list,next_node,order_list,branch_order) + +def dist_between(h,seg1,seg2): + """ + Calculates the distance between two segments. I stole this function from + a post by Michael Hines on the NEURON forum + (www.neuron.yale.edu/phpbb/viewtopic.php?f=2&t=2114) + """ + h.distance(0, seg1.x, sec=seg1.sec) + return h.distance(seg2.x, sec=seg2.sec) + +def all_branch_orders(h): + """ + Produces a list branch orders for each section (following pre-order tree + traversal) + """ + #Iterate over all sections, find roots + roots = [] + for section in h.allsec(): + sref = h.SectionRef(sec=section) + # has_parent returns a float... cast to bool + if sref.has_parent() < 0.9: + roots.append(section) + + # Build list of all sections + order_list = [] + for r in roots: + add_pre(h,[],r,order_list,0) + return order_list + +def branch_order(h,section, path=[]): + """ + Returns the branch order of a section + """ + path.append(section) + sref = h.SectionRef(sec=section) + # has_parent returns a float... cast to bool + if sref.has_parent() < 0.9: + return 0 # section is a root + else: + nchild = len(list(h.SectionRef(sec=sref.parent).child)) + if nchild <= 1.1: + return branch_order(h,sref.parent,path) + else: + return 1+branch_order(h,sref.parent,path) + +def dist_to_mark(h, section, secdict, path=[]): + path.append(section) + sref = h.SectionRef(sec=section) + # print 'current : '+str(section) + # print 'parent : '+str(sref.parent) + if secdict[sref.parent] is None: + # print '-> go to parent' + s = section.L + dist_to_mark(h, sref.parent, secdict, path) + # print 'summing, '+str(s) + return s + else: + # print 'end <- start summing: '+str(section.L) + return section.L # parent is marked + +def branch_precedence(h): + roots = root_sections(h) + leaves = leaf_sections(h) + seclist = allsec_preorder(h) + secdict = { sec:None for sec in seclist } + + for r in roots: + secdict[r] = 0 + + precedence = 1 + while len(leaves)>0: + # build list of distances of all paths to remaining leaves + d = [] + for leaf in leaves: + p = [] + dist = dist_to_mark(h, leaf, secdict, path=p) + d.append((dist,[pp for pp in p])) + + # longest path index + i = np.argmax([ dd[0] for dd in d ]) + leaves.pop(i) # this leaf will be marked + + # mark all sections in longest path + for sec in d[i][1]: + if secdict[sec] is None: + secdict[sec] = precedence + + # increment precedence across iterations + precedence += 1 + + #prec = secdict.values() + #return [0 if p is None else 1 for p in prec], d[i][1] + return [ secdict[sec] for sec in seclist ] + + +from neuron import h +import json + +def parent(sec): + seg = sec.trueparentseg() + if seg is None: + return None + else: + return seg.sec + +def parent_loc(sec, trueparent): + seg = sec.trueparentseg() + if seg is None: + return None + else: + return seg.x + +def morphology_to_dict(sections, outfile=None): + section_map = {sec: i for i, sec in enumerate(sections)} + result = [] + h.define_shape() + + for sec in sections: + my_parent = parent(sec) + my_parent_loc = -1 if my_parent is None else parent_loc(sec, my_parent) + my_parent = -1 if my_parent is None else section_map[my_parent] + n3d = int(h.n3d(sec=sec)) + result.append({ + 'section_orientation': h.section_orientation(sec=sec), + 'parent': my_parent, + 'parent_loc': my_parent_loc, + 'x': [h.x3d(i, sec=sec) for i in range(n3d)], + 'y': [h.y3d(i, sec=sec) for i in range(n3d)], + 'z': [h.z3d(i, sec=sec) for i in range(n3d)], + 'diam': [h.diam3d(i, sec=sec) for i in range(n3d)], + 'name': sec.hname() + }) + + if outfile is not None: + with open(outfile, 'w') as f: + json.dump(result, f) + + return result + + +def load_json(morphfile): + + with open(morphfile, 'r') as f: + secdata = json.load(morphfile) + + seclist = [] + for sd in secdata: + # make section + sec = h.Section(name=sd['name']) + seclist.append(sec) + + + # make 3d morphology + for x,y,z,d in zip(sd['x'], sd['y'], sd['z'], sd('diam')): + h.pt3dadd(x, y, z, d, sec=sec) + + # connect children to parent compartments + for sec,sd in zip(seclist,secdata): + if sd['parent_loc'] >= 0: + parent_sec = sec_list[sd['parent']] + sec.connect(parent_sec(sd['parent_loc']), sd['section_orientation']) + + return seclist From 2f829dc506eaa6261af294c99e67e43f33b59c71 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 09:54:49 -0400 Subject: [PATCH 015/107] MAINT: spikefn.py remove unused function --- spikefn.py | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) diff --git a/spikefn.py b/spikefn.py index 18108f745..cfbe8d25c 100644 --- a/spikefn.py +++ b/spikefn.py @@ -283,55 +283,6 @@ def hist_bin_opt(x, N_trials): bin_opt = bin_opt_list[0] return bin_opt -# "purely" from files, this is the new way to replace the old way -def spikes_from_file(fparam, fspikes): - raise DeprecationWarning - - gid_dict, _ = paramrw.read(fparam) - # cell list - requires cell to start with L2/L5 - src_list = [] - src_extinput_list = [] - src_unique_list = [] - # fill in 2 lists from the keys - for key in gid_dict.keys(): - if key.startswith('L2_') or key.startswith('L5_'): - src_list.append(key) - elif key == 'extinput': - src_extinput_list.append(key) - else: - src_unique_list.append(key) - # check to see if there are spikes in here, otherwise return an empty array - if os.stat(fspikes).st_size: - s = np.loadtxt(open(fspikes, 'rb')) - else: - s = np.array([], dtype='float64') - # get the skeleton s_dict from the cell_list - s_dict = dict.fromkeys(src_list) - # iterate through just the src keys - for key in s_dict.keys(): - # sort of a hack to separate extgauss - s_dict[key] = Spikes(s, gid_dict[key]) - # figure out its extgauss feed - newkey_gauss = 'extgauss_' + key - s_dict[newkey_gauss] = split_extrand(s, gid_dict, key, 'extgauss') - # figure out its extpois feed - newkey_pois = 'extpois_' + key - s_dict[newkey_pois] = split_extrand(s, gid_dict, key, 'extpois') - # do the keys in unique list - for key in src_unique_list: s_dict[key] = Spikes(s, gid_dict[key]) - # Deal with alpha feeds (extinputs) - # order guaranteed by order of inputs in p_ext in paramrw - # and by details of gid creation in class_net - # A little kludgy to deal with the fact that one might not exist - if len(gid_dict['extinput']) > 1: - s_dict['alpha_feed_prox'] = Spikes(s, [gid_dict['extinput'][0]]) - s_dict['alpha_feed_dist'] = Spikes(s, [gid_dict['extinput'][1]]) - else: - # not sure why this is done here - # handle the extinput: this is a LIST! - s_dict['extinput'] = [Spikes(s, [gid]) for gid in gid_dict['extinput']] - return s_dict - # from the supplied key name, return a marker style def get_markerstyle(key): markerstyle = '' From 46c04d73aee1112b8f092f8d5d1ef1f12c54fd57 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 09:55:36 -0400 Subject: [PATCH 016/107] MAINT: function to write legacy param file --- paramrw.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/paramrw.py b/paramrw.py index b277bd5a9..9545ed52f 100644 --- a/paramrw.py +++ b/paramrw.py @@ -173,10 +173,28 @@ def read_gids_param (fparam): return gid_dict # write the params to a filename +def write_legacy_paramf(fparam, p): + """ now sorting + """ + + p_keys = [key for key, val in p.items()] + p_sorted = [(key, p[key]) for key in p_keys] + with open(fparam, 'w') as f: + pstring = '%26s: ' + # do the params in p_sorted + for param in p_sorted: + key, val = param + f.write(pstring % key) + if key.startswith('N_'): + f.write('%i\n' % val) + else: + f.write(str(val)+'\n') + + def write_gids_param(fparam, gid_list): with open(fparam, 'w') as f: pstring = '%26s: ' - # write the gid info first + # write the gid info for key in gid_list.keys(): f.write(pstring % key) if len(gid_list[key]): From 902457a1908215b5689f39549ebfa5f6e0be8675 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 09:56:20 -0400 Subject: [PATCH 017/107] MAINT: simplify simdat.lsimdat and simdat.optdat Make these a list of dictionaries instead of a list of tuples --- simdat.py | 53 ++++++++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/simdat.py b/simdat.py index 53d2fc6d0..9213c2bd5 100644 --- a/simdat.py +++ b/simdat.py @@ -30,27 +30,27 @@ lsimidx = 0 # index into lsimdat initial_ddat = {} -optdat = [] # list of optimization data +optdat = [] # single optimization run -def updatelsimdat(paramf,dpl): +def updatelsimdat(paramf, params, dpl): # update lsimdat with paramf and dipole dpl # but if the specific sim already run put dipole at that location in list - global lsimdat,lsimidx - # while len(lsimdat)>0 and lsimidx!=len(lsimdat)-1: lsimdat.pop() # redos popped - found = False - for i,l in enumerate(lsimdat): - if l[0] == paramf: - lsimdat[i][1] = dpl - found = True - break - if not found: lsimdat.append([paramf,dpl]) # if not found, append to end of the list - lsimidx = len(lsimdat) - 1 # current simulation index + global lsimdat, lsimidx + + for idx, sim in enumerate(lsimdat): + if paramf in sim['paramfn']: + lsimdat[idx]['params'] = params + lsimdat[idx]['dpl'] = dpl + lsimidx = idx + return + lsimdat.append({'paramfn': paramf, 'params': params, 'dpl': dpl}) # if not found, append to end of the list + lsimidx = len(lsimdat) - 1 -def updateoptdat(paramf,dpl): +def updateoptdat(paramfn, params, dpl): global optdat - optdat.append([paramf,dpl]) + optdat = {'paramfn': paramfn, 'params': params, 'dpl': dpl} def rmse (a1, a2): # return root mean squared error between a1, a2; assumes same lengths, sampling rates @@ -270,7 +270,7 @@ def __init__ (self, paramf, params, parent=None, width=5, height=4, dpi=40, optM self.optMode = optMode if not optMode: initial_ddat = {} - optdat = [] + optdat = {} self.plot() def initaxes (self): @@ -548,10 +548,6 @@ def hassimdata (self): # check if any simulation data available in ddat dictionary return 'dpl' in ddat - def hasinitoptdata (self): - # check if any simulation data available in ddat dictionary - return 'dpl' in initial_ddat - def clearlextdatobj (self): # clear list of external data objects for o in self.lextdatobj: @@ -644,7 +640,7 @@ def plotsimdat (self): if not self.optMode: # skip for optimization for lsim in lsimdat: # plot average dipoles from prior simulations - olddpl = lsim[1] + olddpl = lsim['dpl'] if debug: print('olddpl has shape ',olddpl.shape,len(olddpl[:,0]),len(olddpl[:,1])) self.axdipole.plot(olddpl[:,0],olddpl[:,1],'--',color='black',linewidth=self.gui.linewidth) @@ -661,15 +657,14 @@ def plotsimdat (self): yl[0] = min(yl[0],ddat['dpl'][sidx:eidx,1].min()) yl[1] = max(yl[1],ddat['dpl'][sidx:eidx,1].max()) else: - for idx, opt in enumerate(optdat): - optdpl = opt[1] - if idx == len(optdat) - 1: - # only show the last optimization - self.axdipole.plot(optdpl[:,0],optdpl[:,1],'k',color='gray',linewidth=self.gui.linewidth+1) - yl[0] = min(yl[0],optdpl[sidx:eidx,1].min()) - yl[1] = max(yl[1],optdpl[sidx:eidx,1].max()) - - if self.hasinitoptdata(): + if 'dpl' in optdat: + # show optimized dipole as gray line + optdpl = optdat['dpl'] + self.axdipole.plot(optdpl[:,0],optdpl[:,1],'k',color='gray',linewidth=self.gui.linewidth+1) + yl[0] = min(yl[0],optdpl[sidx:eidx,1].min()) + yl[1] = max(yl[1],optdpl[sidx:eidx,1].max()) + + if 'dpl' in initial_ddat: # show initial dipole in dotted black line self.axdipole.plot(initial_ddat['dpl'][:,0],initial_ddat['dpl'][:,1],'--',color='black',linewidth=self.gui.linewidth) yl[0] = min(yl[0],initial_ddat['dpl'][sidx:eidx,1].min()) From aeb8b2c14a65abf20489b6d246fa10fa0198be86 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 10:05:26 -0400 Subject: [PATCH 018/107] MAINT: pass params dict to RunSimThread When the user clicks the button to run a simulation, the parameters from the GUI are immediately saved to a file using the __str__ function of each Qt dialog box. Then using hnn-core.read_params(), the dictionary of parameters is returned. This dictionary is passed to RunSimThread for both regular simulations and optimization batches. The QComboBox functions are simplified and updated for the new format of simdat.lsimdat --- hnn_qt5.py | 147 ++++++++++++++++++++++++++++------------------------- 1 file changed, 79 insertions(+), 68 deletions(-) diff --git a/hnn_qt5.py b/hnn_qt5.py index 3a87c9ac3..fd735ddea 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -24,7 +24,7 @@ from math import ceil, isclose import spikefn from paramrw import usingOngoingInputs, countEvokedInputs, usingEvokedInputs -from paramrw import chunk_evinputs, get_inputs, trans_input, validate_param_file +from paramrw import chunk_evinputs, get_inputs, trans_input, validate_param_file, write_legacy_paramf from simdat import SIMCanvas, getinputfiles, updatedat from gutils import setscalegeom, lowresdisplay, setscalegeomcenter, getmplDPI, getscreengeom import nlopt @@ -526,7 +526,7 @@ def bringwintotop (win): # based on https://nikolak.com/pyqt-threading-tutorial/ class RunSimThread (QThread): - def __init__ (self,c,d,ntrial,ncore,waitsimwin,paramf,opt=False,baseparamwin=None,mainwin=None,onNSG=False): + def __init__ (self,c,d,ntrial,ncore,waitsimwin,params,opt=False,baseparamwin=None,mainwin=None,onNSG=False): QThread.__init__(self) self.c = c self.d = d @@ -534,11 +534,13 @@ def __init__ (self,c,d,ntrial,ncore,waitsimwin,paramf,opt=False,baseparamwin=Non self.ntrial = ntrial self.ncore = ncore self.waitsimwin = waitsimwin - self.paramf = paramf + self.params = params self.opt = opt self.baseparamwin = baseparamwin self.mainwin = mainwin self.onNSG = onNSG + self.paramfn = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') + # it would be ideal to display a dialog box, but we have to get that event back to the main window # the next best thing is to print to the console and not crash the application @@ -557,8 +559,6 @@ def __init__ (self,c,d,ntrial,ncore,waitsimwin,paramf,opt=False,baseparamwin=Non self.lock = Lock() - self.params = read_params(self.paramf) - def updatewaitsimwin (self, txt): # print('RunSimThread updatewaitsimwin, txt=',txt) self.txtComm.tsig.emit(txt) @@ -677,13 +677,14 @@ def runsim (self, is_opt=False, banner=True, simlength=None): updatedat(self.params) if not is_opt: - simdat.updatelsimdat(self.paramf, simdat.ddat['dpl']) # update lsimdat and its current sim index - + # update lsimdat and its current sim index + simdat.updatelsimdat(self.paramfn, self.params, simdat.ddat['dpl']) def optmodel (self): import simdat global basedir + need_initial_ddat = False # initialize RNG with seed from config @@ -702,7 +703,7 @@ def optmodel (self): # save initial parameters file param_out = os.path.join(basedir,'before_opt.param') - shutil.copyfile(self.paramf, param_out) + write_legacy_paramf(param_out, self.params) self.updatewaitsimwin('Optimizing model. . .') @@ -742,7 +743,8 @@ def optmodel (self): if need_initial_ddat: simdat.initial_ddat = deepcopy(simdat.ddat) - simdat.updateoptdat(self.paramf,simdat.ddat['dpl']) # update optdat with best from this step + # update optdat with best from this step + simdat.updateoptdat(self.paramfn, self.params, simdat.ddat['dpl']) # put best opt results into GUI and save to param file push_values = OrderedDict() @@ -757,8 +759,12 @@ def optmodel (self): # one final sim with the best parameters to update display self.runsim(is_opt=True, banner=False) - simdat.updatelsimdat(self.paramf,simdat.ddat['dpl']) # update lsimdat and its current sim index - simdat.updateoptdat(self.paramf,simdat.ddat['dpl']) # update optdat with the final best + + # update lsimdat and its current sim index + simdat.updatelsimdat(self.paramfn, self.params, simdat.ddat['dpl']) + + # update optdat with the final best + simdat.updateoptdat(self.paramfn, self.params, simdat.ddat['dpl']) # re-enable all the range sliders self.baseparamwin.optparamwin.toggleEnableUserFields(step, enable=True) @@ -837,20 +843,17 @@ def optrun (new_params, grad=0): with open(fnoptinf,'a') as fpopt: fpopt.write(str(simdat.ddat['errtot'])+os.linesep) # write error - # save copy param file - param_fname = os.path.basename(self.paramf) - curr_paramf = os.path.join(basedir, param_fname) - # save params numbered by optsim param_out = os.path.join(basedir,'step_%d_sim_%d.param'%(self.cur_step,self.optsim)) - shutil.copyfile(curr_paramf, param_out) + write_legacy_paramf(param_out, self.params) if err < self.stepminopterr: self.updatewaitsimwin("new best with RMSE %f"%err) self.stepminopterr = err # save best param file - shutil.copyfile(curr_paramf, os.path.join(basedir,'step_%d_best.param'%self.cur_step)) # convenience, save best here + param_out = os.path.join(basedir,'step_%d_best.param'%self.cur_step) + write_legacy_paramf(param_out, self.params) if 'dpl' in simdat.ddat: self.best_ddat['dpl'] = simdat.ddat['dpl'] if 'errtot' in simdat.ddat: @@ -3423,7 +3426,9 @@ def selParamFileDialog (self): # store the sim just loaded in simdat's list - is this the desired behavior? or should we first erase prev sims? import simdat if 'dpl' in simdat.ddat: - simdat.updatelsimdat(paramf,simdat.ddat['dpl']) # update lsimdat and its current sim index + # update lsimdat and its current sim index + simdat.updatelsimdat(paramf, self.params, simdat.ddat['dpl']) + self.populateSimCB() # populate the combobox if len(self.dextdata) > 0: @@ -3620,57 +3625,57 @@ def updateDatCanv (self,fn): self.initSimCanvas() # recreate canvas self.setWindowTitle(fn) - def removeSim (self): - # remove the currently selected simulation - global paramf,basedir + def updateSelectedSim(self, sim_idx): + """Update the sim shown in the ComboBox and update globals""" import simdat - if debug: print('removeSim',paramf,simdat.lsimidx) - if len(simdat.lsimdat) > 0 and simdat.lsimidx >= 0: - cidx = self.cbsim.currentIndex() # - a = simdat.lsimdat[:cidx] - b = simdat.lsimdat[cidx+1:] - c = [x for x in a] - for x in b: c.append(x) - simdat.lsimdat = c - self.cbsim.removeItem(cidx) - simdat.lsimidx = max(0,len(simdat.lsimdat) - 1) - if len(simdat.lsimdat) > 0: - paramf = simdat.lsimdat[simdat.lsimidx][0] - param_fname = os.path.splitext(os.path.basename(paramf)) - basedir = os.path.join(dconf['datdir'], param_fname[0]) - if debug: print('new paramf:',paramf,simdat.lsimidx) - self.updateDatCanv(paramf) - self.cbsim.setCurrentIndex(simdat.lsimidx) - else: - self.clearSimulations() - def prevSim (self): - # go to previous simulation - global paramf,basedir + global paramf, basedir + + # update globals + simdat.lsimidx = sim_idx + paramf = simdat.lsimdat[sim_idx]['paramfn'] + split_fname = os.path.splitext(os.path.basename(paramf)) + basedir = os.path.join(dconf['datdir'], split_fname[0]) + + # update GUI + self.updateDatCanv(paramf) + self.cbsim.setCurrentIndex(simdat.lsimidx) + + def removeSim(self): + """Remove the currently selected simulation""" import simdat - if debug: print('prevSim',paramf,simdat.lsimidx) - if len(simdat.lsimdat) > 0 and simdat.lsimidx > 0: - simdat.lsimidx -= 1 - paramf = simdat.lsimdat[simdat.lsimidx][0] - param_fname = os.path.splitext(os.path.basename(paramf)) - basedir = os.path.join(dconf['datdir'], param_fname[0]) - if debug: print('new paramf:',paramf,simdat.lsimidx) - self.updateDatCanv(paramf) - self.cbsim.setCurrentIndex(simdat.lsimidx) + + cidx = self.cbsim.currentIndex() + self.cbsim.removeItem(cidx) + del simdat.lsimdat[cidx] + + # go to last entry + new_simidx = self.cbsim.count() - 1 + if new_simidx < 0: + simdat.lsimidx = 0 + self.clearSimulations() + else: + self.updateSelectedSim(new_simidx) + + def prevSim(self): + """Go to previous simulation""" + + new_simidx = self.cbsim.currentIndex() - 1 + if new_simidx < 0: + print("There is no previous simulation") + return + else: + self.updateSelectedSim(new_simidx) def nextSim (self): # go to next simulation - global paramf,basedir - import simdat - if debug: print('nextSim',paramf,simdat.lsimidx) - if len(simdat.lsimdat) > 0 and simdat.lsimidx + 1 < len(simdat.lsimdat): - simdat.lsimidx += 1 - paramf = simdat.lsimdat[simdat.lsimidx][0] - param_fname = os.path.splitext(os.path.basename(paramf)) - basedir = os.path.join(dconf['datdir'], param_fname[0]) - if debug: print('new paramf:',paramf,simdat.lsimidx) - self.updateDatCanv(paramf) - self.cbsim.setCurrentIndex(simdat.lsimidx) + + if self.cbsim.currentIndex() + 2 > self.cbsim.count(): + print("There is no next simulation") + return + else: + new_simidx = self.cbsim.currentIndex() + 1 + self.updateSelectedSim(new_simidx) def clearSimulationData (self): # clear the simulation data @@ -4009,7 +4014,8 @@ def initUI (self): # store any sim just loaded in simdat's list - is this the desired behavior? or should we start empty? import simdat if 'dpl' in simdat.ddat: - simdat.updatelsimdat(paramf,simdat.ddat['dpl']) # update lsimdat and its current sim index + # update lsimdat and its current sim index + simdat.updatelsimdat(paramf, self.params, simdat.ddat['dpl']) self.cbsim = QComboBox(self) self.populateSimCB() # populate the combobox @@ -4065,8 +4071,9 @@ def populateSimCB (self): global paramf self.cbsim.clear() import simdat - for l in simdat.lsimdat: - self.cbsim.addItem(l[0]) + for sim in simdat.lsimdat: + sim_paramfn = sim['paramfn'] + self.cbsim.addItem(sim_paramfn) self.cbsim.setCurrentIndex(simdat.lsimidx) def initSimCanvas (self,recalcErr=True,optMode=False,gRow=1,reInit=True): @@ -4168,7 +4175,7 @@ def optmodel (self, ntrial, ncore): self.statusBar().showMessage("Optimizing model. . .") - self.runthread = RunSimThread(self.c, self.d, ntrial, ncore, self.waitsimwin, paramf, opt=True, baseparamwin=self.baseparamwin, mainwin=self, onNSG=False) + self.runthread = RunSimThread(self.c, self.d, ntrial, ncore, self.waitsimwin, self.params, opt=True, baseparamwin=self.baseparamwin, mainwin=self, onNSG=False) # We have all the events we need connected we can start the thread self.runthread.start() @@ -4179,9 +4186,13 @@ def optmodel (self, ntrial, ncore): bringwintotop(self.waitsimwin) def startsim (self, ntrial, ncore, onNSG=False): + global paramf + # start the simulation if not self.baseparamwin.saveparams(): return # make sure params saved and ok to run + self.params = read_params(paramf) + self.setcursors(Qt.WaitCursor) print('Starting simulation (%d cores). . .'%ncore) @@ -4192,7 +4203,7 @@ def startsim (self, ntrial, ncore, onNSG=False): else: self.statusBar().showMessage("Running simulation. . .") - self.runthread=RunSimThread(self.c,self.d,ntrial,ncore,self.waitsimwin,paramf,opt=False,baseparamwin=None,mainwin=None,onNSG=onNSG) + self.runthread=RunSimThread(self.c,self.d,ntrial,ncore,self.waitsimwin,self.params,opt=False,baseparamwin=None,mainwin=None,onNSG=onNSG) # We have all the events we need connected we can start the thread self.runthread.start() From 66363c78f4298c8f516ef66269be87f0088c53d3 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 11:31:16 -0400 Subject: [PATCH 019/107] MAINT: handle exceptions when reading spike file --- simdat.py | 3 +++ spikefn.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/simdat.py b/simdat.py index 9213c2bd5..c02549f6c 100644 --- a/simdat.py +++ b/simdat.py @@ -122,6 +122,9 @@ def updatedat (params): ddat['spk'] = np.r_[spikes.times, spikes.gids].T except ValueError: ddat['spk'] = readtxt(dfile['spk']) + except IndexError: + # incorrect dimensions (bad spike file) + ddat['spk'] = None ddat['dpltrials'] = readdpltrials(basedir) diff --git a/spikefn.py b/spikefn.py index cfbe8d25c..c0161d8df 100644 --- a/spikefn.py +++ b/spikefn.py @@ -167,8 +167,11 @@ def __get_extinput_times (self, fspk): s_all = np.r_[spikes.times, spikes.gids].T except ValueError: s_all = np.loadtxt(open(fspk, 'rb')) + except IndexError: + # incorrect dimensions (bad spike file) + print('Warning: bad data in spike file:', fspk) except OSError: - print('Warning: could not read file:', fspk) + print('Warning: could not read spike file:', fspk) if len(s_all) == 0: # couldn't read spike times From d5a7f6c95e8ab6414dd823a6c94c0592694dc9d9 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 11:31:52 -0400 Subject: [PATCH 020/107] MAINT: clean up run.py and flake8 --- hnn_qt5.py | 2 +- run.py | 341 +++++++++++++++++++++++++---------------------------- 2 files changed, 164 insertions(+), 179 deletions(-) diff --git a/hnn_qt5.py b/hnn_qt5.py index fd735ddea..7a725b820 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -639,7 +639,7 @@ def runsim (self, is_opt=False, banner=True, simlength=None): raise RuntimeError("No cores available for simulation") try: - simulate(self.params, self.ncore) + simulate(self.params, dconf['datdir'], self.ncore) # success, make default ncore defncore = self.ncore break diff --git a/run.py b/run.py index 5ddbab77e..7505f2cc4 100755 --- a/run.py +++ b/run.py @@ -1,187 +1,172 @@ -#!/usr/bin/env python -# run.py - primary run function for s1 project -# -# v 1.10.0-py35 -# rev 2016-05-01 (SL: removed izip, fixed an nhost bug) -# last major: (SL: toward python3) -# other branch for hnn +"""File with functions and classes for running the NEURON """ +# Authors: Blake Caldwell +# Sam Neymotin +# Shane Lee + +import os.path as op import os -import sys -import time -import json import numpy as np -# Cells are defined in other files -from paramrw import usingOngoingInputs, write_gids_param +from paramrw import usingOngoingInputs, write_gids_param import specfn as specfn -#import pickle -from conf import readconf -import os.path as op - -from hnn_core import simulate_dipole, read_params, Network, MPIBackend, read_spikes +from hnn_core import simulate_dipole, Network, MPIBackend from hnn_core.dipole import average_dipoles -dconf = readconf() - -# # save somatic voltage of all cells to pkl object -# def save_vsoma (): -# for host in range(int(pc.nhost())): -# if host == get_rank(): -# dsoma = net.get_vsoma() -# messageid = pc.pack(dsoma) # create a message ID and store this value -# pc.post(host,messageid) # post the message -# if get_rank()==0: -# dsomaout = {} -# for host in range(int(pc.nhost())): -# pc.take(host) -# dsoma_node = pc.upkpyobj() -# for k,v in dsoma_node.items(): dsomaout[k] = v -# dsomaout['vtime'] = t_vec.to_python() -# # print('dsomaout.keys():',dsomaout.keys(),'file:',doutf['file_vsoma']) -# pickle.dump(dsomaout,open(doutf['file_vsoma'],'wb')) - - -def setupsimdir (params): - simdir = os.path.join(dconf['datdir'], params['sim_prefix']) - try: - os.mkdir(simdir) - except FileExistsError: - pass - - return simdir - -def getfname (ddir,key,trial=0,ntrial=1): - datatypes = {'rawspk': ('spk','.txt'), - 'rawdpl': ('rawdpl','.txt'), - 'normdpl': ('dpl','.txt'), # same output name - do not need both raw and normalized dipole - unless debugging - 'rawcurrent': ('i','.txt'), - 'rawspec': ('rawspec','.npz'), - 'rawspeccurrent': ('speci','.npz'), - 'avgdpl': ('dplavg','.txt'), - 'avgspec': ('specavg','.npz'), - 'figavgdpl': ('dplavg','.png'), - 'figavgspec': ('specavg','.png'), - 'figdpl': ('dpl','.png'), - 'figspec': ('spec','.png'), - 'figspk': ('spk','.png'), - 'param': ('param','.txt'), - 'vsoma': ('vsoma','.pkl'), - 'lfp': ('lfp', '.txt') - } - if ntrial == 1 or key == 'param': # param file currently identical for all trials - return os.path.join(ddir,datatypes[key][0]+datatypes[key][1]) - else: - return os.path.join(ddir,datatypes[key][0] + '_' + str(trial) + datatypes[key][1]) - - -def expandbbox (boxA, boxB): - return [(min(boxA[i][0],boxB[i][0]),max(boxA[i][1],boxB[i][1])) for i in range(3)] - -def arrangelayers (net): - # NOTE: will not work with hnn-core as-is. this code modifies NetworkBuilder attributes - - # offsets for L2, L5 cells so that L5 below L2 in display - dyoff = {L2Pyr: 1000, 'L2_pyramidal' : 1000, - L5Pyr: -1000-149.39990234375, 'L5_pyramidal' : -1000-149.39990234375, - L2Basket: 1000, 'L2_basket' : 1000, - L5Basket: -1000-149.39990234375, 'L5_basket' : -1000-149.39990234375} - for cell in net.cells: cell.translate3d(0,dyoff[cell.celltype],0) - dbbox = {x:[[1e9,-1e9],[1e9,-1e9],[1e9,-1e9]] for x in dyoff.keys()} - for cell in net.cells: - dbbox[cell.celltype] = expandbbox(dbbox[cell.celltype], cell.getbbox()) - -# All units for time: ms -def simulate (params, n_core): - - # create the network from the parameter file. note, NEURON objects haven't been created yet - net = Network(params) - - # TODO: add arrangelayers() to hnn-core or remove - # arrange cells in layers - for visualization purposes - # arrangelayers(net) - ddir = setupsimdir(params) - - # run the simulation with MPI because the user is waiting for it to complete - with MPIBackend(n_procs=n_core, mpi_cmd='mpiexec'): - dpls = simulate_dipole(net, params['N_trials']) - - ntrial = len(dpls) - # save average dipole from individual trials in a single file - if ntrial > 1: - avg_dpl = average_dipoles(dpls) - elif ntrial == 1: - avg_dpl = dpls[0] - else: - raise RuntimeError("No dipole(s) rertuned from simulation") - - # TODO: rawdpl in hnn-core - avg_dpl.write(os.path.join(ddir, 'dpl.txt')) - - # HNN workflow requires some files to be written to disk. This sets up the directory for all output files - ddir = setupsimdir(params) - - # now write the files - - # TODO: Can below be removed if spk.txt is new hnn-core format with 3 columns (including spike type)? - write_gids_param(getfname(ddir,'param'), net.gid_dict) - - # save spikes by trial - net.spikes.write(os.path.join(ddir, 'spk_%d.txt')) - - # save spikes from the individual trials in a single file - fout = os.path.join(ddir,'spk.txt') - with open(fout, 'w') as fspkout: - for trial_idx in range(len(net.spikes.times)): - for spike_idx in range(len(net.spikes.times[trial_idx])): - fspkout.write('{:.3f}\t{}\t{}\n'.format( - net.spikes.times[trial_idx][spike_idx], - int(net.spikes.gids[trial_idx][spike_idx]), - net.spikes.types[trial_idx][spike_idx])) - - # save dipole for each trial and perform spectral analysis - for trial_idx, dpl in enumerate(dpls): - file_dipole = getfname(ddir,'normdpl', trial_idx, params['N_trials']) - dpl.write(file_dipole) - - # TODO: this should be moved to Network class within hnn-core - # write the somatic current to a file - # for now does not write the total but just L2 somatic and L5 somatic - # X = np.r_[[dpl.t, net.current['L2Pyr_soma'].x, net.current['L5Pyr_soma'].x]].T - # file_current = getfname(ddir, 'rawcurrent', trial_idx, params['N_trials']) - # np.savetxt(file_current, X, fmt=['%3.3f', '%5.4f', '%5.4f'], - # delimiter='\t') - - # TODO: save_vsoma is coded to work in parallel, so it should be moved to - # hnn_core.parallel_backends - # if p['save_vsoma']: - # save_vsoma() + +def get_fname(sim_dir, key, trial=0, ntrial=1): + """Build the file names using the old HNN scheme + + Parameters + ---------- + sim_dir : str + The base data directory where simulation result files are stored + key : str + A string describing the type of file (HNN specific) + trial : int | None + Trial number for which to generate files (separate files per trial). + If None is given, then trial number 0 is assumed. + ntrial : int | None + The total number of trials that are part of this simulation. If None + is given, then a total of 1 trial is assumed. + + Returns + ---------- + fname : str + A string with the correct filename + """ + + datatypes = {'rawspk': ('spk', '.txt'), + 'rawdpl': ('rawdpl', '.txt'), + 'normdpl': ('dpl', '.txt'), + 'rawcurrent': ('i', '.txt'), + 'rawspec': ('rawspec', '.npz'), + 'rawspeccurrent': ('speci', '.npz'), + 'avgdpl': ('dplavg', '.txt'), + 'avgspec': ('specavg', '.npz'), + 'figavgdpl': ('dplavg', '.png'), + 'figavgspec': ('specavg', '.png'), + 'figdpl': ('dpl', '.png'), + 'figspec': ('spec', '.png'), + 'figspk': ('spk', '.png'), + 'param': ('param', '.txt'), + 'vsoma': ('vsoma', '.pkl'), + 'lfp': ('lfp', '.txt')} + + if ntrial == 1 or key == 'param': + # param file currently identical for all trials + return op.join(sim_dir, datatypes[key][0] + datatypes[key][1]) + else: + return op.join(sim_dir, datatypes[key][0] + '_' + str(trial) + + datatypes[key][1]) + + +def simulate(params, data_dir, n_procs=None): + """Start the simulation with hnn_core.simulate + + Parameters + ---------- + params : dict + The parameters + data_dir : str + The base path for storing output files (e.g. ~/hnn_out/data) + + n_procs : int | None + The number of MPI processes requested by the user. If None, then will + attempt to detect number of cores (including hyperthreads) and start + parallel simulation over all of them. + """ + + # create the network from the parameter file. note, NEURON objects haven't + # been created yet + net = Network(params) + + # run the simulation with MPIBackend for faster completion time + with MPIBackend(n_procs=n_procs, mpi_cmd='mpiexec'): + dpls = simulate_dipole(net, params['N_trials']) + + ntrial = len(dpls) + # save average dipole from individual trials in a single file + if ntrial > 1: + avg_dpl = average_dipoles(dpls) + elif ntrial == 1: + avg_dpl = dpls[0] + else: + raise RuntimeError("No dipole(s) rerturned from simulation") + + # make sure the directory for saving data has been created + sim_dir = op.join(data_dir, params['sim_prefix']) + try: + os.mkdir(sim_dir) + except FileExistsError: + pass + + # now write the files + # TODO: rawdpl in hnn-core + + avg_dpl.write(op.join(sim_dir, 'dpl.txt')) + + # TODO: Can below be removed if spk.txt is new hnn-core format with 3 + # columns (including spike type)? + write_gids_param(get_fname(sim_dir, 'param'), net.gid_dict) + + # save spikes by trial + glob = op.join(sim_dir, 'spk_%d.txt') + net.spikes.write(glob) + + spike_fn = get_fname(sim_dir, 'rawspk') + # save spikes from the individual trials in a single file + with open(spike_fn, 'w') as fspkout: + for trial_idx in range(len(net.spikes.times)): + for spike_idx in range(len(net.spikes.times[trial_idx])): + fspkout.write('{:.3f}\t{}\t{}\n'.format( + net.spikes.times[trial_idx][spike_idx], + int(net.spikes.gids[trial_idx][spike_idx]), + net.spikes.types[trial_idx][spike_idx])) + + # save dipole for each trial and perform spectral analysis + for trial_idx, dpl in enumerate(dpls): + dipole_fn = get_fname(sim_dir, 'normdpl', trial_idx, + params['N_trials']) + dpl.write(dipole_fn) + + if params['save_spec_data'] or usingOngoingInputs(params): + spec_opts = {'type': 'dpl_laminar', + 'f_max': params['f_max_spec'], + 'save_data': 1, + 'runtype': 'parallel'} + + # run the spectral analysis + specfn.analysis_simp(spec_opts, params, dpl, + get_fname(sim_dir, 'rawspec', trial_idx, + params['N_trials'])) + + # NOTE: the savefigs functionality is quite complicated and rewriting + # from scratch in hnn-core is probably a better option that will allow + # deprecating the large amount of legacy code + + # if params['save_figs']: + # savefigs(params) # save output figures if params['save_spec_data'] or usingOngoingInputs(params): - spec_opts = {'type': 'dpl_laminar', - 'f_max': params['f_max_spec'], - 'save_data': 1, - 'runtype': 'parallel', - } - - # run the spectral analysis - specfn.analysis_simp(spec_opts, params, dpl, - getfname(ddir, 'rawspec', trial_idx, params['N_trials'])) - - # NOTE: the savefigs functionality is quite complicated and rewriting from scratch in hnn-core is probably - # a much better option that allows deprecating the large amount of legacy code - # if params['save_figs']: - # savefigs(params) # save output figures - - if params['save_spec_data'] or usingOngoingInputs(params): - # save average spectrogram from individual trials in a single file - lf = [os.path.join(ddir,'rawspec_'+str(i)+'.npz') for i in range(ntrial)] - dspecin = {} - dout = {} - for f in lf: - dspecin[f] = np.load(f) - - for k in ['t_L5', 'f_L5', 't_L2', 'f_L2', 'time', 'freq']: dout[k] = dspecin[lf[0]][k] - for k in ['TFR', 'TFR_L5', 'TFR_L2']: dout[k] = np.mean(np.array([dspecin[f][k] for f in lf]),axis=0) - with open(os.path.join(ddir,'rawspec.npz'), 'wb') as fdpl: - np.savez_compressed(fdpl,t_L5=dout['t_L5'],f_L5=dout['f_L5'],t_L2=dout['t_L2'],f_L2=dout['f_L2'],time=dout['time'],freq=dout['freq'],TFR=dout['TFR'],TFR_L5=dout['TFR_L5'],TFR_L2=dout['TFR_L2']) + # save average spectrogram from individual trials in a single file + + dspecin = {} + dout = {} + lf = [] + for i in range(ntrial): + lf.append(op.join(sim_dir, 'rawspec_' + str(i) + '.npz')) + + for f in lf: + dspecin[f] = np.load(f) + for k in ['t_L5', 'f_L5', 't_L2', 'f_L2', 'time', 'freq']: + dout[k] = dspecin[lf[0]][k] + for k in ['TFR', 'TFR_L5', 'TFR_L2']: + dout[k] = np.mean(np.array([dspecin[f][k] for f in lf]), axis=0) + + with open(op.join(sim_dir, 'rawspec.npz'), 'wb') as spec_fn: + np.savez_compressed(spec_fn, t_L5=dout['t_L5'], f_L5=dout['f_L5'], + t_L2=dout['t_L2'], f_L2=dout['f_L2'], + time=dout['time'], freq=dout['freq'], + TFR=dout['TFR'], TFR_L5=dout['TFR_L5'], + TFR_L2=dout['TFR_L2']) From e3725eefc86ea1ff3f28cbda930731b50abf15f3 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 12:29:52 -0400 Subject: [PATCH 021/107] MAINT: move RunSimThread() to run.py --- hnn_qt5.py | 548 ++++++----------------------------------------------- run.py | 475 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 525 insertions(+), 498 deletions(-) diff --git a/hnn_qt5.py b/hnn_qt5.py index 7a725b820..68dd15738 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -1,41 +1,44 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- -import sys, os +"""Classes for creating the main HNN GUI""" + +# Authors: Sam Neymotin +# Blake Caldwell +# Shane Lee + +# Python builtins +import sys +import os +import multiprocessing +from subprocess import Popen, PIPE +import shlex, shutil +from collections import namedtuple, OrderedDict +from copy import deepcopy +from time import time, sleep +import numpy as np +from math import ceil, isclose +import traceback +from psutil import cpu_count + +# External libraries from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QDialog, QGridLayout, QLineEdit, QLabel from PyQt5.QtWidgets import QCheckBox, QTextEdit, QInputDialog, QSpacerItem, QFrame, QSplitter from PyQt5.QtGui import QIcon, QFont, QPixmap, QColor, QPainter, QFont, QPen -from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, pyqtSlot, Qt, QSize +from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, Qt, QSize from PyQt5.QtCore import QMetaObject, QUrl from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar import matplotlib.pyplot as plt -import multiprocessing -from subprocess import Popen, PIPE -import shlex, shutil -from collections import OrderedDict -from copy import deepcopy -from time import time, sleep -from conf import dconf -import conf -from run import simulate +from hnn_core import read_params -import numpy as np -from math import ceil, isclose +# HNN modules import spikefn from paramrw import usingOngoingInputs, countEvokedInputs, usingEvokedInputs from paramrw import chunk_evinputs, get_inputs, trans_input, validate_param_file, write_legacy_paramf from simdat import SIMCanvas, getinputfiles, updatedat from gutils import setscalegeom, lowresdisplay, setscalegeomcenter, getmplDPI, getscreengeom -import nlopt -from psutil import cpu_count, wait_procs, process_iter, NoSuchProcess -from threading import Lock -import traceback -from collections import namedtuple - -from hnn_core import read_params - -prtime = False +from conf import dconf +import conf +from run import RunSimThread def isWindows (): # are we on windows? or linux/mac ? @@ -400,7 +403,7 @@ def _posToValue(self, xpos): return scale(xpos, (0, self.width()), (self.min(), self.max())) def _handleMoveSplitter(self, xpos, index): - hw = self._splitter.handleWidth() + self._splitter.handleWidth() def _lockWidth(widget): width = widget.size().width() widget.setMinimumWidth(width) @@ -413,8 +416,6 @@ def _unlockWidth(widget): _lockWidth(self._tail) if v >= self.end(): return - offset = -20 - w = xpos + offset self._setStart(v) self.rangeValuesChanged.emit(self.label, v, self.end()) elif index == self._SPLIT_END: @@ -424,8 +425,6 @@ def _unlockWidth(widget): _lockWidth(self._head) if v <= self.start(): return - offset = -40 - w = self.width() - xpos + offset self._setEnd(v) self.rangeValuesChanged.emit(self.label, self.start(), v) _unlockWidth(self._tail) @@ -460,57 +459,11 @@ class Communicate (QObject): class DoneSignal (QObject): finishSim = pyqtSignal(bool, str) -# for signaling - passing text -class TextSignal (QObject): - tsig = pyqtSignal(str) - -# for signaling - updating GUI & param file during optimization -class ParamSignal (QObject): - psig = pyqtSignal(OrderedDict) - -class CanvSignal (QObject): - csig = pyqtSignal(bool, bool) - def bringwintobot (win): #win.show() #win.lower() win.hide() -def kill_list_of_procs(procs): - # try terminate first - for p in procs: - try: - p.terminate() - except NoSuchProcess: - pass - gone, alive = wait_procs(procs, timeout=3) - - # now try kill - for p in alive: - p.kill() - gone, alive = wait_procs(procs, timeout=3) - - return alive - - -def get_nrniv_procs_running(): - ls = [] - name = 'nrniv' - for p in process_iter(attrs=["name", "exe", "cmdline"]): - if name == p.info['name'] or \ - p.info['exe'] and os.path.basename(p.info['exe']) == name or \ - p.info['cmdline'] and p.info['cmdline'][0] == name: - ls.append(p) - return ls - -def kill_and_check_nrniv_procs(): - procs = get_nrniv_procs_running() - if len(procs) > 0: - running = kill_list_of_procs(procs) - if len(running) > 0: - pids = [ str(proc.pid) for proc in running ] - print("ERROR: failed to kill nrniv process(es) %s" % ','.join(pids)) - def bringwintotop (win): # bring a pyqt5 window to the top (parents still stay behind children) # based on examples from https://www.programcreek.com/python/example/101663/PyQt5.QtCore.Qt.WindowActive @@ -524,399 +477,6 @@ def bringwintotop (win): #win.raise_() #win.show() -# based on https://nikolak.com/pyqt-threading-tutorial/ -class RunSimThread (QThread): - def __init__ (self,c,d,ntrial,ncore,waitsimwin,params,opt=False,baseparamwin=None,mainwin=None,onNSG=False): - QThread.__init__(self) - self.c = c - self.d = d - self.killed = False - self.ntrial = ntrial - self.ncore = ncore - self.waitsimwin = waitsimwin - self.params = params - self.opt = opt - self.baseparamwin = baseparamwin - self.mainwin = mainwin - self.onNSG = onNSG - self.paramfn = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') - - - # it would be ideal to display a dialog box, but we have to get that event back to the main window - # the next best thing is to print to the console and not crash the application - sys.excepthook = traceback.print_exception - - self.txtComm = TextSignal() - self.txtComm.tsig.connect(self.waitsimwin.updatetxt) - - self.prmComm = ParamSignal() - if self.baseparamwin is not None: - self.prmComm.psig.connect(self.baseparamwin.updatesaveparams) - - self.canvComm = CanvSignal() - if self.mainwin is not None: - self.canvComm.csig.connect(self.mainwin.initSimCanvas) - - self.lock = Lock() - - def updatewaitsimwin (self, txt): - # print('RunSimThread updatewaitsimwin, txt=',txt) - self.txtComm.tsig.emit(txt) - - def updatebaseparamwin (self, d): - self.prmComm.psig.emit(d) - - def updatedispparam (self): - self.c.commsig.emit() - - def updatedrawerr (self): - self.canvComm.csig.emit(False, self.opt) # False means do not recalculate error - - def stop (self): - self.killproc() - - def __del__ (self): - self.quit() - self.wait() - - def run (self): - msg='' - - if self.opt and self.baseparamwin is not None: - try: - self.optmodel() # run optimization - except RuntimeError as e: - msg = str(e) - self.baseparamwin.optparamwin.toggleEnableUserFields(self.cur_step, enable=True) - self.baseparamwin.optparamwin.clear_initial_opt_ranges() - self.baseparamwin.optparamwin.optimization_running = False - else: - try: - self.runsim() # run simulation - self.updatedispparam() # update params in all windows (optimization) - except RuntimeError as e: - msg = str(e) - - self.d.finishSim.emit(self.opt, msg) # send the finish signal - - - def killproc (self): - if debug: print('Thread killing sim. . .') - - # make absolute sure all nrniv procs have been killed - kill_and_check_nrniv_procs() - - self.lock.acquire() - self.killed = True - self.lock.release() - - def get_proc_stream (self, stream, print_to_console=False): - try: - for line in iter(stream.readline, ""): - if print_to_console: - print(line.strip()) - try: # see https://stackoverflow.com/questions/2104779/qobject-qplaintextedit-multithreading-issues - self.updatewaitsimwin(line.strip()) # sends a pyqtsignal to waitsimwin, which updates its textedit - except: - if debug: print('RunSimThread updatewaitsimwin exception...') - pass # catch exception in case anything else goes wrong - except ValueError: - # if process is killed and stream.readline() gives I/O error - pass - stream.close() - - # run sim command via mpi, then delete the temp file. - def runsim (self, is_opt=False, banner=True, simlength=None): - import simdat - global defncore - - self.lock.acquire() - self.killed = False - self.lock.release() - - while True: - if self.ncore == 0: - raise RuntimeError("No cores available for simulation") - - try: - simulate(self.params, dconf['datdir'], self.ncore) - # success, make default ncore - defncore = self.ncore - break - except RuntimeError as e: - if self.ncore == 1: - # can't reduce ncore any more - print(str(e)) - self.updatewaitsimwin(str(e)) - kill_and_check_nrniv_procs() - raise RuntimeError("Simulation failed to start") - except: - # if it's something else we still want to print the error and then - # handle it like a RuntimeError instead of crashing the whole application - txt = traceback.format_exc() - sys.stderr.write(txt) - self.updatewaitsimwin(txt) - # pop up dialog pop - raise RuntimeError("Unknown error") - - # check if proc was killed before retrying with fewer cores - self.lock.acquire() - if self.killed: - self.lock.release() - # exit using RuntimeError - raise RuntimeError("Terminated") - else: - self.lock.release() - - self.ncore = ceil(self.ncore/2) - txt = "INFO: Failed starting simulation, retrying with %d cores" % self.ncore - print(txt) - self.updatewaitsimwin(txt) - - # should have good data written to files at this point - updatedat(self.params) - - if not is_opt: - # update lsimdat and its current sim index - simdat.updatelsimdat(self.paramfn, self.params, simdat.ddat['dpl']) - - def optmodel (self): - import simdat - - global basedir - - need_initial_ddat = False - - # initialize RNG with seed from config - seed = self.params['prng_seedcore_opt'] - nlopt.srand(seed) - - # initial_ddat stores the initial fit (from "Run Simulation"). - # To be displayed in final dipole plot as black dashed line. - if len(simdat.ddat) > 0: - simdat.initial_ddat['dpl'] = deepcopy(simdat.ddat['dpl']) - simdat.initial_ddat['errtot'] = deepcopy(simdat.ddat['errtot']) - else: - need_initial_ddat = True - - self.baseparamwin.optparamwin.populate_initial_opt_ranges() - - # save initial parameters file - param_out = os.path.join(basedir,'before_opt.param') - write_legacy_paramf(param_out, self.params) - - self.updatewaitsimwin('Optimizing model. . .') - - self.last_step = False - self.first_step = True - num_steps = self.baseparamwin.optparamwin.get_num_chunks() - for step in range(num_steps): - self.cur_step = step - if step == num_steps - 1: - self.last_step = True - - # disable range sliders for each step once that step has begun - self.baseparamwin.optparamwin.toggleEnableUserFields(step, enable=False) - - self.step_ranges = self.baseparamwin.optparamwin.get_chunk_ranges(step) - self.step_sims = self.baseparamwin.optparamwin.get_sims_for_chunk(step) - - if self.step_sims == 0: - txt = "Skipping optimization step %d (0 simulations)"%(step+1) - self.updatewaitsimwin(txt) - continue - - if len(self.step_ranges) == 0: - txt = "Skipping optimization step %d (0 parameters)"%(step+1) - self.updatewaitsimwin(txt) - continue - - txt = "Starting optimization step %d/%d" % (step + 1, num_steps) - self.updatewaitsimwin(txt) - self.runOptStep(step) - - if 'dpl' in self.best_ddat: - simdat.ddat['dpl'] = deepcopy(self.best_ddat['dpl']) - if 'errtot' in self.best_ddat: - simdat.ddat['errtot'] = deepcopy(self.best_ddat['errtot']) - - if need_initial_ddat: - simdat.initial_ddat = deepcopy(simdat.ddat) - - # update optdat with best from this step - simdat.updateoptdat(self.paramfn, self.params, simdat.ddat['dpl']) - - # put best opt results into GUI and save to param file - push_values = OrderedDict() - for param_name in self.step_ranges.keys(): - push_values[param_name] = self.step_ranges[param_name]['final'] - self.updatebaseparamwin(push_values) - self.baseparamwin.optparamwin.push_chunk_ranges(step,push_values) - - sleep(1) - - self.first_step = False - - # one final sim with the best parameters to update display - self.runsim(is_opt=True, banner=False) - - # update lsimdat and its current sim index - simdat.updatelsimdat(self.paramfn, self.params, simdat.ddat['dpl']) - - # update optdat with the final best - simdat.updateoptdat(self.paramfn, self.params, simdat.ddat['dpl']) - - # re-enable all the range sliders - self.baseparamwin.optparamwin.toggleEnableUserFields(step, enable=True) - - self.baseparamwin.optparamwin.clear_initial_opt_ranges() - self.baseparamwin.optparamwin.optimization_running = False - - - def runOptStep (self, step): - import simdat - global basedir - - self.optsim = 0 - self.minopterr = 1e9 - self.stepminopterr = self.minopterr - self.best_ddat = {} - self.opt_start = self.baseparamwin.optparamwin.get_chunk_start(step) - self.opt_end = self.baseparamwin.optparamwin.get_chunk_end(step) - self.opt_weights = self.baseparamwin.optparamwin.get_chunk_weights(step) - def optrun (new_params, grad=0): - txt = "Optimization step %d, simulation %d" % (step + 1, - self.optsim + 1) - self.updatewaitsimwin(txt) - print(txt) - - dtest = OrderedDict() # parameter values to test - for param_name, test_value in zip(self.step_ranges.keys(), new_params): # set parameters - if test_value >= self.step_ranges[param_name]['minval'] and \ - test_value <= self.step_ranges[param_name]['maxval']: - if debug: - print('optrun prm:', self.step_ranges[param_name]['initial'], - self.step_ranges[param_name]['minval'], - self.step_ranges[param_name]['maxval'], - test_value) - dtest[param_name] = test_value - else: - # This test is not strictly necessary with COBYLA, but in case the algorithm - # is changed at some point in the future - print('INFO: optimization chose %.3f for %s outside of [%.3f-%.3f].' - % (test_value, param_name, - self.step_ranges[param_name]['minval'], - self.step_ranges[param_name]['maxval'])) - return 1e9 # invalid param value -> large error - - # put new param values into GUI and save params to file - self.updatebaseparamwin(dtest) - sleep(1) - - # run the simulation, but stop early if possible - self.runsim(is_opt=True, banner=False, simlength=self.opt_end) - - # calculate wRMSE for all steps - simdat.weighted_rmse(simdat.ddat, - self.opt_end, - self.opt_weights, - tstart=self.opt_start) - err = simdat.ddat['werrtot'] - - if self.last_step: - # weighted RMSE with weights of all 1's is the same as - # regular RMSE - simdat.ddat['errtot'] = simdat.ddat['werrtot'] - txt = "RMSE = %f"%err - else: - # calculate regular RMSE for displaying on plot - simdat.calcerr(simdat.ddat, - self.opt_end, - tstart=self.opt_start) - - txt = "weighted RMSE = %f, RMSE = %f"% (err,simdat.ddat['errtot']) - - print(txt) - self.updatewaitsimwin(os.linesep+'Simulation finished: ' + txt + os.linesep) # print error - - fnoptinf = os.path.join(basedir,'optinf.txt') - with open(fnoptinf,'a') as fpopt: - fpopt.write(str(simdat.ddat['errtot'])+os.linesep) # write error - - # save params numbered by optsim - param_out = os.path.join(basedir,'step_%d_sim_%d.param'%(self.cur_step,self.optsim)) - write_legacy_paramf(param_out, self.params) - - if err < self.stepminopterr: - self.updatewaitsimwin("new best with RMSE %f"%err) - - self.stepminopterr = err - # save best param file - param_out = os.path.join(basedir,'step_%d_best.param'%self.cur_step) - write_legacy_paramf(param_out, self.params) - if 'dpl' in simdat.ddat: - self.best_ddat['dpl'] = simdat.ddat['dpl'] - if 'errtot' in simdat.ddat: - self.best_ddat['errtot'] = simdat.ddat['errtot'] - - if self.optsim == 0 and not self.first_step: - # Update plots for the first simulation only of this step (best results from last round) - # Skip the first step because there are no optimization results to show yet. - self.updatedrawerr() # send event to draw updated error (asynchronously) - - self.optsim += 1 - - return err # return error - - def optimize(params_input, evals, algorithm): - opt_params = [] - lb = [] - ub = [] - - for param_name in params_input.keys(): - upper = params_input[param_name]['maxval'] - lower = params_input[param_name]['minval'] - if upper == lower: - continue - - ub.append(upper) - lb.append(lower) - opt_params.append(params_input[param_name]['initial']) - - if algorithm == nlopt.G_MLSL_LDS or algorithm == nlopt.G_MLSL: - # In case these mixed mode (global + local) algorithms are used in the future - local_opt = nlopt.opt(nlopt.LN_COBYLA, num_params) - opt.set_local_optimizer(local_opt) - - opt.set_lower_bounds(lb) - opt.set_upper_bounds(ub) - opt.set_min_objective(optrun) - opt.set_xtol_rel(1e-4) - opt.set_maxeval(evals) - opt_results = opt.optimize(opt_params) - - return opt_results - - txt = 'Optimizing from [%3.3f-%3.3f] ms' % (self.opt_start, - self.opt_end) - self.updatewaitsimwin(txt) - - num_params = len(self.step_ranges) - algorithm = nlopt.LN_COBYLA - opt = nlopt.opt(algorithm, num_params) - opt_results = optimize(self.step_ranges, self.step_sims, algorithm) - - # update opt params for the next round - for var_name, new_value in zip(self.step_ranges, opt_results): - old_value = self.step_ranges[var_name]['initial'] - - # only change the parameter value if it changed significantly - if not isclose(old_value, new_value, abs_tol=1e-9): - self.step_ranges[var_name]['final'] = new_value - else: - self.step_ranges[var_name]['final'] = \ - self.step_ranges[var_name]['initial'] - # look up resource adjusted for screen resolution def lookupresource (fn): lowres = lowresdisplay() # low resolution display @@ -991,7 +551,9 @@ def addtransvar (self,k,strans): self.dtransvar[k] = strans self.dtransvar[strans] = k - def initExtra (self): self.dqextra = OrderedDict() # extra items not written to param file + def initExtra (self): + # extra items not written to param file + self.dqextra = {} def initUI (self): self.layout = QVBoxLayout(self) @@ -1003,7 +565,8 @@ def initUI (self): self.ltabs = [] self.tabs = QTabWidget(); self.layout.addWidget(self.tabs) - for i in range(len(self.ldict)): self.ltabs.append(QWidget()) + for _ in range(len(self.ldict)): + self.ltabs.append(QWidget()) self.tabs.resize(575,200) @@ -1013,7 +576,7 @@ def initUI (self): tab.layout = QFormLayout() tab.setLayout(tab.layout) - self.dqline = OrderedDict() # QLineEdits dict; key is model variable + self.dqline = {} # QLineEdits dict; key is model variable for d,tab in zip(self.ldict, self.ltabs): for k,v in d.items(): self.dqline[k] = QLineEdit(self) @@ -1198,7 +761,7 @@ def tounity (self): def scalegains (self): if debug: print('scaling synaptic gains') - for i,k in enumerate(self.dqle.keys()): + for _, k in enumerate(self.dqle.keys()): fctr = float(self.dqle[k].text().strip()) if fctr < 0.: fctr = 0. @@ -1224,7 +787,7 @@ def initUI (self): grid = QGridLayout() grid.setSpacing(10) - self.dqle = OrderedDict() + self.dqle = {} for row,k in enumerate(['E -> E', 'E -> I', 'I -> E', 'I -> I']): lbl = QLabel(self) lbl.setText(k) @@ -1309,7 +872,7 @@ def TurnOff (self): self.lines2val('weight',0.0) def initd (self): - self.dL2,self.dL5 = OrderedDict(),OrderedDict() + self.dL2,self.dL5 = {},{} ld = [self.dL2,self.dL5] for i,lyr in enumerate(['L2','L5']): @@ -1345,7 +908,7 @@ def __init__ (self, parent, din): super(EvokedInputParamDialog, self).__init__(parent) self.nprox = self.ndist = 0 # number of proximal,distal inputs self.ld = [] # list of dictionaries for proximal/distal inputs - self.dqline = OrderedDict() + self.dqline = {} self.dtransvar = {} # for translating model variable name to more human-readable form self.initUI() self.setfromdin(din) @@ -1511,7 +1074,8 @@ def lines2val (self,ksearch,val): def allOff (self): self.lines2val('gbar',0.0) def removeAllInputs (self): - for i in range(len(self.ltabs)): self.removeCurrentInput() + for _ in range(len(self.ltabs)): + self.removeCurrentInput() self.nprox = self.ndist = 0 def IsProx (self,idx): @@ -1714,21 +1278,21 @@ def __init__ (self, parent, optrun_func): self.ld = [] # list of dictionaries for proximal/distal inputs self.dtab_idx = {} # for translating input names to tab indices self.dtab_names = {} # for translating tab indices to input names - self.dparams = OrderedDict() # actual values - self.dqline = OrderedDict() # not used, prevents failure in removeInput + self.dparams = {} # actual values + self.dqline = {} # not used, prevents failure in removeInput # these store values used in grid - self.dqchkbox = OrderedDict() # optimize - self.dqparam_name = OrderedDict() # parameter name - self.dqinitial_label = OrderedDict() # initial - self.dqopt_label = OrderedDict() # optimtized - self.dqdiff_label = OrderedDict() # delta - self.dqrange_multiplier = OrderedDict() # user-defined multiplier - self.dqrange_mode = OrderedDict() # range mode (stdev, %, absolute) - self.dqrange_slider = OrderedDict() # slider - self.dqrange_label = OrderedDict() # defined range - self.dqrange_max = OrderedDict() - self.dqrange_min = OrderedDict() + self.dqchkbox = {} # optimize + self.dqparam_name = {} # parameter name + self.dqinitial_label = {} # initial + self.dqopt_label = {} # optimtized + self.dqdiff_label = {} # delta + self.dqrange_multiplier = {} # user-defined multiplier + self.dqrange_mode = {} # range mode (stdev, %, absolute) + self.dqrange_slider = {} # slider + self.dqrange_label = {} # defined range + self.dqrange_max = {} + self.dqrange_min = {} self.chunk_list = [] self.lqnumsim = [] @@ -3330,7 +2894,7 @@ def __init__ (self): self.fontsize = dconf['fontsize'] self.linewidth = plt.rcParams['lines.linewidth'] = 1 self.markersize = plt.rcParams['lines.markersize'] = 5 - self.dextdata = OrderedDict() # external data + self.dextdata = {} # external data self.schemwin = SchematicDialog(self) self.m = self.toolbar = None self.baseparamwin = BaseParamDialog(self, self.startoptmodel) @@ -3479,7 +3043,7 @@ def clearDataFile (self): # clear external dipole data import simdat self.m.clearlextdatobj() - self.dextdata = simdat.ddat['dextdata'] = OrderedDict() + self.dextdata = simdat.ddat['dextdata'] = {} self.toggleEnableOptimization(False) self.m.plot() # recreate canvas self.m.draw() @@ -3705,7 +3269,7 @@ def clearCanvas (self): import simdat self.clearSimulationData() self.m.clearlextdatobj() # clear the external data - self.dextdata = simdat.ddat['dextdata'] = OrderedDict() + self.dextdata = simdat.ddat['dextdata'] = {} self.initSimCanvas() # recreate canvas self.m.draw() self.setWindowTitle('') diff --git a/run.py b/run.py index 7505f2cc4..3c2d53866 100755 --- a/run.py +++ b/run.py @@ -6,12 +6,35 @@ import os.path as op import os +import sys import numpy as np -from paramrw import usingOngoingInputs, write_gids_param -import specfn as specfn +from threading import Lock +import traceback +from time import sleep +from copy import deepcopy +from math import ceil, isclose +from PyQt5.QtCore import QThread, pyqtSignal, QObject from hnn_core import simulate_dipole, Network, MPIBackend from hnn_core.dipole import average_dipoles +import nlopt +from psutil import wait_procs, process_iter, NoSuchProcess + +from paramrw import usingOngoingInputs, write_gids_param +import specfn +import simdat +from paramrw import write_legacy_paramf + + +def _get_output_dir(): + """Return the base directory for storing output files""" + + try: + base_dir = os.environ["SYSTEM_USER_DIR"] + except KeyError: + base_dir = os.path.expanduser('~') + + return op.join(base_dir, 'hnn_out') def get_fname(sim_dir, key, trial=0, ntrial=1): @@ -58,18 +81,72 @@ def get_fname(sim_dir, key, trial=0, ntrial=1): return op.join(sim_dir, datatypes[key][0] + datatypes[key][1]) else: return op.join(sim_dir, datatypes[key][0] + '_' + str(trial) + - datatypes[key][1]) + datatypes[key][1]) + + +class TextSignal (QObject): + """for passing text""" + tsig = pyqtSignal(str) + + +class ParamSignal (QObject): + """for updating GUI & param file during optimization""" + psig = pyqtSignal(dict) + +class CanvSignal (QObject): + """for updating main GUI canvas""" + csig = pyqtSignal(bool, bool) -def simulate(params, data_dir, n_procs=None): + +def _kill_list_of_procs(procs): + """tries to terminate processes in a list before sending kill signal""" + # try terminate first + for p in procs: + try: + p.terminate() + except NoSuchProcess: + pass + _, alive = wait_procs(procs, timeout=3) + + # now try kill + for p in alive: + p.kill() + _, alive = wait_procs(procs, timeout=3) + + return alive + + +def _get_nrniv_procs_running(): + """return a list of nrniv processes running""" + ls = [] + name = 'nrniv' + for p in process_iter(attrs=["name", "exe", "cmdline"]): + if name == p.info['name'] or \ + p.info['exe'] and os.path.basename(p.info['exe']) == name or \ + p.info['cmdline'] and p.info['cmdline'][0] == name: + ls.append(p) + return ls + + +def _kill_and_check_nrniv_procs(): + """handle killing any stale nrniv processess""" + procs = _get_nrniv_procs_running() + if len(procs) > 0: + running = _kill_list_of_procs(procs) + if len(running) > 0: + pids = [str(proc.pid) for proc in running] + print("ERROR: failed to kill nrniv process(es) %s" % + ','.join(pids)) + + +def simulate(params, n_procs=None): """Start the simulation with hnn_core.simulate Parameters ---------- params : dict The parameters - data_dir : str - The base path for storing output files (e.g. ~/hnn_out/data) n_procs : int | None The number of MPI processes requested by the user. If None, then will @@ -95,6 +172,7 @@ def simulate(params, data_dir, n_procs=None): raise RuntimeError("No dipole(s) rerturned from simulation") # make sure the directory for saving data has been created + data_dir = op.join(_get_output_dir(), 'data') sim_dir = op.join(data_dir, params['sim_prefix']) try: os.mkdir(sim_dir) @@ -170,3 +248,388 @@ def simulate(params, data_dir, n_procs=None): time=dout['time'], freq=dout['freq'], TFR=dout['TFR'], TFR_L5=dout['TFR_L5'], TFR_L2=dout['TFR_L2']) + + +# based on https://nikolak.com/pyqt-threading-tutorial/ +class RunSimThread (QThread): + def __init__ (self,c,d,ntrial,ncore,waitsimwin,params,opt=False,baseparamwin=None,mainwin=None,onNSG=False): + QThread.__init__(self) + self.c = c + self.d = d + self.killed = False + self.ntrial = ntrial + self.ncore = ncore + self.waitsimwin = waitsimwin + self.params = params + self.opt = opt + self.baseparamwin = baseparamwin + self.mainwin = mainwin + self.onNSG = onNSG + self.paramfn = os.path.join(_get_output_dir(), 'param', self.params['sim_prefix'] + '.param') + + + # it would be ideal to display a dialog box, but we have to get that event back to the main window + # the next best thing is to print to the console and not crash the application + sys.excepthook = traceback.print_exception + + self.txtComm = TextSignal() + self.txtComm.tsig.connect(self.waitsimwin.updatetxt) + + self.prmComm = ParamSignal() + if self.baseparamwin is not None: + self.prmComm.psig.connect(self.baseparamwin.updatesaveparams) + + self.canvComm = CanvSignal() + if self.mainwin is not None: + self.canvComm.csig.connect(self.mainwin.initSimCanvas) + + self.lock = Lock() + + def updatewaitsimwin (self, txt): + # print('RunSimThread updatewaitsimwin, txt=',txt) + self.txtComm.tsig.emit(txt) + + def updatebaseparamwin (self, d): + self.prmComm.psig.emit(d) + + def updatedispparam (self): + self.c.commsig.emit() + + def updatedrawerr (self): + self.canvComm.csig.emit(False, self.opt) # False means do not recalculate error + + def stop (self): + self.killproc() + + def __del__ (self): + self.quit() + self.wait() + + def run (self): + msg='' + + if self.opt and self.baseparamwin is not None: + try: + self.optmodel() # run optimization + except RuntimeError as e: + msg = str(e) + self.baseparamwin.optparamwin.toggleEnableUserFields(self.cur_step, enable=True) + self.baseparamwin.optparamwin.clear_initial_opt_ranges() + self.baseparamwin.optparamwin.optimization_running = False + else: + try: + self.runsim() # run simulation + self.updatedispparam() # update params in all windows (optimization) + except RuntimeError as e: + msg = str(e) + + self.d.finishSim.emit(self.opt, msg) # send the finish signal + + + def killproc (self): + # make absolute sure all nrniv procs have been killed + _kill_and_check_nrniv_procs() + + self.lock.acquire() + self.killed = True + self.lock.release() + + def get_proc_stream (self, stream, print_to_console=False): + try: + for line in iter(stream.readline, ""): + if print_to_console: + print(line.strip()) + try: # see https://stackoverflow.com/questions/2104779/qobject-qplaintextedit-multithreading-issues + self.updatewaitsimwin(line.strip()) # sends a pyqtsignal to waitsimwin, which updates its textedit + except: + pass # catch exception in case anything else goes wrong + except ValueError: + # if process is killed and stream.readline() gives I/O error + pass + stream.close() + + # run sim command via mpi, then delete the temp file. + def runsim (self, is_opt=False, banner=True, simlength=None): + self.lock.acquire() + self.killed = False + self.lock.release() + + while True: + if self.ncore == 0: + raise RuntimeError("No cores available for simulation") + + try: + simulate(self.params, self.ncore) + break + except RuntimeError as e: + if self.ncore == 1: + # can't reduce ncore any more + print(str(e)) + self.updatewaitsimwin(str(e)) + _kill_and_check_nrniv_procs() + raise RuntimeError("Simulation failed to start") + except: + # if it's something else we still want to print the error and then + # handle it like a RuntimeError instead of crashing the whole application + txt = traceback.format_exc() + sys.stderr.write(txt) + self.updatewaitsimwin(txt) + # pop up dialog pop + raise RuntimeError("Unknown error") + + # check if proc was killed before retrying with fewer cores + self.lock.acquire() + if self.killed: + self.lock.release() + # exit using RuntimeError + raise RuntimeError("Terminated") + else: + self.lock.release() + + self.ncore = ceil(self.ncore/2) + txt = "INFO: Failed starting simulation, retrying with %d cores" % self.ncore + print(txt) + self.updatewaitsimwin(txt) + + # should have good data written to files at this point + simdat.updatedat(self.params) + + if not is_opt: + # update lsimdat and its current sim index + simdat.updatelsimdat(self.paramfn, self.params, simdat.ddat['dpl']) + + def optmodel (self): + import simdat + + need_initial_ddat = False + + # initialize RNG with seed from config + seed = self.params['prng_seedcore_opt'] + nlopt.srand(seed) + + # initial_ddat stores the initial fit (from "Run Simulation"). + # To be displayed in final dipole plot as black dashed line. + if len(simdat.ddat) > 0: + simdat.initial_ddat['dpl'] = deepcopy(simdat.ddat['dpl']) + simdat.initial_ddat['errtot'] = deepcopy(simdat.ddat['errtot']) + else: + need_initial_ddat = True + + self.baseparamwin.optparamwin.populate_initial_opt_ranges() + + # save initial parameters file + data_dir = op.join(_get_output_dir(), 'data') + sim_dir = op.join(data_dir, self.params['sim_prefix']) + param_out = os.path.join(sim_dir,'before_opt.param') + write_legacy_paramf(param_out, self.params) + + self.updatewaitsimwin('Optimizing model. . .') + + self.last_step = False + self.first_step = True + num_steps = self.baseparamwin.optparamwin.get_num_chunks() + for step in range(num_steps): + self.cur_step = step + if step == num_steps - 1: + self.last_step = True + + # disable range sliders for each step once that step has begun + self.baseparamwin.optparamwin.toggleEnableUserFields(step, enable=False) + + self.step_ranges = self.baseparamwin.optparamwin.get_chunk_ranges(step) + self.step_sims = self.baseparamwin.optparamwin.get_sims_for_chunk(step) + + if self.step_sims == 0: + txt = "Skipping optimization step %d (0 simulations)"%(step+1) + self.updatewaitsimwin(txt) + continue + + if len(self.step_ranges) == 0: + txt = "Skipping optimization step %d (0 parameters)"%(step+1) + self.updatewaitsimwin(txt) + continue + + txt = "Starting optimization step %d/%d" % (step + 1, num_steps) + self.updatewaitsimwin(txt) + self.runOptStep(step) + + if 'dpl' in self.best_ddat: + simdat.ddat['dpl'] = deepcopy(self.best_ddat['dpl']) + if 'errtot' in self.best_ddat: + simdat.ddat['errtot'] = deepcopy(self.best_ddat['errtot']) + + if need_initial_ddat: + simdat.initial_ddat = deepcopy(simdat.ddat) + + # update optdat with best from this step + simdat.updateoptdat(self.paramfn, self.params, simdat.ddat['dpl']) + + # put best opt results into GUI and save to param file + push_values = {} + for param_name in self.step_ranges.keys(): + push_values[param_name] = self.step_ranges[param_name]['final'] + self.updatebaseparamwin(push_values) + self.baseparamwin.optparamwin.push_chunk_ranges(step,push_values) + + sleep(1) + + self.first_step = False + + # one final sim with the best parameters to update display + self.runsim(is_opt=True, banner=False) + + # update lsimdat and its current sim index + simdat.updatelsimdat(self.paramfn, self.params, simdat.ddat['dpl']) + + # update optdat with the final best + simdat.updateoptdat(self.paramfn, self.params, simdat.ddat['dpl']) + + # re-enable all the range sliders + self.baseparamwin.optparamwin.toggleEnableUserFields(step, enable=True) + + self.baseparamwin.optparamwin.clear_initial_opt_ranges() + self.baseparamwin.optparamwin.optimization_running = False + + + def runOptStep (self, step): + self.optsim = 0 + self.minopterr = 1e9 + self.stepminopterr = self.minopterr + self.best_ddat = {} + self.opt_start = self.baseparamwin.optparamwin.get_chunk_start(step) + self.opt_end = self.baseparamwin.optparamwin.get_chunk_end(step) + self.opt_weights = self.baseparamwin.optparamwin.get_chunk_weights(step) + def optrun (new_params, grad=0): + txt = "Optimization step %d, simulation %d" % (step + 1, + self.optsim + 1) + self.updatewaitsimwin(txt) + print(txt) + + dtest = {} + for param_name, test_value in zip(self.step_ranges.keys(), new_params): # set parameters + if test_value >= self.step_ranges[param_name]['minval'] and \ + test_value <= self.step_ranges[param_name]['maxval']: + # print('optrun prm:', self.step_ranges[param_name]['initial'], + # self.step_ranges[param_name]['minval'], + # self.step_ranges[param_name]['maxval'], + # test_value) + dtest[param_name] = test_value + else: + # This test is not strictly necessary with COBYLA, but in case the algorithm + # is changed at some point in the future + print('INFO: optimization chose %.3f for %s outside of [%.3f-%.3f].' + % (test_value, param_name, + self.step_ranges[param_name]['minval'], + self.step_ranges[param_name]['maxval'])) + return 1e9 # invalid param value -> large error + + # put new param values into GUI and save params to file + self.updatebaseparamwin(dtest) + sleep(1) + + # run the simulation, but stop early if possible + self.runsim(is_opt=True, banner=False, simlength=self.opt_end) + + # calculate wRMSE for all steps + simdat.weighted_rmse(simdat.ddat, + self.opt_end, + self.opt_weights, + tstart=self.opt_start) + err = simdat.ddat['werrtot'] + + if self.last_step: + # weighted RMSE with weights of all 1's is the same as + # regular RMSE + simdat.ddat['errtot'] = simdat.ddat['werrtot'] + txt = "RMSE = %f"%err + else: + # calculate regular RMSE for displaying on plot + simdat.calcerr(simdat.ddat, + self.opt_end, + tstart=self.opt_start) + + txt = "weighted RMSE = %f, RMSE = %f"% (err,simdat.ddat['errtot']) + + print(txt) + self.updatewaitsimwin(os.linesep+'Simulation finished: ' + txt + os.linesep) # print error + + data_dir = op.join(_get_output_dir(), 'data') + sim_dir = op.join(data_dir, self.params['sim_prefix']) + + fnoptinf = os.path.join(sim_dir,'optinf.txt') + with open(fnoptinf,'a') as fpopt: + fpopt.write(str(simdat.ddat['errtot'])+os.linesep) # write error + + # save params numbered by optsim + param_out = os.path.join(sim_dir,'step_%d_sim_%d.param'%(self.cur_step,self.optsim)) + write_legacy_paramf(param_out, self.params) + + if err < self.stepminopterr: + self.updatewaitsimwin("new best with RMSE %f"%err) + + self.stepminopterr = err + # save best param file + param_out = os.path.join(sim_dir,'step_%d_best.param'%self.cur_step) + write_legacy_paramf(param_out, self.params) + if 'dpl' in simdat.ddat: + self.best_ddat['dpl'] = simdat.ddat['dpl'] + if 'errtot' in simdat.ddat: + self.best_ddat['errtot'] = simdat.ddat['errtot'] + + if self.optsim == 0 and not self.first_step: + # Update plots for the first simulation only of this step (best results from last round) + # Skip the first step because there are no optimization results to show yet. + self.updatedrawerr() # send event to draw updated error (asynchronously) + + self.optsim += 1 + + return err # return error + + def optimize(params_input, evals, algorithm): + opt_params = [] + lb = [] + ub = [] + + for param_name in params_input.keys(): + upper = params_input[param_name]['maxval'] + lower = params_input[param_name]['minval'] + if upper == lower: + continue + + ub.append(upper) + lb.append(lower) + opt_params.append(params_input[param_name]['initial']) + + if algorithm == nlopt.G_MLSL_LDS or algorithm == nlopt.G_MLSL: + # In case these mixed mode (global + local) algorithms are used in the future + local_opt = nlopt.opt(nlopt.LN_COBYLA, num_params) + opt.set_local_optimizer(local_opt) + + opt.set_lower_bounds(lb) + opt.set_upper_bounds(ub) + opt.set_min_objective(optrun) + opt.set_xtol_rel(1e-4) + opt.set_maxeval(evals) + opt_results = opt.optimize(opt_params) + + return opt_results + + txt = 'Optimizing from [%3.3f-%3.3f] ms' % (self.opt_start, + self.opt_end) + self.updatewaitsimwin(txt) + + num_params = len(self.step_ranges) + algorithm = nlopt.LN_COBYLA + opt = nlopt.opt(algorithm, num_params) + opt_results = optimize(self.step_ranges, self.step_sims, algorithm) + + # update opt params for the next round + for var_name, new_value in zip(self.step_ranges, opt_results): + old_value = self.step_ranges[var_name]['initial'] + + # only change the parameter value if it changed significantly + if not isclose(old_value, new_value, abs_tol=1e-9): + self.step_ranges[var_name]['final'] = new_value + else: + self.step_ranges[var_name]['final'] = \ + self.step_ranges[var_name]['initial'] From 36380b6ee23dfde76c54259f8ffaad7f86c5d7bd Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 12:57:29 -0400 Subject: [PATCH 022/107] MAINT: cleanup: remove NSG and linter fixes --- conf.py | 10 +- hnn_qt5.py | 48 +------- installer/README.md | 2 +- installer/nsg/README.md | 184 ----------------------------- installer/nsg/install_pngs/001.png | Bin 167334 -> 0 bytes installer/nsg/install_pngs/002.png | Bin 245659 -> 0 bytes installer/nsg/install_pngs/003.png | Bin 190074 -> 0 bytes installer/nsg/install_pngs/004.png | Bin 121829 -> 0 bytes installer/nsg/install_pngs/005.png | Bin 115031 -> 0 bytes installer/nsg/install_pngs/006.png | Bin 238258 -> 0 bytes installer/nsg/install_pngs/007.png | Bin 352683 -> 0 bytes installer/nsg/install_pngs/008.png | Bin 347659 -> 0 bytes installer/nsg/install_pngs/009.png | Bin 239711 -> 0 bytes installer/nsg/install_pngs/010.png | Bin 211396 -> 0 bytes installer/nsg/install_pngs/011.png | Bin 217476 -> 0 bytes installer/nsg/install_pngs/012.png | Bin 216171 -> 0 bytes run.py | 6 +- simdat.py | 123 +++++++++---------- 18 files changed, 71 insertions(+), 302 deletions(-) delete mode 100644 installer/nsg/README.md delete mode 100644 installer/nsg/install_pngs/001.png delete mode 100644 installer/nsg/install_pngs/002.png delete mode 100644 installer/nsg/install_pngs/003.png delete mode 100644 installer/nsg/install_pngs/004.png delete mode 100644 installer/nsg/install_pngs/005.png delete mode 100644 installer/nsg/install_pngs/006.png delete mode 100644 installer/nsg/install_pngs/007.png delete mode 100644 installer/nsg/install_pngs/008.png delete mode 100644 installer/nsg/install_pngs/009.png delete mode 100644 installer/nsg/install_pngs/010.png delete mode 100644 installer/nsg/install_pngs/011.png delete mode 100644 installer/nsg/install_pngs/012.png diff --git a/conf.py b/conf.py index 233496465..c7b554351 100644 --- a/conf.py +++ b/conf.py @@ -15,10 +15,6 @@ [run] dorun = 1 doquit = 1 -debug = 0 -testlfp = 0 -testlaminarlfp = 0 -nsgrun = 0 [paths] paramindir = param homeout = 1 @@ -153,7 +149,7 @@ def readtips (d): dbase = os.path.join(os.path.expanduser('~'),'hnn_out') # user home directory if not safemkdir(dbase): sys.exit(1) # check existence of base hnn output dir else: # cwd for output - dbase = os.getcwd() # use os.getcwd instead for better compatability with NSG + dbase = os.getcwd() d['dbase'] = dbase d['datdir'] = os.path.join(dbase,'data') # data output directory @@ -166,10 +162,6 @@ def readtips (d): d['dorun'] = confint("run","dorun",1) d['doquit'] = confint("run","doquit",1) - d['debug'] = confint("run","debug",0) - d['testlfp'] = confint("run","testlfp",0) - d['testlaminarlfp'] = confint("run","testlaminarlfp",0) - d['nsgrun'] = confint("run","nsgrun",0) d['drawindivdpl'] = confint("draw","drawindivdpl",1) d['drawavgdpl'] = confint("draw","drawavgdpl",0) diff --git a/hnn_qt5.py b/hnn_qt5.py index 68dd15738..fd611256a 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -69,8 +69,6 @@ def parseargs (): simf = dconf['simf'] paramf = dconf['paramf'] -debug = dconf['debug'] -testLFP = dconf['testlfp'] or dconf['testlaminarlfp'] param_fname = os.path.splitext(os.path.basename(paramf)) basedir = os.path.join(dconf['datdir'], param_fname[0]) @@ -104,8 +102,6 @@ def _add_missing_frames(tb): if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] else: plt.rcParams['font.size'] = dconf['fontsize'] = 10 -if debug: print('getPyComm:',getPyComm()) - hnn_root_dir = os.path.dirname(os.path.realpath(__file__)) DEFAULT_CSS = """ @@ -750,7 +746,6 @@ def scalegain (self, k, fctr): oldval = float(self.netparamwin.dqline[k].text().strip()) newval = oldval * fctr self.netparamwin.dqline[k].setText(str(newval)) - if debug: print('scaling ',k,' by', fctr, 'from ',oldval,'to ',newval,'=',oldval*fctr) return newval def isE (self,ty): return ty.count('Pyr') > 0 @@ -760,7 +755,6 @@ def tounity (self): for k in self.dqle.keys(): self.dqle[k].setText('1.0') def scalegains (self): - if debug: print('scaling synaptic gains') for _, k in enumerate(self.dqle.keys()): fctr = float(self.dqle[k].text().strip()) if fctr < 0.: @@ -768,7 +762,6 @@ def scalegains (self): self.dqle[k].setText(str(fctr)) elif fctr == 1.0: continue - if debug: print(k,fctr) for k2 in self.netparamwin.dqline.keys(): l = k2.split('_') ty1,ty2 = l[1],l[2] @@ -2811,7 +2804,6 @@ def saveparams (self, checkok = True): return oktosave def updatesaveparams (self, dtest): - if debug: print('BaseParamDialog updatesaveparams: dtest=',dtest) # update parameter values in GUI (so user can see and so GUI will save these param values) for win in self.lsubwin: win.setfromdin(dtest) # save parameters - do not ask if can over-write the param file @@ -3094,21 +3086,18 @@ def showSomaVPlot (self): else: outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') lcmd = [getPyComm(), 'visvolt.py',outparamf] - if debug: print('visvolt cmd:',lcmd) Popen(lcmd) # nonblocking def showPSDPlot (self): # start the PSD visualization process (separate window) outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') lcmd = [getPyComm(), 'vispsd.py',outparamf] - if debug: print('vispsd cmd:',lcmd) Popen(lcmd) # nonblocking def showSpecPlot (self): # start the spectrogram visualization process (separate window) outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') lcmd = [getPyComm(), 'visspec.py',outparamf] - if debug: print('visspec cmd:',lcmd) Popen(lcmd) # nonblocking def showRasterPlot (self): @@ -3124,7 +3113,6 @@ def showRasterPlot (self): return if dconf['drawindivrast']: lcmd.append('indiv') - if debug: print('visrast cmd:',lcmd) Popen(lcmd) # nonblocking def showDipolePlot (self): @@ -3139,7 +3127,6 @@ def showDipolePlot (self): QMessageBox.information(self, "HNN", "WARNING: no dipole data at %s" % dipole_file) return - if debug: print('visdipole cmd:',lcmd) Popen(lcmd) # nonblocking def showwaitsimwin (self): @@ -3311,11 +3298,6 @@ def initMenu (self): runSimAct.setStatusTip('Run simulation') runSimAct.triggered.connect(self.controlsim) - runSimNSGAct = QAction('Run simulation on NSG', self) - runSimNSGAct.setShortcut('Ctrl+N') - runSimNSGAct.setStatusTip('Run simulation on Neuroscience Gateway Portal (requires NSG account and internet connection).') - runSimNSGAct.triggered.connect(self.controlNSGsim) - self.menubar = self.menuBar() fileMenu = self.menubar.addMenu('&File') self.menubar.setNativeMenuBar(False) @@ -3372,12 +3354,6 @@ def initMenu (self): viewSomaVAction.triggered.connect(self.showSomaVPlot) viewMenu.addAction(viewSomaVAction) - if testLFP: - viewLFPAction = QAction('View Simulation LFPs',self) - viewLFPAction.setStatusTip('View LFP') - viewLFPAction.triggered.connect(self.showLFPPlot) - viewMenu.addAction(viewLFPAction) - viewSpecAction = QAction('View Spectrograms',self) viewSpecAction.setStatusTip('View Spectrograms/Dipoles from Experimental Data') viewSpecAction.triggered.connect(self.showSpecPlot) @@ -3413,7 +3389,6 @@ def initMenu (self): setParmAct.triggered.connect(self.setparams) simMenu.addAction(setParmAct) simMenu.addAction(runSimAct) - if dconf['nsgrun']: simMenu.addAction(runSimNSGAct) setOptParamAct = QAction('Configure Optimization', self) setOptParamAct.setShortcut('Ctrl+O') setOptParamAct.setStatusTip('Set parameters for evoked input optimization') @@ -3620,9 +3595,7 @@ def onActivateSimCB (self, s): # load simulation when activating simulation combobox global paramf,basedir import simdat - if debug: print('onActivateSimCB',s,paramf,self.cbsim.currentIndex(),simdat.lsimidx) if self.cbsim.currentIndex() != simdat.lsimidx: - if debug: print('Loading',s) paramf = s param_fname = os.path.splitext(os.path.basename(paramf)) basedir = os.path.join(dconf['datdir'], param_fname[0]) @@ -3631,7 +3604,6 @@ def onActivateSimCB (self, s): def populateSimCB (self): # populate the simulation combobox - if debug: print('populateSimCB') global paramf self.cbsim.clear() import simdat @@ -3698,13 +3670,6 @@ def controlsim (self): self.optMode = False self.startsim(self.baseparamwin.runparamwin.getntrial(),self.baseparamwin.runparamwin.getncore()) - def controlNSGsim (self): - # control simulation on NSG - if self.runningsim: - self.stopsim() # stop sim works but leaves subproc as zombie until this main GUI thread exits - else: - self.startsim(self.baseparamwin.runparamwin.getntrial(),self.baseparamwin.runparamwin.getncore(),True) - def stopsim (self): # stop the simulation if self.runningsim: @@ -3734,12 +3699,11 @@ def optmodel (self, ntrial, ncore): self.setcursors(Qt.WaitCursor) print('Starting model optimization. . .') - if debug: print('in optmodel') self.runningsim = True self.statusBar().showMessage("Optimizing model. . .") - self.runthread = RunSimThread(self.c, self.d, ntrial, ncore, self.waitsimwin, self.params, opt=True, baseparamwin=self.baseparamwin, mainwin=self, onNSG=False) + self.runthread = RunSimThread(self.c, self.d, ntrial, ncore, self.waitsimwin, self.params, opt=True, baseparamwin=self.baseparamwin, mainwin=self) # We have all the events we need connected we can start the thread self.runthread.start() @@ -3749,7 +3713,7 @@ def optmodel (self, ntrial, ncore): self.qbtn.setEnabled(False) bringwintotop(self.waitsimwin) - def startsim (self, ntrial, ncore, onNSG=False): + def startsim (self, ntrial, ncore): global paramf # start the simulation @@ -3762,12 +3726,9 @@ def startsim (self, ntrial, ncore, onNSG=False): print('Starting simulation (%d cores). . .'%ncore) self.runningsim = True - if onNSG: - self.statusBar().showMessage("Running simulation on Neuroscience Gateway Portal. . .") - else: - self.statusBar().showMessage("Running simulation. . .") + self.statusBar().showMessage("Running simulation. . .") - self.runthread=RunSimThread(self.c,self.d,ntrial,ncore,self.waitsimwin,self.params,opt=False,baseparamwin=None,mainwin=None,onNSG=onNSG) + self.runthread=RunSimThread(self.c,self.d,ntrial,ncore,self.waitsimwin,self.params,opt=False,baseparamwin=None,mainwin=None) # We have all the events we need connected we can start the thread self.runthread.start() @@ -3783,7 +3744,6 @@ def startsim (self, ntrial, ncore, onNSG=False): def done (self, optMode, except_msg): # called when the simulation completes running - if debug: print('done') self.runningsim = False self.waitsimwin.hide() self.statusBar().showMessage("") diff --git a/installer/README.md b/installer/README.md index 00d931778..cf99bb984 100644 --- a/installer/README.md +++ b/installer/README.md @@ -14,4 +14,4 @@ HNN also works on cloud and HPC environments: If you are running into problems with the instructions given for your machine, we recommend using the VirtualBox VM with HNN pre-installed: -* [HNN VirtualBox install instructions](virtualbox) \ No newline at end of file +* [HNN VirtualBox install instructions](virtualbox) diff --git a/installer/nsg/README.md b/installer/nsg/README.md deleted file mode 100644 index e90052e4a..000000000 --- a/installer/nsg/README.md +++ /dev/null @@ -1,184 +0,0 @@ -# Running HNN on Neuroscience Gateway Portal (NSG) - -## HNN GUI is temporality not available on NSG, please email us at hnneurosolver@gmail.com if you have questions about running HNN on NSG - ---- - -The [Neuroscience Gateway Portal (NSG)](http://www.nsgportal.org) is high-performance compute environment sponsored by NSF/NIH and the BBSRC (UK). Availability of HNN on NSG enables users to run simulations on one of many cluster compute nodes, which reduces simulation runtime. In addition, users will not have to install HNN on their local workstations, allowing them to get started running simulations more quickly. Below are instructions on how to run HNN on NSG. - -## Getting an account on NSG - -1. Create your NSG account [here](https://www.nsgportal.org/gest/reg.php ) if you don't already have one - -2. Log in to NSG through a web browser [here](https://nsgdev.sdsc.edu:8443/portal2/login!input.action) - -## Preliminary software (VNC, ssh) - -You should make sure you have a VNC viewer (e.g. Tightvnc) and `ssh` available from a terminal window. These tools are included by default with Mac/Linux but you may need to install additional software for Windows. - -[RealVNC](https://www.realvnc.com/en/connect/download/viewer/) is available on many platforms, but is not open source. - -For Ubuntu Linux you can install a vnc viewer using the following command from the terminal: - -```bash -sudo apt install xvnc4viewer -``` - -## Running HNN on NSG - -1. You will need to upload some data since NSG jobs require input data. The data can be a zip file with arbitrary content. To do so, click on **New Folder** on the front page, and enter a **Label**. Next, click on **Data**, underneath the new folder on the left hand side, then **Upload/Enter Data** to upload an arbitrary file with an arbitrary **Label**. - -2. Click on Toolkit on the top menu: - - - - This will take you to a list of computational neuroscience tools available on NSG: - - - -3. Click on **VNC on Jetstream** at the bottom of the list: - - - - This will create a new "task" that can allow you to access a desktop on the Jetstream supercomputer through VNC: - - - -4. If you want to customize how much time you have to run HNN once your VNC job is created, you can click on **Set Parameters** and then enter the **Maximum Hours to Run** (0.5 is the default). - -5. Enter a **Description** for your task in the text field next to **Description**. For example, you could write "HNN simulation". - -6. Click on **Select Data** then pick the zip file you uploaded in step 3. - -7. Click **Save Task**. This will take you to a list of available tasks to run: - - - -8. Click **Run Task** for the task you created to start the job. Click **OK** on the resulting dialogue box. - -9. Click on **View Status** to see how the job is progressing and to get the information needed to login to NSG using VNC. - - - -10. After a few minutes (or longer, depending on others' use of NSG) you will receive an email from [](nsguser@jetstream-cloud.org) stating that your VNC session is ready, and including instructions for how to login to your VNC session, which are as follows, but are explained in more detail below: - - 1. On your local machine, start an ssh tunnel from your local port 5902 to the vnc server node port 5901. Use the password in **Intermediate Results**: `ssh.password` - - ```bash - ssh -l nsguser -L 5902:localhost:5901 149.165.157.55 - ``` - - 2. Use a VNC client from [above](./README.md#preliminary-software-vnc-ssh) - - 3. Then start then vnc viewer to `localhost:2` - Use the password in **Intermediate Results**: `vnc.password` - - To examine or retrieve files on the VNC node, ssh or scp may be used: - - ```bash - ssh -l nsguser 149.165.157.55 - ``` - - For the hnn data directory: - - ```bash - scp -pr 'nsguser@149.165.157.55:~/hnn/data/*' mydownloaddir - ``` - - or for the entire job directory: - - ```bash - scp -pr 'nsguser@149.165.157.55:~' mydownloaddir - ``` - -11. Click on the **List** link next to **Intermediate Results**. This will bring up a window listing files after a few minutes that contains a link to `vnc.password` and `ssh.password`: - - - - You should download both of these files to your computer. - -12. Start a terminal. - -13. Enter the following command in the terminal: - - ```bash - ssh -l nsguser -L 5902:localhost:5901 149.165.157.55 - ``` - - When prompted for a password, use the text inside of the `ssh.password` file you downloaded in step 11. In order to see the text inside of the `ssh.password` file, enter the following command from the directory where you have stored the file (in a separate terminal): - - ```bash - more ssh.password - ``` - -14. Enter the following command in a separate terminal: - - ```bash - vncviewer localhost:2 - ``` - - If this does not work, you may have to switch to the VNC Viewer folder before running the vncviewer command. For example, on the Windows operating system, you would first type: - - ``` - cd C:\Program Files\RealVNC\VNC Viewer - ``` - - When prompted for a password use the text inside the vnc.password file you downloaded in step 11. (Note that the vncviewer command depends on which VNC software you have installed on your computer). This will open a VNC viewer display of your NSG desktop: - - - -15. In the VNC viewer window Click on **Applications** -> **System tools** -> **Terminal** - - This will start a command line terminal as shown here: - - - -16. In the terminal, enter the following command: - - ```bash - hnn - ``` - - This will start the HNN graphical user interface as shown here: - - - -17. Use HNN to Run simulations by clicking on **Run Simulation**. If asked whether to overwrite a file, click **OK**. After that, HNN will run its default ERP simulation, producing the output shown here: - - - - You can now continue to run other simulations, as desired. For more information on how to use HNN to run simulations and compare model to experimental data, see our [Tutorials](https://hnn.brown.edu/index.php/tutorials/) - -18. When you are done running simulations, log out of the VNC viewer by clicking first on the **nsguser** menu in the top right corner, and then on **Quit**, as shown here: - - - - When prompted click on **Log out**. NSG will then create an output file that you can download. - -19. You can download the data using either `scp` (19.1) or NSG's web browser interface (19.2). - - 1. To use `scp` for the hnn data directory enter the following in the terminal: - - ```bash - scp -pr 'nsguser@149.165.157.55:~/hnn/data/*' mydownloaddir - ``` - - or for the entire job directory: - - ```bash - scp -pr 'nsguser@149.165.157.55:~' mydownloaddir - ``` - - When prompted for a password, use the text in the `ssh.password` you downloaded in step 11. Note that `mydownloaddir` is a local directory on your computer that you want to download the data to. - - 2. Refresh the task list in the web browser. Then click on output. After a few minutes you will see a file `output.tar.gz` in the list, which contains the output data from the simulations you ran. After downloading it, you can extract its contents using the following command from the terminal: - - ```bash - tar -xvf output.tar.gz - ``` - - This will extract the data into a new directory. You can browse the output in the data subdirectory, and it will contain one subdirectory for each simulation run. See the HNN website for more information on the content of these files. - -## Troubleshooting - -For HNN software issues, please visit the [HNN bulletin board](https://www.neuron.yale.edu/phpBB/viewforum.php?f=46) diff --git a/installer/nsg/install_pngs/001.png b/installer/nsg/install_pngs/001.png deleted file mode 100644 index c7678d378dc988b483370c1486bf5eb97af50b23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167334 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 z 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 Zn1;`2&S`y>Xk?im-dC2*m45p6zX5xd&FKID diff --git a/installer/nsg/install_pngs/002.png b/installer/nsg/install_pngs/002.png deleted file mode 100644 index 7e1c32fef817f5bd4385ed0b8555cb9e17edebfe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 245659 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]AE5{R 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�L2TQFPTV;`vIGKe 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νjlj|HbzE!xFA;*V_m-xqe*(WvQttbeqR({Bu7*kC-HK;_P;ztMtqQRy0W9+!{66`Jf}^uo}i}M>!=WRSyWOdU)Fm|h1`S3Gw#LtPxWsc z&zCUbXt!|sXeN}!R?=N$G@aM`KIr5cfuiJi^m-I>?>O1`d~@T-7D8ZsIRE2lLd!W z`BI8dx$`FuZ6>p!@AJ4~dxB9uauZ#xSTDiK9z#Y(`TH?t>bT2?i}%Wk0+X7>Hn(hY zAiGLWab2>Nmah9sSlC+YL#1w>gZ^GPKBA4_=+$G}0}34nD=Wfe_Q$pLSJ&ztI@Ab3 z9V+}?{QMX1v5IKn_-gm#4DG>cCmoj`6whKXo~n%iTOKa1gO>Hdz!T2d*_D>tT6xkn zYNyHqZo=8iemKZGxBnzm1=qI6k9_Wb(udc!IinjVj^Q)&xv434 z;2Qi{8lN{WE>|*Oci{qAO8PB%$RaX~_XL%DV#>LY^Z$awJdHndo z-`kibmEATJr1EX5G|+*CNBMzK5iEAt#$Tu|fzt~VfB%rsAA+`MpMI$nlUkC=J4js+ zmo&K=$De*;uy3N*y5LFF&1syTsNj8~`)YuNU_vUEaNzp!rsR*f`wX|!2iT!VkA&onhu?-mDBb_l9+=N3B@Mr8dFR^mUTIe-QU*L_R!{nQY{4rT!YH}* zxgL`m;|94@sGE4Yg?;C{M0fAsKAkuFO-G|DDH>NZE?>zF7PtL4#!f>K z>L8%z+o(Yv+|Uy6`eQ`7lW& zvkHbEJIGs3lT!wa0>2KPwQW;F!-iP@!r}l z(PCL#a6`Q}fKP&waJ7{KIW?mB9otHB=PYcE!EL~KrFiuGyDA;3TXsco(JW(*Xh@uP zy;0|DKv(bKP2Tzg`BmiISJO&pEVEVvibgJyS=IV!uwPqUsoPjceHM}!+}_(tTwyaLQt9HAuS3(W|2 zXmr1y@n)uME`a8QghZgs#cW@`cML9I&S(8G%LP#34J{Ga*!xZ_J90~1czCWvGqouJ zjJ7{{@88=Jj5*CQ9Q$ASZPE@uY%P^U@-*Y(0-(1CICL_KPoKm2gsS;aVVi9sL=K0K z90_-JR{Y9H;5aZaF;PH)0W=qUa+1$fok-WNxMNveD_7an(b%&dH&o~f?zHo?)s+q3 z%>^eZ;JTvKzX z$~PaXR9E%5X6q@xTAY9=U{YlqNjqlperFizkgAf||3lVWKvlVRZQC~;BBg{hs0c_) zhm;}$A|N3kNT*15s)UkCr?h}{cZdkm4bln%(%tpX^}OHn{qGo`!#&2{ZdvQTS6tV; z<~+~il=^e&=p4Pf9!T8T(Sb)u__}c%${m!U0|gX%@Zi0R5{Qp2mc4?#g?iJDP8|S( z;Lti{SORflKaL$XT@*2IqPD_x{;29!_}cmCY|~D)1?C8&71gL!oX5T`H#{#;Jx{x9 zk{3>KsNAn>tTr3=8Bta<5z)1*yyB{879P&*a$;-PPISll%vQDiYNR0!b{^XA?D{rK znXa3v1#jaolz%?V`+Xn_Pw*ehf!%SU%w4;=Sb ztY&ptpWVHc{I2}z{*^7|$7|@^1!nnYBwjaPu;S@j8y+UzWi6;mSZk?KoZg6{(rDfl zt*H^ZEph+oed|Nk_`%8Su=a(}${#PybbAYXSnj>(DAaYwZSSo9uNHu%YEVB`*<6;( z2JgvZ3%U1>{JTeLZ~kmm1j$}`X{3Lc!qt;o#)Q8g?ucbB{IG-{y-jLkhgqoh((mF*e|RBgs|wBw?|uNnc)*`N2CZ-~_=K%+AF@cjaVsv-Z#t##LhC{U|D-P#J1%;$SnXHLMU#YR^bt$D3+>2%9Vu9h#@v)J z=coki6X^ONp8{2L!I2XFlG{nzuOT?3u|~^pM?qf^s8!_xRGKW;!4nL7VI1&4Bcs=z z$B08m3985OSCF_W_MIDK4?CKmafc{)xMYY~gvdE)jJ{uvQOzAbUgMxkQlE z%EBVY7Cp$vf!hLlH$*Zi|JI`C2f>1z%uHkG%EKZ7FBqUW#T^zyggUMu;BbHqz6MZ# zF(&W~Be4C0j!xK{8@hzKYC%?yMA`#jeh3R3D%*RBVH$MQBbH8)qWyMJupL8l0xZgp zyzG_)1jm~+^1K$JHKAR}^hGuZ#zHtS3OHDU5c&a-Q-Qtmm)pum%6%(H@1+GR6MVA( zDfip+4R_m|YFwC>(G%!)fTC)2c=)}bIyJyS>OiXo_!ba~Wc)zL+g#yz4skn~nV5{X zB0E4*0!~lJ;K|om_2H}g`mi@!9J&*5tT?{U(*~pURp_UIg4cBk@jdbrxrQmV_V#(u z25hh{-kLAo7KgsjN9~Xd7r3ge$qW}8#BYx?MDxWWT&VF>O8`pk2&)$kP(5-hd+EY%k-!JPQQG1PBN6HS#j%y5Y5K0`C~Kr@Y`h3GgJCKD&YM0NPbfOOAI!wqYs- zu;Bi3E!;V;zfKxyqsBNNh9o3uaN_3Oq?#$OpxcUVA?z-jpMMMGUcYmad#m8DLgkZ; z0hCo1#!k?0PXOc>^lbrv2xuSFp5n3rnlw{8|5H#XqDewDPQqDrcz7RtHO}XPxq9Px zGAgLnanpcu;||?su7gqfOgIYEi3rk zeEs?rWG3j%@B@3IB1gV={y@_$p4UVkGP4W~nZ9$+`dpsF%1*kit+8Lb<00bA8v(^qA8B zAf_9r2d!{&PH|g|hi$Z)&yyWvC(p?B=q5W ztW+poX{{HfLCDtvoPq;1>!6S*cmB#6LR^82J#UW=PS0~Fte}0_8(l*~EDh2V2yD;+jUn7MIyyRNIvYUO0nI+t zK{wsC3FFBSjE0}Xk^DJ5=<9aN`tDQT_jKKT&L;g2z-}R6azh$68auYT%AK>& ziOl}{p^jX{h~nAIYHgaXCnf43- zElSRKAppwzn2CJwEs^S*QPaP89c?dq$Cz3uH-to+o$xC6m#4mo+V1}XHYC&1hT9HZ zq8kUtygj>AkRh#aZ+Ei8#Lo5=WJj&_CVv$*yBtI_ML|P{Vw2(nqV` zz75w3TPPFPrEA|@shAPohZ08g&cn@GhbGykd=LAG z>%N9#dM|VAz^;y^o3u1K0;|Zl$}Du|**G}XVS#YU}$A&WR|e zKWfrEStY7U294MFwj=_R~9VP!U)n1B>a@3ZYk+u86v1wm$$Nf-Ge=4G&xH-faj zq`0B_esadYNuzLaXAnA|;Oqn@kn#!3!ah7d`8*J27T}F=x{m?JDOh}y?wN%>7h22B z{(J(<8!YR4n?9GPfU3jr1!+kLEHa?jfIJ3B4FpRXtadO_BpFjhQ22DzY0S_S)HYtw z*GEB`#uQQbOVGX@LCg!^<0MC02M1I-2dr4oZUK3`>Gls;5>a_QaF#MWXaBqe=E)yc zONuXa?O?+STQ-H59T>7ap(6%+FD$5cZr%F3xL7Gf!o|tC0XGRBg^G+v;PiA|j5lk| z{0Rye(2;CA0U977VMWM;a=x=ueyQ-oDaGAS1AIWTqX{s8_5r8fEJVO9!67m%S6v{n zAT#s9)Fg22^{XaPH23WS=sdu{5E4oGVL(8|ZyEK1lOAAtP`!RpN2-e zhM0;z`Wk!FOPnrjOkJC&uCJ8DfYuXv!F2~!pCW~6exZ;u8c(v=nB2bz|pl8#O$zw5jK zX(Y(6=QF)Kb9WtAqf=fbHJ6IDNvgpJdH$$q0JfpP%F9~+>FCnRBLGw06{W(7lRm<%{M>!u4w zEJ>uVI!lhS! za3q3i82+$8gU9;~G3gLlBH!cu%o*7l$3W&-MW#<;a_X}Dehc-& zPk6-0-LkKlGUm0TS&BPjxro~JcNPmUT;F0A%rpu##JP$sV^vl4Zu}KFrj!^5-xJZp zh986?RBfgC&(Ejr4ifeV5%{R4;6mz*wnplF2>oUGH8Y#3FKw692XBRQ4IT^;!G$j;s*!^p7NDt3^4~75Z$sTP>I8TWpGSM3 z+MK*#i-r@)ktkqz3L#Y{Cx_T-<81K&x)mT`fFr1aj`myPTYL)(>8Z~>9=mx}e86~s zkuFKx+@?Mj$7!~rWHwAb=HN@2dZQW1=YQd!5_(8aOTW1ACG)~o*z7;29n^s*h*)bj z7M{pl2Rz(WRfn)306KnRX3&^7cVq=Ng9_zu(~q@ZFAUtp!il^aY2_AjyIBrFT0^pr(f1LO9m}pA{VuF3z9aWZq3MFLobWt&&sT9mY?5@pyoDugnN$won5LPUC5<#P%4;y{V(}! z$K9!?7?O>z8UOpZT-s34YD6%ho`k%^xO9=fEAg5F*QM!~G={C~i!T=HW0QWg1>}*9 zjOMd3=8-Rr;bn;wrcE#nhvU8tG%Qd4KGh)3XIUZ77A?R^_7rt_@Vj*uM+RRX1+Th% zV)(JE{n5(8!a#i^sa>IR154qn69cRjbUv9Y>3F8!xF z;s1S3pZlMe;PBMVed}!J5f2p5VWFYco2G!hz7J0S?Cd+zISte(;?RG7Z(lM>5&sN% zBZ0LG*r<;Pxzw38G%d1J$7c%u|BHBEsVvr4_5WNf&@8R^5)NKt|M$ZF^PuG+`OU)$|&IOd0_2n5{^?G=Ms zf?wNNK)SXjzQmsy+?V2AouMhu^_NeXba5Jw zr)Tz@U*_Hheef#^1zQ3@AFOT_3X=h2X>`v?v}7T zEy!OFwPtTxlq>wvO&GjOGci&X%Hi-;(adJma;1QV%Upf%h~;o2jZ<~8NOPQ_YS6Vt zQbPD!QvU9-o}+kIJD<#Hss7Pf?J=wn4Sx5mmljRc(}pKU7r`*&t> zP`57fEa}--BF9LHOF2v#DQ%T`O$zyf{H?Y)Ih)v~Uw6X4wVWsEJ$r6Z{`~m|zc7Mf z!q9!&XOAcAbZt3`8fRLrZmx~A2>?C}3WsE{t2Q-Vf+s_rO?@(+oKhxv{PT-J+mhmv z!R8aIzu#W7)xd`d8$oMk_MK@V8;R%r+VjWB6#4f%ceFEJ;ul56fGZrAhlkbvv*7}t z5C%{xTt$_&2NEr&WhkQ_=(uyh3+PdWH3L&X;1BH9A8Go1e89AQZ~*oRM`h)YM{{tl z1yS)O92y4q=!3*2)(>EO5U=E13m)q^Kt0;m)PsO}eFS-X6l@3;>&%Qbbi}7A3#Lj#+9aX$GH1y&AVxR;jTwm` z%7%aSydR_@$z1nRMzrc=YcIEN)|wk%6}WJW*LrF0X{uc85b~wE_hh4|mitBpi&SpZ zU?6@dg<}jq8=k(eMu6s3X6OBZvjt77!Y1O=_DSZ_va_!*-Zh1If0dX$)0JvoR*%`u zlEuR!bMD4qK=9BhsMK$*elZ#2qPmtFaegsNVxy^}VX|z7y=%Q!d$r!TC@Q{;dL1`4 z;X8yhC=cKw0(u1#`^JuA{Rfx+r!HlsXK74BPB%pxXOk6nzj27O{Hy+W8L6Z$@XPrx zHJSb5bj(b_y;9NL&6uS*z~+n?eq3X^ZB>mBukVi8EhCfEo* zuk$n4nNACxwNKw__)GN?lLkTmsA9eyy>jO}Ul(2Qp8ats<@hCGOnYFVdWeLoP*rgI zU%!LbJ-SH@GjkK$44xwth;#k8PaCYBS&{S zMzmZOsoOE9Y98;AADq)*SS^~~IX|%8l~`Cx9NDDdD&%n>#ia2iVBF9U_enSIyGG*0 zG(CCOr}x!-RSJjO@q^{UM=}#L_HYj3djcUi7|oy>g|0E+HQ?l_Gub%M^7-di;pmr} zOws$ej*s}%trtToG-Q7hw}^egOgQNOX?XU6x~K9?dx(OSEW=O?$9d~xf0M+NC$y5F z^&Aio0P|phTemWEa{hsP70VYq1{M$r)#OoiNehf~Z)itbT3JojF(L?XeS$(lXvE$5 z+U@4((*V}owH(U~C^{Yy>ZwqI>{+7-j*3t889(n$8rq8e>gxV^sX z_Qu4kQ>yN)p2FF)-syIpfRfaz+mz_UHROxaVS>Jak!O^(A|ZF&`s`o+le`;?L}$#? zn9_1R4B?|!ytuEkuv}kb)*YUtKqKX@oH%|K?^I>UKD!{myf}JwZSS;6hK_i7ag%L# zKrVN7gb|Y=>tl$t%Er#Xbp2YAzJWjW!;`xU%$UW0)V2dUO7NFo@}FWbDi8cjX%sZD z9Z~jS4Q)9azN84uAhyp|v3j<`g87<2bpY4MQP!qtc+#sv75SMfuw9>O!h-*XA~Xkw zP(~HE#XPfmvQFfhhgcDhtd!`6byk#nT6lOgS^9;|$t0Cuv%l2vyC=905PW>Mft-qV zjppL@2j8;CoIa7VP3Vr^dk{?g;;wnr*koka6MCAv&v^w@k11*%S|`~u)-~ouT)a#a z`jSc_+b^g*Eiw3nxOkxRC6{HrmbD6gOX0mAS5n@88f59$EPL%s|6TdtQ%g~qH`JkV zbrds`>LryaMbTtx_8ln>zL!D4!cJw4Tw7mMaQKI~)K*v7x(fJoS$LQTio9a0RxK=A zeGZo$xS!X&w5KxUSDZA+01E?Ny!F0edv}# zQp?%hHdk=%fbac0GG{CVT z-~@7?V0;8&kngLe(xH*_KYb9WmNKC8d%3{;q3qACh^E2zn^I@t`XEaHvG-&h6u9N# zl0-i;{gCa;p`BHM>3sejp^{)Tzh^5+GxkeX;tA4X0h2$jYFxP+sgRCdv2z+|eLV6d zKsJv-KmG;jpCT1G?TZm-TeHpVr&0S+Dq{{S?k=|10!5icQ&fNDq)KwcwqjW#shX+s zzp)DB@e%ZZwTJ-OjOl6hKl5LHa$2MktFmQ{-@dxR7jciJp!i$w&VUpr@=@=3I2H}< z-?=ZJ(0oJ5conGfHF`%L($U(QX}q85BvYf*5)DMVM?m{K>tOT(XPJ?vZG&w&j_!~* zQ?|`b;@B z=VOz+StGgm>1PI#J2ZOVLpTK>{7e(~Cr7A-$QM8T=x#+B!6MlEN233b=n3CK4zylKBHe_|{g z*^fDq%~R$4aMdo0I7;w&o>dQf7l=RbALoQ?dtEx-6AgFhx=J2Qi;)vGht%NO;{@P|PVXa3k{@0#;_uyuu+>j5r{cM{}51$C!FKW>*D^-opRES%B zhk4tT;7#Emp}>9NqWigZM2IoPsxZL?=b(JUG*5;9f}OKP#(=Tr*GxM`m&K=Knm6uR z_E4rZ(ryO3GWjWLY-qL`0G6df$eE|GD)GV_%Je z2$N`miC6iG%pa~go3QlXSo@(a`-Xy#N~`?R3xj~*<4Y6Eew`l*^ecG-oy*MB#<4dD zWT_vfZ6OpEWjtSvh|sx|uV-l}+&tqWiWBt^u5<1R72%hD>g?vs=1V;!%$+#QU!vh49~ zZkHkZYqu0&!97u?K#i8FgF|$!i$a}BE9MzXCeItrC!DWMq)x0-c2z9NDgYyW)?(`EZEHV-8 z*B>^Mt0%HKGKZ=TvJP`p4gN~4QoX*gnYH8e2@k0XrsgoMQ)_eF8WWLtfgX^A|L&VU z20~FFHl;azfG4;s#3^}hT;DVw@nq8Zo{U+8iZw@3nV&l=hOUW>s5uXUj)~kBanPzz zVKoy8tc(5E&F^ID_c1(>xBt8`yYCf+eb>GK^`oKMM&-)?Y5{(JSHCfMQnp-Zj2&Q! z8Dc_!g$RhIL_E7|kP}C&Nhc%a|HeqT<^>g5PPE9r-!+D+7Ja@6Vri<-H9?~av4AuA zj2Zp!lwD*Iwi2Q9X>F8)K`T9`0{seP|FSw+_~R4>f@ClBEN3-Lk4LD+d$NPXS^s{~ z7Z9Hw7&xRa(&V_PVfoSQ?fTVtJ~J$AeO2+5MQF$Cd1C(rPu1u#9R;>M+_d|&4V^3b zWc;#{#J>x+9is6NH?lVD2LCV1wmt>B99I#o1?-H00JQ7&7K;7JIx02`g{I4xddfpA zO@odc3KV2HBLWH*<>RlQ8?FOtOIO$Gc~s;%=*PjZ2!r*;jsx^Ie(cqndXWrTH_H)? zHuxlc_YKrJnKP%mi6dQD(R`{jg?E+#>^2* z7|1okI0<@9UY;A7-Z0z;gEH(cpdj0VVbx@@!8@Z2uPw<2&R1KYjurePic)s0vmi2S#< zY&FMDnX9Nm+W>l!*aO{7NOiKcJ5y)fdP!$Tqh0>}G~1_qGLGw-Iy77ZLo z&|e8Cv9`s1v1lhjONGW0xJtb@8xDDSc}1Mpo`d)l+yG!e@>7OEARIejP6-bRboPfT zFl?Ul6vwHX{tPAvJ}R_kfKMDd+iL(^Pgi#u5-#9KoHe2)Nj0S5#0(nCsAbJwP7 z%61QE+ClRO(a$JTEsOvjYif>Dx-|BR^r;h+g#Rzeb+X z>Q^hvezdjk3vd+>AYWHGme{(<>GU?;%(x}iZU01TcdgLT*@%(%7#*$1FU$?s9$xeO zy29>pB~*`erNq2yA-6WoQkFAqMnqFfUXwy;QxR>$gPWdxs>g!%2NKiKRwS%doBgx8 zw5U%9O&ej!5hxH&@TqOFpKQU9j7r*=XY?>~m8+8Y82@8O457rN`Ri_`yltg)e(zCm zHfG*QfYsQG!rrp&6rwBmyDC0*jeEg=S9U`Wur-vq)siCU=od~}-gB2^{v3VTr8)gI zktBcO=&5GF+u^g5M?_>jK~B`qEoQuHc9vv4h(krJvWkfCT&sJ&KJD$$!%g)_J&}}c zxc=FWE6pF8K64nQV{SXJPG=fs1`G!GYiO__>(z)5E=sgMba!3@^)% zE4xd&Pp7|*yMFyUhFzI(4N1+8xG`7cxk~>j?AObZ-qL;JKOYRAeT^@}*vN|zyPofD z=bBTL2L{Xh;tS)B1EwL0$Bb^#ZPW3gRWN%}YI zIya3%?sK2Qv)WGu26p*xl?XKY+=Xs+pY}FOnw4|ik8>+%bM9$HdpCNxmoI21k6)3D zX8y+?jrIkXx;B2-)&A+gYYa`D#uTJ zo%nZ^Vm7#VH|WWtR9>HPD{$cosmV4?ea~=-YOhVfcTsx>b?r^8%Jwq+j%Qpk_o+B&B*vI= zs5}Z*n`Ra7vU{88w~}ty=4798pIz-hhev{L-eG*7w^?3zcdwaYdAi4hFF}lfdsuV& z?gMJqLz(X__@}*OKGAxyul0kq(#jy4mGyKfwb?I6RHi>=$cZaCFODYt^OU)cL+zSz zhWG<%rvW*=8&pTONrFh!nARN8V!K@K{)ep}{-xq08kz(K_9rR2;-9Ihm}KpHoV_oL zZci4j_EHNsSBD3=F?Bjk6f3*zCv&ykyBm_%)K{ls^Hji2^4oXL!8hMl9vWr15!`fM zI5>=CHILFW*{I>z{g)}n8}adS0sxa%pwqTWyIxILLa zYv!>tx7$Kv*^1(-!+2}UG)lMVNn&{|$zK-a8u)MEGtWG_`1V)HP^hhUp8t`(cgI-D zU>UMi9PFr^vsf@dVx3~ESAF<63}NzA6U^ffKy#>vm>piH@Jlsp(gfXqz5V~>rRJ+Q zcd`S%J{oymrJH}0`;Cq>O~mSU(bX$-q#oIO29FC&rU%Xa}O%3g$|5z5#UICtzckib<13SG? z4bEYifp1HCA-@fkrp@jI4N;A+lJ_Rcp1;Tnf?8b9BzDPin8mV$}+x3!I~wD4UwWZTiVq*XSQ(qVqYm0*69D`ZWE90u4aj~ z_%1EZKe&T^`mYEd+wJiPffILz_n)kU{8&*zB7MN0qav0-+tSN*a(W8*7Eh3}_Fz0G z7mZcVE#ld_ev`jczohgnf=;%peKth481PXFS(qNBENZ8xixwEy^OnK7}M1XfvJWDNxn5Bq;X9gQ6P~l;K{&#s9 z4}lXV?98ZyNf>-bgEJ&0h5q{Wy&}k4uUYzxfh6`Ev{BnACh$GZ9wt089V_ z|0#$QKpGEip3Ac(32-wAg-yZ;42Ib3#KTQZ0X z>cM{`BH|9`Uns}`vIBcB8w(31uklZy?9u%B47&dM(~UZj-8wZsg})GP{$z6rR)ewvJcH3_AXum2at+Dod*nxeBHX3be3c#K+_jP?k_Omo}WoI z=z(hJ42D8=fV*CI&Qd-8pUBF*9wJmO7c@|>U%#F+vI6e_2o}y!K6j{U;83olM2J91 z;xL%`c&@t$1>ArP3NTxMsz|I4h+5zicm-7D0<71d3o8ddC7|eVaBws=G(dsG{=8Tp zY5+vPLL2Z$V029W{27H03jU$0qOuKQI3UTUKBiH2gcku6XRjwK3)~6{A!q>J3&37) z#{iHTs>$#0)-%ocY2-OUG8D{2zEVooL!}5NgK7{?!QgReaS@ez3dSRlkU$**jkK=? z1prv{I$6#ZeD#Y#R+@vn3#vqzQx>S^nkS5|?sL^|wSWo%qMAXr2kj6Lu|rD%WNsf| z*cb`pBr%~*m4k5T3ty$e_Hs>Y>BLK<+j%)Wpia2;r)T2YGXGqi#22;hqnhg8Hmp%CduB#11>FBkgMKY=skMfda&0L+iotiozVnfisOcO7 zCKVppo$OX!1K$rGyiJ^Z2llamNwVFPxtP|yrByLXLAW4sG@eSrmoT0e&p%dbS!g<% zRI637HTfd>6YY)s4QdCo;`aWWya-P7@`4(*ky%ZW^az60wfH1WiQ}b+VeHen*K`v1 zsm_XC(>2c@KC>z^S#C{HmCGr#*LHGgZc}EF{b8&Yuko3cntt_Qf%vjl+akl?Ve9P{@A*%|$otXaDY6pLuO3kAo~ ze5HSXO*O&2U-LXIO68{5-wY9tm@yqq8m``Z#hdLO$=>PT4w%!vE^!7xNXjjJ|0Mq$fOL?XwlCQn}C>3bGUuV;NKr|05e+Gx}2?VSP#A30uGl zX{Hp?yN!V*<78J`iZu7DnPJhg*(8{}=W!mBlc4z3}ko8SF&-CIas8z+=>o};6dZqnw|Dl1SHlW;F;H-Br{zw^xU9`ehr{_pDA zQ&me#_OoEx>HA@ages4FW8+UYdb`>0y+1wMus`m+tG-Wu5luO^`3TGHYuvu(SMIhj zEv7p|mS5=Z=9cZOx_fw~TtAZ!xzdGK)~ES}oUhWW5b>Sg`oM&N!7cQ>Kz$}wgk+^4 zVTy}EL^7jRhsOOpyhfo$uh% ztD04b%bd*(D~9^(xRxu|=U?}qBP*A{=<|uCSY*9)c5SX^AbYo+AFN)bus5f4UqZe7 z_n17@?;+PBFCr{V*7yQJD+=#Ojz%4AE4Ot?4f)U!9jQ&Km8!-nmk%E%<20(y)flC> zmgO!PDq1;zb8C)MJeD%hlShtj7r5`a9FJM$zw?YvJUtD=SBsASMd8hMab}!W{S+^> zHYYAhe)r6Q%;Cg!lWHzoTG_y`eZ96~S$6u)yK{~Gtti8@XCId9x=aulibSUDRAr5f zIJl1KH=Tb+&!@Ce$eVO!Z_3)F+DO}6WZb0;a*DZf9IA@%)?pE<$~5CmyWp6Ares;| zCz~9l=>6_IYyOAKcRr7R6Bp-b4a)JVMlRyM=x?2UGqJ}CpBUR7p6tK(J#=@E(W=(; zVLfN9D2>j@n6cMRPi@6uz)a0F@ui^Q8vVI^^E<|!fUh^ue3CR=ICO9j^yU@%oyzI_ zXr^EMzih@BUgD=kk6Bixu)iuv#J+nt`X(Ra#(sr>w}EE(zE^z(>56NKMdwU+3sqTl zsJ!(45O>OpyzxYhD$mr-FW&d@{a%kuG}bFK2ka#N%;#32oFpcj{nN;G&zOLphmnD? zb6O?zVyt^2_vONu(FvyVh$rV>*^lbJ2u?^;zo__Qe!t78(`Rc|u zZ#$|lYBuzBca3IyCWIRr?ysyb+{{uUr>;t>+g+~ObsR`EyUXHx)vNx1=*quT1Lw@K z25*Kdh}cNP{yVQ>)rrpez_%kF*D!SXIUaXm0$@5y#y2t5seu&qs2ZGuJ`IE?`v#-xH^ zmzx9)F<9#(A|nA|1nDbBpnwF=E(RhE!Gq?o@8bK(EU zCa7gl5W(LBU;ymrQ02W4!viwjWxK=$55%*8l=0S`J1=V;LB`Ym!>bDeQbtpm%h5ySIeGv3J7O#BXl-SF$X)yft(zP_^NtT(-G5pO9)nHBBqjOZ$5 zD&B~k<&!nRT>gfe(`fpdUzHwakbGkc7Z}U?T5=^toNhpLYNQu`gUX^f(NR;@2&*gn z-^%pu?yJb&7Y!!bu&^!HKOcQ{7QWnZKYnoX6-|;YO*4t(Xug!(0SV<ukJlI(7v5b6fR|_phDcjISld2GbbT8@Yb`(R=A&TO1|3&3s^5 z@#DD(XRb}fIIdJy^d$r9D>XrSq@U%sf>PAzFSq1xg|AI$S+GJ2V{d4BM}Ncn)dqnS zmC81P_O8wpL|GvQSVt%FCYm>*DVIz2U-2Qg91lv?>dqaq_Q&#T2V8cU*2UZ$e)XeDk^IpZ?+Qo;lmCx>?^Ar8JZI0102IxNPwfI z=So+%{k!wn;UK&7x2@b)iGFjYjVi7ED~L{5lb(JP@+_-OhfH=mb(`ch^@3u4ilzA- zE(CXP=%QToyQX~fn3ku{-S#p~Kb>&G*SKg{#f#>Rq1UK{&z2c{N_aMtSgxiG40F=m zqg!rfaLozyDXQow50&t!Gkrj@X`g&tC;g5_f4tup?;IVCf$i6RfsMmzPm{{qhJf}N z&K*w9h}DWpS#YWKA5(QPJgv!-ZNuv&$-ngVEeVu8c+ivzQvUs)1V9#`}x;dFCgb+wy zp~Lqq&!&Hp)~dHybzsIMUbp(HW9LH&Gj-MFm)=NO8EJl-urL@2W7OFqHFxm$!fu{; zuvCWOn-VARvfv`M547$zb-y?9ucvbG%+%Q>7x_*}ktoFPn;l`{u~65IKVK)fBBGG1 zd3qeOPJJs@`6=bvy@MH3s#kTKF^_P}TNCLq;)Ltjc+`^YZ};!vEOkZ`u;ubUI>|v0 zv^XsT`|{QFb|Z6412j$>&&X@-fA;K+`Uqc#lD|G!SfL}`n~=P0KRLpXtdcu4&>8Y! z6)@JIG&&oWn9b+pE&ZdtoX~#L5WO57oBaES{1fdfHYK}}fdPwSCME*EWa^`?;RGVS zz517P_9pbVX%`BnwjA?cNGHCU3XAlwdp+DM_L9H0s;Mbh@m1yL_DK+yyX<254*9p` zvz6ufUAQVfPhlY<*m^Rmt{Gyv%7G}W+l)u+WgFyFgkut|&tV|6XT#437tf+5k6bHQ zyR#RjnE2l*nT@VEGKZAJf3I47bycGcJK$bMUlzs&Ytt<^wE%TaLbQMH4^MopLEV&kFs<_&#a0nx5Zlfb11 zJ)U)~Nal|p7?N%ohl$!;46d&=PW&!ZkdNeMXCsRVnomVu--vxBxbJyNp(aFBiHW2^ zZWnzDMGu4Hy5!-##IIi)=5TO&#VZ(aoOJD*i@OtpOUtf3VPfvNza4BpW^t$Qiv4D5 zA$d`bto}ghtTyFZbi8T?_Mj?}YsEKoUrO-(slaHc2Rhh*T8tkdxwJ zebzv%6}JPM)@?M^xe#AhDGeUmp^v|A1)mr{`yFgggj*WK0K=?X1X5_b_0Ja$Brc}S zs8#07XSKsS=C{SbYmGXpa=?_EJsvs=(8U*n?F5J369p6OY{~xYZV880)EOHb4#k!u zY*lr4Bf@y-ARZF-qk<6jM5qM-8wpOvt>BkuZ|AE~Xad+lVOkG#4CEtl?Z6xYHO3}1 zJD{B)Ked8l&}yhjOSf?O9Cyvl&Habq$ft5$CXKjn7yF$Qv0uvB-#bt}G^`fZwLJ2G zh5(?3gglRmRX@%wJEC+a(6Lw&;sgOH?0YEHimuBvKU6R#=P0(fU3q^N> z4e!}SJcNNjt~aRsQP2SS3l*D)TeL#9l8-A}D61+ez<6#8B6c|YuVl5+-QVk%fp!N5 z0Lzec9Ohs=#0Aq=ACNOq;Rd&n z*ocUoUq<18?X(8rkWpKRinAM(R^WQbwy)rNQH?UYcSgN$;40{<{|o3H4d4iZG1r!N z0xGK+ufu8Zb6g&^OAr#dLRf=;X*jCm12Bjx=q?#|keVp|gTfKoNTm3VH80@Z+5!VO zM4soK%|X`-{O+i`hJ>nQ@RYq}kaP*6d8jgrH)d2X_)ul|;jxLmb)hb+JDIiWof|c8 zHMRn{k)%}uTBj>U`}vA;Dp}83;*8>G%D#5q9v1ZYZHShiX@2gM+vXEhJHxfJgtUeB{us~c6c-80 z?@ZLreHhTNjtFzY&T`v~Aq*Q;>M9OV5IlU!%Q|9=j(%Tjb%590IKsf#1ucW*#;DeE z%z4GK>7nlW zoK=Dtw@_vN4;T|^1QmSrk+jJ8JitSo|qx-`@b;HU&`x_SbLjE8shzJ zH^26(mbfu}YsAeD*5sXuM!9!V@l$aYB~&Ab{-C{`Na1&`UON3}Z3m`pD^@0kOn&<4 z$PXtv73OW`ktq_q&aBVm1u38C&nMn24uvT<+%q32O{v7(RWrXxH4hHMK+3}Eyi>iN zMT>f(??gU1?qGINW)okstChj|Djt@aFDv(v)2dXMz!xz-oSo2jF-=7gzf{lML89Dzsg&k_VabW@Y&y`U~3 zw=R_$S-a*{Pk=n!|LllKMUEHYSvvTJ=Z{d^PbTuz(t$~dZ`CDJSu*CT5=OH1crbJ$ z>ig;H=sYGmaxEh2&hDS~3nfik4~1sm{+(P_c~gb=^ixtE%R2;pU9 zm36v|D%9~0#`&)npzhlqi?uEg)4yQ!FO0 z7E`@rSmR?Ro`uaj>81GYvtc4)%wt(MB8&)-RKxy{#zQBAN=@&%+|k&AZz;;9_^9+c z`3dkH2U?C}$rwdF|E%#tk)E`!raU7(a#nZd2BAI)`n!8|?ym){FbODsCnT{T+&S?N z%H)}TQ*a|ueAUlLa)=|0;r(GWN>?wDCD)=<)7b1);B)o@U}@H@2tf8~=fgzZxip!C z0G=lvV-#XF@pi>LSsRN=LxV@|2ZDYJ6YI(yqQ_->Pr04t*F&?DOn;ZNFsc_2>Q2YPYX0AGpb1)H47TezbIx-OlTv@%mY`b!AGp5q@=E{F62u# z-l&R+KVNv*$PzTB=V5JZ!jd=Q4uEz)WjGH593EmLfKCMjo`CQV>6DQD2Io}}r%_zL z{%if0Hg|#JLuuz0k5DiArB)Xqqw;E4inh8pBbtG zxv%pz5aVCoSZ^(T<>67sJ6Ig9CB+(aqLIYj^=HNL`a_jiCVfLeN3U~fUrIl@L(Le% zl(<$Vj^GRT!R?(AXM$$*y&-OQLN`}-gMVID-oEe%gcDPK>YLf$SW6P=R}TwsKlA@?pVD@p|cQ~Ak8P}w4mX&qQquoXx@t_C4Jm_ zYvAkoem%P%F{2;xzdbxF=i#-2!wY+z^-nZ)df(9zY(vemmG>W${5ob2`PcIPiLH>n zCG8z8WoJPC8FgVY+)j_V_l`It1habY(C33>%#+>b-;`R+G{LV#I7O9GD6_`CQX;r` zs>;mRVdJ)6=AULK@gnGQD)+F%ww)-}+7UMFWhUPGi1k{sdxLsY8t+KdoVQGRMYm5&G^`HuK1Hm3*wq7C>^|H)zv*Q>n9 z`{lc$?mIWo2A?G1OW~r`uHE1cS93fw=&Ml!tjd7cR)*tk#i3;iL#<4+0i1`uvnjSL zbxB_g_SkoJD3KRZTK>)HrY(_6C5Q&c-A^`&`4q6e?#%7S;2&PFxzs%!z9|tYtG(o)J1F;#(drtnLer+?%l@uX-(|CHd zr9^JSY47al>~=fD@3_LB=!omP^e{9I>AWTj=bsOb*W`zIX_@}G;UNhaq*D5-DfE?Y zQXzF?k9Yiy1AJ9_;%+Y9e$Stm$n)V3rBCYJ0n%&SU*opw3)!Vf(ISKJp8lbz`J_># zZ%Fc~$>XXxgs8V)mE&vEs_=S{!0r3^ zM^5$D5wziLd42ER3g&gjHo5u^opJe~@2dvYBK#C8rf92miZY8^UO~ zXjg+<(wHQ;$`Bs5#YQemFG^fre<>BV>lubg7oQ*F`+cnR#YI@jo&Nfp30fHwprg4+ z@;&E67)XlEd+C+<-x;IF20gua8(GKG4QFJ@eOiDqX%Sc zp$WkQcKn=a?XG6JE?I(p#AwY(+lCDz@A4Kd(73t@p&7}iHt zgD;W{A7f(=3seLU2)e-Zjf=a&JO^Rf&UFoCWzTLDLP#Fdqhr<{y}d;Ri*=x&f#9oi zldS;g&cW%f13CiIa;T(Qvux5WRp-cgq1)b$te=cVhb=397_xG3kUcDc4dwp*3yAR4 z{2F)nF0?8EmhiH7;QjTQu5^}_{S(+dt{Tx0-qK=^fgT|XNugXRyC_Waw0DSI`D3DIFTbG|&X7!c^GeE5_Ewl;jYq}DNJ<%#J0G88`Lf#AD*`U z=@qOw`m`Bq!ee_`{EzjR2P2{fN1is^Tji=Grvnwgau_zo$@`yCQW>WzK9dI`n>DFw z({w+d3b4{@l)Vo&KcvU$6ECe|oozmU?cS7s%OON-cTJ+pDsRe*Ae%{2?NgDS>>W47 z(u|7#kF2+Vs;b@Iw>K#w0v2o<1Zf4NI|LL2m5^=(k#3L{kPf9Aq`N~JL_z`S7Lo4m z{!iZboNt`p_#6y}0eh{z_FB&~9iTsiW9xVHcJ_`TZa}br_H>#!g2(&o zjZM4ug;DOs(U(It5ZFE_Xm+*xqm<0E8aRI9wqO+a>pL5cr~M{I6-Tf+3X1qU)hL4q z*8338C8q;EM}UEV$q?-tS`Z&(ly@Tu<_)k~yiHFTXv-uqecPKjlM z;*nLsh~*0$ty{tZZ11GCZ08;^4K%6Sb_Msg1bb>2TCjBKs-mOw5U}Bd8Qgwb{hL99r6dr6AP)JAGA?np{ca`1w9Nb{BIK*bE zQoI~!zT}&_*a_y9leD`-YlZmm_qZdD@@FEI>V2ADU}3{HxXQR(k6yzf5$&xV7atp~ z6VVcVpGtF=QR~kmjXkXzliK=FRFqGC+F=ho@x4UEe~OXbo){{n>{MwhrGN6w6yYz& zwD|}XowbdT`AHG0HqfB2NQ8m$} zxxA|C7`4D%tzCXIjGDtl1682lgu0(SVKANI_R)nw)XkrX9Ib)CYw&@2I@1hhH= zy7(2aJh!ge5d;LlV3p(b5lUuxZ1Qq!z9VJGz+Cy=9Mo{0JE|c9QZF=PwiXGYMi40# zJiN_Jtv7RcgTdZRGU!oX=K0p@czxD{Aj-D9`wA!XiA2)PJEd8_9MT#7hW&UcsWsPW zPJ`OZPwIonL5Lrp)dz$abm0s-*b17h?(N)rBt+`Zqq#}m@x?IqH($q_wg=i8koW%wo|Fo5KqW+9#>T`NUFKNeR*SC_dka(i`X?eS)ad z5*jP)_UqdZ9CqEZTTcqJuhK?%B&gK;ePX9f%wNwfF0|8TutigT|`Y$e;1FS=&SkL zAxZ1GUK_a5&YcO2nElcn`+9t(WT6D=Y(7*IhvZd%$Lb!d8>H8i48nbL9PYZ`p39dZta{e+l z(bw~D8^m~sAer9PAsNY_sB1g}KS%~;oXQ^wN-9syjJcGTWdG^bwBInMFeNBZZACYj z!Nq#lSF?xIq-?Sfu-)hw3;Dy8r^1n$%=S#@rB_jDbspDyg||yaiPuoxv@gjLA7Iar zB@s*B-8J*}u%Rs;k+} zS6`xG721`Sws}K`%%kx{@pNm@j^GlM%vy-qcV5Mfu-6Pgq})9UKM^P(Bs0pS@t}WC zPbh*Ql?;;+zaIFQKw@4Pf8K*eKZuPAy^~U!GC<{1F}m?!dB)h`5X3aXWLI!v@bF zVBs~iU3n1FlmfXWRal6@96G+y<&qV!m}wE zdteSY!O!tVFpOAZmsfg2T&ll8K-$zC8sDF9K(d(ZyipAuO^ubRLdqxyXh?4LfU(0B z6_m#kh{0rRdgxbzG*;_*5KuwEv!=fOd`DkW@Ox`(wMHRKV0C=FYOgB16da;HxN2kT z0?!**J|T56Y;7=(``YJ?+epEcog6I1F9r7%=d_pr3VyMdB`h1q!2p7}zfB){O{PeQ ziBtHCj}qw%_L25ZFx5z?-?3XEvRC~=(PdkW_-Wq3ALwe9*K4zvH7_UH_4wI&qS^0ccYYH8%&ft_wd*! z7*iu2(^gyg@KVnR*KsW`7xkRaM<`&%BkUGc*RV6Ea;36qhE0mEyOHmY`Y7T+vn~Qd=TxN1K{&$!Ucp?bBY^cM%&b4LU!@x3%THe*a& z<=RjsK3?w&`{JA*EXK&YHFYMADn9F)h`u!9{ix!DLqVJiF)$FzfRyG zZ(kvrhg@tW|Ko1$kyG+XE#=X_-62C3LX&c%#9KN$W&TnG=Z$n8k){Jeyd4cCbvE-! zZ3q^F?@Jf<+3!v28!_!F@d7YBNS=PTc(iVN)XFc+d^)x?$`yLFQlf;bV^k+Jo_@^?jT9M)k%@y>^1#ws$`9y>Oz>R`$oY z({4J9{Ovr!C#&+Rfefg_858uOI;(4R7(KZ$=yAGihL$!{a z?OkNVHxSe|Y2mo#wwM`Dg&Eu}^*mW~!Z+959 z$hRGFQ>mex(wfJrw@;n?mg0-~^yBk*SGsM({QSqraBBixmyV zKgPItFcCaaeKsB6*+>Et|DK-~8lXyEVM#%%5>6{CpQCsMmb^G#1RmFD+ME7+I;!^2N`sMB z6@XVk6sMmzv|`H#w6CelgQ~`xgkq0lWqJg5yFf1tOnuEqBe0w^1n7M&ukKj{v+(Y& zE{<3k_lw=+O^6-=;{_@j*{lIDeT1K32fv=B#Le9Td@&FdYXeD;f3wbJHkcbs+~GvQ znx_$(NieB&L3qg54^6$a)YKDLSjWbWM;b5eKyCxFA`A~0+P#Fu^$LgzfKou6v9`g~ zSUezDfbq<78&PixO}1LN2t~jY1n^hJpW`bV5>UNB_wdi1Enq9AQJgP*eoC$i?-AHP zv$M0{`F8=M=G6|BY@uo4==F4Q>DcF`NSl4$LJ16*I5T2r8jh zdLd}JCW8VS(Zm|0lvj|K2gPBJN%O4F%mFnn8k=MGP_>-$X3M$F+9B9Tf{!Tj>qI9# z7D$fXVnD&$#ARY84j)`tm=c~WnR#W>ei223yFXA`)cx$2?9ytK>z0eg9RC*fd}(Se@{v-) zgl%1-O76nV!<`MA&1k!e1zS4GXNHtdI)#L(WqGaMv7O+V|J15atL&mabFsLC(xJ%y zo~yge=CNM4CVLUa1NrZ>@%P2JQiEPzS81U_6fb=hektR4ZnRn>-4j=)MfpawF3EfA zhsWsfEpHhT+Fs_rv{b1G_RyD9pq6*(n;XHu@7LQ;e3V+UTJ4l2?#BMz?Y#IaiR6RI z+GB?oylyu6=hH1_+oaMXrI!}QJ56uyAdsAEJw-2tzi!yxWtl9?lKZyxEb2#+`TN;7 z>LRY&D*i!Iw?aQ`^8E^DXYL_0-^TySGJuNkZc;_5eE{-vyPuzq9A5Ged-by%a$lQU zIPJ%AzzKEDOQ}$b&By-`Bvs*||GkjiWXQ^EW{-cxwKg8rB6&Zgr{XOgk{{iLb|w+| z>6z4_v-p@%ydM*_5+%Aw(?@1`VosB4LV`E-<<%!D&;MrVo2|#p<;EzY9ZuvhwDi~X zOB#kgqc`3&%eFi(=&C49w~3S34`kcczRUj|zs;K!Q57uvyDDb|2AQ|49w53ZO!RP8 zyvV+k6$s5U^Gj&_oE6#;Hz$t%R??M>SG84lW&jZ{Qq@Pd_s*S}#Zc*2=Ke(JObdp~ zRj)U*_b;|1%H-_lvSW}pcYn{#l}twNh{xW&uF{jb74-&nUw}YPSsIl#U8@QA;>_d4 zZ6Tw{N#~`f?>s5Z3ic;UcS)N+6^g~u4*9YVqy{r0o`tfX8QPz)a4S`uwzl#Uui;25 zv%l{fpU6sojKx7p{OOHLeHR&Z=i%l%r(HD;+KEa_8TmA(y`HcT7GnVu#>pJg(&DA& zX*LJ#=uA_&Z|O2kIsM1d)1E^Ud$ZY-$7^J>}vP{M%I9m>Zu>S10JmTB&%Sf8@Y%GL)Zb zaw9SER578xlxt^?uB9atWE!<*zS44V(l9;7oB-W(?UrM z;i%D(((#fu+56=Ul?Z=%ywws_qKC;!5%%7O?s7NKt%tf)Y8SX~ls@8{G|rdUo$<`i z;(DEEx7~Tjd5k#8i{YR?>{<(2J1XpW@r+i&)8)zc3VMBcDxL6Wk6vTfyQf;67gQIy zy{6+6YhC~1Ih>LtAxRMYtXEi2c%qj~Jc*N8i2m=8Gf8MA<{y)-R<`V9pV&`&PD>{^ z26K39a zk>VlRn#jU1cCYDv41N>x4UzbDd_Ib%nbuMI{c@Sr$c&GM1_rKRxcX9H`#TI+0H-!7 zA>sGC@7(%ytffB zE(A1C3a6#3Rv-LJX=bJ&h&5p3Csy8BJ!i4rs4o>nqs;1+YanU0a&azK{*N9k41 zE|`dYp`@K0AcbFEJ;V3bVa?`vAk`1f4>dxj&g+VE%R0y_Tqa$3*^c62j*e` z_y~&M_kFm~pR|Ni03SyI+of0Fhht~Af7MR}(Fy2jmBv1U`||vp&z;gUH}}mqg~ec2 za%C8KMH7bY9$pECpvT9@0SWB_=OlQ#Rwn%-Su*YYj6dC#RR|C{m^HqB!oTt_m;%EU zI1gnXCWh17f1Zl@(FX`hRwwa0ydbiJPpt=H7Vtg=|M7>arQdZ}*TyTJ7bsoDBhpEl zii(3EkiK#S0|Q1-!Om-UF{Ni;GAQ3}%cNhqOmDNl?6an$dh;#i>!{zfa-!RIZ!A|* zbm{5X&S@+P;ttQN{*}Eg$uy_&Pe)sEbS9GpFB<7P7;kdZPbR&ooqMyci!mQ!6M~>( z<(Wt#au6*y=`5j@`*b_iHtn=s2leUm2{ryF#0AC+e%>9N#UUF1)dCFt7G3@u%O&;6 z4s$=7xi&0L?)P6aqiuZD>uhb)%Td$@>#`vtdiip(7gC>o^kKivV)8>qgz5ZzUSi5W zNU0wdYf7GAXm@<1NvDp)3?{?BV*%r+*pxIgXjv03DAS((#R!~EB+km`F?)Rn-TYKO zhu2=u^QPNygDNCxf(CiL-WqF!(dTgfE?%P-q9-r1{fS%%kyWzk;?A1j=kqf3GXB|% zjwvePb!wYc@T=I~a_)vGJLh|B1oP4G(P*tmT_4DCf-Aqi+1N z7Wqz>%lDf+316>lNz!nl1tY@izK}})3T#4sC)?+>8$}_J{KRA^Ardxi>7c*ldA(?E zbbyLqD=W;{*>EWnKS3t#*RRb^*|t25mt-DIa~xL}lTI&)IGc@EyVv=KF8*4kny_#` zR_X7w@3_r0!(}WoQ3T4O32X{{~uJw|H-C#@X6%?qg1B9qn9d6jM&=VnST6~l`^~H zcXonNLENNkom_;aQh?p7+y^u>7SiKqZbH1jco_My5vX+-CcI%s9C^-! zc+y6RQro14)xLay$@6;E-270NXmu~{OKfGfR>dNe^h|9|R71$(-i_pqxk@~w5=|0% z+?TJ6BJx^2rS8{*=cRvA$Ybxk9$6_ewtP3VX4ev~B3?;m5rxjc|05y!k20!Z?Mng8 z)t%G#GEJ5xCQ??pe+GVU5K2g8Dd0(0wZ`_02=cg9rhRE`w=z12xjUaf!taIgc3g!Z z_*bB8v9Y@5mzIq6FmF1XfWR{2$R|!qw(%^KG_A3cZeQoKVj6TcRYi*3l%GZ99u7hwYn>?N{O>uZasrUP?e1a@LSJA2_vz~S8`1}%< z4pF?!_!lCsyo(u(;yO{QmvVW`GwF`A+4;{ijUtEbqBtEti0-!2vSYcrrOs~`RCuk$ zjaYQr9DC>5nEU8H4(}Eu7yIhj|05T-@g)M?K5ru~A$#k~s6)*p(e~Kukp&ik3928C zD_i1uqW7?*)I+(fnTeywGQ_peuWt$s5#wM({ZCibK7&Y$mM)KYIvjMls zskFddHimqwD&BK%{BSvTfll+45H?-0gS%Dl1->9_FXArxe)6>MVD`k;ym-+nOGccv zsVcWEfb;6r&@x0lsa-i^5j+ZWL^vka+Xgqy8uINVe_`iLBt}^I~rQRlnepQ`!=ln7ENb^Apx9M|(wEafyebw9WzQ z$0^yvxtLk%u(O~bK-~fEI#-BiVC)lKfocT-!_;^(ROFOTG1poY7*8?wO0AFT1K-&O zH9q!-Bo~{Wzq;qlAg`!1amC2CO;Jk3)A{J>fLAefnYMOoOgYJgE?@?fs}mCw52W-| zxuEO@+3@F9AA|=!%&2^hiXss6n>453G*h6vbULe9c1 z!b^&)8YN3RkS&*XKQHnM=y>H(!T}x(53a0G=S9F6>dIJPXaiG14$7x@?d<4L@?GK= z4g+llQj6YXQ`-&Q61xD+Di=4mFvJJbNLd&e0*`s1hBy<3(|Qk9zSq8Z+qZb{&oDk} z6h;sVmY)E!FX@4o&p!L*?z4fgmy8~LsO_kF2*ewP_5kwOh*BdlrHG6o1o}Q&I(qT1 zB*FOD*!LBuVsP*WS56mslh*=L>HqmEBo$k_8sV0fu9=Tg$HXJYL6kE8$8lkLFPVIm zZgh`Zd*Ef%>PV8cXFBQwRVhrY4w6s~f(ZT>!_K0Z2vj7>XVIvB*`=uF?Gaq*a^y0{ ztIL9=BNAwfIa|5DwUEDh*AjAo7=n1#w%0f>pB4T2Wz{jZ$EGDEG4#E7LF3IuR zw55je23J8`t)WQ5z@2P!`$Wqva@Ctnh+_QvMQ#ItC1Pu`MBsG9ps-0X_53WC=?v|D zQKI?ld)gqL>{rRrp$#l(!QImXv9EQ-smMQ9TSAi)eRg#%_xiicHSzEl=S9C9q%g{3?eiI@@aNz-5H*GgLp)3j!-Fw@;wLA=>;krbYJ@efmPPOS2z1o0y+DN zRb^t-{={@(x_?38<_FF7+ecVfI0#fsFJg7sH!gSih1!K^`0aJLSi@19c1aQ6f zlYNbdg#Ys?oluBl-I8Df*DtgW2e_eW?0*lo!~EDJVdbFoF&YeN%?_y`k(I1(HX2;E zHLi%mEQZ5pUka2%DXkscLW~`}{_}DFds>e|nvvS;La+Y)VE+Am@Bx)?%PmmxRn&_9 zd*%M~JK@v7uL}SBox(<4LmP;HZ|48}zW?!c-Viw5)s0W4{CkN1y#GMp^K=hwp#7ik z*1XLP19<=3goVUOi>^WxVoSyj-v2$tl}k!7zivZ)q+0W`wgen}@Uk3MF_4LuGCNoB z!(IIEtL?jgqLI3KigOf|YpE~yjSzwVeTl29e|{|bdqi~f|NqUim48D1 z?@8`Iku=fy?~(rb4`CyY^Z!0I|GYam0PrP?>?!{D9n-IbjRGwI)_Lu9^Z$E+TiMDn zuRhxU?`!{w4`7a92%ylO`~SYi%o4LVeBYpZ1kLO(kH0tlJn4NeF{!+HLQ0T>T=jAK z%jneqeH~4Y+Htb0tA3%};ca`;s`N5DeN16+C2wU;;ySe_67!wPZGFX187{m4ZaS3; z!-rX!y3GH5BQ;+lVdPMyWajD?$%u5O{hl*wlsBBIe7<-(iz{uGD5@HE z9VJRXwScd7+J#%}wvhU!7*?y~vVK?GrORPMUgob)JuDrM4Uum+vhS?(*;dq|BQ{d* zVg+2c*Q8j*7Yhgs1nZz6N&PwNp_4x4cG^M=A2Z@P^u+@U=By;CMUrLZv8h`n` zjEPj)RKAjdI1#f`WPm6RDh_^FacHy&X+tGK`2G(bUM5EF;qQ-?-O2&(OEq9mN!CMzuuQdgErTb~+i3gjxsp;Oai#NWH zt_&?W!OI`LR*eG0KTil~TlHh|2tKa?gUQGU99UUnWiAiez~G_fX~Us<3T$JLZ|tAI z3}=V3fofr1-tOr+kP)V}-q-&DioV)wz#`oKle8T?GN7_-VFp+U_(WE6|8m4#dh&||q&5g+RBWiOGR;=x zMUl)|Hpu=*`rIv|%F8!z52EdJfn3csG+BMr1on zevyR#Za0Q%b?xCFXGnf~sXg|g7$CgWg!Uq9z`V+r5G)6`V2T9>E7sDQ`w2E1h~E|{ z_;Ih~V+MPVDiw*-`0L0?y)R*pF~JnDIPC?Z0+tkgp48Cw1D6O8eajBhmTzwai*iDMVgT3+|2NlcQ!?8b9KFK=P` zeNZ*9C?MeVDnO1?ttZj$hMoBZ3!PXyv5Sd$XBknUm^}#sT}=MMIYTa0Kwm`r%MYj{ zKoP?pCUZXLJP}IX1YFDexzA7ALMbR>kMzsN04Q=kkbJQRSC}w3z9VLk@BB26FF*~| zO44gG9pgI>s#sIcTa}>_rC>UmXkJg_V7~G65~8tBIP>c$1a{d(5qI9oYxb##6)5q8 zD`eG_8$Wnh<&=M9GdDIqfte_9Bo@O`g^`?l9H)uj+uIxAZi9zt>gg=lnt*d`vik*Z zyOlS-Y7{>|Ka`oQfee=f1)4}@t>XrYS5jf+Fwi4>dE5g%bQ0*2H|uOU3VLH*SEF9!wtx@Naybt(-7d2rC05-HSxNj5j*qkO23J*;wg3pm~t`VpwfF4<2A3 z0BH;mmWB%D)5FbVP7@_iL4m_hrcCT@BB3W(KvXq?a9Q|ttVdi99g(!C4!+8c(;oq~ zS2bbA6@NLO_V1RqZ_y^3Byene_yJbpAOsfr1~QuGaSQDam9{w?~^(JmI{`ak}bnn;5$6y6qIj5!`_KeFCfOCK_iUkB@xQ62+dLeL%gGSEo$b9v zhr%Td9Q1ngRpa&FZo2dNM{T;>x)LGI4BkF;a=9E7PBOLIWfCqDXCakjA$8N$H}GOd z{(0s5Ql%op_&Y^N+FQKTMci(ew5>8~+1r>1e43f-(pZ##QUa=0%`hXoQ+`Ubn#V^s z=-GC8bKGe|BGd%L_I979Dw9vRW|AR{)m)dxo-7@=&>3s29t`50X`(n?Y>}YCl1>Jd8tJ*KVrY*>uaq*%x@=hZ>+6(<_ZLAwXm^PmNBBcLvq3!qeoJ`-H ze6^6~N$&CukE7-bpB#5)@g9oGMwh+VoA)w;|5z$$6IOkp!~K3ynNr$X<@7w#Y5vyU zx9QjMc`j5rb$!k~wOM_;zGX`HTpa(BvLtR=s-`)NwP*Zf_L=<*y+8f=fCjGM1IynqJcQ2o6ue)VnVC6$1Zt&sFL>&qG)-H| z#IR07-&fFT#=BWZ1YAe1Q2t4ak&)nf-w63#ZF>4#lJka~Ry~ydA)k*>4h#su7XxY* zCZcL07#}*-BT%+ije4#02dd?iSktng#DsqHv-j_y`VFN8F)Z*N*(>q-d3ry{VC+hU z4!~zfT_b36YHEn7_Mlh&v~U+R-qhaM*w_N(cw$CZEp09{c`iP%t_IMi4_+LN6sXS= zxGl(6Dk1ZztmK*>dPNV(IrQyscb3AFpjK;N5Vm;-2lI-k4wO@0F)GB-SdZJms`h0~ zO3GiTslO&Z1ArDJ+V##3U(w*qYL1(;0;m_%-XRpi4KRvo&<)jifLL{wlA2fN=B_|H;I)?Pb}QlG`h+k3 z)gztp0==^o3=)=pMlw;8EU&HohQ}EK1gMlaCu9N~19pka8N$YAQc~6X!{7rvN&KEj zeB*CMMzbO|p6OLiWAiC;eaEE$GG`bRj4As7erv*DhFbkii;{eR-l#5K*G~U5s}; zN9>2|NB+M28`DRenLl(mmt}w>W-)SlVkPs~YnXud4$g1Efqs@a+wxiQUDt_~ZkU+- zZux4Ayw3Ich8U*r%!fM1^ww-K2d(Jg+U9SV z7Nqz-26S^8mKH_IWh~4c!|zlcj_hwWk=#u_D?iI~qVS~*$53iZjo&5C%Tx~`Rr&L zLV&WdAhw1j*L{e4pqHYipulcATHG-o^T{2+W#gTrn;7!nk4_aKJ_! zal&WQc+k;gkCl<8CT!h3OP2~q{Dm9hY8agW9Kajk1G*yiOiW!2r#ZTailQLq78m1X zGOs#w5Nm)Q{qoP3Ot@^f5JBXEYFPshHNI{EF3}knG)_Tpg6hg?q;j*f9ig5I!}ZR6 zJ1YN463CS0<;Bq8Z~KOQRt&^SAD@*eN3aI{s>~ua$=eHPH~G!(16Dg0@1Kf%TFgQ! z=+~MDAkl-htaOvQU7t2+wOr`h%x7isQ$m!2Z@$(3vPo+QjqyLhlgqF6b4XKw(+lSU`N~Uvtq);%`MiGL6(c*a!HJWr^AK(G9NFbYm6#uu# zBW4KJ$S5Bw(cslY2hz89O;it;TmZZ;#}KfktPMs*8aBsze&=L5++woTmNYaSN+()B}zUV{ni)vw@a;JXa3r(P)eG5qUu*5Yr{6hVc|)Q zk0nzfS78(9h=PKGumsdXAQ)d4&zhHnc3}^c7qcyx(MU|`hW)cJvm86O1hFjBDMr4 z11dHW3UVNm?nChI4$%Un1G~OZ=lAyYoq{uwIDHLTJ8%>&RI4zCv0_ATrJ1^g#U{w9 zrSgV$vJ9yh7?y#2@QA!GBy_N-e6C(q|F*Nz3AF8);mqTPR!?OX@i_ON%iyMj7Xz&k8 z1!`B?zU}vxplv*YFcX}d^|aRB>Fbw4Fa<$M#~A=Tu9=UQ`!jDoxiU2SK6R&+bw6qd z96L9C9^4f}I1#XuE^biXa0p|3XS-ugbh-1v`5wo|8YiZR(sogzpbr?xiLpsFu^afd z#=6=+bQnJ7+TkD^c~uL@CyZCWH(3g+BeMIEW;CeSA}u=9U4B&M%_GH)?MrN#H3Yg{mVcWS3Ty3lA`RP`QcVH`|o`7!b*8!6!GJU;bN8c zyn=MO|7rnd_?Y@$&_q|J%Gr?g&5>&*+26iatz$|U`l0(?l34k&lm9zzv^F!+^cKFa zs2&wDA$w(g)+(LWnAGadGGr}s2#$}$TB1cNW?v(fABttWr<|wQ|FVB9>QUxmuDywk zGe%6yscbM_*3l7+>+o9I=+;e5m;BevzjVI(f8Mx{y;k5oO|B$~V~iGvAit-$IL!K* znVe+Wkm(Mkhp6tO3+c~pBy2Yzgx9cJeAz#fr2jBB& z&z`-0eJ{D;`zo;OmraCru0zPPw3EGz^H+`Kke8xbI4E@yI~WtE8^Dt>=?vX)z$lHv0+VAAcv6A*VW zxVq0Fs~;r0xG_6h2J`Y@vINCpVjLex{L4*7HVUf#@YyWLB;IL3^7Ha;Tiw6F{#sM> zB8Ij3?%FKcqlJ%-!R^scK(7*l$7Q$i`#pp-3uL`rH_7-SLbaO1L-TWUu@U8J>kue_ zas!wFl9&VtqcWT4!5_zg?R^9r@&Kf!6pPO!Xb%2*}oF-HbRvMO7W167pRn^ z*42gl)ceP@&ySWahTB3*q2~j5R#M|W$%UCLs*irDw{FYRKkn7Fh+9!Jw^I!+sT;S< zks)OM(xN7$nSqPei=|=q;H+x+_6Jz)JC`=P2Z4$)(az4R2tjaRaSvQ}N;2MXsMgz^Az9D3U z*v}s)R+LTBG9+w+eK^+J0wPnyir$ z1cC!Q?7B+O=A!Z;-aErMi^+G22(X!eRxKHytz#eTH688k?RBw(0}hNs2G|)9ez)Z# z{3PYEjl7X*a(aWP3p8TrSZ>P8iTEa+C@s1VG0_6;n0Pa#2qP1|SfK)3dUCy}d`r#yBtPctW7N{dlNd?(T!n&s3u3 zEuiKOk5E@&=TXsW-6ZrAU0htcsygPa8;*fg_y~cHi5b^84qHiE&w$G}uDg>ydWU`R!$_*W$A*z{9&pw5tA#^@VEX~hj>@SkXX{`(w zT;-%tWrQv@+|#%Sxa`@Q7PVJk!&iAjY;0^u{SPAe1O)al$OM_jPPM6fm|B4lK%t;S z0iyKy=;(GUQn73>bIMaLa)aDi=3xuuuPrStAg+Jswg!5-i5vwwsI~5D>BG(fU+#C@ zrwr=AC{|5^(F;c(%{QUoqZl!Y$YPXzXh?eJyM(x`pGCc#g!iAuQZ~)Mdro|@9g>-x zzMn!TcN-n?>eZLirQVi1?>YZiH=jI z<`?XjMlZuoDei}=13{XuEly`i!e?DOCw&wV&Q?og5V8!347pRvj6?=9r4KAanb-~p zDFaaeNiDZW+(00G*zDBtaB;ONO;yVbyYEv|gAmZ@^9KXBQUbbPMTVnIs*SxLA8WJq zx>yQz>E62kj#955QLmK@lV`VY>q+l};$aEaZpfny9*+W0C1GpcP z9~(&P=={wqY9J;g1knZ|3CSq{PPcr|pm_m<0Kg^Z4%RUi^p7{^%JHv_vylR|imU1tl$;!3L=M!WPC!2c>F6W~L4jpz5~{x~8k5Xf zXL@MsCea9GjSN7N!mKQ0I-k9~yp-}dr?T?%*C0qiu2eT*;uU7jyF11<|#z}42t&+R~J zw`2bdaqB_)E02-ccAbyn#R7vLrni2mw=Ql(lJLg#_f>~{Ua>j~{nm*Q_n~m7Y_mLm zsGYyOx*%#cyb@pRnaN)+EtU($lC?mE*af`Tl0E}A$fUXZL&E288%)k@+_U5FwOQt= z#2>z?5GQMy?8qfR-E)ZY$6zT_G`XgoQ6-iwveda-ZD7cjA0PU2mTm!7H>L)G{6Uv)@uiRC6Vue(MlqzPo%9VQ@` z`^q9G|5kS2KchIjHCyz?bw2pEzxbC|*^0S@u0HKZ$k$bD{oQb^njT|Pr?IngI%Ueg zWga1Zt)~Cw0~;$dJjk7%%|^XMowjY*i6v@1M!`XSO_n$cr}`GKm1}5_aw%v>1!FvY z+78^dcN}q$Prp`H-X%G9p-@P;fzT>Rub2t0{#gJ5gDo`X2}oi7v*Kr}P_ zh!_YJwpLa-+1W=d!{1|7zI**j;BC29Y;0@{fiiu7@jzxb#PQHe0cbDOw8UC%WW24TzNNEIR-X+U1L!z?~^yZh?)`-$?2D&oG}MQQxju0J`0 zID>5th z8@t-%goFUYslxeCU<8V{GNR#G_#QY?Y}cHqS-WP1nbDYUFn3xZ^@DhEgP4)A zS^P*M*2kz(;4emO{@_6DTT9E8RtZ$w?QLy4>PH;mK|weJLQ`(1>+Y-T#62x7X^{S0 ztr6h(RJF8!0%0eoOT;Vs^6Wik1_o7t4I{P!X?EtUkJT;eAzFl`?Ux;ROl*=}Xu!IK z(9tqg0W)mpLvw%eZ%?_8BC)wG{%9yFzH!rm&N?87`R!I{2m&ve`UGv@hGci`j$YpW zKouzs-Vb?DwT;)BkKbrC5(}51CE+w4gq^v&wz5dGw(_&@?qn}a%Vu4zLBEX71i%Tw z9rk@T{kB1K>z?SqIP=cM+@fCjtN}7!>vYhKT|ELU+w1Gy3WO$jph_=&ziTKgEWF#3 z_>UX*jD=?e{_XFLWS1@s;>9zjFBrUTp;CDyi17v0*8XTjPWHRJ73N+g|J9#<`(aD- zQPfYvO8L${%lau?oYKoWlB!5VL#K@imSuV)H?w^q1&tErbJmcu&^AV#zw4?RrD)c z93=D*$O|9Og{vu}-Q0$vx!Y8k(B*b#*>CME#J=Ll9YATMOE?vGMU5SY0mnJD$7B zSd@dS2n^&MF7zdNi9N~QH}y?X^NCQ!#9f#nN4J_Mn$ z0Gr(wS#McsDZnnvAZu5{8Qq$Q9gIGOA`krE9_-GY^E;wmn3|1lt`KrUfp?~C5u7rh ze#!+cP1r9XO@hZ11QC_+#^75_j$BOC;oXBA0#-)orO-roDi^4=9x|B7V1l0qq=+_Y zX=%_|F=RC1+`rq|)%6?Z4*+uc_sUL82MRzS1cgGaSJfY zI;=QEkKt|6Ffio&qCJ5?2HfQUsP-N9bA|vIG7kt1g$6oVV+>wrDLio#Wp6{{<0qiA zk?frLpO8_o8D=7e2W`OGgMmo(Or%?YRme3+5j#i3`NxsYspxuw3=__dslm5{qyTTR zn>Tvfo=~yn6%P_juVKI5?s~oL)Fk{HMaa3Gxn1@#WL~A zqtG*keTRY*!N@;KwB^w^MjFRgX6pM3+#`PH4o2(#R%1r4WswfIeJSXf-Qf;F3l4Zs zDI)2EhKOoW&Lhe;9HoU%hcuUh@SqZSr+5TGtmthlH!TpWj{dM)Ql zqYTCVI=0W_mBr#Ktlh^8eyB032;~|?t4mBxF?KT#VSn8FDiXgv{ir)JUncoQY8*=U z0Qcj^zo|LV+S=LpaZ=bU9R`D{HmuaQE46+m&Z=)*2sZA>y{=5MDIw@tnd8V#WS^3F zPLq;pXVb&?*(&|jfHXsx)antZBzzhulyr3MgW^9lfT*vHFephZstNG(E3W&7<%3bZ zcAB=N^%)aipn;ba(+5R|wOtQazw5(i6iks0p^zRbbf+}Mp!?^f(4;Gr3YJq)umP^? zcptvHy1K%s0Sw#1UIE#3JF+K^^9BM!fQhV^$$$Uq2pKk8X=rJYCwJjo+l*?~P!I-76@ z&)mCvR{$OyxL-NUK0l*@?I4j&->20Zg%a{BW@ctV!O1!{0pSMN$c@9#HWjkYJ+-4;Vz@$Aff1 zR^UgtED1_1r>(yqmKUa{3gMuc56|w+vbdSS11W}e6|g|$tKS|X2+t(E(2FF2AB`t4 zP+L=7T@RBtFzAHh0Tw^tjuUAyY<}@&P{<@Hr}Edpz%D#CP#`52a^XR^0RLW8R1~VK zxkY9K0y%8xh>v2;%0_V)i`e?`%risVB><9B5(I=^L%bbOYh?$m1?VY*$oQkC@&JMCSWf%VoQ%$4v2rW>9mNA7v8`0kUM2aIh^A2H)A3LV`>?*F zXLbv>n^k!@$-XCAE5Y$*zkz_1wz<{$vjkFn@giUQ$*j z@$CcU-;Q8o#Pq4G2pR%;@hy6}Hoj*C!%K}c$X>ceMvZmIGOD*>MSXZ^sJBCfP1L7P zpKuJ62wh>OBz}dVIsV~S&f4(i(+IQXGwW|Zw*44SW_!l>?8166KayiKG+PC>(oica z)LP#+&1GfHHM5rfu<1@GkJhP}r}tiH=;GWQeMq&h&pJw7{2KSM&))`}L^huwsTk?J zpYAE5CY)Xf{(GTQFSW?0Jm!`$u?`eAW7;&>3lU{ZdRnGJrMRre;6Lr~AsD_d& z`}?K2xieBz-NkRWgTT3C2=?COQYJSvKpxBa6Tz6{-(V~r;9+oDL)a5GFgN^iEm zfGRV%A4{+H1maf^PF#Gm!7B6^{d`b@2~j#*d&$KetAC;+n0U|3SLj7$=FV; z_VSOs2X}L%sLS-4lhZ>P?^WRLB<6tiw{6>oji0{OYVl7EV)~KNkOAS@d?xjLs0bnk z?lsnKU`|Uy*h0$!VmUKCeQW#tfxi#VaZ0WX_UUFzOlAV=|E)bg71}W@_(O09CTxo zK4Od!7|HDJufla7Zn?cxIo|3NU(dEg{q5$y0^ZM>=ApDg_R;(koyy(aWpTx6rv>DG zsIKk&CrOge9zLbVap<4N`d<%sXC@+#_#*3ne~~iPv>k%YD!#6L~&Jn)}W*_5NHazXXO!*Fy_abr*cnAN_0`~E)l@j;d5*sXdfLY)XB z5&xU5cz5+tXeh16kJb4)zJUK61b_H2$YKx}@!zMkY?L-jn6Ozmr@`B3R6k z>%Y%(qbU=c$0Yv$i*fhF|G60PCACTj`31$ za5nSJ*|FW*ZZ_;{_iPe-99ivNn9am?hVAXR-iK$GO}hH7&##r;em;_2bng;7Q&n&k z?|^Eaq=35Zr$e`|l=fSD+*&UjJlvNxoF}l*-SOM(VY6UL!^9-nQLgEd2$N?=*T?x6 z-V4jwom|BDRN*L>a?b048~yAJVbZS?((LBUcI#Za*DG|ec-@BG1UooIWuPDgRf8@$>r?1%ObL2?j zTLtA%7FSTPf^G|HV^vpdl83$zznM|v{ui1#4^-?qFYJAx$;-rJMbCft!@HI&tDtLH zBcuD^g4mJoJJcl)VyIlvm)_nTdqls3xk*zc<$Fg~J*{P7vTFuwmDkM5`%xxqu90J7 zoeJSGk?{2W>sQTD8K{ee*#WT*lw2k5?!gO0$pILRxR(&~h96Tq;pH1n*MpCEEh9EE zawk3gExP);y8P>RD`eBPvo3LA`~v+_DfS8N+Bo0@8>oSSfg&3tBLjmy^0wfv`c~$s zK{0iQ4wQ@ayzup}m1G;|zLdE;PMwF9)bjbWkB(W)wj%p`dwKB$izatW_m?%!i0s%A zjuA?nBSQVh`g(av;)b2(M59ZWczt!Sf4Eu|vAC!tgotm4v->Vi=!OkXu>KR!uie7X z)dwo_e_mD^`LCREdScJSoJW2hIeQ^+OV~Fo3npv}%%PyP`PsFt(pCLUd6HKv6z>EyvN3jwYNf|C3bs%k(>r~YRasJv|KfXV zmAEMF``S-lKQyg&y6&g{b^q=+eRqGI!i4F4Z*PL{<7(znEG+_01@m>Hi5IfFD$SZa zPcR7Tn`M!2=y*;~Y&MoTr%Qf~`+lax&3YyJpabc^I=9)UAu?udNI= zgmifrU>_bTm4=iy%U+KQj_u@jXV;L|=a-g70kW5%eHDa1K!4d1x@F8V##Bvch{1Jv z_s;UZ&5OIpXTxt2Yzf=H{^42Dd#)$2w2*bb>Z=2{NBLgT7=O+4-4C74-;EG$KK_m% zx|5bbbGs|?`pZSPxckZ`QaPxHj`gTKEBcgvBmF#;*ftMw#~JnG%hr3_{KRh6NDV%E zqx(fqO@vw1)c+gC z{0J#Y|K}(#HMM&h8Iyw*6_-Mk(~hHyh~Q8_1@>)hOn+1h-oNM|#<3eLWFrABl#ppT z(QR(`?%g37pKJaZ?V$Ns{Pl|x07{Cb|E*h4Gd9e;U2_0JPkQs+kgjwU=abl)4Bb1t z7Dv*J8&X=G{RiqOLsvB#qpwIskBHZ#6$dx8+l%{ilGZEn?k1@4W-<}>(S5h)-}Pp! z^dSxG*N+|h;?)3Rw=`Js&^+RHsvU?dU#JV^h&&BEuXwZN>-rRNIT!Fqde=1iA)>3(aW!tzXZgckqRH0;1BM?r!yDnf`^Rp`}Cy81yf!HXmz;|IGzJ z07ESbgNjL5s$aMb$AWan!M9HxVe-Vx#)eL50=r7B8wJQFeMtf|hy4}3j?LtWK|H#7 z$6z{CVZCk4nzH^L$N9m@{Ky^hUHBY5PFq z#JmUz)$A~Lw&}^RhHxq4Bpsis$BhDQp1l=v$&cq)EWLuLEXD>~f5fgao;)zIbc3@o ztz5wEh`G}%`j?BE4_}er;By}+b>NmukA5Ab5gfR8d?b)lTp;AigZsk1C-#oF>DSyG z6y3vA>^MF)b*SQcMu|##)agKrjo(f6uh}X1FVpnWQzf)~bvS4sRgj*xILxDbd56+W zGP|B_9NAq?Hlr^sXOq&e@wqus)wU9##TmXe21C-6+h zJDW#GrGdvqDCvfrjVB*GL_A?CKEaRIf3I!eX}`iW7wWOd;^f z0`3N@{)!4;@RQXJa|FD0pV`O6^c%tjLn#88oK>hyQ3+SZ`>*zCnMZGheY3kKV6D&j z!~UYYO}e7MV{$*&EgIB0r~UTq6lAzluM-rI^UR6;#NMo5>Atc$We-oMXeufyvO;{E znOWD+pqj3YB|7;;Ud6FkyyI>^9~Kr~)6rod`b4H6W&;QZh}iV;<4#I)YzF|`HtgP1 zR|js#81~{QDOIC!H;93k$y7_)*5sQPgqg87*}~GY4Yh6jQ&a$z6>DIQs;a1vlb+Pn z)Wim$#120}suD{QO^_TtIvJbR6&ERtk!^@Q=cukiFn4 z#v42VkZ0;48jwnKVnv3z`U2ajsO*Tc+tCJJJDRG~>8;>N`TA~3x;NC-9rNAr!cL<8 z{{9s~h{3@rrvc}jntHhS5GN-F@$YQKf~$w{YZBx?V*W_vbwRCh;mQ>}2UCDB!T88{ z@}v)35i0`(8rat)aG!y82lFtbrn}Wc4uET7WAQ6)05=kiqnV)Jx6cO>7BJNzRq9=* z0;Benq9V-c7Kc$`L7fP-n;0_s<9r+S*P=7(`y&+EVt z-6@i&!j9sGMHQ0Z>2KD-rX+HAiq$`r#0VX|u;T`=tZ2ixKeHZY#`RqVeFwLkI9C_b zH1k8qOtK)*U!I&W9w?q?%JXpC#(Uj#rDkiiT#BB2{mRn&rPnuDUz?VkD6UcVdqko0 zx#N8v_eV@dXQI4BgsQ7PDTHB$hVd(^lpMG{pl9KcTfnO%E$t2V1k4s5iCwxs9v8Qx z0F$RRbPR>e$2s1J6{_V~^3aC7v)Ka$*sh6TR+3tDttW~DPC8}f6{@JdN zjPES4nxu3NHlI6Qr&J%S{{C6v7eS(KZ23#DUN(UC2@E^cNCdD+9K(w}N34I5KSkPE z7Y*ECF^C9&Xv0rnmGS!0*-88go_~2p#r$?Teh=O5}1c^AV??@hznLjLK@%& zc;LW+x4?^V0CBo^c532gRcq$=umgT%X2#5>gYhr9QhPi^yd9t$tZ`{+Xc(vhiA%)b ztR(67(^lk;((7}7SA@QYg3Q6j=KKx0uwm3U`kI=*=9^`0tgP@>UBRrTsc9NB1XK{) zDJfy?h$IZ)ZF3~nY*_)q7UJS}^ow|5QN@NrkAnP-iham3gfH3-F%9<~5Ee}pRt^n@n%H&qFZ7Kt(~wF)XTI_!j@rmzCKP{3>Jz%s2mmtxigHQOl~)lg~E$ z6BH?be;8#`7ZBB@yKpbp>V4@SFZ=J|leQnkRD^O?hHUvlBgQ4_JcIVO*Id;!;wCH$ zo)VP%TxnpfJK18w>;19fiksQtf|ULBy4IHk_qEi_)~+#1sJdpY*o6>c0&bNB#*BEk%PlV!a`oO5 zBrX&3cWb-J+igy@HaW~CUN+Y~Z#)@$3ttMF$ujwYS(-`*%+o|9nEHTxwdB0|`%ooAj~GDWk* zypmKV-cRJY$#+NuH&OA-^cFW`JTN!Xu;_KqHR|I#V=)zNEOg zthRA*o2Mrxo@r;vD=0wdfa$}HdD1t4T5ZtsBI?!7?6%Y(e~@q7@X&{cIVL;nXpGJ! zAt`eadC5Zzs7Zp!2mx|7idzm+@~kSmDdi^&rIYF7o%7nijRU)fJUmvAoZoJ4tikr(7Cff$rlRsj zdPh_RHzNtpKVIY&dA>k-3l@wQ?ZE#;{ecoJUOokN61wmuVvqrX(QvTnp@_d+|%#nroq<2g)H1qB?h6=q{ljr-f zY4j{aCR|8}YNBK1?Hw$6C_)e}^WOZ9PfTP}Z;OnKMBm|sep%3yIZ@X4Z9&0($50&; z6SiHus)Rp{Ci|M2EoT3vv`>OH0Q^88S_LnVr1?8P;LnKl362RwJ^^u2AjZ8Y-i{wX z4m`&LALU0=;^g$S>A7=l2vK;s&;o}ti(m&NGSDn0H6RX5L7QBA6@x6|S}i1!l_jF% z54IzN1b!5S7)XKOD`2dQph8YTVc_z#p|KGTGQXjHIR*w*mX4vm{@CPX89en+mbJf4 zS`W|gkcwmv-n;{^0<66*L109}IDP0oYze@tA}Wc{e_?LCE@#WR-C zACs9I8z29ex6S-MF6{2z5-`I7`8ZI%1)u`BKx)XdZ<($iJ_)UhLBV(Y><-^GAM@W6Ir?cm%Yz`IuIW0l^kpn%w-OrMj+Xz}Wx6jO|o)PNw z@!+!QoeaOBGElr?;E~SeWRSCW6T0s9jtG2dQKnAr`LYUugNX# zU-3th{eGtX1WLCe&zb8rm8W$5TkETgWMs+&$aXIoX6rdn1yxBnw{vUr z(NcO#3%%=BZmsG1?P92_k|k7V98MytBF4?G*GiyX^H`#FBPTfSo-~l{`y*54>8Y%H zO18Htmv!dRr|s!ldmSe=1ljDGpOu%dV@w5||B=JCL$UQk70=n;Knw!^`|x}BzGt_X zy}TW=^7Zg&DXkxdi8VH1D1pl%aKhAc4IBmRPq*z!L6#)uOET<_Bf*CHJui=9Xm@8DQ&xK?? zF2K_S_eF4y7ekNU_-&7WCj6S8hs<>qlJDhTsDA#m1Bb<~q0nZXxuvCKSzn*2^ubRC z@ES>e{1F0Ayoi^By@+re=#tOg7+t;kB5P-YQg7DZfg$s`vC!cX;!MvgXR#Cs6P1k2 z=K8gbu%ReAaR29F>G*I4vE6BAKssz#7{(n(Wn?fD{tb^R49HyvJ~9uZ|AH9w=GhiI zD9El~U&b|nIHWmw9R11Mx7+MLe*9QnSphnE_{52|v~ZcBW>uls9YC1@q#%rh;hrRf z5Xo_f9_wFmY+?n6b6P2uK~DtSgX?V4&#zzKnT=wIj)+3MPY8j+$&ATdAFr}je11|` zI1hCS%HKu$E9J}V9&fXy@ znZx4Yh#jaCLqbDypFel_+J8K3etsUq|72dj>b#SY4<39FzNe&l0$N254hsO->mw{s z5dxq@*t}@!v&@ZpmWqJ!+(+LNwx1tEIso&lfdhWgxNzuKeLU@oR+0ON5WkR+!}m8T z#2~O~O&Y@KLj(ZdgHkJV*O}I>@IqrS*-mxdCue(^_Z}PjdSLBYRH4r4I8sK|+f(-Y z&Uf>WQ;M9n{GQawap81VfhR}$lS(bcwy|nb*4ZVMTOV}7B15RIZXui~&A&mN5Fp6I#V1f5cf{}tm$5Z-drZ|=(PWv$N|2R>gr z_C@0Yzwe`-;r)!}_d;2f30BdssmN>uA8Qxzbwsn=JK21JtjeQxOylkAv}8e9P8vLXCck=0lqYl;pQe-*`^-mvu?` zRcCx*U|`Ufk&YR6=6RtnK&PE^NBIDit(-uEwxoaPJ%{N-3Z%~zoAt&G4k}$^o)i&g z?G$FMb|5!zh0tQ)8_Kmma%@2L}c=K;8J+S2|Cf zctcLk&qL|H-2TTr^-}L`eXI`A--XYQ((7H}!zD0Ic(|2nyN|=C0qBmM2xmaV%9%@IEc55#4 z2m($8WSqa>(cv4BwFPyz#nufFyk+h7d4rX;wX-OBz??=IVhCmq^lvE7jRsd~ zXaP$Z!crbQExh7l*MFI#*+hfZG0kJM(fKrQ#ZzDAjvLsp1mPVow*|f@-n!o6yqoSe z2AwrETu90gjIA3608IJ~e%j(<(b(6ft3<^*=HA~YChC1ZV50x++c!ka2Gl{D{8Ll3 zTNL0%0?hfMXq-wqDIrS-5}r?Z`)Ju^`qpoILn?zlaX!R&w8sRLz`?lpOU=m z(Arqrn`=wOsIy1rcqGT;$Ftic7{zM}c}G-8oav})9A9UR`pNqd>;rU8_#L?$c+S%! z!L8GY?gw8&|4f`sUDk=2$Eopoz73bZSx;Hpl)4$E2UQY|(i`%5u{cGlk~0(ZH7k*h!hm5M~=IQx+DM3|X&ZT`9Y&I%@pFyfa9Ff%?wbm*J#YftmQ66Up zv~UX-`_7$}v3KI%DKnoi1_9>*kDHUrvt$G z!}15{!JcDDH^w+Qzw4MSk&WkfkXqXNh%_n$W2*7=)fg9+b)DrJ8fIgXE>%3RQJ8f(a% zP06x52>7)`PtM7ERB^R)pJWqZk`Z!##p`*H@5lP&A23bCdWr()5fuz+F(?OW+A!iA zV*eSs2jC)%gyjXXHY1+sdWazQNqd%mHt50z!zoncSy@?NQx#qx2Z;G2ltOU&0O1Q{ z4{~zvP~rY-UeChCEN1%;DdOqvZ^zFlgu7S0v!Ky*m%C`r-#a|6%To01{hI=joel)^ zgS*`99Ho-6E*#V+1kQ?cRp7MweS=}zo|uQkkzWcaTKg;vW0f{9nUWlhihURaGp5ATLY7<4k+`#LN@yEdOqr_CS^vmUfQ#uYw^jz=9+M6QZO z0bwD?+1ux!L-l2&lSi$HO&xDh;y?wZPI0v;k3P9FHa^~P@M~yAK3Lso^$NaZc1?m( zr=;b@pq8tbP+=#=vNC%qUXOKek<6R984ePs@5NA8KO&M0mX6Gd_=*mV$!9 z<-GdyYfd34Ji%~z`iyc9{V+ctAJW0!Szkm1g1GqHhi_+C4brt|qo1vp{mM3#S<>I& zUc~{hjsrd=t4QQ_GgXJrUj6o-0xwzHsYmdIFt5A?u`=MNm1b@W&O?WuJbenk(>^3( zmnCP`-M~1n4lBS0B zmL}+4l97-O(Up8<*sM3HA05N1I~hK}8{Jx`bbu~o;(9#Wo*~W3p?MGS*lC8zJ)fK+ z9vBpHXFaOQe{I4hn95RBr6qH?E=)fEx8b*e!D|ietHCUm8d*Q04(_1!JbGGjakiMm zkEzM|aC%*^a;*cCRkYon?Jt$5Z}XQLN>B3aeEC~b-XtY^ixXjAi$w=@&?)N3{mD)ibKDF1DAG!|p+I%YjgSRhiF`726D zL|u+T+kJ7OLpcp)ddT{H4|Qsei^9y{cG~vbkBxOl%073SWuKXy&Z#B#ZQNV8FDTm! zJ9{3?kbriXnORuWYsu=<)h~5^m34dWJsP;fK4VUvwo@_FZB!f;0P;7{)E#SRfYo01 zsz?Dqk_S#F{s!`fL?R2}K}<}1vZ0<{XRkSW?wm&)&OzDOY{ld z5sDKQ=72nJ>K!|Lr}s$j9?b7scfl3`kQk(aW?`XqoElMm1I)9E;Q)pqz$@wRl3NNy zeFWf31R{WwKiuABSA=de0itH91gG8X?1PvEqjhT$o0^_(I_yAb9?;_QQn3d0cIDgC zyj{C??UV;zvQfGGi^lyl)!8<|Q;2dn0S-1tAi4?BuVC`sfiLQOp z?P}+**4!Sx?}k&ih=ks!7UvI_PHj2zjq%`Z8ylq)UYFUwce_R={Ham6d zn}*=e)w10tb=h%yWB=I2)T=16`{)~_TS+tCdXdQTti#Sv@wEE*^qz#2n2Cq!qw~M} zcpSKP4IJ*W2+B5}JuPdM;a8jTVo!fx*-v%xn6ZJuNTamRpy)mF0>9!PEJL0+WAW$bsx<12tJrw)IDtff01u*?%y)eewtAI0)=w8R%u*)=%&QgGma#=1g z(lfqh-}{O@?mlDc=;-**xJ1yj{Nc=* z9Rx3fNnBX)_-BTP)3mde4~cHHiYGyZ@;;Cn@aB)n$-Vpbnd<5;4u{La62Jl2V)L6s zI(b}Z;};i{aP2XWzI^`V(~JzL`(PiE@mm#mE#Viyz@yH`)38<}q>PdP14oak9&y5T z5Zo@ai^7Z?3@GkHhaUD+#SJRKy}6|1I`{xjKmO%xwh$^eU$_s>YU%Wny=zV^5Kp)$ z@#gj+^3(F?Bly@TD9HKuH&b2kpt_}PzfAC^IzXj={j3h%UZ!{+wv)Um3Vwv~H{8AM zJ%9d)D@_3dOti2W}hsvcAWh!WfW#2b)jhM3$^=^ zmv`rkN7GA{Uw+VCm%Zz?(uki?nAOLmw#C>)ohK#-ToS?`+>*R{yq;?hUvYW~290XyE+##)O?CCvWDmV~%F@8$h&sehfR4TWz%H@+hb7*KHSLt)ABzT3kH8**Lr>XuPLr5}|;&Ad_pC zzC$rAFD9u}Q~Mm;;q=rYaNzQ%;n{aH`q#(Y%CJY%d&h2a4jCCAP@xKAkA_J&Yn8!;i6!rK{1=%E=bsB^)KGXf1}Y^yxFz6!NNYr%f+_M49nYQC>Hr~< zGvpfk9?M1xxTZ-G@(pt_ihWy2d5f@HHVcbM@m`ew zdQW}0{1+)v%!xF@Bft&y- zUGv9}D~KL&63U4&CXv(B{LJRw_X0%)z?RHy7}r3An}m6B&6!|eA&`Gj6XMs|If|Z7 zn8830vflR0eV$_+6(VRU*H=c9d(i|gJGo}aCp7-}v!5p&EQg8nCh0z%1N{WRBq(i- z7cF%i+c9nszgS1NSc}L*BxAzV08>kHKY)|O5}DoW*G)-Z#6i>m;EsJaIRuk|3`Y!s zFlyF2t7B^Vd|(r88a_KHC+~W--=H_ZV936J#Mjn#3|t>sfE5UIq;d#l;PJ0OkC%NO zV!Oz=xZ_}yIX=%vaYn?Z(UKG7{wS}Qrp>swF1dyc!;=$h-3%&iQ3@|Z=gwLcJ)dLQN(eB*%G=ed z(Pw|w9_-p5z)TarES&cA<3fj$XyALk+x;9oR2`EmCBkWrXO}ETFEdbCM;#TJllN%o zrq8b89V;?cv8U_0p7^ktf=TE2mF?jN_yrosw@zFTRL=>_a#yhu*U*ZfRQPlx3fhzeuP-NjH_98WRnMMveBO2q z|KRHCiVE+!^|$b3*xO)*5*S{Co%gt)a~pSoyPM~Z!$Lx!S8J%M@(T#O?6wzZP*ze} zaUFjSo0%Ou28%AouCh+@A1ND3lU`9>()H8uO(6}@j^Nbna~x=$xGA%3l#kUvhV6MF zDY|8Q zdiveFxf`6p)HcmKd*%#4DM?qAn30z+#T?IPfi4RNPfTMx3wM=P%s`++c zp!j*mOrBsv#hCl|Q#T^7mKx*Tq@|)#QB%{Y{W9g|>R5T_4yEHuG}*WI3)AevOSxyy zxWpAx)1=qdM>sf6GE}2-!WD4*V}d?eODmEmn5HVzjAqd_yf!L2+UkXVc?Qgt@V^MF zIIHfjU%&D@c4a0(O3lK;vS*LS(1&dRU!|YmaugJ3UkS8+p^w`igaf(0ab(j^OFv{KkiIu^czP>)(x%PG)`8uFRUfdi3POQ7x*rpQT0?iSi zdF@Ur&t}>4J^1qmyB5O;%)SH#1x*Sro>8rxv(YcI>mZh22xfy`x1UvhTM;TWuZ$_L z5*HO_)uhrPQQC4~eeLOoS?LSTd?v+zmaeP&lTMHwukz!u4#-av6$tNpz_hD}lzMf; z3j|>yg4&~FEk<1P;KBmyB_iJg6Z-;uv_Rb>5?D174cNGN0hX?4z9^G zack{GOJ|j=Mr)%|XDd6q3CHa_2oQGcVq_EtrH5lzeTtqIi!Sxdvzx;bVgTpRwK*nEn_Z6((MDlXG>L#jV2j_LU1W@zXKx z3m-*!j8wCFWhzq#14gi)8mA=lcvK)&XY}!Q#sGbFb@hi`0He|+s09Z1-KprrobcSa zb9>zr=1%U-3F{l0I9r_Don&$&GfY_WpoKWY9Z0&&?~n$Q@3Uc;e7p2I--j*jL~)43 z*0}h~78|jL&S*P0Jnwhl4zMli0spL~cZI`69NyI^Wjag^K|<57y^XWwko^uWe}as+ zr|RvS#IwKF=WyQJU+_fnu@e=}?s_BPN=@&bx3Q8lY+T&IQpG=_}L^IweV|Miosmz)#qh?np0-*MG;s=ISTpY!i8jj>b$L@@UKvna^6(wY$`oshUGD!r5+#R=dKK67e;qo$hPav?xCZgi znO4VG!;lck|M{^1Iy)ABqvHqubD)&Vdyaru|9^h+|9pH)LrKDaegfAz zAT%^|3cDKrc?AA-7Mg9xvO9WOMTjTqd-8tH1Mw1-$wx;sGV2%!lmrX8s=}St3SM_k zHeVBDJNVB9*pzib5Eo=4!(SI-5AeUt9MCl!t1cOCq zx2F(jzIk-AKAC(#_g(tC+6(c#_IlBcb0idTt*^HI(A%k8W>o*_ITLwucX=8Q^Fp6% zR*ENW_FL`I2O5l>WyYxo_*t8)1NQbRQe3a?m6{7brLrgJkm8mn)mv|v<+-cKCi8HU z)2FFT4c#Jq)j4T$GO+5faE#SUNf~L5FZLyx6qfF)H_qzv7PKBadQvCIEo8*NSuF2u zIcmYT%7imd_k4_5*7Wb^oR&1LJVNfJyi;C`MM$*$>y2%`T(Oyt1nl#BqzazczO=Uw z;N|T5d?0f9_(P*Xu2x%7W8S-X;pd4RJ@|)K2F1PJSg~2wKYhY)xP(I?NX-4ZhDH-o z5rdLQ95_0z*o=LCJ1<{eekO!OjvBlt)g*+=v+XN=ToY$DqZa;oQKP`+-nOI0>rI=6!Ktj$o-cE9 z?pZKbRD8BFK!MS=8De(MAh+|=C$;d}w64h;3u7s5<36JSD5`{F`8hd9mTKLA3Sfl` z`Wys>LD^niqGH*=Ag*?pBS2pu2I+8eaaDHerw>X30PA3Ge9{}1si*8q&f*DViS+Tk{Zu!gWjZEy-Pot3rg80)+qZk7YIlx2EiTC1 zmZN^EK6XMQfx{L0xB;E-_XlSZHHcb8in?JW%O_Qwe_iDbd9?a^}md|EfX@+3!?Ei{cK9 zXxKf0bbJOSZ~TcaAjaHk51oj!xW!c>u7v#U0UI5Uq^*7+Tf^hT9(Adgn*Hgw zx0_X{$z5vEcrus!@^hCd_|81$k(0CVcoP#Mx-}r)CD1jj$UwJ9R7xLQK_)1OS|$DZA>`dMKI zh|#pW^ohaV?rI;J_7`_g$otv|C}-H>jeQKV1?N`km6($?-}X4a=OH z;(Kv-hi_t`yUOk`jsUsLpcWN1uZUMBPbwweSTEL17+eo>=@jKKe05IEZ+z~u%UzX+ zXUGWT`*j)uW)BJW(OvH3W#CCada^slam4TK)yDAL4Os+&Jn zx#=UKoEFq=^3#d3o}2sf)V`H;ku0fR(G#snygA7pC9jS44_;JC=z>#6Ib?y5A!f@; zCN?26!5cpo80G^j1Me22LnEUwd_>#7iEf-|;*A+K=-5P;V9>r%A=yQ_>SKuU2+K6k zcY~aEENOHG&f2IC7iR{2i3Yy!3Vy=j1Eed*;@$)`8=eP0bWey_{CxQA4putb#MHZr&?=pY*`> z;ZTg&#nUph`RjbD0(=Y5*nrs$My3l0+EUG1$=3(ym0}wx8htr^1d~PunW!%r$E?U| znwWU^UMa@#+zBHQjB{Wqj?P5-h8wCkaI9eFgDq99$#g-7m9!A+x_~aqu9W{RGeWOI z!Bb1lB=38T;hp+8ys2-zdUgGX3WFb=`8ET*8-p-0uUiUoJ4GaAOt*U3beD~#L2O=xhH5ZSUIlbzO zWb(F<&ro!#irTmg;+^sMht^wv))=TB@#XIk~sMY45OgtDJ;{H=dtE!}< zTk`SN`P_bOh4>r$^?1IR*fkvbdaU!wsMkR1q8kYht+4I&uosewaLQCr^L%?ZI)OmY z)EV})Jf%giYDjTeW>0i0UmqobcKd7x?P&3j?jP)VY^`s9*SMt&%nE7EUO%M5%b}Pz z{IV~-FQ$<<`2ME?wFoWb-Bg}k@}x9UwRBYGuED`M!ZenmzkFVZ9aB=$BlwMW`-M=L zX@pCD2hhV0Bz6qq@%#|#-@_PUmL)2suy?FZK#X|%a zc;mu>!_0dzI=*!dXL*WhGCcN+x+g3Yv##;e?$e6=PEfMjwX;3j^>nC7U6#}LUWzic zmr|EoX0837*H8Dvn=o2-)*AU(HU$osK&}D{46eZ|wziKSphL~>P~jD4rm?$=WtVPc zCRz;gcQi6Cm!EF5Essr)2yel?p~~`i+Qu(3 za|sJaSO|HmDrsQ~LP5yxuq!geW)s7b(Y^VD0esmVd}rthVsF3jUl!6)$?CAPVpe{m zZxEAE);wTTjFn4hv0KIt9HLFdq^MBzk5QMP|g zJ$-o3(2;1#1V;)o3e6DAl&%*F-_M~VuqiychPa+&o(AThbm{-iUp*k z#`=e|eFR*BRibw7YtC+XaW_3x^vdZAlXZkJ4ps$uwfIw8xo9r$TJOkT&U~!e{NmKy zgxM-z*;0XJOyKPshfn-oUA>kOe0WNy%;mCA-MT`vkGIcYY0quhrUC(}zAGPJj2O*d zaad`SR;n62;b3rmIc6z5PgdtgqblYVy?;2Q}*A(=X2`~m=l`@TJ^Z4;&Y&se+FQ0u?%K7aofgBMig*(2-X+dL=qaYM-nLPzzO&eV?2)nQ_b}qjJWY( zurbhSG|~N8R|&QPi{Bjk)4tDl{pyT8TnsdeGl@3EFmvYkjuVE?lgLlh9bh6hn<<+V z4j8HsyTimhMV-(}mW3~4RkQDDfVVt_3mx|){en&||k0{Ol)AQ9n}NNy1M6)0~Rbn3|$_bY|kUUt7b$oczSDVW($)MT+YxQ-?(Pw zC_VrAHunidmenHaaXK3+>eiauCh_09L<^sEPKekttDJmr$*t6Tu~LOhK~1Vx=!bSC zh3Y%bI$F2NVro6Vq|w8j2Sf$)fJ*|--T`o zYl*OuWh%3hPb)kXDB ze;(>CF4TBs@MMtNInZ?c=>BlFaN^ZyH&d8H|8WsY0u9U4%XG2lryiBc6c;MZJKg){ zlK5+|O818I1U0L6vy;iKn2&{bqa+v)1x=jb+Cuhjt#>Y#HRoREi*tq)l%mr5Bd?bl z=4b9jY<;yOym)MSdsmDkXHdkcq0lP_Xw{h_|0QPwqi*0l-tU_Eb?}=oTj_TgMCQae zSO#|(NsCK%37TI*ez%$MmlOZy0@wp}heb66nZyk?@dA3zW&N1S|K`utKK~7~_u?zP zdfuGSWukrpxCgSU&k?U=U9AhclvQ1?t>Wk@2t3hN=`|ca*D}3&Ux;pdYU!o)qbZg=)Y$b$P76$c|pOafmBi@t9OctywZIeNAtWLTWGhv3^u}H6%9N|M`o`B zZ#EX{LRcVL|AXLy|8NS#RfSi2UVen~TTPAJ52LaHx85t@o~@6ih3m%<8T;rdRv{OF zdz&6ZHB{?FDGca^;MJz5r{g<+BFbSeC@3qQ%7Y^}>1bbXXSzWQbs)ID7@7F-noR(G{$k09v}6i3wyL;#Lc2!XO8MXz>?_M^RBxj7kQe z7(k8%&CW80fg!2O@PfOC9gJW=fNokkcO~!n^CZyqFxDdqy1_D3OCSOv8bw^Kt!1%V2CM+)T?6l^{{Y$+iZIDB{qfw(#B`%o?XgFQFV+fDgz zp^b-PpkiY&@AK!+xRU+Xnx#SixsEn-PmtCQ-yd-{LEG6p0rda;`4jTh9{X3XU%y7k z71Yl+huG|_^r8-~fBSK;ncy@G05%IFO+_UoD{E_+xw?aB9*n^iLT`vM0+wA%VU$aR zX`2mB-6a+U?W|3CL984L5{d*D8mYHJhmAhH`9+_8hJ|aibZ@!kf0u#cI3f` zI(li6BpVPq)0g5t*NK=VWhVJNo@+fM5Va^*_OgU}O;D7)FLmyBvlXpOmu>W=Jj$xa z3v@F_Of!SxigLfVKRrMoY|Y+lQp`1t?$dJk}{`}c4BB1%#rAtJJ6MoQV)MH$(nL{_%QR-qz0L{Vl63E82N zT}dbs%1Cy$%>V1^zQ4cc`~05c_&d5g|$k#p{A9vmR8i^|9r{#sjgEC}qyNX!8r^iw*9P&kDD<`g^aKRPlV_{=l3f z&L%ytn0lrD=*UD&p>XS0QPE@h&3}ffLz(9%?lNCgF*(4+_xttY8rSL}vwk!8i{8tV z(ib^y6fXrN?{WN|av+w;!O`2a6BZ=U)zVEz#&8m;$^86dP}qcqhsSY_LVk;}c~P&U zIk+dWV%dG51%L{$>#t*6badMgi z!Fm!!juWyhadWSLx`pMz$Stp8xK4rW4>CV!JL$OoKnmYYfEhC^R@w_LssI6uBVnMU zBNAzit#x&D24}8f?q@__3UUvQ>>nUgj5W})vZg$Eu%Diun9^sR;-xJ8cGO+S=moEc z$n&Pk)Mst&HsWJJp!1o)hy@zuj*#s@|3M{U0-Y-9h&}3pl!Sz%AmIMPA4(siBJvh$ zMIbE#+bE&J@r9(%;>4fnfXzzO^4L~H>_Kxs@c^Hwwc=!wE*PAULf0mP}*P(hE~1HEBw z?%^g`ASOUa(((T@J4}oz!(@f<2rv#Rl5eP(AOxpiC!z;v=3$cg*uT@Md^jGm*9;JP zv6(wzJJ`AP25;#8hIK9JI^sSMz{7@e5vNmzQi|Cj8)OrzE5KVP5QV^NJQ+rS1rP;$;}!Dj&9@1$+jSbeF#K|)`FHJ~5bx3qUWPATE5CUu+2xYT zmCUxeF*i?WU%ytBV|`jZh=%aB?q13-=Jn-@m%?Ezt)I(u(zkSkg?1KfIUck{?w(pK zb>o5jg%=VoTRxp6VdH*F&G3rh1m{f^(!i<_yLK<`DC@60WMB7_vVWB3TJHC08Si!V zNEDQ=e@jrMv-CEc7BL?i6Y2Ne)8GO%!(DBsQ@QsYD@Kt;i4Ph91MM(_O{#pwlX7(c zIW`~OzxSIg{-S(`aR!Ii(Nd-4{x8bwZJyUs^m4)@BR6pqaBY8jPQ7BoL`{O7568?`Tli8ucN+xsFkH9 zGOwOEq#DF)sHzf8-aul)s|dit4gh5r?sC|q+#pE8*PwCRekHBro>RG(KEM3&q~ib< zFA^)A(Z<$i?J668Rtd_{l*V~e%(pZy<@@G;8HX>$IK)&=m941W!&#*Pkk zoJ8*ZME0==$hW^^UjZ1J|KODonCXP>V+@fhyPd{46RsxB7^w z`}2{Pp5Df}RYpvpyzupU|0FrHTW+1+>z}`uhb;hYu@yO<+PP!aoNe%^aYNaw1eMZi z*YiKFeZ9HLLy(g%&iOF5F)!C0NxrzZd#9haeTaML`F#h?8yQU(Zz=7Cm+exHd|AVxCx9r@HlsVKazNufcvp?@`yslKM zxU8#Sc-@`d=(7;tpCdv?t|^hsQ*E#4(of-H(_(Tj;M!M9XrWdYwxl1=Wo6mIn#e6I zLpf%o74hP8!D_E-apxA97Kzlvn?v~I2)`#)JC{`mj8Mr}C{Ix0!|KPZe-Uwv7{70# zPxKp((`V`zBK)@BIXZKd@M`*~G-lK&lP(X4OB6(fdaIq^r(8?Ra`Gy{F#dMSBn{W2 z&)`)g=#TlVviY6bDOd(?f!?9ozxXJnr+)q*)#bnv7i`Ypdt8um>4>3!wz5VDx= z!_uY=c;vX!L^x%Z;i*w|M^o1kS^ zky<9)|Mie;oL=iJOE7s{#1!ApZNw$4kS)Wmdw@Q-<&Ttc?$VmcdFD@iZ!!k$_9m&` zYVqsdlG@8rBuA%SoRBYm`7E13>!{by`0=y}$I-Y0r)s2~cCN{cJo&-L^iVurkzKW+ zE-C+Rpbqa||4}A#O68qq6_R?YamkqYGGcanAl=w#>g303BG$ZGwn6S+hB$f{|{^Rb;|h<3AR(1lyM zA3sNH=+ho)D*g0!aOPO@hrmEW|6`X0;llcWWG!`;UE4Cn+1Oqg>t{}xmfzSz1p=p98fzP0bK${LF%#}P&IviY z(q^)4;l0aGX=S;OtlXtI%q)NR_U&5jJF9&r|3q8;p-i4g)gq0cMy>vS$es6>PI%2)s;h?{?Rg_YRVy-f54uZ~V{o>UhC&XU4)j;3FgBpk0~+&nxnnGO zDbK$0O>p)ciWDL;HUI3^e7HvVh`yk;&eGtVk5)_u(##ZYCDF`P`rsUQxz8_4AExBs zF?CdI*<`wNDO}r48?c zin9A$x?`nC&fgD^Tc~(Ly|Xp`bGcZKl+*s~+!vo)tY5x)JfX@a3gbzc!@LzZ$PJ&Ut)S|{tD(=(Y>O%V*@UF3!X_lABS z>P$Mqb6n9W;fZnTljwT?Nrjci!6lSOMGE&{`eZ;VZg+KTZ^7mEqdIx*GgF`-Bqk=( zhY@+7)wO7z$q05!F6L>xa4pIKoG{nxtnoe&4r51TOr4#bahPi&AJf%zC1vH6 zyxv26e71#(6pX<-bcT01)o97Oxo+)>{6)hle^<#ci!w7bO_lbl;!PD-$}|;fC9QZR zwP;SZjiv~Y+c5JIHhX!s4Xy|Df-7j!1KbWAtm!boF$(q=U#53e-+2*6s;$YI@E1=7 z5Z+vvEAasO=Fo+`BdXxKr`#fbkMVAb`d{xh1IdY)4{9P?Gz zP$KCcc&zz&$BrF6NEC!-sXpt=I1mhAb=}{FmuF5TE{wL1KuTQA$FD^P)*{RnGdu0j z`IE?*s{YEn6wMTnzDBga5)*4bn!wrO3fne~qrF2K{-*1+FP}euj+Wsk#NL-5)IQD2 z^8?Wd3ckIJjM-DNfDX<2Np{`5h-SVTlL4RdSd zmjd*Kw7zsVIaoxQQ+a~I?vxAQ)DLeAJ*J%=9}^0aeUSUrIm2T8hwHR#KP$hio-^O= zN9-B7eMO@NnbY^K+-y0z|A~!r=xgt(_=pR2w8hjbT@8j|62rND#z)|5ioksP~PtxbEWF}HAkWwQf*CxfnWS-Mp_cH$L^BxnESw$|YADF-3o6fo5eaig7J6g8j^;!{%+~f5eD7k>UO624vqVYxknQ7Tea8-kB0XJ2Oi|>(bSuk)E}W;OVE3A zs2tI8Rr%ScDmK=q19A)n)ol^x1W*nmR+1y>;C-Dud7~m;J-yAuv-b*A#{G!)FftvO zxr!s2oDeC!c<*>>&m=lHeK%_Fs%msiFbb=Obfv=+F?<{7z@*ma+p8V!5WZs!*2g+j z{WY=cY+9f(L7r5LOp8>F6#wbbDZMw>(%L#RP&rmZaWF0;f;+{YJ9_D6B>*mx?^wk8 zr($_C0h5h)7mc7qN0of~^nPUbz>e&tZ8F9HP^w8Sc3m7(z6pO?!FfG~Z!5Q3T_V_9;266&~ zF3_a;58UR#rO+V&Ye10Vxd~K^BH#6sqqXf0C)P(EsDX1GCge{^S_)sn?F%i>ldPc;7^$5i0KPzLiS+I3xesKFbUeYR3zxJ5f%==K+H==QnMn7u^C(%>-g^64lwsv)f6s}MPh(z_2H_{uc$N}c z2**Pj?}q#;A1>vdVfgZq_pXr@<*e1rLmC{vkfV;@?V?>IJLIZ0(MyvfOOMBoY>cPWN3i6`l;(ij3P>ELd&lfLvJrCL zI_xS9AP0?*7l;s@B$)P~{02e{H#tgnOZYNjom3+rkBIN!HvuF=qyN{OY3EUst_yQF z5BM`4vAgT}rw1GQ0Z=ATaU+DhMny*#cuX3F)gjd>U|L&IEru!I zLx7)ucEc9Pbt@ERMDxS;$7kST3InUnZtI6EK&Ch%rAQfeggtw}tj)XzI9mA7A$xcU zfTm$sa=9BWV~gO3Ao~(>G);~ACmPk5FN0(3)_dg(CU!%&c~YpATrhG&Hv+Evk^Aq* z%9D&!KoAES7m7#=__(ga9Yc_p_Z~bOrI)_BfgSBN{W2+JPMfaoKA(LqC`&{`GZby$ z+M^W6Kcq>>L)FA2J?n^+*2zYSI#2)Fz3wI#l%f@Gj!?I$wDZpiV0l~MZ zS9~Zx7B1B@U5&7PaN?{BmFahq`#|Z^1JxOI{t9bgygw6>PsTbWm=-% z4(TPo2yBhwQmQ#0;#r@DeU@lDdYhCt$mSS5f#Ow%v37{(?X``o=NxVr(^-XwoQj|< zE5Mw2Ia66vS#b4Rc1N|tQ!%$Od**L4Q-V<|_(p+^S;y$`8AIHxIKkNP*e^eer^^re55aGDWrRhi$!Il(?S} zUG@JZO22+$&nJ(Rr;;)HPHq$=TQ}-od~|HOt<=MMK z_vBdm-yQ|ba{$xO^R~86N_TV;{Q_oZamvOeCN|Bj!50a~pS_65vfghgSzJ-m=Tk;7 zg*k@K1R5rX{%Yyt$GxFO@0-|5fMHzt0a+P{X5Vm$yn`~$scHe0WT|?LIJOfAvR2Ox zSXx<`;z$oa@o3AdY$2DaEnJC8M+hrBR@u%{7E;BWwo*z(XLRcz1*OSX=cqv5S))vI zI|Cc%J@4E6wJa@%@cex5U;lGEGV-C6SD`t>Q(Of#SmeYSq1=t9bq^J3mfgZ|k@zNH*YZv{7IFWGy&k-b zzzlm37k33$&rTRAK+)OfbMNlmp)apZ)7yw=ezj=@6kVUtYr%>e2D~R9x96FZyTU4L zOyxAiz7rm~Mw6ZUlQoGb6S_r=>3pPE!|D!W)Q(Eu7sNzN5hW5&!+@aEAY%?* zwL|mb#ft#S>5w+nZSJ?2?OMbcoIXTKk@psya^hudSYxsJ^$ z8M`N7%YFOyp=6nxdivzc_;kg%Q2$ml2ukd{+j4OA_q879HCxA`ay{Hlqdv z!UN3_m^vOfaDa%$OZb=OexB)0`PSV-X-c|-YKPo4ezrq|siPsK-oNXEUaF7uP9Kog z;kqWjoA646t%*DQFsGU9$YR9g!29=C-m3his{L|`_;b!ji-Sn|&ce2aFdpzO8Ny3_ z>}hbG5lcl3Il6}`Q?IR<<(=etv3=JiOKPqtx+uOzO4TS+i+=w!i|KBrN@EgAHk~LE zvh9{GaxbPnj9XYJ+__gpEOV$8vL(rkUBOweh%fJ*f?Sfw8fhxg2`D@3 z90PfqdUi1gu5P?rQD6w%%1CdyPm$ynDHS;z+n)1Y-@OQwp_OhTCr0aoME!J5k}i~W zGXHb$@BhR~ND;sFXT}o3ScbSLzB=@7I740p14Dd>OMB~o@9!x%_dA1R`(W%5m-FN| zcM{~R2@ZtkB(JsxexMN8=EyMqlsTbM<};6l-0Cg1Es=kPG`QDif%~yQiATp@A;LVv z50}aud*;Rc@1=d8&7Q9oZ1_M|w*3}aAjzXw{QG%4C7NwheQS0_Cx2Dss#9UUQ-4T& zQTgxNK;eRATn}0xFw~puZy$Ye*Vq}kYar(2)43LhD?8J8Ni*+KHy4I#o*HsGXl21{o%|= z6+(n&iMRXvS8CP|p`d@hCI0(YQavD>|0NkHfFBs}c-#LaHmIHR{oiT}0LP{7u+sXU zyFaqyei_GnX%_DxI<3^P41uZumSBMbGe}1BnZP@je<$wM~VcP!OP3+&t#!oAT=otR< zs@_{ba#Z=h-XzgNf&cZ=!wmBDVw*bt|Fs>*z@hK7Gd@m*JO6^{j*PR0sm_0dr6^7w z3%RiGllHZLpV@{?8DOJ}C~N+%wN(3vjI;Mo3FKax63kvL?}~TYLzhUaaFvvvC6?yG z#6wH9FiLS-Nx@bN)_(*!?DI9XGgAG3X>l>RXSMELqmHmDJ9LSiGqE{TfsYz;jGYb3 z)rXox1r8B{M)t*1_oIQ_O2`Z0Afuzae${rGLNLHKSK$d;qw|b)a>)7(p&9)$_i!@C zkaFcuhbhVBwcVxMhA%jl-XBC1h|mc=Ei(C!cEv*j;d=p?SONYR?&f{fE9Z#w#ur$(sqrs_ADeA{lUZ+B z4UjVR3%<~EMptIFF0o|C?(vZWxBS+>Pqv)#opE!iIyt{<*~00`M)3NI@oCi!bKP;2QsCK@sw%#buq{^ltR6TO;clg? zzZ!50BT#tq6?NJzRKw<^9rIcMorm6Wt2V#BUh%%W({Na5pFv7Hb%;Kl9_=)JI9C1KtSe5ZX3peFT<_fdd>R70{>}0?rN!3rhkyPQGAvYl-2NmL7YWfU6ny%c zfJ^e!sNPS&;Wi{B#M~#heH1l1&w}?l3g7iU*K!m3uwi1+%BfZ(+h~3WGT!qdkO({} zCjnyhmrd4J)wv4eV>58Vfp7V2L-m4QnyA~`9VvL?M9+DVTyxj~ZrySHIa*^JvKv4C z(L<%02P2o@)Za@P#3|Y><<#%vo`zRVkJ|CGY1=mbiqev|211-1;`?n`8g2Kzp(Z6zvCnLc*lQKRUdQ^!H&{&Q zU3SDf!2*xNbMz@%yk%YH*%B)kyT5&!&HFRWn$t$uInsS~kJH|`TCT-+PAp3vHunw- zlxI>FJe?faVgArqYhu~mW^HeXJx36fJU9H!F}4pB+{}#+d9YpLcPt zn~9~t{gepvi*HVq`q+uJ!k$0V0OJVtpskI~Rg8M@;aeJh`0yb`%4-&|3hG4$8k!j& zZl0ifr?W8a>p$zSL;)@z1i>KQ!@t-VsK&H0UU+JPeXobhO#IrHDLc<$+a{vt#}BQ&_FG={>y$Wo9Ic9hj#Z5`h>U`rmy1n9G3k@; zS@|lHq|irnTGWapT3S(Q<=Ltn#DLMC*Wb$P3 zJV)=Aj>9V>2L~gSem+&sE^RlG-1}7L+&+Pk9Xnk^Pe;sGFX?Wc#3Q{uFEY_zV}2*^ z7P#q0frDXg;0nviB^O5mTWr&W$0}$YdbtKO2MXFJiS%`hZvkR~AvNK+|EQx0h8rN= zE>4#AlgeQr2eHC*|Nd#}8!tC!9qurOT)>~M14`3K`9N*HH&3ZOIlzaEyBN2k)ixVI1#>fuIEs1o-xsKnCIpLFa zhhG0um6!pSmg*DN_G!xjaf9v~iAOrUnHzRgA4g>GBj^4T+s!rgmRBYwjrYc1>m`jn zarMAUBOPt4R4!fb(pN)^74#}OB4s5eUslx`wOEShl$}yZi z4J(V&17Ci9Uo?HnpHuKOF6MPF>0poTw>>s5+-~N7p+9FB6?pJ^=Z(QF*WC{jvPF-_ z2gEvAk;`uPVk=s-OWF8yf#sX$N>2|nGqK8qGo2?1A5=$>CBfTm4VQ$FurSdi{{Bmd zo`?+&{C(eVF%XK+7w>kno8T{32vxnG?D?FuVF%eEf&%~cVXky5y4Nn(HKK(<>Lm7-Qf3l&x_1#%$RG500ivhH=*wseo*;t*Coqco;S$QMiJ_CUMGSCYUjaUAnV6i8%x+K?rXkRz9wPF6<1MQ*=kXK%lSlr$|pU78um z+F)TO#Km=f{P=x*+!&#l_?%?XngXyxb`Kgv=sRv9-WUUp%M!+lif{3rPT_vg5TdQ* z2%_Dy=jQGM&vf}ozN06T#X$+t_t^Bb@GdiCVxT2H4a5%Z>-&+|`}cj&vVlKBw75oi zSOECZ1iHO@VXn>z7g7vYi0mnVLv@@Dur`3_O2?NkFx5w&2xpAYKPIr}$M_uH70BoU zs_k0t3y>P0QcWe>zgO$@K`^uYYDNMtbeSjV2|7H^@Z%ivk=1=rt)*{p(ap8-l;(lN z*cYqQ(kc5Hha=3`31r`MGLCQ-jJlUw2j_ANyo~z%y61LO>>kH+J%mSJzb|*ceM`=7 z;X9n`oL{KJuK`^j*XDfj~p9t)!14_J(k8<&&b!RN2Nao7^>;v(MCeSNYtLp4XH) ziwyJ26F)<87EU=P9kpcM@F=rfDPbP=uIzd}^j(6^I6$EIWyuMrp}w@b?)oaPESkz4 zGRMshj~|dcbKsD9oc+cC!^tLH=atwSKT;E6PXm`>*w|>MgcFz2yLSkJl2VJYh`xM=PKl^O z0}~gn%la$_LKc)PrI&E&nu+LJB_g9em_r^ zp4{Fex$VqX$+UOh%M2H8<{T?lI(f1-_Kb`qoc4>2df`79ng+lBW0$)iEhY)Pa1W>F z3KkeEq(|6wmnM5us1=clh0Phw6@+A%gCHgz2RO{VM};UBkXa4oV}Zc^yLT;%y0OJC zVbvd|YF`_py-i$GSS83!0fIZ_AuODaXbA8DRqvRQydrvy;o;NUy3$p4SD8Hol^90H zXhUppHsPWLj~RahxF8<+aC5=Hq+`t$a0k)qFI>YO$|WR0+5ZE^v01*!^%UAMph~bo+?cXE z|IfwiAa5_8)IS}3p=+eh|vWmCX)KlE{B=+k#A@JbsvwY>_Sr@)->GOFU zq9=Z=U&f5v)0tWL^Bq+xJDIGui3~#X)ecdE-l0sPRI@2_j=(Jm{F~7ifAMqicrN&4 z7n%DP56_({E@gUtRwG$l4;W1g|S$&Y`wwt$7KapJ28n2GmyFM zcW|ee10XbH8PgT$=I}*KyxY5XxV&frLT%_Rh)OGPf8qL%@ND?p5dB|Z`l9^=s4D!D zo59YyRXnzBC)0e_#mHnFmt0(#O~oKUR9QB~papY$6-WXDPIQ)d=6Iy5#Hv6_;Ap%R z41DqODzMUV_27WY_!D1B6zx8g-6ZC-NL;?Bc<{$DAwIs*f(F)8nwlQ1*EpxYZamTD zuXCZe{)l%BdlBkgkg9enG&VGh;8|k6aSTQPNaR{Ri(^t0vogS5C1oKgxPq{y9R;}< zb642Mf+NHVTBWeK>(k9hj%d;z`jRI1ouq`$aEPJ6X!>Fs{lU&LUK? z(b1v^!YeoF8&ZDT_HAXdz=CAAzZR|M27@0;)(n&>H-uPPaNw>cKyHfUpOs*4;`99L;U512@_miFa53BLc7T@HkR33Y?9+w{; zs&y#);aIX#T)4Orb`riAX9`}qLt&JJlEjZ2B-d zX;P)rC{|W@$hgt@&j>0%B;UDdEmf&POZepTshu)TpQ5n4md zdm`+|jxiE=L%~SD2}Rs15h{6VNVj4AhJhl;IrM`&Xm2at(eXvyCa5XL9gEq_I_8r2 z#H)c#fLj5Z(Jo9Y@Pmrj5-dfyLq9_09a`CRR~;mrLD0jVwb^voEuab}@{wh1wH~*_ z<{4mMD+Px-2+X2#G}{S zdj%?oUZrFRWN>MJuCHDGo2G(A3rBj?eq{3Q6J10DV~crplarX65}eEZH>K2*vM^kB z19cun4Ke2$D;ozoWv~VMd>Cxjm6n-23*NyXgSoG`#wkwP0>*BTB~F~VuIU(G z;8b(nPZScsCL1TXi#(BKM^*bgVEft)d8*p48CXq`Hq#>xm$i~7-JkvNE?2U;a3zs}qCxjCu zB)&>A$TghRFg%w-L(N>9AaTr=bZZc=-#O2S)>vv0sn4rn_6xz>^)^;7__q_BNylh1 zTvc3jT|>7Ks)L0$=}26YsHq7gyF&E)SlDT)*SxLIdp?&q%*wV|n*fpuyhBVaMopQc_WBtu)UUJn|Z^OWI5Qg!B#) zLR4O*9}meFD<{awc`DH7O+KX~V~3%>ghWZPl08ODG%ZvG0^?rvsjp_vUusrUR^Z@M zQ{_`orY1CAx}wBy>%=vjpLH#WG$)5e?UZUL`P<*$2grGZqohXa+%$xOi1FkH2-IZP zsAE()ks{|jU2dNa#W=pOfkG^D7nZi2oxNxgi*5iYq@usY+>ayTo1kmC)bZoTWn~|- zQ51EXpxHZn_Bj-sNm+GV^!5*7k3EMD#?EDzxCY`jL7aP!xH`MyOZc+1Bo=pQL2y8M z9#4i+4>OgyZ-@VppdQ?)K=T0%AO6sT+Vjx+{Q=9oM zoD(hi^}!$EpFX3R32*nz8;L=-hAU784vpxcUB9H!-h|ryQjr(XlYtuu*|h4$$%`Z) zvo(jgHYsKrg@S^D6i)`MK*(Ma5&%-^0ID}NO-@Ka&D^tF8oNAP?O-Z#y=Axps}q+g z6fXE?0cmRuVtY>^F3r+1qf)V|%rT|+ZP^cL3tvI)oKB+mu8j?m`GBj@!NI}Mup8#I ztB4x0*d+Q`!)Ro#LR^sbzXegW{;^G;QqZmm2$*U>G*Gfrw&lX4xUJ`^@Nl2aQOkaEE zU8ko$cI@ru3UWL-Rt4wA`}(u1sxqYHj%f_(_f#l-E6(vAydCr5TIjvA8f6b-hH2Oo zV>9kunilBuifNQDK;-`W#6==HWpRI!~9&qjfEYvwX)pFim&x!8WNM2OZJ;K<-- z*k?5%LO9K$K;bpjN!#u$wZ)C_J>uMql+$0|z_%I~9I^IoVj{TZwYqps=9>BXT{o)D1F#dJh zfzz6%3PJTO?2Y*a{=S*d?kOFj^&1Jlqt!Ah$5-c){pOeN_b;E!hIj5_jx-^q{MaPz z)GvL_rF(bjbjlk_f8bp!W%T%CD$s(epFZUT zEn@@&_00IW?Q*1L$CyvK5@Qoy&AfW<&9IG*e2SV%n3wFDqem^B64VObW|@MIJB}1w zof*L10_p5lGP1^D*V~G&SQ$R^h*BdXRGA&1{%tJ(tiE1Zl)#zL0xNJ`{!S$CynVqE z%|;=(!Izz%f9CY*HQ4+Sy?D7xxg9Mp8tQS!Lacxw^6p6w&m`@O7YC7E4E7L`j?AzA z2*#5pVoX@4sb{+`m*C?(virOi-Ch`C(bG$~|2o_1;o|79Jq!uX0ULitAZoLOp6+T5 zF{UW}EN_PP6NCNl-?KEi-dvP2$1dxIf+oqGzCfT7N$;q3h;)fxALaSu)H6SUPF`PM z-=EB0D}w4e;72e6QDn$1%*D-38ra@FE1BWY`iz#LS&X97!pon~h^#_PF3_HXv~nn+ zWiKUptq3rL=iQeD7pL3O8*nisiAzXGpdJ41s{ile(zCRe^{f+_2-rr##{TGabBDy? zbKH+tAKDe|Aeq0Muc7zp$zj*Nqe_W&A)mZ`wDKk`^(J`|7sTZ`S6j{r4pZ9F&EFxR zNiZetZa(T5M6Drr`gt=S8|kBzlQ&NI^!ucUZ#PY^$}xC7b-w19{)OiS^gIFlk2e%; zFLFd`rxzdR@{3$C%=2RFus?k#p5}Ye<=UGnlym%obyrH}O2-oXjDKm+Xa;Td*hxkZ zj&nbKH{p8F(KF|M>KRSsp1t7g@3&ZTgP}R_>hrmWd>-vGa(i>4yY~G!9g(sp%0ywq ze8x5mi*k_Tg(s1@vrD?ma6BWFe`sGy`IJ}n!- zhs1&R4Gq@V=7{wpSY^`iP+28{HUN1Lgc7X^?6dT8x3;$Sz?3t}MXZz2g%!BC5g1{4 z$f!yhh$ajVR>q-nQ1p{HV48rn!2`&}oLYpKT#If2xKc@pjK&=xWhjJ+`v}bR>&|ge zq+z3c=)YnMi`Ytp?}@6#!0!H-Zb~2{0|o( z@4itUy;#vRvnoml3Nf|JPY6rB{H<&T++Q*B^1DSt^j= znUVm9?lICrmu(<B)!ohKIy1dS1Av{)Qs^P5o1n z%M|qsl#4N?4?I6gcGxSW^?y0a^dd%Y%N`a5@k`as%<|7;cFj3>zcq8)GO%{j^jVdz zgU_Z(=jgYs`;^RbJo#1AZj`RInWYi}L}0cZz`WnegFdCqq1RVq)8UyK7yBWH{lQT? zUpG9FH?2KFcod_xXEi8=uQX}$+eGglQ(wzY35kZr-(Y4HoYTk;zvWoiq{SUe6bB%h zhCZyWr6nisRPm$g>J41u;GsIwzKT)LKDINty^)kXu#zB4`u$i+IvLS5Q<%`OrDanu zX$Q&k%eNRh`_BAP`6AD$R|60Bm)2Q#*eKAULlnsfE)!NS+LbzB7I0a@P-+tjNd277 zyv!6OMMYvi1*2X8wHN>#xj8vLunmFl@~8Ipx?Pb{vlN7PNk_)PCb}IKc1TEwzUZ16 z?&@c{t#}evwg-Ym(VoGO9Z_%{Hsan@8z7{?9-$k`=&M(c5i%Y>Cg$nDch?UzPzwtS z#4G6M8i?YD@ahzOQ-*d61k*!wp#as1rQDxClF6E_OqLYvF`PGdGA{x|6*4LNjXQu( zi<_e(%!=$QuZ&B;I#xm`OCixvzFkIw`((BHP2hEM}9;2wyh*g2Rc`@-`tZRFT6kDe0LiI)`1^K1F4Dw^-pdROOPC93B7_5j6z_#jeK1gw14Fb? z2KBEBc1wx2>h1bfS-3Y#n$f@DU19#`?JwEb$Oxng z=QOK7Hj7G;20o+ts=Y(MnX*;M%T`yL;9RIs61OFF_~mZW?>E$T?{CTdP^P!35#Ii4 z$NG!H1aESwuVT92+-?#K_p!G$*Hub+>~_I#Y}3!#{`i_vch8>VOI5mF$6Du4=2g9U zQ_uHnNDdCiuVj(~PHk1A_;MMehL4?Nz2xEJp@KAr?VNAfc|ZJa#^N_^a!W-?PxD}A zkog|b2f0xfhdGW+&6<4tMO&C_@-;tpU+*DZ*QQZ=5aZ3)ok-p#e?S!uBCr8-Dj}>7k zzW-j&++0{D3soAxvt_{gNIl=%;DrmtbE2!w;CMp? zu0nhVE5Khu!VB-jTFcH5&hpYBd+{qwkCY=>cMw>8!hes1Q@!F34zF3L6S6a*l7_k zQ#G(H3$Fzo0t+tqLaUayF3^U=J$SGN-8S+eI~p2P^7Mp>+gG6#{%-o4bmRrCIML!@ z$&E<9z#av-_iYX)c|AXzUtIn2pRV%8c|&@AhgZ)xt#*Yty{Krs#2idbp#DC{<*9$5 z^q6GADFNNnQFN(#s+O$7?*w;!{xL*&)o=VmGQMrVLXw2w`@%Je(||QGSLhmpF69#= z`7LsJZSn+F8|@J;4#GQ$`nun^H^2NKf!Od1k|fk=yxJoR#;-j{t~HGf9J`jLYW_ZX zR+;|9xU&mIs@%i=rrn2o3`2%V*f9-0Tv0c4IAo82hhDB)M@HoPCc{Z8HCm(M)426-E`NqP2z|JDrH3E~M|#sPzWX;Mz0$oG`;0T`$uh_& z$S4S8&rf_xqhsc&3;s29zed>bY8jO-zq8X;I%?`?=hka^mcn)|Y>JKKdrd96o}}Q5 zQx_$#zf^SF>Ndr7EHD_`<$sCg9G#qeUzpow^k`i z?2Zx8grOyHA8~2D zjD%UAQQq-nP43uzM9)2d?7gdpSa#j)w5w_eR=m!}7_J-?g$$!u>3g`B%M!rlTGJql zuEkSqw~@*1w7YU_KLz^}-KXuN*wcwfAOqpurJ|PMS%7cetR=b zFK1fo`NNSvc%lVslJeBrPdeuAo=j-5I-ID>A9v5rTAXRvwS0BoZO;+O>N<;po=HH8 zQ+Fh8ynf}fU!Hp4gHDy%^zd+|F2A%2{g;pDMQ%k4ho<@Z`8i13ud~1d(v&KPw(3%o zAx5a64vmu_nS8b`f9C85bAkO0c!jqWuLm5zMWm51E()vUg7^Rm_+K8$g|3H0PB#XU zP)`22hDq)SDbo2xLOjP@#z2o+)b zEyA_Ne2-jNgWsR0?yD4ykwgblu&JJx{Qh9~pSH+FibwZkKI34Glm8Gh|-(&lr6+3ai(q59oCVZz!d62m6r6hwF?1V z5cj5HcfrgD5s|a|dO3cLet63JWXFWwd9O#ATlETMzx=U^pG;y6jK4wn{`}_I4=!3U zPpY4<_9>NFzWX2I5RnYN%%x79ZCBpVz(cV|Eh%*xbn*N=Zd^`P7*> z^(&DTUk_eq8`7?DD^uROG1>7hvXrWSZ}%fj!lCV)?5bXxVb>K#X$roYCC9a~Qjpz2Eab^Z7S1C;zYo?hB_z;000}PpwIWW%t}R4v&q#c zXHzJ~1O*-BqD{);(~8yk{qK^Xs@*vE3oy{C^fuxG$q#0u5amYakMf4XW| zy6eThfLZ)Mza%nI{YvTEfB)opjkrlZ*?--FYV{NcguByA`~S~x;U03&4dG;;`hR|! z!A9|Sn)L5u5+C_9zXEJIS62F_ZO=1$1c25DABdQ@!~gZ%7p|s6hKFa`uK1rBl4T7~ z*JoU;xszp^seOBk!GGTR3!A&Hvr&_|)L75-2fjLW`@bF`WIawx`-wPmQ~l3u`S0gm zYoy$4S@_SJ@_$~o{oX`K>xo2@|G95`lXs9>;s1T=zVrWm8^*;1Wn?Ta`?r$(-w&gN zZn1Y-^53c2Kfhv-qZ;$JlH&hn-@YGt6id;bRP!+dBJ}?*h4q?r1qB6d9i6{Rt>~2hK9&;-p&w=1&BoS z!NWYYb70t?Wxs%sP|+LrtdIB3T};q+4=O#bm_xA`v(39{?d6bB<{gGx2df4zCD)A& z_@2{_ySrkoU4Ytg6`>_i6rUkF(2eRNajM*J)#L2hv#~y#NAc|#XwgwHHxSxL7QH5X7;Nc2KlNb>m?l5Z?28ttaefa565y08N@JT>WFgzln z8u)%Mq=BH?jCK{IV-h0krn1QoD_wU5RZ>WA!FZ0LDp#W;9|=Zr7$@RS@g z_%VFCHuQ3OU-Q~j56!D^_3^RGgKZWq56cq+b~Et!dbF&0#HSie+nfs6R$Jqf!Ryj4 ztQ=Mpo{%kmEM-}IH99%?`tJk3Js}lKz`KJAlZWvAm?P{9Fkq>49S$`S5*83Rt)Y=D z8&KVHWn{~sYdMinO(X@G2t96>MA8ESG7a)Rb6-5ZKLYoH3iI9QOZ&EY@AZFRWUZV| z@B@il7|h+9o107CiShDM5MT#?5&;$C7hRvLIDVe`@T|D}k#&&MtKbZbj@G%F z6G?0oIG;?>5cvECL~+T=s(0`jyrEXWq3*t1BJ00rlO6{eDtOn;^0I9q7mz~+v)IK= zkwbs4?&q6qM4r$;M7VWDT57derljvQKfa&6kSEQ3b~{0tc{TlYr@mS=85zg*dDZOl zo0REpI??6#W73*VtZye22T?>*Ti)MFNaDP1qo7%wx3szAI$@^<*{Mc}zU#epe%`gz zPcnW*Pg@}%NvC7qfULqCeFvpt9x=05+?v} z;f$gdxXK8>p^gV5cNG>)G(}kdllN0n{4w|diK?vOg@FLxg7=M$1j4}7nXe#p)__oo z(H=6+$O#DXNsNkm4hb8uzRf9JzR)BoFW0MAmoY59Xkn4$`CM$R&caVCS%V|`Pxf9t z`Um@+3d${2Rm0lshTP?o*OiM+&(QADRM63lQZ{rbycBnTJuYU~HM9AL-Ub#cUEoKf z&a0E7Qn+Q+jechrlQ=I2$3-|;Zcc5vjw~w7ex@cT*M$!xvQZQpCBx(rIzNzF;EsK@ zI_cp%1cTHbyd!?oc4J3!DhsOm0+`d z@2~U@ej9>_Vd9#r+sivKv5GFmN*bvVKl%jy19I&x|G%ET1DxvregAD_6tar!Q9=mW zD~aq`WRFln*?Vu1tPnXy2-$muWG7_rk&%&+c_Q?CPM`1pf37RnMd5VLYdp{W+~Z}G zTG}F(G-uXb?*yeaqq)(x-`a|(Ub80(5kwa>3M`xVT#7q3_zestI`0cyn$P;Bb9TsG zQ$G8+vUbT)F^sf?Gizvg6&JZ}C+Gjety*B#Af&~ttezA*|24@)4Mh%YL`CROC%HSC zWkW#I(0orvQRYpU46l*JF|;t;gf9#q?` z^sK0O-=e0Rw@Rw27!_jf5tYO>{7@0870LWK+fgeLceRQ}@7GKNC8|&(vxbl8B0((W zT2?}XUB&EDA>a5ifBoIO_h06%Dr*5mX3Qzk=R{r)>l&#gCTf_SePD}j9fG$F0;}sF zPa3#+fa#w{fOvN8`LGse0uDZjHxOMhvjrH4@)5y`O#=+79=!v*3IwMHAWsBcc}z*k zO$7Qd=kZcAfHizU$_*mtXZ<#y?EpEtGw2*)baBZ5uw?+a7AL}1js^#{l6pc&Srqx1 zn55}+hGA5-lJ4gVD+A=@R*tOLGF4H16duHM>POZiTrpKqT&;vxTRl0#gL$K(^&@bZ zbKjM<+@nbiyr))RrGG8<@xuuLeoloOV;obIAx~osdC)`)?35A1!^41Lt${5e2qj_Y z2Om8fr8qg2a_TE=s=(F|j6x+O40DGZ=#t*Pg=jWthLn|+^%8T~IpH8J(uqU?D5IMQ zc{kiQZ`SB?A@hri6m)c4O<#Cl4p=>4rvwg=qz%9uv}}6@6u8V_2n{tp>|`7APm`c52k%N9UET4kA&|K~Xe-1T zOc3~McNc;w=#f$`1Na+Y9Xr;6SopP4&Pu$C31nS-wyug$Hz^ni4Gwm9&3IZ5S$IuN zz(0z*M*+z$3yY+2SH$E+RD?ELp0jX*X=pHe0V{3NO4o%12J0P z(Di?ZsON7=8KPEcX{Ow+Z2OW8yeFDPC<; zI+qDpH05&fIE`s0n5X5_v>$oyPsL$LY2lWxhJAS?Zf89D*8e?OVIlus%C^!?TEim} z5(Soa<+#g*x*}paxwU)3+0ICAVfN%@pq2+KkrrA>WoVq>n!ChZo2C zy&p{UpZ`;s617qgO>0QPa4rApqIuQOvHWjOMLTW1Ay=x~+3%Oh9oLRh-c<++r>)ab z6-#;*oVDIcxW3QB%8C%4Rt>fcq!1r zH8153xx689t1*)gjZatj@j_KScn$3=r6e`WYf5)MB*YK3vHKm}ZwzGE^1UwCZfY(} zfAg%m%dz`QAVPoXfjZ+_B+h5APGI;{v9!iWUc2c$kR$h#pJ$9gkUy6Ek;NP9(U`;A zb0180oQ#U&m1Bk|#H{~Han-knXEG50T;~h;cbk|L?i~r>kw;4%=QTGsH(c!Bqw5?H zP>r*IJ{Xi@U_V8iyiucT2Fa(e=cf(MfnjbfrNUgNf7xnq56qaj<3b zCd4Mk#d7G{pp3BQdVKR~NXt`m(z`4&(s%)OSEUsdX;6n##IKZsPSXiZ1^bB>MEY!n zB7fo`a2$bJz#H-wVK23lU~oXVDFH12-r#q85jbx^GOewvi*X(=YXu}8G-|Ze)C34j zrwTuOpn-t|0GUARKZgKG-=x}BPfLp&d7Y2|RIF6EC;R8$QscD&Ru zbHEJ2l6pqQ$JvEgf`KPi26AHnV#8rR%K<) z)?Afj79pUbIm1W+$^*!26-9!>!T@V@1^{@WfS8yVn*AV^zdS=vYm7_`DHqsC+J@lFk}GV~zA5L%qQI(REiK(a3Gds6#*3j8=9 zK^d@sXZ*iIf@2A|sQ+5qhJIW`RIV+|V=a3LopZS$=$2@0DovQ8e+$az>$F`RIkC>; zfwYgU%hkB3{Q@lJx5v2biXwVur=9uyI?|a<5!YtCHzXEt1vqpX)>@dZ$O+|NayGC_ zuWOjk73+*4UG58DX58O@qHAVXW{IAdJKv)`Q}M(fRedS$$yJ3;I>m3)JVVDQWA&+k1H^GgGZ zn~l}`g!>0^Gow>~HhBgOCTcu?Gtgbxi4K!7OfCKOXr?}^9r<=c>DKkhNIBE*-$E_^ z9=-6TRMem(%YDC2YedU4>O876k%I*re)KUAd)`OJGb`s#Of>Egq=uR>SZ!P z&%#Qu0CwtICn(4vwt=z(YJ&=0F4u-rkZ^*|-YA|xPC-Edrc(VV@i7bgpb-z=+(hI{ zAH{)C^*{^Ij8KaVJ#ETaI zk^+(0p(gSVK1>v$hG^IU77h@pQ9{pBT1@Th3_*$oJM{{L$oTj)*3_T@5d!iyIRnEh zQW!8??kQhrXcM)Yti+7%^r0#L2LXeS05VTt&S7I^WgmSTVvqrAoZLKwz79N-LEFK5 z>sCek)1S`uW5!;aus;F%gF7Ch|D(10H~{|`GMGSa0Z74rDVx9)#PaX9NCB5m7zBY0 zLq7)p#@KX08?|X%2}}ZKcpW0CZn}!lvBfK9Y82{MeL*=vjOUHSoSW?I^Bzr*ro_hf z9u8IbW%h3N6-`aPz*2=$69}Pnokq>RGmk4EXC4$}!i`w}9Ue3hHiZa6k5-hu^Wd** z<%)VcAJL!m_VWrW*CIz1qDRD^k&L!qsQIe$Chqx4LFxG}RDmM$a-D%x#|)Kxd6HRy z7*Uh8?Jc}nQ>ueM#@uEjGv{jH&bq4QGj7T8P z`jgF@j?8&>&K@_mr{BMjv&*jWDttoFniY5NP$pRpyY#WFh4uB@@ueHj1gx&R_M`s^{_=SqfFS99%*i24~MN?z(5VCB(1z@ z4TsdBe=`SZiib4JRGucRd+ObDVTt)@dN@_yJB zhu_9 z1`B~>V`HE}%7%rEX$o%np8SUWs>hYc=7l2%`~^5ZsM_5p0NwzjD>l6QV7&sTChXZ= z7|=!(2*a2k?(+WRTPqAs=G1KL?0Mj<2OC~VT^*=`1evghK(Yld#Va#k@qTDk%z)ww zh})%Yh`Bq2FrArl%SafnpwU#g0gEF2{zMojU6f zQ%G+B+dM2JL}mvPl`ydpK=1~&RWH6Q0Z`CD%_RBo<)@v=n#4jyF+qtwta@Pt>u4~)xz{wPBLR5gr49-uI67!5>!s%QnE7#SJT zL~7uPB`cvXJ7i7$|Dc@hK@QEQ2SIDZb|CoYAG0Ee{1gG^Bx|&t|gNW z%3D#Vo?bbZ!^j$L&ItQof$luCSf6m1tPWmQBSp5yL7VQlRrlo8k~vB`e5Y1l$IdU| zs*$qCVIvHgB(vpPWcm)DG`m}(={Yyqu1DU$XmD>YueB<^>$;}0LG`^L5%E{M=CSy_ ztFx)=w0M$l%#;d8mw<^efVvUeKaWW;a((HlnZwz4M8`NmZhMIk+j}JLo#U=z2$dd={Uw&Wup{%^c zVpR)^c`Wz*OzfusRAg$&g zpr}9)h}er#GvU>Eq)Aqwf)G!JcxApcvRV@{m~#xLIv&txz!$0<(U|7U869-Yhskqv86e zT=ZXPmaNf=5NCfTWDpr$0)X+B?ah?;z*}GrO{NZ=fI%{_BxcHP2QukTi>@lF#Cn0D zJG4w^KwW_87|=BN9u_OAJIJSvSOX&9R%fVFpgo=EzCCnIDkGrUpCWhxX^W>b;z=5? z`UoPy_%x^~-2Q^L7$b?wf@TZMUky_CK;SjAoO6|2QCeF1(W7@>bLnps*za|=5&M8X z<7$EuoGSm=225kXwtzNg^NbffY5;@*z7L>>Fn=~~#DuEAcn4;1QXHUX0@NBtcmjl` zAQ}X{OAdh8(3vm-Ng5tdphq%t{=a_RIkFv6+y9ufvT7)Gxt=M>H67VzYBFX#Ad{8J z_^hlpMXKe2OIL0u?CVsv>xkM@2%^Bh#CBGKy<_hCp|}}WuVQ_gF;X_Ra4QwS!ovz|^y|5PpsBnh&|Mzb-W|z} z9q1k{o$%WTbs{*B&;6|=mZ~6#0&ymHJWBo$Xntv-02*EH>uXwK z7MITi9Ev7g^9S!4=P6!@xxHtqTS9{V&}qn;VY+FD9!=u3Eoxx zW|hgy%xsyOYG|iSgKjRGR=5}j9DsG%GhSpVc8VvWX_z5Fo{7e)63IyJv{7tbPF7*N zY$;3=H9I@&+2{-6uQ|WJK46>+Of19{pjuTtW&#-)v5A+aOq1x=(Oi=+4`);4AOtfH zg21v{L_OkqugpCnBYVsEG;z6~aNq?fOY<<);b#93E#+6`Y2*D0xP4n=3|*(3ygcTu z-@}zu2iGg|o>_6aSFql}Wl{AF@Am+FhT6`fAXRKX;3BZr#$KJc8rfT+Q}4BH78xY$ z@qX)9`tQ<|)Q9Y`3eyMk+jqXkL1#M+#c8{kA)LHVwVF^XM-Y~=4mCQ|;1qWs_NJTK z(2;ZkGY1mZQ^7(Rg(61a=zcz!tN32a-oA8b4IpUML=uElh9>HLcauW@@~+|3K7hnHctO{Y!)Riti5sp`Fpn2JDIKe^zHi_A}1=an~p<&rq36y>E05jf|^6B`Rh69-W|ij!=_&*Fu;8|%>M zv)UNKr{_|Wg?}ymIm%kGk3ZQ;yxVu0w#^bQAlPIwX13QpnnqJ_<3S%$I1w%~*SR0@ zqgeiIh2_$kotBJA+S?eq97+48gOF3=pswER5mH1%bHo>YeZ)NBYkby$LFg*Jyp74} z2Uq$05axL!|7+jIT~wW=e}%L3upiSzl|g~$|1&fN!9Mb&i`g}vI3wm7Op{+ipJrh|!J*x^iS8a8rwNh+-9^{rHe zun_jS;}MOjRE_hFiOjg{Aq1$;-*->eTQBswZp8*?JbB+i^0VYtwfK`p6Dfi4gGTX< zs)m@)y28m6?S{)Wn1KYCT2GdmwO+XSasJPA@rX5?!yKwDd;D*E?W-t84-_r$`<7jL zn)N^rCOd%d*1~uuFE6jHO&02iu-hkQU~1_5Gp69g?Ng+yF)2H#22>Coq=5;v3i_ zTs@e;rzX2S;*{Sy9W5g|_itC7^&i|<0@_m?@5ynEZuiud}TU zOw(XlyT3&#pRjnstxz(Cer4>DbTBp8P^ir!w}0|HfN}%Wse`$xYPs9;?rBN0UwK~| z@?fOrm}XOo3O)7o(W360f%*v`+3R`lAY*P87 zh(aG-Q7IR-o8`;XzM&#!qTr`X`n~<>6!PIkL(9(g?Jg)SzUaDEODDqTvG;ybBBT^F zOxh=PC~p~e1R0bHySlhs zA;N3^d;^+{erdgXCcVx@(Y&{>v5|1Hc5Frc2<-xw}cj zcJ1ELhy45(5fQs*Q0SmA=GFN)P08_b3Y%1b0C-0VK;s3>E&+LdDJf%XlVwYxx66*A zIS+D&z(3m^Hl@16POu#{8jLN82njtl$~wLKYYGcDpa;M>Ft(T~z^nuwYHB>}?7Yb; z)Sobxc;8T=J19~x#f#TGuXJT^_!E;_21^cK!0xJP&K!=Bf8Zs;H*_cHoFd|9GY#s= zXFr_{VqVrtKJ+v__hDR!3|m*r{!6Ocueh0Oykk<{p?P}e?PnJx^qh#0pvNCmu5H7rD7?3IwO34NFf}0`P(YcQiN& zgpD{oj6in?xhapz+-tyK(iTQ%&3|ULp}DfHnfmALSf$H_LZM!3Rq0DY&>c+lF*N)Y zkLb=laB{+E!=PW&Vi`HW%$eZ=hW-o=<#~_UO;^x30m%lIs=U3RXkJs3am22(o0}i7 z!Qr4fhy1Q4+}_y00O)*=f=#wPLYXH`6CEXLMdm%`-QkbqC$CSe1V9Vbq{N+net7ilq-V1L2?wTf6|V|6(opA~gdQ#uq;Zg?c7HBd6&sLI-Xp zpi2e9;VFJ`=Ox%M{asz-^)5Ego)uN1F+K?G5tqPv#nS@wz_^bzdmrsW?))8635I@* zO(>|lAS8z49;S1KP8ZA=fs%>k-}=3)zaJA{=$Y$J7zn@c6mqDP%<;WEJ;4uc{s%er zKReAcT@Zys3QeMaZP%hc#S=0Cq<_JO?M z=L?*BmYH=46puslRb0@DPupj>Vnr}2HxczugRtq_}3?mTio zEY!z))_Us)QLhq}_j^Eyi&ttP z6TUv7w}Z>XDO+Wx`^$?;%(1_!S>JQwE8)=H*F0&X`vgWa&;6y>3eA@v_NN3g4LYWg z?3`?h)t>W=&HeZdodEnxaA_O7FO0y97DBTS7*BzrNK{Qn)MBmIR^5~3F9}eHz{&*> zJO%V0fX-U*Pk_iDK-9;5sS{of93;z0=PF>{UN9mCBpXN^-jnzX(OM9l`WSXG!VAFz z(1%Kb9Sl7^Jzfx$GZ*}hhteU(VQ+VLa%w6kaSAc5nSyzoh|MTh^WnYoA-2ZZKNdZy z2#7S&liV+Mv0swo)FPE5mHlPtMLYdbN=4Mebk5RB`0BYa&+C6wQbl$`sEplVKR*Fn z2-YeyW>GLt3sV`!lun?VW4B9I1E z?YyRyJJjuv${qo*5?t>36^~UxGzp?L_z1cjXePi~tf9UhbP(AU4WL~>p_qB z9OD5!S_CM;pZq@ywCL>D5CLV^^ky#S7Bvz0j4*)x2(XNRsx&t;a?pAE3?p%Xv0Qx9 zV@${^C?YVPDQ2yBcSs>wjfzWm{#jXiT-+{9$>8RV2N@eiyHrv#2WK1%1Xp_BuFS+b z%foX6tP&ItE->?e!Z&N+4aC{PyS%itB-Q`Vd3KfR<+S%_37J)@{K~Su4Y>lD4vX2L z+Y0SMdi596?9M3-ZS!B1v*rjn*IPU02IqF9Q?1i6`r_l|)a$Ds#J0~eJ*#IGpRxS-9AzD&b zc|@wy%jat~DcdK^v-VGi!}^lna!V4H)$cQ#g;-v8dV4qXwW=cDYL#%Fi1^qyZM!W; zQ4jb=PSLBg*U=xcKWhz4%jvrAG~#4)C$Rl_;TXvju{T)qm1mz7o35l>Fr`5@01uB- z{(8tg{6FYhV#Ypo`6De(3_1EzLZSkFx#Xoi*XWKja~j^>x1pp64GzKQz{>c%Q9J%^ zVyKl&=J;H}V4RGPo6*puPyL{h+=rq3$5AiyY&S&oo>H(IiMV9Q_^KL*7QMY~JTW`6 z{0<@WR6U;Oz4oW(3>|i(utP`uu0pV}xRj{HUp*)JTpN4aZ@-z3BSK%rCv3ia>A~LE ztlzEWZ+HGHp_X5@U;?B~_ z%8djirTp!Z7wi!bMvTcMNF)X?zmMhRaatgy0&fe8X5F~#%1YP`S>QV`t7tzv0=+cw z+z6OKY5of(`Psn`E^>2oRs5W z5Uzyb7MPvD=-#^O>{>m{dbK`G0*g>Daam*wuqbYBZmu3M7~=yNIwQjrPA_;)JV`cC zHiCZw#*h*(4(3%*EW#bTrmp))+SZ?v6#=aalpS87sk};B8xNwUm3NXVofX_4T z1bKRT0&TDrfPeQV7wTuW0t2z5?m-UGnq)Nq2tvZbIMU!Mp|0NlhR+t%+7ojEstzJSx z0!;ld8yx;0P;cudf_2fj`hirEBhI-yZ(w*i>{Qg)cnadwE0CiB-bDKh@!gg1bO3k( zuuajxR%q&bYuGa(DD?qn-=Y1avprtM3WgE1HaFm%;O1TyQOnVSAN&2p3e)k;B=h=%gZJtab`Fj!;6S(S z{_WXMc%q{}4`4waOz}iic1+i~b{^qGNxHr|cLgVqKsvxF=oNz{~pp+?M{yRmb^O{xbN#$8=X293p-l`KnRnlNg zA}buX;MV~L4S`G`h6BMD7i3YD%!vGFxcxVG9LQF7qhj!>vrljg7$jgUvfx~umAYSZ zJ-Rakr@stBgBM{G^eBqY%;xUn8#_4UkAes@8}G@UJ<56iUcYD*Uh7w{NMyPI zbmxQ9r0O`yMCxByb@1~WZ5I5HOg6jz&qwz;mL{no&l8us%1^7-tUQ!GB7cUf;$BDk z5#CP(Oj&074i9*fUr*l@gsUUQ?xi_U7MEky>9I4As@$YOlA=u9Iq5s1iTo>4?iOqV|vU2 zfe-aXWneoA1fZH@-v7Sx@5}47K|Z?wYTbWd{I>ZLYNy5#aAE)d-}+7cGXors|NA*I btJR0i{b(N;=Bb|a$(%m2}UD8T-BOubom12XUI+535Z^%$Ke+KkfhX!=*BKmGA%e44R%lxnFpVsqJc}y`GnSc0_-Q7EXN(4wY zQ}-1N>{H|L5#hyP5>WpfvE27}J_)GeO@EEkWpj-!bxsoh?GIrV>()mFrl+1ZdAcLf~N1AEgVS3_)8D!#{(d@Zy06hbKI9P&sIr`y00)PAm>_NDwTfz%#o1aF7Dg(m(kcHvqMh zz?Vc6KIr?&%e$0SM09_gLFGR7h#ZCpZiNZ%rHMCCEa|b^#OmYNOKLU}TuJOIX9T*f6{A#@ zyU>3*Vl@$V-K_=q)Rr*!Iu%r#{!KZ}%6SKaLMZK&EUs=#AX!DdiPsl|NAJWab_+u8 z&21Y}T1{NE<}70#y|9Y5X#zi<8lXB-3P2ca9BJ6uA^TZ1;%{;i?>D20h(l1$YWanK zeu?;Qm!FvX{4*N;3fmtXp16M%86+0pcn5K8)x9cCl?-d6uB%@lV(DLX1P;v5-kf{C zcS5zWw$+ncP~sLCo^Npd)Lm3G2A3l1*ruj>ClPHO@^H>1PDO`NU%79)upDX7mzsua zukwlFcM%Es_hy$MzqggsulcvI?-Ewmx~qE++V@NK+)XDdmG@yIH*1mX2W__p)UQ8B zkqT;BSLv3&jv>3>%8hBe*y09e5xLnqpE0V?EUoYq&)9tS?AgqBgJ#cj>*9^E z*W|6Y2hChBU$$t(7&N;MedDBNXLp#dwUQ($3B^y{%lPH`+RDkTCZW(@l>=RY3(63H zB_0HO3*mr&%OJ+B@F@U`kjC{@(1|8~Gy{DowE|K4&Zd4xCco3a>`*cR$2F<;OmlN{ z#Y~a=hug!LsHn3<=%d}M^x+vpFT3sW{1v~)`@OjuxU9A|-{lq$aOM*o%>|`) zD{Vg0WjYX!?nhA=Vs>us`qlAzDzDuQ_(coz^LZDk4sF+odiKe$9CPMu&2=t_4!4sw zw+k~3>#V2NdLoUyk9&+fS?)(CCu{AOn(z(X#bJn$t3QF5zPAf2np#@zcPGCq+HR~( zOxDrr1}B^ApkiEd&CifDnm&I9`xjSYfR5c99=+k$qUE z{|L`kQq|^` zD<`*0dg42&5>3i#Lmi28ycraf9TB#DbaQ`khA-mb=n~MKh4j)C(iOZq;V(AB3ddR; z?J}9cseSa!0!9mJdJ7wzRq^ZrZ)qhUWGG2)-|r8l?M5t+2Q!T^&NLpzsYzehxgNxI zowJiVq?@UtFNtWXs-6t-t+43U7QJq@wz2VgyuZx!d$^X3qZ7W}tMP1YD=oFTz1Zin z81~-IPkOl96eUIv5og$n{l90Y(>-3fxVMEhKAm*UB&>89Qrtwe>U+iTvUR_;%eY#0cL&Ku?W3QLk zfh27$Jrj23#frGV4bqj&x+qmON%k)_3;Tkud#^mtwnQI}F^hxO8(O_CMIP@? zF@?`Y%j=g@GBVguwfPo5bd`dmf4!OcT3UK2Q`9f^SH;(_U%~qp6dEde{Wp%EpTD!S zla!RywC}S)i~Dg3k4;W)u9K6K7skW=piriJb1 zLS!R}ta@a=SMSO-Z|_b=Vu*N{goN}fW@=b$>KBNKi6bK-+CYf|rPy}ihuz8Ffkz#Y zGDfw6y!?D=Y3ciMgl*81`RwN76BAwgNZsBT@zu;N&d;aB#vWzz4EztGdE~j1g8NPV zRlVln;rLxk7$;?mzo2b~fyMlZO|woAJnFLg`pY2mqamP!WT{9Lla7hUNaE-cqtKXL~IJ2C) z=xDjV-TXV*k-de52%&bCY;hb96U%NdVo#Cqo&QaWJEJ{P_eulD*ndIoo{lcrSp*G@ zGBi?Dce(0XKQyo_nWe=q>%hT{26No1d8npo_C;|X65?fEa`n1Gn=Y*+W$0^$;vIdD zT8-DaOvG;0)yyL57H8KktZ8yK4ySt^_uw-#GuPDA@DN2idrL_CB!4&x1>0INL&#&X!7&hhYA8};|MSmz zwn)iI#sp+?+@aBOj81BpI1XwbF~JM+EK>97QtguLi6#5X$%RJ6AX^g6(RfX9LT)CN zZnDqpc9m)n(}ckhW@FR<7%px?eEj4kb=<%isJ?pT)B6kcqM&O&?KxeJ-kzSGXZUQ~ z$#0cJuK$8+PlAW{&qqvGSJ&0`$C#!54^YTAe!q?)z#<~Lk&nKh=Wtx>Hu8ibNKDiY znLm5}eCXFNxi@cOiF6Y4830b5`}9Ag;q-8m9!(pbvWAx@x`vYeeM*@Iz2gyi@)?FgzjGT(6 z8uxOBk|e%cH!lUc{CdcT!H&tifp;#*aM_MdPlkq3Kw+Vtz%M^iP2WX`N0{T>xgpLJa6#My_78*56`)iD7 zgx(tA?>r}_ZmJu50ykOBG!5l*JuxcMu@i7Dc8V+CMxXI)$3On65T5DbNA>n?IxmPa zcEJ4tnXGNL)@?FiGsNM@&5HKa&@YKNP=Sx zD}H^xG-d2SWaD+2`Qhx8*1_TIFPlzgwpFU_DsWXTMX8HY@kn3M=j0lmSYgx=lOb)9A13$k`Q`_&29jr>o zpVd(E;r{+tAO47WdN#Q=^HI@h;?GQOw7qv!!zrEMV8fEa667BY{}%S`1;;zFAys6a zT-8z6Br$3jA`2Bd)yb$-cUG2vflkm7ZKQms@&Ki0Zi<=j^xAaBTBqTAGc$0Tq$1u8 zKYmO%FPYbXB3@Nqt{{hKK_F8&(bLy=xfKpk*3@)8SmgKfd#p0;BNK4$H?QGD#|Bj& zAD_O_icP;^baeEOYjgf`f}EM9rRB$uCrYD?LS#xF9v-Hqx$2Zfnx(n9xu&M373TsF z@Pnc^Te=V~mpDhv%YHStJD_V)K`bj!iX-rd{7 zk;bC@yS^TmlCpF7XzEknx6a7M2Ef8oosh}p2l&~-qOg`#?9H1us;Xb*@^_|kC$?D_ z838^Dpr8oiCQ?vURW({2+;>e%O5)?;kv;9Gw*BzogNaFwTl#uuAPPXrKY#v|0l2yj zCb6@#!^F%?iV=#FTrsnY{o+MHK)`na1Cl-sb@j=a880C+h%o^j1qB5^|J+g`2jA8yj&LL&V4CB>;&^D=ILd(8`5<9v+^rmv-Ozp|7vMI6CSNLqkU=5%G2(NouXEtgNfkNU`2VtE#FJ z)$*wLA7E4wlu6dL$mjE_KM!NNYKj+wa?ACZs%hv_YjN4{ByqM+gRqKxqt=0Cw2Ke<31SersFx9_b!7|E6MIJpl8 zNy!d)-m3N0hq1W+kkwjb`cGNasUjRTL=-pOR^42B*CGb(d`dsVL$SkSQ&9AkjTPR2 zBDu6ivyGFAo_J~L5^FV%l;vES<^52uL=K7udZ`{Mk^%-Ue9Rm3KEhsooz;G2OWT}n z)l{ciy*JO!!%x(_+S$_;AB<%eIwK8e@!XpAoS$hIW>MAd^}~kF&a&QNwFyhieqma0 zLLuhH(S7xkMtQQnFIBOwLDzsvQv4gHmwcel!PSwlg|1t|kIX7}&@Z?cmbExpdma{0 zSC`ir9cumjO0J~>bEk&|k;+Vppsa#S>RfhGfBG#EAle2$wUF#WcS(_vq$xify;l&| zV^nHu!Fo<*%)`y?eLBPkz*uZO!=J-LBTdZ$Wd?Wz1TRwdat@Rge#LT43v+WidU|FY zv_o2k-mAX@aS$MFQ3a!%$A10#^5ftBIC`bT#6)v5vvV&UO!jFV5?0>t6Tx*%c~tRT z(H>Y(%5N~L7qB<=p|#q=Ja&pa@;ti2y!BgHP#Huh7Kc0HmuxWwYKbwt!dc7nTHPf& zp7p}!o7C%?KkMt%Q23EkVb+a}jZ{7d5$CPZ-e_VkqKfv18^`tDC6^fkMn*R64H9FfLf0{u^4TcNiHNsVFOp{~Qw@p3L#?C;0wTA6Fno z`cYf^cVlCJzHYZ>L?liZKuU|jZ=4pxX{gwAXSjjLg-uO2>2Ax72Ti-+sb?xYY2Wdt zJ|2aN4rYpqzI<7|8B7_ZuBqt@IKuVyb+haKS0cdO%||kX0mx9vm62s7>Dymve@wQ> zU}Rwt^0_(dBlq*(8l{AA5JmGXyRVn&)Yvy|(;2OhuRMl?gs`{YeLP%l1yn0RS5-}| zPyz7k`C4N8hU`s_$ewDa&0+9}4*(#-(cqP(3b^dlT1`kuus!1U0PG1qK)-(}+p*@+ zmX-DAH+%bHqmzY|Rk8D1dwY9D#bLng!R;X@Gp7#yuo%gRCg$_KTyk4lT2ft4<9D)e zIqoIoGIx*cF*7p*tF}^Y_WNL|IX|(swiduturQ`SgHXrU+wLzG;3$NJm#pg^u1EZk z{iP%EfH-i!HI}=*z0I4t4>}35e*&`>TENjD*N8394aW0i$J>Gk)^l=l8l5)bJKB71 zTae1^7Jjg5SN;JD(Et`0l2ujZ2$T@7%Y&s_D>W^xBXnI~=&D^n9B%hxa58yW8ROz6Qkl_qrF95M?EJhMbNzMKv zc|N3G@n2%?R(hn6!bgjBQEtE%_A3?HQ*oH9kJrS|Zo|h*(fq%7Tl3$&| zUc$N}#2;}RcML9xC!MB-O({BnQ`)AIb&lCh-NxmH(xE6A+WWJS9Jx$$ zMK6|w@CNV;b>(bq9p5!IzzkHsO=sm$-%b4yoizRj5QWGqWw(;K`~rXMa19DHL^-}R zdsn>L#uAo;$7DigTyIK5Ib>D1XW+6Y}NgU!5Vr(aVhSq|qdO0MfL=#OM`&boV}n({;Ru{< z_O`1vP@hBXBPfGNgxp~enP?&&cOg-QiLQ|6yJmPc_V$K=I|E(guyHd3oW7p!?%KM# za)aiJ1TDix_v1An=cH$3U_hXh!4laM+si-=oGR67Kl#NRiN^|126!K|ftoiEM-OmT zUlbv=!J>UVyKb%JPzn#;+by_c!Oo#l<+%q@uoeohal&j%z`Rh0WLhQuFb(i`A)uJPrT#_V#Z4PV&3muYW4` z0OQ`@-4PKH8M?0qgSz7C=7xfZ2$X@#HV+7-gCHp}kxjQYY`p>80v9#4WU)FPKK=@5 z4~2^9COQ`xUI!{n36Kv!-URn|d4IVQ6BagG`A!l*wb!rvE?4~8ZRe`7=@nbR>jXjr zw*mlebGAP~r3wiO0_97XnmX{^x7XxA#xAjbxVz5I&VK!wBs?-wt#k@}0p|}y2~bNF zJ-IwGM4h*kot?^*1C*!eJA91^GK5Qcv_N@JzTdP|Rh`$~{zPjbU;I)N5C+_8V z>)~-7O77cWK13AR^MZhY;hrFy!TRcG6(iL2d}k^+IQVpPMAYlTRz@bIqJo|BJy>c` z>8c0b-vH^fx~2w69ANei@FdNCkG>h3n?tGexBp}EJlI@q!zW()dSj4f7T5XslvOSN z;)Lo*r;%kNKrq=gbry*bgT%5zP){^4&PuuEF)!qQe90H#AD=z^{$ZcY@p`C=u6*ED z@@-f}glr6NTKNu>tt%0OyNAux_r3}$V@ilb|L`+X6z1kPE z$?-%V{)q)<(7DEZa~miq+2^e+m!ZYAR*ebceM|VSLd&4_p+8Hb#oK3g@vu~eA-0G1 zpzH^Ek=qrMt^yj-qxMrG9rOPK05#}NwM_m;>T5#rKCsX`lpIt_b}BLs-u4hR#p|EWXj>Y zuBf~irMT(|Z&Z-eZR>dxNoO$l%ToTw*i>?yFF3XQa~9bKQCDf|y+`1Kzh zmQGfR0-{Bk+;9S9LjP2#m1SZJ@(#OKm+Ttd?y#_Zk&e}};yMYxk<1 z>k1d~Z)dlULu4tHpVglAY#GbW$0|3`8hL3UeLd~snK7%rz5Q>vw#hZqj_dYT>YKW} zZ(Kuu!$xErt34JFQ^ZKBcO_1+eD5rJvY{kX zU3F##0F#D>hHklQcNmtcyqVbsu|o@|%g!V?e-pO6K#PUS%FF*A931@h3ly6ffX?RC z-h{zQ%F5DsY^DL!DxI=M5+AkX28wk8mpb&-of8L2S*Vnm zC`a1*oANe4$+PG5?&)Z_?$77TCk+GC#=sDkQv})wV38y*=U2Yn$gjFyH5P(KSZ zGrQ#$enCM&R@Ubb@Y>Fo+*aoHF9K08cg{Ui3i92|L49k#T{_J0_?NuY>;`oGKYxs) zWdNY=;&^y?5We6G?V^OjD@=U)^hq^$%yMvGVBq&}uf3{1&*h+&7NK;-Le=8DH51vV zdjNn^qZO=JT^(SN847Z8IaWlJ@j$1|$any5f%n7p=936J@|-HDOp|%! z6m*NQ@Ng`|Pi8tH?|uN!$9_p5u(J|0p6+g`ZC3zr!NMf68F+#P`1kLh;rYh@xWCs* zt3Z6xO}sg9{v8sKNS6BoxbfWZWN{|w^=82k|FF&-JVHc*u=a@j7AZQum>Kf#Mm~81 zm%oO#=lSne9pY+ozZb>kz??m$-ex&edxq=b^ek^gA$A1H+P6?jDd5&N62OwbG=P3X1w)h?Tw3GV{#Aw#^AB^_1^jh zsXQy;=R|$y3F(hI@nM^}n^~$u#;bdU7#exl&IM7VS+ZwgP2opxLnsw$wcPn&CpaU{ z(Fu{wo}Y4~xn{6}5MX7GM2w0a4M__J1mMkdo_wB5ru;>2pA0kBw(e~BGe8XwXMA$x zzrI1bz*L4NCWGrUG8;S4bq8mxs%z{LxgI&Lh#UNi)qZ=?K6JB-Yo9J;2R1-olQV7_ z1>qQceLS}`FjLNkk@L>a&P`GNvPj$1-*B};10G%iSXlO71fQGjLYCh|(Y>UCu84?;i=c?LQhoAVt9lY878e&Q z{At77l~M&YBvrV3BEqt<;In`U2rtHQe5Bo#g{l&QWnL~LKIQD$Cb%#*$vR%v4}mm# z=e_aAuL`9nGpzLzs%jIph*nir9{oc7ngvgOge{1q4`?M%s#H-?zkU7Y-@k)t{A$*P zRyQ$zcTbc^LPCOOPP+OO5d~%B8)q(1IyR{w6wrV<+b2QDt*VeAB3CB*W23&?l8aW1 zN^nk5Y%FZxPSwar=>|{!GPK8JuE7x~L{I)&iw0AITmm>MlWI!$;D|SalN8Ek3@2t_ zKuXySfT19t+}dciBwl3Cg1#hl%#ypYv61CB06R|rYdDSnN#y$?m(O^!YxH;nN=N1^ z$29<|gugtdr>8&N03dlWFgzlnl(;wq2vEOOhv<5rERh)_zk9$FJ!#Z~UF{{8_0 z@Us2HmN`-?wF~>>MxMiF1a!i}_Z^=Q03cE&T>);IQrasOHMJUt6_MPrS*K^Z<-B}+ zmjJozw|RRQ8ZMK%Eq;RiB*2z{QZ8rPc0Vho1=`b7L2o&h0lM12dw72!RRQb#iC%MX zd;n}vwTcq*#{h@R*zP@GJS8h)s%#JMq38@|8w109AhJ~ zSTX*-r!bN&W%%OnADfu}`GIa&>m@m5EH^V^KXt5OqfU@N3ic-$Bn!e21c6*qt9}xr zSPWGegJxa!Q8J~N-4uW6K!j2@T8T^Z{AiI&RT1}bEwfVnn)y+=6d#U*ks&VPr;iM! z@s^&_WolGMGBx}Uyt<5{ts)z@Iizt`V?!Er3G|Lkw@GJ#>puGP%&8>=`P_jngx>C3 zQkEJLrkcqg&1ED9c$In-XlRW>Yma;1*I%tBa+>ojw1;f`{oqp5b%5o9{?Zy(cn23k zFgdv8KJmchWy6>BM)D!}v))Tzbn_GAm;9O*ql{jncA?2|!xLgK#ztJ-FrBEfHK~TbjLX78k zW1IPx@2rBPBqVs8WzIJ0iVS!%R5yi$~!->%-+c_>Q=BP;1Cade>c0D674S*Ht zSnGdNlCH@e@11LAFEv;u+SW*Tma%!cH|GS1nm&(ZL0j8HRUg@t3b9|m@^oa*-SUg$T^%rE{5hM3)X!$GVMg%PY`1YDj6zDvM6C8^Kgc+ zw28U)UyM+R71Z|x=0^c^1HUJxr>9GWd3kvm7#O&?PQejOn#@sVpac8O&NP%W@x>x4 zr7Dw$n_E4Nn0rj$%8DOA4a-T<5fPqi;Q%kJp-O~t8%u;SL_|kN1AGABO2BAL;u5=k zy%*4nRPDdSW3bgx2LVEr#}3M&nYlR;dI+Uq986-ux!tG>a9TyQ(i1uwn!KEx05cs% zW>zL9xzZ_D7ngH=@svBDSgC7i0p@a^W?8=gj=}8i7za8wu!De_YXzjI+DU0+_ zII&{&g1o#m0T($mtkcWOk^cTs>$=?6F~0wjEzE~f5)u-mRpNEaGk{Vi##gG$u=ZaU zU{Q@go(A#@EiloIIh1jDfcV!*FIO>RVPT<$!{c%CR}`cLo)i}{#4Zli`FJTwNl9sG zB#1kooRpN*8=i!(vesK>Zdh1Y<3~VG#wR2k;22zP^)fXv0kIfU4gw%xO=H{Bl2V+* zLu6%Te8eenUV_xO1pq=Eeg{4rF%i+9&@wiM#zZrn zXqmXQw6vro`u{7W_FrykBy6-YURJ>r{%ee+EH$fS@J+FWMKH~SvRkimzf#u+L|6bF z{z#Ijrx4LEiV7Lge(9`Xjva{N!i`)eSA8FHkwH2&Qd9C87gdu|Ccva8>%} zzGq1PiU)=GV~7_9o~m4!YMvYsP%687^y!n0{R)R;5)kV_G)&enP#Fz}vo3@ZA*t&L zFEhV?E7qq<#18kRfC170VIm^*A*gVbV#^GGh~YSzCcRdoCd&TD=5~|vBJ`-sfRdm* zTL-OC+GHE=)fWb2T)tbX=+T)Ox7gMlDHS2Vhr987c|QBaSKzB8BVUgrq1?8!2Cm*1 za&jCTbv?c2j~l;=)e0}l(Z9m`v~Uf#QU>`4)-xc{py4PG1QDPyGAL6>QBr(6V*89& z6^Z>y{4dhqwH`YBl|O4@15DCUoJV4>(1qxP=*Zrx6SzHV>9~x0ivn5ky|n|Pr<-tA z-?|3?6<%rV_iG;;pLAAWsGpuOMtohg0E!5}*!y1IAVZ*{qGDxb1$g+w{jH6SjRunj zAXV%dDmc;!a zOo2}A_3P00ZYTD8=W+Oyd77nOWKzJ=lE`LYV%pJCR4tuiOZshHC+9~FmhW+9n8|$F*6Qhi6JqWQ7xH0ZUN=_wyJKb2n0Dg644LeF0!J+xfH+e=o-!TQmBvrz1mTa)Zx@- zC1clbrRIwf+$Z~t&q>GqTk5J5POLPqI@hNr*Jla-`r-Zh{Q8h08G=OUZ$ac2mC#Q& zuJn}OHminTV;L(`LdpZkaP!G=^YNj!jFyIN^@`!j>CrZOA83PbtM8FMy+fy>ghH^# z>&IgYV3L#+{>q0|-H15c8?7JmA3WLZa8q@pccxqfnBycuR8=jWVbG`7KGaP zOrrU%O2gwt}(Wo`0`g16fu0rgpDFtP{LHp8RJ}2t5qVA7Y7f zw)9|)8|g{P^_IdKHxRXPx|NN#_)WPcX8f0K$3=jTfM7QSbSn`M27AgRicyM4(oj$s z1E}eD{o4rWZTf6UZf1lM{l*;JJUq|WQV~8kxU*28Qz_%*U~xdJpq2y3YgjmlUWhCn zN$yw+!V-mg%1uZiaPU$T9oR6APQ+JH9xSfMYhp`)^VtE_pp~fA)aNEYC&vVcfa4j? zTKTQ5_q!GCzJI^Gh8P2v0631|tp~j^D0V27ufDd{o%}8IOD)Htj0z3_m&ZS!vH#uN zEG#UP|MHYu<4zp{+f8_(={ad&=g}|b3PdENbC=BU982ER&^BfgiKkZGg(+J0{ky%D z)i$lM@aZ7e^5Y`dw?M=65;%7kB8#C17*)ydMy);BB!@vpTH2%jjQQsa2~Xcw;JX={ zAVf{>xs~mE2yePrGiL;x*#M+xKzCzidb)rqeFQR&-*sx}`1w6RL~Wu_X~yzqZdJW> zY8k9ActIedF*P-X&u%CLwmTevKCLMRdTj=(B&)P@RN;H5oD5C@3fh$EF8|*c$`^KvgO)F9*J?aB!?*A@GmVfshMs8)N`%ZEg4B8P)!cWLBsa zqk$AE8d^(p?DDc9u?boLMTCi$*DZ*@czb)_w0#VU&HNzvKMACEUEd~u+&ry<&`aLi zGkCbG-3018bym$7QF$M&#T2{^;w&`?B$#3_3!O{fP=2!h6(svX;=3=h{V;~0x>@P| ziL*zy!G-}7(YfqU6R+<6V!P{%ZTXK(7gWEf^ZU&u#^uZs0`8a74fnQQW=WwG?}@W| z(IALX&wofqWv=hLhIjiKdgLssrEji z-OA646jzoKSQ`ecZtx$Y5pAn;OHv|^9=y3Chsx z606U!w_%Dycdq)DMY7STa8|8yl<@wXW2MP9bw-_-HV>b0Na`2BraNeVygvl{;qj_Z z^q}Q*ID=uU)3H$>kWir!kbFDeYnYkHb;e2^>OJ+Yu%yY@)JY_*ya|UG2XG#T5nu=9 z$u^`7V)$3}ql)0HR)%qQ5@2IR20&Xzhu;NB!zW#?#XnF*XiIY#2OwWDJsG6njGQH2 zy!yPW^<%3?45Qi*M129j76R!`w?)T3KykrFy1ToBhlh`nF$K*Jur%e8$KiCrde9ue zH#AB$pOcF$0ta?a%+LQ7#~^}Cf@^yE zM_nB*K7NH>{Q>A;pa>vX#k2edabP^j|q{(^(A-}uo?TYDDla~!l_!wMkEA6yFlbWH0p`BJ@H5#Hbb zhLLk0xTNFw)3&@oB|A7c;56;M0|(0+c)Flt=@}Y|_&s=U<3t^UiUGKz73fZwB3Be3 z7VYVUfPm24+^nak7dl=clM7mzf`URlz#osQgh~IcWnIVoDLj;T)jdlsYBW_wv;K=( zfi6)%f2Q^N{rcHpEDQ{NP!eQyt_iHnvYH6JIf71Q-kOI6T`~FcQSN~2O^q=3vPESMljkyl_xP}qEZgAfl@oyW~+ z=WG-@h1#Gc@bDP2MX_I`ocRnd=Cin0soizH9ro9IFLV<{#YLUPj|D@dg*gUPcv38k zQgXC$)~Y`iUeQvb{1!YsyWDOxicA;OwwfC6r~GPLquFWbgV|U5bDUvZX#+6`QuoR| zE+qVUOIh|26~AxW)a&xmB0}0OxsfF=Gp{KUl7R)2nz@z<3mTB^0UHv8$5AOAfKmk9 za!*;6z|rO9hjDp!ACO!C4$rT_!Lx@qI6WmLOb{@dvz0)i1TC(p03HuP|9T({qM#i> z8W6?^k8?BTC<8S{hrfVIjaTHISyM$&gu%0)C1Rq&spP>h@{sRL6hVF}?DBTP(#~$F z$`l0xbO^s+@r=uI$$jg^MMVUbe3A|z=BJ~o`tse+AVUevkyE@#q>$LaX@oA0rxfdI zSE!z@t|zLt&Pb1zVYxme%Jt&E?^W(u6a}5&_X4FfJV(!roV` ztO})5VDLt6xa{og44e+PWskzTx+%dGgP-fi`YrBbCRH?abjTU7Z`Wy>Bfo+Xn}1tgN_*l(e)xmfR^r=8Sd1%*>^AbuLpS8bd=vL9pC00zAAQ z@47;sG+OZ!jL=+Q-{t4if>T{$bZ3ZC$zsEs3Pf&9a*?u`T|nmYmhbQHC&tH{nw!}T zTeMYG!MjPPl);idJ~gGGtgNh~(`eiou;9?Xv|zXi;#}xur7!UC==R@K0^z9Gb1e1i z*B6mJIaAhN_vrt9ioRTrZ0sy_J+hB6ZC9Zd98(g7`YW)UUxZI%UZ@|kS@Y}6X(-zX zJ7@cJeQBB!Ed7z_hJ_C8U^-j3M9mt@=e5wO$z2iMOl=ZGapjl&n|HNBV)4PSTqqXV zveJ_YWouVL%`I{W0YZe7KyqNCE_W;0x#BisD>C+>y1J&J*R>_bX}#*^d7)kzi3~)n zf2#pb(vN^>=_OSb7DBXgZu;Yz`$UN8c^|XD@Z`Lrx~sjA=3_qVmXRHy(QgkUyp7bTqd; zx78$cpIFXs4(2N`r^wi9(!TEqIJezL2G*_M)VelG2mN`WbkID^lD@H7+C(YmfE)HD ze>&ZM$+fAt>bI#&c>eQ?!x%oiAFr$sJ4UhBqlq>~D1hYXZ^%QbO>MmPuUi3$b!tCJ zt%En+KBX#qr?Fh)vA?3!zBvs=N(~A}fv5;)WP9-a(79e41)Vj=N`Q;22GrLiD{$a| zxeLtMU7~1n0DAzmAZ_wQf)GW^=sNw|+>~!_JXZ$(2Z+HXCllh~P1k2^0WwHQNr}*u z2J=U|Rz^gn(tn@^9}yHLFlq4;vX(NVcT~nODVQ`Ym>d%i5hnEqiMG?6HlM`VY4Fhx z8Zf%Jhzn+2$g9fK1~(ngXT4)Pp0Z8 z1Zeh;AH1@Tk>TNlJk}sL1nP~z#zzAzFCa{{frbF2*)83!jGtXyAP)>SyZhh%FNVZO zI6!v!_fG`GbXBE1ZESWyRt1n=_WPR9jt;Ty$PSLTDT(6l5v{|4lV zYrjx+aUcw3Oj#SWd0z*iVgn}HvJe;;nExC*4;x(h-1h*h7tP>;fXkEN3;6~@Kosza z+_r}XP+AjHQ;_v_+#1aWzZu{RP&wfIy$q&378mum3;vU!dH?=F@M;^QEE^m9 zuhV5i<>%JAgmf|lNjt{+gYQ2=t#t6R*%WOZpYH(S@1APAaAtfpAP!4=ZZbM}mXry8)|HBoL&v|n1yGuQAc z_a|$t)Bb@u3MOLM*Zb2v=*9fA4P;8z@ei9@u?g4b0b%?YkL#B5-+oAzW|fguEz!Od zBM2IP6~sp$OdqOG)}qp7n6&3s-FEBU{H%E(HI%4N-PKB`WV#o(u2BP;;&HMT)Dfh- zG{U1S+In&j)j|Jb29e8hSGsg)fF7}kAI?wD{aF$CFBb!n7-W7v zIaY^rKZ+R(h`A(gTC3&qLCf<#Y`ydxAOVRe@Lt*4+h+iS&agrhK$Qy8W9`J}&z{9m zCGWd>`TDl?n1qLi1Ah+=5;qW^nks109R|E(sd7!F3?crPFURfc$$?V`<}a8S89jE( z>+`gJcs0gG6!=;Ey@=V}*!O4~KXAbOw zA{#0*7BjR;6snwU@2q^1%Ln`R*T~4@LGvLuQFQ#4x|&*CW@bAGIRN+sdY!?PX#UJD zuz3N0ip|W-jEj?0iSJqk+A3&>UBI6KN!p6MQ*oe7n)XID9`_Q9K3tiSlV@5@6o5_r zE@}iIRPZ&JkpqJyl0Ym(=*d5SCcyyoPY1u-g{Kq{2z3C}5WqPp85tsUV3Z2n@w|D>_PJ+FA9cjqAK%ueah9c~y2{EAAgu!A zE;SPqDv2V%@PP;f(s&5|;1&3czGnLGS>)5q?smzJ9L9KD3vb!3@BGS__q1LR1gVa5 zgQRFy7JuTn7xk|M-qhH8zR@fjD~wU@QQk5M^`elJPA6X3Y4coG$ZNluN~^?on+hpK z;S%R|d3JvBV23|M(#Co4k?y-9z^^zL?w@iAlIS5`yC4c!GW-SnT0%X{B%%sEvoRWOo;lLeL&=Z9O1eD z!IUHV7wdu3zC9Wo{MKK`TG{jYv!d=%n z#4J6EA?R{489#QjN#^cHzDrODLt{5q?mD7;t69-_Ytg~>SL)?8I*jZE;J{spsD z?E2UHj2aI-a;po;SMAePg~Q@d6{<`Nqty)z)qV93hW+aAzxr+TaMCCG;0PW9*-K1RR7ZjIr?%(1H_Ken@^Qhg z{v3_+CjoFD;UQqtBJ2LGK^Nwg(wkz`uXKc`{FAPu1c4%p!;mOMq1yhaVN_7?S?dBN zEh~2p(Jzx?eaf`_!4F16BmnX%Al&8yvJ>E*Rb9R8?Dl~i8kd~RZ@<_8rdmLzvy=TL zP%S}%!4HHkDq1h*L98=gE?<3NYH~9E%a^gqNoUZ&vgH*)eFOi(`v3&nz=$I-DnJe| z$`M3ULY@RHh7jYiXdgWVOVrgtQe@XzAaw}5b5%f=dIY}l&fKjM+K=}2&w_Yt`mcZ< z1@H)nE2^qK?UmqX_)m$yz}F#!(K4-uVA`<_gf>8d6FTZZ096eR5-fp*i`!FO*0mZI z6}88yfrNyVpO=S&i)+y2Y<00WCp(s03AG` z2dJ`uFbk6@RmiOxc6WEP7_|ujDXVVAP|)e$+qZAgCEj?h0pbB>j}syY|2w)1a7P0# zoIFf6SM8N0wipecW2=)N@@|O4fca9jhpgMoTz!J}#gsOt%f~c-Y@W9fD*TkHqKpGL z#6@Eagbw%#VFR?xezGogNO{CgqLavIRKh`P`GyC> z-&;2@rVYC?u7|Wh*}mZWwJ27KpI=RW8z_bY(`w{JRhd6Lo7_qA3$V|BZL8dXc0Tz* zm@5d%Q66z^pm=pY;G3CYy{u!!T6xOSF)4PQYxNMPb_ahh`tLIqyk?C1U@yWakF>DX zC?=|q&us6=l_WUM(mUE1EV(Ji9Q0~eQv(tWHqu|~rQx!5_A9U7Pu2Gj+LNu4o2y>V z3&+YZQ1>30$C~@;Y`R&@$JD-JHjpSV4h(8#F5&s8* zOdjJ#9KEXMPFh+sF*PO1gUMXmPEv2 z@h2e0D+|vlsv?dSSBOfHGLW(N1qq-`p$|;%^k2u4aX>1ZaB#&G45~oe+ zlgivoJ|w^HHgj{e|GjtMVBy<5-OvwMi5HlEc|VBtH6;ZEBeX!1oq6v6a=ognWqw77wZZK|C4=SzA3>$Y9m^%V?$=*`)C5Xnk9xkVYS)<)u8{qa# zSp!WuA~G@+XYJ3QN)VQEa5w}r_5XnW0fA&I6982WcxhmI2NW})C2suv3kKYO=zI4Hk_A%wyVK>jz!!S!7Ohwa;(6X( ztX<|cfdK(eLs#HvKCv5NFFYh>I0&HKpfGxXx*{YbltO|F|H*i=NClWhD*$kS$%C3G-mm>ogWGr-8~0o0gQz7c7C(v^%~6*2RW9f0A8DqAkoUs z;bA+lz38$tr_1#!^c!E&(7XqA5tu{L*kEwWZGRpY1~ZOAmCIL!W;>>pX|rU2|4*R4 z{u2Bb2>=?6b_36eT-ehYkh7uJi{R$)*bSq2CxLT3+70;F*58B2jG-j;6lJdkTJ<@)0PVqh`Ig8 zYB7^Q$Qh`YL~?W4k|+}&ff_npVbl%^C1@Sli~ylc6evV~{`|eDXcok4z=8mb(__M^ zFae+#xGL`0#KpwKe0+Soz3%}018_TBQvkqOkcVC$JODx}_?Y!0TpOyvF(sIma&$Zf zLngrA)7I3yX!@@7=pPY*1~wxo%fqRBm_1{L1rrk<8{V6nBzEaSo0dcR0G=zi9vW$2B z8e9;Et-b$eCG!FqA&aoXf8|Bf1iF7LqJKg5u-NVyGQ$0W78EiBK|nx9&!RM@7~LI$ zHWk)n$v>+K{MNVGy3Kd~pJ(pk*kjL4WYIZk+x36<*vSwVukWv~ZEf3HtJb<* z_jR7#XtlOaTwyoGEVFGXyZ%zGgo*~|$|P4X!=-cxES zrCmbQ+sKI_-gLU>{3h3F8wz5q+ygkp*B?y`wf*O-mpF|QtPvZvP??cxaOe(%l3nrO z!Hu{AaS@RuB=)3!r{$@|9O*a;yHaO1!Za?_5+)&S6`%d zyUgtUS$8+-;g>((Cd%#xdS)?ya1XVl;-C-bW+IpbLX>D|%x>*X_L150{ho)Hb6QNY zG3_q*OBVZ4rx(8;wZ-&5t2S_C%df_!l1&Xuxd{)4V;sFo*>~RWbD#N2nZTjnMzE9l zQ}AwU4C9bCcOwfC>#!01QCjI@*IdSG$i+$)9Qpt9Nn))(b~Hitl_bC4+1Lf|Bs*0 zz#Pt``kR4P;FG7%e^!C?536U#7DN8$@BF|0?El}qg#Ql@V!BXE&bHdXpqkp(LmxdP zl6b5;w`ZC?!l^5TI*cInSze4A-kVf9PW$|NTe>D!Kh8O=jU5qvwcBI>(#mptQceK3jk;cZMfo%%G zXRe=8%dlG?tTVsHP2TnT@e$dwz2CCFElHd|l1d$x`RdAUk4ph1$9KdP-M4<5pijX| zEmD``$~yUcrDn;1?;i)zBZy*o(B;d~J!toaDG-DKmF8kG zZSFnUozTg)*!wJHp;7uxvi!UE?|+_??vmNf%q#`h1}7S9ajW`3+3*rA8=EG3Q#~!ro_Uu0`)2qkn3&E_T1qplM7Rg-K+6SE<>1vh_NbLet7A+Q z=;WcfhHgwQN!yUh$`u>oy3|zOy?f8V$VrKtkw`VngTKVR$Bz%AyR)lH|6}qTfTTSDtZ;7hZA~L6o8mig@ke-*r=_kdEWL1tyN>7t3ZTo~=B<-fY5MRj z8F_x$BBMK2;2?T)@dogQaaq@|xi2=T74$tm6i|eQH~Q~3o2KzD#bC=Z!G^;nZH z{PpNnXQ%SH8Ka9A4~BP*j9h*d%Lkf%q1JGd9nup9p2I=Vh@hp7UqRt_O0mk7EB_#C zfLdYmU2)B}YD;b&o=tR|zB{t{x8CDsFjcc8=rxYXfu8CZoxF$>lEJFOXGbcHteh5&7ri zI4gLfPh_RPKz1v2b#G*{(v)kp{Zh*pp}uG)X1d>n4qRHfhL2?jY$P`Mn<{IIS0XLe zkKE0p&si|p{U-mzsjr#(_vJ0~%gTH~F#?VR`1(redbqg2Unv*;UlJ0>Pd$X(dw%E~ z+KA=ltDLlZW>-)Q37kmzI5JXgdDhCxYH2S)7>V=&VMBKXjb!p)R~VB7)^l$J1L8x+ zfucX^=$|B?Wyng#?EhM&omokv91>q#sQ=1YIwaoBE zwtK#6ah{Xap6^q2%{$rC+5EcS`ZvR#BU`VgRaD6A+jpbIzW#3<)F$|a9EYTKsH&%` z0oZ3e@|X9HX0}dpM79dGyPF%sZbcbzG6AR7H@)2|74d+F4qXC!xKJzZRS5YCHVt+H zv~o;Mq$VeH4J=e?<6p6D-9~DrCq@^x@&#c*5fCEj*nd9laFa;_k$~;-7zFr2?(lw+ zs?EK>2$XAZz7yq9uNR;d4VU%F?I?$?3|l z(V_Olj2m)>;Zf>7w{x(MXlOXnEE)W~ktfYbNJeH^LNp3j{Omlr zDFvCSzzd;mtXldMFS4nr`6xm{RZiey(I7N3o~t!w%v?PBq>8bsM6(fiRiHO;vMFOo zu83c6_=tbbmpF$AuI5M@s7k@Lg{8Iy!l#-*{IKc z?d{auCzZo4N6AK1v%CFDx?5`&jNAlab-z;*wjszO7jj027HJh<-G*E&u%a8gT%wBd8dV7TC>A-{$e@ zvZ^XQEiJq9!R#H|!g#5_K%#*qq}{fSUFm|iv@vuiNGgdCEIC>fkdFAVxM)`6pNBfBrhpW^TEi~nmDnq?k25y~PR{}!J1@Ii3M?#gXJls|6TdG%{pR2EREtADcrMjm%iriX*Qj8r zSqpCKYm%JXSc~#3xE(Q4_fl=k+=`5vbcw-&pCOfnf0>w2HT&8ve6dc+u=)~q^mGAu}QM|!(&nD0Gp|w33dl-c(!_HBc z4?{YRe51XuEA(!jG3#?0moIOC@fb#me=?!Md>%2GjDVBFHd0So}f%u3@1e^}tKXxku{-;Kmfqyw#L+mY4>gmKA*R&$7(R5*u)I zO7nM&Rf|4ZU@}V(0vba%FDk_aDa&lxKf}qML`FPF_IaXiN&YwOj?^9%x1x&5WZ`EM z7IYUT4b@EwS5?m&pM1u-{#nzfP*zeZ!8%7;l8%DHwp7^fu*I?=e|{Oq-cFS;-j}LH z1GmJCrxV3#V6Qo`(j`qE; z|K+Ah{kXlN;qd)u0}mE;_h0qw%0dTP|vcSlS1?cd(7kn* zct7iO%5B-$w{wXjLHqcFr9nF0J{kQPw#vtLE)kio;_cMcnOCpxmvShjR&C^WN%}A? ztl1-aQ_|bii6vE-_2~O7=EBg>$LA**RK-_xYj;T@9eLByz>mILsLWA`jAZ1oe(GW7 z=~-x)hl&IQNL&nU{<_q5K0XJu?pliPA|UqJ#+T{(2eNM%J2 z&*D_0OwYM3h_5BfK|e#IYY>lMX(}pL-vBWkszUsCUbVI&+;mL%CS{Wma6kam*ZW*m z&^H6Um$bArz(bZ{o{O z#t?i&k=!-yk46`|<{uZ7V9huF#=Ovk|IXA(V8uQ#Oyz?T3zSA->ipeql`dIOa+sKy z0ON1H`l6{RT}%96;>m0yk!~+OE#{4x)8DBxl9D*CzRz25;6cR8>gpMGF4 z+*16sFw54fWJJhqDzrokHq?}qFzNKWRr*$QO<4MQdtVs2NWlkq=i=d^euT%k51e?# zz|Wt)_15pcEg1-jR+^7=b*1*tqI6wp?s@sLay~Q6TVPwWv_=4|?2Bvf@`qEvnKRdJ zTUW3{zg7w_PXkBR)_=O5v9s&``U`K{tC>>C>W=y1 zt$?SK;2E3xt9mBgSl*A7jg|t5_g)s78>(*-5<*&KYeiZ#*T`jkrm7n1VpwQ2(0QpiyO(*Jn}gO}ywKux1LhkP^Ew2f zbr+J;V$NMqMt}9Kwl0=`I-DJyX(W=+?#QId$jIn_P^J03nW%}D*0#vzF>eKkxX~G+ z_~`A0#~iY9a<3hwdYZ&UMU%Kh=$d=`u=Nys{Io?`IcxvCb}UOz=kLucOiyq6@Znyk z)9~={?8+akxc`GhZoDda8JW}*bRs&!EOOHiP4V^hh2L~`dU|?dvE3xQTdN>QQs#lW zg`ROqCj^+ocrC50wuPm-a>#P%>grByx+WbvM812N zNp(Dd5^Y3B{KGgquraV(@I`xic0ietVvGCH&Ti2+4y|xq36xeBZOrCKXA7srLvFaG zMWfIpgLeOg&gxsaMk4-MlHU*YL~*#?K{|*E9}XVQ3W%ffL5}FH1mKD7$G5qQsP9hw z^bDMYP{UP*183afbU=h%*FFyqHwyBjabkP0Go;wz?CR~E`{YUQ>+1aeO7d$v z0coB;x6bHJtw<<7!|BR@p=qJacAn90scq)k`=#6Yh`k!;^ zx@j>h!6LinekIy%HMDKywDWnJM+dL;t5J_7)*9Q6$@;d_r^p68zNe|@{ih=O13$Hqp*tSi1v8(n>g%Ke|dmrkC4S6Q{?^hfi!hZg($IquU~C$K-CTs=Np zepjj^dPneceWp5*@^3@K+uhF|e^R}>Mp0Vu-@iwHw~o_s8ZOlqSC5=Ap8UJPxVbF< zp6-c@e#-Mx2WIaJjeKxEDA6!o&+j+2Iw!V^#!veUhq9;QQpid`;}_2P(`UGAPpwrL zJ(b(r@U6AB=*UO6+L_g_mt&;4jb>f4*ZLBA^Mg!2-8S_**4vJExBlDh9erNUcPANGT9R73wh*^@j@%ZXmw=ccGA4%6O%dWCf&jEdUGQSuU|0ETNDNlL zkC221LYgcW6m&46PC#6fPbVLdKgV1Px zZsK)9s|fyl9;B3S^+$gvbmqV*97~bpM1g)&n;QRVz|d~#gi?D1O_cd}%euw;yq%hw z0*^3$AQ)9jI7?r+croqSmT>)?9w-b5qOQIkMqovVGRPQzW@ZZXq!R=>#g)#@#QT`3 z3&uf2c@|Rb)9QA!xd$sad7Y; zFK^?6$_-R~r$`NX@Z~K8an?!$!N?w1YbXzL$nPO+@oz9TJM#C(mLxGj!DD$w&wZm6 zABi72v<^{*?59BsQ`0w(l~Mg`_TL`=%MrTisT9=zAgkO;e4U&W+`AX99_eSO=-9l! zyxew8)xgT?QEY5isl)N{)jIF7u*E8fHB!CTQ2a~k{f35iA_S$(3}HK@k8r;NxpPt; zH&GpU3%EQfD=SAQqsuP{?+K=JQh0Xz*4R)rBRM_O1o8OsW2CPxuknM)2}r0o2b3ltQn61-_^>nq@7Lbp(H1UF@B#X+Cy zJ~XFBO-4W`2On)x^&jNZt>GCM3}D@LW^7KK^27V6aJ~blyxnLzY)`zMR2d!~PQ}Pg zB6Ss{6=%9`OXn9CAHgD*N~A?ab)X9T{ZswymoHKftKlJ%&(?$KgCEj+wxH>*G?WcF zyGkU+J)nIhNWOHC^UM#Hw_Q*~TP93K?4utOsB#)Gi3ijK}i18cKntI;X&P_0e7L`@04znB$35a@E2 z7i)O(WDZPIw!>e%qa@fV4v`~I#UH_TV#{T1%IUrWa89*dWO_X^`By;}EIu?Zu?^MD z0sh@WpfU9u=;2oKEJOTY(WxhSg4Je2w=9MBxjs%lVXC)(>-CcKrAH^=q(i=nr7;KMeLA`LTSp#7s{PBVmgzs>6x~bAqUF z>!a~x%`&Wp27+jW^5Hhu!7#<{fa&*mqWw&&(Z`msFP=S9aOlxC=&ifBBqc2!eOiXv z4jApvsG5#WCo~k%h2c;Fz|a`vuuGs?djBk>02N4Kp$g*&Rz;c*emEEbYy2&YcwjMm zfEYy->YQVcaU=0fKqZjEf;i7@gr5Zz-+8I2js8wa*)YR^_rMNzcGtkK&W?_3!=+74 zs=cAgyu8Fn=<;m87fiQcazPOBg3QPXgw+sN2AU&-FDM-i(823f-vl;<0T7-*%IU1< zNX-h43AfS(+rXS~L(`zuL1#v8rK$d6o+Rmm2dm4exxLA>6`ql8hjUTcxXB zLQW3@TL%Q*g8Y0~Iokh?Xhb-G)4|Cee>*$Ap|`IP0AM?GQjeW_3~p-R5FHgo?BO(k zZR7SnvquJ6_W0OX+mEzpYDT;}n$wKnro*`keIV%4NV}`cZ3{yEcd zrjV$$GT%J$_x(SAYz);Jip2<6nhK;jGxPNIi};tX2R<|1T)k4Jnm=+OZOZZao7+0R z>@I|UP6ki>zPGIHM}D1pGdJt{HjA3z5)NCk$QM>2c(bn?&gfa+*nQCV<@@(#h+eG& z6hvxfrh1kke7$;(AAb4b1zG3X-!;D;i^j|0uDP~4w` z!VFFjI8%Z^ct&K1VLQHg!--J*Qfmq_M*k1MrWf8~tlBYz2 zX5R_^l|^o6UdTFPNCo*1CFL1sXOac-n$+pQKt&QOB?G@yzyc6vxbn}G4}#}FitynB zt12~7@?QjorI}eG`hlN%4pWCcgEau^7pVF}EbjJ~DOooMhcqfW8fMm36)pxiBlKIF z5q%L!W0R6<06tLAixqG^xf~l8cS>JBHX&gJ>dv8o0W=I_%EQNo;9y8vA=z;k6r&96 zK5cFN95Yw0zVQArdK82x)IAHB+k&5o{=NASvMsl~=DbE~WFbEVyn-w8z<{Y-vXy~> z4+0he`Lwxt4YcD0r;GpnXl4XT2|7cS zhz2dFB1ypnmo)CgsTE|WBhP%s;VHJ2fs2iY$Lq|QtUfoC6jD$byzpZb%F7@qR9$=$ zv`K^<0^p^K7va|Up{J)jJKG7gAYchMH#fwW2SykGkF4=x;137d8$KQ_-asu%wLGR=}x4=+Okmv3CaykiF{bSTc)IXNM^r^Lrs zK(_{KGHjxZUrV{?^b7j1aFDV?{AG*vLOhW<<;O0Y70UBRCWxuT?Ab?ZgVrul~eB=G&|FD|@KR>ut0{^bS zK>=ePPL!HHOiqTvc@W?RE9H*&n`^?x`&7T)VYUg2 zE;e>{3~f;WRgA(*nsXDZdLAALzOwdn0^hRLfsxQZeVX(rcmee6QjREY;x@+61ze1| zg@um}`ekQjox!PwCeRo1D|m<_ApjI}dY-uVj^+n|A5=wPPki!`qA@R%k+Ct_m_49L zMDVkC=FK;hHr0UsD@|=}5@8KXQB8qB{0x$|9fc!E=|1EiFptkcr$zdP3uqC5_n}{D(1`;C;d_#pcbIV?d@cR_iR3I9INYsI zq?cTcI_~b>!yC&z=nKISM?bMN-PJM~w3)6!C2NXIMZ*A({Rc=UB)h)6%B4w&R5+HnrzTnE<>8uo)Qu<2ZMm^CFSPp!<0`8S?%XMR;fL)F^}7flh()NddJdGlU~|CN zk4CU8wQoU$?Rs92o2!We+0C0nh=V{n(E-On^A(y`2Cfagv@b${vwPL0s=MVkK4jZz?Uvw zK&6MIa7CU7^T?Be>qw=sp#E5%@NkOw(|7FH{42G!w6M$HB3}hFrp|v52N-9UgfbD& zff=3TjCM>d_oD3`lUz{I7CiZYX~&LJ#>VGhPl@IETzAnAeF3evRk0aK2NsC~-cBuC zRS@-Xx>4B$SY2ITXu$h_W-5xWOL|!Brr@=S`zD3v$WqD&JwjCoJ{&ATEOt*nnF#oxM?ezug@CYulX*i6G1T~&C zt461CAa=-%PZwFPp}q{5_o>>i?d{v!ZR3gAl95bcHv~|)1Tio%S+(tEIwG##f0;^< zBEy;2EQn^09^q_Hqawi*{MQF(N+iJzMFzTNOIUZJdlJJCPTUhcFzF6T&m55nb5U>~ zeirdd-3reP{SZ$L(OftA-F+>_Zj{K9gnyR2Qt@SmWN=F zjKy+snZlv9vb1!R8blQVnp{#E2EB5fIIXjqS~Kckh&ZyiA+d5viHY#f0EEBAAv*(G z-QPbuB$BlXQ7exeFQzIHj_W>S&S4^xE1{f!nXj*mT2)uNa5wm3Kk4(CRV@;~+{8>@ z_NiXfM_T2_K!k+;_}jDfl3ZT8o&0G(s)8fS!;W{tQ<7hqWmcPK@{%b97Pt1BeLcG_ zoyR}ndR4oP!td4P z{*L5rwnwQ`jPlf3j;al)zLEXbU@y5nnSZ3vrn`9MeO%4n29tr<5FKXEx!-Az?a3l* zEQ;!kc>P9Wg;IL`_I>H$KFj}UbHScL>#w=6L;0)l=SAP|eT+QwdJE&sHIFj&^tI{0 zy)OjifBE<^`xQ-g+&J+dv;g`Sg?R8-O=>AP@(woQ>7Y0>(3F|)p~Cp4tPEl;Rv{^kC@ zg-P|%;}7}tz6p~W^lIdqJ2G$b(F<6pNQs6~5aa|kxeGlZ6)UK@wJH72v8&dftFz`i z3f4QU??0Kr4@GAmiYb5-WWb|O1LaHSZEblYo43)>An5w;ru{DoJ_eNhnkZI;UI)_z zl_e#Apj}D+STR8$*hfc2aZ1`Y&h!=F-2M)F{=ydWCGT_RMu0_e#L)|_#{`c!y_CaN zPd(TJ8zJo29@PT-Tm)!b^M%F5^1JV$ONeym<9Y+95yN%xJ*Ka<*ta{DA?pS1xCH4M z4A5J}>2!cmSqX2vG6eU)ix=5#9;SbnL#B_5>!FM*LeGxKX1H4-^+2vpYVnSWIsuv3 zt!8n&m|nLPfPOd@RGQCsjGHmlTL4ehle|}gw*Ux^84MVqxC_vN zJQN;&2zq_(?HEt<_VsHwG&Zy*&d$!B(AGA2V%iOAF^qRP@bXf#kxi>pIWQGEO1zh+ z3@%-|Bz}K?T>H>d&ssbwa03H}%qxX2zEA>Sn}giqQuNP#^k{dpBz|WYw=$4`jvBL6 zqA2W*5Uoiv9K6!VNGiqsTgb`bOp2t2PEQX{e2HV$%)lV6+XXEv$)MW->Fcc*zI{7> z_bcvDi%K`-QUV0p(Y5vTHZe4kG-?9_bx`#VRSCK64R)TAmi5Ag3+fsg>;?E3aWsZG zzKVrj1%;w8-aohwVq?7lP7v0~C`)i9;x%5uaX5N)YbV-)@s?E6^abPA*4JY&dQ+Gs z>G{oS@Nn3~=9O+Mpn*Zdnp;@($n1t60g@dc?I3tzV54NmTqD^y!vzOhp&uUY6;!Zx zArZEK?qF7-(S!#aL@@A~-nDb*+V9^on;T`=OgKg3q`MsK<<+ZOk~>ZEjIcp~Fiq<& z_+Yn0TkV4!?$oJMC_IoRk)Z|Xc6u(jM@GgN1QH;D3xR>@0gS-HfYsx^)j%V&pBs*F zyoCnf18Vs|ozOKlzk13`xyAa#R~lpXRiw3c87$c-e* zJ$D7xm#0s`YQujT>;d|O0~{Dcsnez((p=-0l|2X8EPVGihj*UQ5J2JoYU1sK%THUI z5X5*$ak2YxieVAEgXS8OYM#-|_;~ZTC5)$&fkG9SyQaGOM#~paun5U0+&KuPo<92y ztvA5KPL7V49Er0zz48a`Ht6`q(fM(q4&(e%&-zR$w7+?ST{Z=-R^RX~Ixz=_hj}or zVC6v2L>~|IF0cg0Jurlz=;$cm#FV93(u=jZXY#zEnYjTsmk`a%N<7T2M6 z=oX$tUQP}W7+5AWOO ziU1{CGxPoX+iy!}q|D%Gj6Dv6?I8%A!Gc^@PX|sO8XCd|9~c~D|7C?bH8Bbl1=~Jh z4;0i%SQBU#8ql&bspw-@(}iNVl~paaJUR~f1yadZuU{)({OJf5rM_M{Tv9POVAh9+ISa1|w8JJGle&=7LHQYdtS4QAHjKCHMufSfkuAU<3_d|J44z!3;iHBc z_he2Fe3l0~JENP&{1-lrG&S)bII#WNyL%Egx6h6ulCVV#;*4Nj(A3^FgUK$SF@GRo z!W}V!TJf7VClT^yKVTf~Sx7};6P!C6FEv~5`TUVQYmbsqwAeXj2o;pUYU{KG?3ZClkjSX-x(88L(1zd$`5mn zJv+Wt@%Gf0I=k!lHD8S}@#^KD2sX6Yv$?4!v4dda^_O+lq4&EnGuJ!VFDUp%?4w$4 zWQYc{EX^a;UqgCTs?=`rfvj4RgsHdLocurUjN1|WB@)cyRA`T8=KSb5%)LkTA(g-H z-!iYL17zp5w={1ZwYm6yPE{+8GGVJiZR81~SU+c*Ve8#1eRB$NM6R!$Qc3%=K{Ss>u^dXmb-Hz|AmH9Bk!k3>~srA=S zT>O#S9N_NgHuSKK#@)I9la4?R-xl7M?3${|+3AJRvr9vz2#ga@jGz^{-602l(rk3a zb$1|G6s86z_dCMkNp3E6v?OVyTXN8a>)F$A3@$LrhjApRModq3fE5Z<@<=7nsCNY% z44T`Je;0oW$^Oes@XlySa&wQjxxOW?7*GaM_oj=ghPx%HvZh3=LKBVV|f z=kJZ7Av#*`>px1%4miut(7DMpFKil5vsK?SnNB--qQRx>YoD-9bxlJ=^B4LBnn1i>8>`R9jCec;Y$cJ-f2OT}RHaOI3&&U|63a=Eu}!32XIywI`E{Ix6t9&m`be}7lXyBuMfgoA<0F6f~6{rf`v z4hjQd7IXrjJOY0UAVR>YV!AX~v&VP0Lbr>QxYH>kQ;yX24#ovkYSMItDw8^>v!-jb z%}q^n^YaKt%1TP`{E_9*fwd}CTakUYy2Eo4ynW^i9G)Vk#a+VpW(Ul zg8l#20%T->0*hu&#Crg)iSJ+}LXPEyeAe4hp&T6r3wreJD#*zAe|+Wx?6Kp9x^TlS zgf+MVfC$AC*$uJcG3mJ!N=T#%GH@&;D#V|jHc=c^Lir6PY5Z0+x<){kDmEgaFw)l# zd3uE4MZFqT;XTK0O?_@?h>D_xa%u4b=wkqn{i32Wm&Z229xgBYqYn)Cb*f&&9RcuL zwf@sm9s`o-%|;o4BZ`uMNf-_ha(OmN@&S*hF){Ce5aq32%*rU}&&g}+=#cmbd?SM&Vdw>_w!{jPoB_d!0Hdlo zs74Dgx07a;Va?{|D9A(W>o3EG8UXXkmBZQxuBOhf=GX)szt4uf&MId9V=ag^YA zodR71yP2|M88@P%HLhixC(W(I3x9s;m$*n>O$}*CBQ(i4YADRYP7N~4w{OK|WLT7= zV78T9!~z+&TRNm+a8kfC0dX+W(yF<3Ur>5{|Ndz(G+@Y3;fza2xMsMAjQHw%5u>KS zO`)iKD1PBxU!N@CNXuSyDp*(XN7&)!^h4xCo{OdC> zEr)6gf-uP_3;z{lhLAS&Ho;U!4*J33Ks0P%9sK{w`8Fj)f2PC^ndyw0BeK0i-NJkd?< z@Vcd{Y0Nutb?%0BCLZ_Dp;|mTf~-DE0yHC&@c0`*}gw3%?o|Jdctm22lJn}Dg zqvmkwN5Lw)1P6AuzS zhMXG!4_p1SqXIdRT2v&CF}9GdKpHE+3|>%NSeRP%H{Kg?JQ78y;qGYm;!0T_>jtIb ze>oTj1!x-?)FG61@M-DmJje;413?agY!%y)m4$^MAj2i)+4y}BgrroZZQOumVrB+x z;Ado?$oP{@1o!Q0fAPW&+&Z3fYK5=Hn~{@5Q`n)9a~U4m`-3$>T3Q$Hx}lC!?y^%j z^DtW8Nz=8)KA@spzaB*f!TMKumk*zdM&gaDR;)+<9l!g=H{lb(7v66iakbv6-lG%s zV`BAQ9g~psmT+eJjZ9TnTV7)HY^h~dmMitp@Q;U{$*Y}qytZ+TZ5qpFA)fWOx0kkihiEA+Z@v@s41d_J{Etz3i_ibXmxje{=C)T=b)phg@rsHCwXe`d%f2?3F0OpKuo1~Hh%s$NVjZc z>*=}i+DE*Y>sk2Ls8IQ59mhcdVEd!D5Lb6hE7!&}KNrI{RJL#c1`+HLoeQ5l(SLq& z*RK26Mi5-0ZoH#@FL)jYOqg={_;SC>gZ@;p(;RLx{#Wn0c6CV=9xck)8EBrDVQijl za9mnu+^J>ui^E8Hy;bb}tgH~R9u6Z#^Q;WxK=ZV$2@dn@w7upvN85T`6Pc@vEb}j~ z%=}}NUhYVPjmGZxudLk9T*rfbHxI|AwHgmGX-1LTq%w!D?%AJZR?wR}k}|SoO*+0H zR62lXAj40pQ*|sgMXs%L;>2uU!F`|f+S*ILzP^yuMK&h_jlm-?j!k?<9!QM3=LVCi z!%fSIZhyrQNCp8GNV7RJ!*PKfR8&mbnu)>k-#<6-0Msc;vEh_`|NaT*4yaXj>GvudK7SU-nwJT^Zfj7Zf?|7>;q4@LF#SoB5wo5FG&XOTTP{=wdEA<8I`L!XFt`sk|fCesIxfz3HiIv-$Q0CoQuXG^z)v~(O-5=xYfL*%1~Ev z#m%1G-HwzKKE5cPr5zS+-ExbUx|h-cNHv@D#ivi)E!8KZ=KBhEY25|zhMtmw(9C*| z@QHR@rusF>dpB5N*{a|_6}y#mHlJJewMzu4nrT5}-_LIBFHU(>h6-@g~?ic?JNI0+Gf zrY4qLjynsbnD4{CHU};(H45Kc-fYOf^u3+ypp$Rp$I#fTgy2Ue-Gg7_)l5!%Yfv0i zHz8X~7asjmNae#e25LcM8ck$2n!mG6h*9MC_LnE1WuGf!p3_*dSeMj!R+ zml@}mjRH=eHa@MqqO{gs~j7XxhN2FEVG1@YTNp)yV2dG@?io~u5j)C~!y7oLhfsPGWZC+Jg zvD7=2XM_vzK;p^2cR(0U`75UBU#B|I@AMH?LvzA2e{}r!DkuyzH}BcM-yN(RP=1r= zF~K;v0pI3Uu9?qdryw6&92--kE-hgBCqXrOi%!^9zbxnXRBj2`qhN-lB@@jEjohMm z3nj`QZ671Z9@SGazX%9$t5kjcI$hw1Yi7#vsJ&5?Mw{ZFa%edB)1BuCUCDX<^sQ0V zZpK$+6vTA~qc^ifgIDU0iDf>uEM)H;Og3jXOG;bG zV_DMu^<%?$i@WX0H~*efCse58r9!*Tc=B)W;ir+JBd8@kW*^fs3BQzy3?ax5HIF^K zHP2VjG^eAsHO;$Hxa-@l@}Rs}4yGrsMBFpUx?~k+J9VSKG%$uSQO8AHIB?)*7&Av1 zbqUw?AI!ADtOH@bd@Xz-6K{Tu-2Y(aO0CZFrl_)b;E`B5sy5`KqbjBC`@&;2QlGsY z5;7dQmvZdHX#LT|las5StT)tUbgC5Ye~BxueDEUkpyH$NXlWxtupz7btf=^2mU?w= zvM@18Vz#i}k&Oz(%8M6tgMyTFuk^chN8dItjbq+MA;Y8}o1GdP#!u}EQQNH%zT>*L z9j>u3^)MNB%{jg<)IBy8?@RE#Bt}PxW9^nvszpX%}L09NmWfKhTz4-{w@+rpA;Yel2)=R zI+>s$h|~Kz%6^MsNSMP3s^WjMHv#HxZAZ~qJ!<0Qccd2MvFbKA8!eiAgwIl;bgIHD zynlc5m0$&M#;FihA~izo8Q&4VP_O|Vmu0y4m_@;x7vqH19J|Giu!NVSrP%>&Vm=A~ z9driCFo#ee^gW&ao9Fjs~W05Fk&!QJ$JGoC#FecFbr?~c6bYc_A!~NU1&#Anh=wa_}1TN zZEan|ms=RxoCK8m6cz%YR$;seK?GQnU(-F20g|rClhx!T%LgO>fh#ZcqJrrS^H}6b zuD$!fFXI}hsWFF;*}n!d=l=OcRvcP;wrmZH5*6MbD=RJg$Sq@_ORJ{22oMXq(>LcT z9L-q0-d0`E2nTD}F#Jl8iP}3ch_)RB!1e1c27e1Mgj=C`3(3NWz`8c9?`EwVk9l93~={!5x>eG*A5}`3!AL`{MWXU;A}3E@P3t7z~!qlI&ct2 zw)(6v6($(T!6h)evaIwP(!-64>~n1)c=OPaBS#QB;LNY2#C80!T!S|3_J)VUnevT9 zZm1g?8GZFv^pR7`GF-zDErP_|XBx4y3taX`MbYq*%AjlWRNthatvgq~ zYJXq(U+@_zcJ47q#;o8c7WpD6DG6CbUD>>TRi$|$6xCu*T&?1AVBkR(7xkq>#n$SV zXLQq{YWSIqs<4m{CUQS*Nfkq{=zmEgR=jo6TVx)gjV9j>%@O=KllxCQRJ&T*-qZ3CbmEyyJ`&C*7EY}*6L4QDM z3kmt(#hw$8Km$lg@vM&_ga%1;*pR^T$!H zecTMBhLL`!SXPkJMZ+EwTMpfSs1UVmeS(g02v|^+@>ujy;KC;X47n_>=Njf=k*$h5 zBUMfs>M>xDJ*3klDYhTC`U z7#-WYLnUG)xxeIN`|G`7^n($fm~&aS9a_71-Tbt341<2%kMPy#9a)cA>Wn={0xI*B z860SC*jnCQHA*Xe@ile(?>UcyR3WkCZ>b6`wv$)Nnb4(e;j0X1-H=L+$=(-j8e*zT zgjC;{U>fC#57QU^+w$ex^T@6(A;j-}>bl`CV@2c-C7aX4`Ik*Se#@U~>%5uLW>VkB z7ZReTBA&UI|CemC37_NkRK600gUNA}qYcuh8Tj<3CG;W)qLD0>X(N5F3iZe5o2-Yn z@P!bjrv8<0KlwLE;TB*bh=;@jkKLbE$bbD>Dy@FKqOeNmc3KxrgV+ca&7<2X$USN*rFCAp z`bpA7D!e>s_Tl}@sX1Nqxp;2^b3++)iRfMQ4Y{yK!6eCN7;P=cA*gZPuDyyn_u%Qk=qD0Iip_pZK6% zyJ=D|aXE@f^+FcT+3+3>oOJNo==m;3L5Ae3Fx9;!C(L&OMR}WYC;De8_mjq-fg1qE zuhxB3x@~7b`sC>s@5_HTc*-SrI-$r1q8(wyb-opYK-TF^af&X8!@D7g@f!afYEFOe zR`DQx{yP;t+`Hkt(z442%`)>DF^6kF&Kl;ubh?gE-LSDbjBLQOVTv_pZr+QNu?+=g z6Z8-P_1!xIwi7=KmE+Z`&j1yN8(J&|3n>qekDrY~Ih3(q2wj1GJxpMJ|3NQm9wn|$ zUD?JA@VYmy-Q2?#mqgH1R`XbisVKdRJg1Qp%1F^|*HQ zX`&KNq9e@{pH=+7VR8KAj%);#L1%odQ_r}QPP(Q(^n>C^x zV99LTq9<*K+20;$<6)tuI2-d>H%jl^xys1MOd1bb^A9dDpIA-f!K(9;6TbQyEXQ;J*Wjw1a#Nt84Qbt?Jb5EUC^8f(PgX z7b=1As(sJq>9E z2>05N=QtZkL$^{;R5a|Wa`}AG_DD6V6bPZ<<$#YJV6Qj%b z#lCRt7`+KCDz00o1X0(#d0e;sGB{cp0O{M>$C#7H@L4X~(nF8GaPrsZXt}a5yH6S4BM6(^j2{ZT?A|Mp3Kmy>Yj>lS12Dh*7?O z8bR+@2^k?wlN~N{Avek-@Uc&~0i?U3xC zjbM5uP3aD@?Gz?L&R$Nd`Lc)hQL|BeRn?wx`s9|i%58eko-dloR-t#$LaJIX)<|by|DuaoNNoDHFbKP)-dpr1aU-1pRcC^aqhJp0mO>RG~Z)0PNuTJkq4i2aC zIS>edU7%SZ|FHr|fSQ-hzRR6gE6))Gf=B25N`}ZSZMT%Xhr0jmjUGAc^*t8`(;Xd< zs5I@wP!K6-D@i;xhfFTmTqt*dyQmo%!5flP)F?--@z_UgV>srQ-0H&>xoaTE3RgZ_ zYtEs|?|dynO7!C5B9u@?KfU3F{|RL$eBeRQA81B`YzEy|u#1POvPWOjD6LuH2i9*r z5K0E^Xen}pg*z&Oeo-fbTuP*NJ@ZQUon^StM@BT}4=|R7{6dc+jC=5bD5jxQL=rm! z0RxjFXGMYL+5CrpAC&YnC$T{xc?9{y4#sDPp3-)ddLZ(`><4cJ6eO2ZHvi_#|3u%m zhC!j379w|eSLf4NlPnn>#@&j&eIAeE<9k^GC+*DNTljITeE6~o6s)#f+957_X z-IsyR_C0%`A}hUlaABigx8!hY0dwO1fx3x>g-s@1txhah9w_Zjb@&Yoe&k#>2ra0} z&Yr~J_e9Kv-%|X3XvzL~9hEi1rLO>am2yfm{xy&(+?LyU^@SuFF(42i%?+jcd<`M; z`0*10cOlXzsZe)|6mGx_Ag&#A%rGXdJLq)YK`|2p*eakM0%8@c22vWq1ntE(t1QEF z3!gT?Ga{G(?Td?wew7HlOhg}Zh@$l6%ITkl(r)-vG!%bO8vcW74((ST;rohYOHxLr z;v#wCaoW|#e$m1KEgo_XD=MONH0JDAMv`JR1->Y}ZEO3MwvmC-914hY2!UwQ`h`RokO5M(WyNdaWHzI@9;Z^cZDud&m4rln(67f7=8Mw@<4{KKFMIcb25fd zY~aYmUCT|%VmiqeQ%_BxxIP!x;CDmIFs+C%^VVQz%3amJ8$Y-zT?KOT=NfEIXXqK* zdEL*_UKMCHeGsB7#BK0t@I{;1vor3i5(mFuba)nj#fxQG_-g+z)mINl-}#0Jg9K8m zn7Z)T_Ps|;KAyMZ+O$cWkLB}V#kaC2mS3pG{gHdoQ(=uo%EFgsoVJB@zzIWY_1d`G{TsygWCQhjDb4 zVWd(rG(+ELL?Y|1yuJK&Ht=dw?U>#9^EcTf-cRSiROD|^$->lPaGx(=yTGfMO*KSN zLN+ONu?xQN-r#U8+BlN(Nq^e}v-;7G_HW8|y_=PnNp6zP)nojQ!No z67zh5h0kHvHHew&)%Bcu6#;7P-cQA-Wp3UJ7MdyDy^oEJLw#q-ouSX+Lx&#we%n># zwEFxx|Bp8>3^RRu{NU|3PxlHVR9MvQ-_MZ1lzVIjB6%FS&N)339yV!o2;k5zI!>qc z1zV%cd)8u};0RhKBnSa39;+3Qj;V$q^rmFlvBO>*D8$3(K}Szo{qT!u_LUA8!Vs8D zUk9H6hZe{{*Q}RTJ~TC5dUbf%^8kU1hlldQngBB`yLWGI@Kw&u;m(-uu`5`Znoc@C zr-lb`+~dcAfW1LhOi4+R4J+BK(K@o*x%~QDq(l~WjqJ}gJ!bUFaq;or zGAwFCIaw}bdwv%LK>Yd3myKWdDny`Y0@ril6yt|azb=p>>wFr114h8J^!WSztd#u2 z=ins#y#K{!4XEDsUP`#MF{`FB?NBnvewlnxc&g_M&{>&NTRO~#%i<*6;75L z@rh2W9H)yJpL#x?Z!cdv z`(WoLV+MzF)!cdg?g!}!Y7D1+)i)87Oe5$0Z?0S1?VhxnrnAeHUiW;iU(d~uWW{*m z+|&Gkl?BGCeGMmq9oI2&*7ii7Ozi;0--ZGLeOfU1_ z(M~(OckeR*Q|N`JH$)L72-hFHKFkHYW6;#1JF-d1O&XTvJzv921U^A?`c$^ zhK4Ntn%@|Q{|v7XQ4ON*E+`-mGM)JnVS)PcWzAOEV>6I7i}~yoiI3!h;9vucbnwoz`+&ay3Y8T0U{H3pnfaw7aQ+(|hx*yJ)Swc(!Sw z`m@%TmKEiSM&4r~PSDNPfu({WqQ|lCV`J?g=fKS>-gdbqUN0SdqT(LP{OoLoa9XUy zksVyGH<3dHn+dgIA3z%xXKO|z3?+dSq2wb^rq+WyZJLF`2P|8O@{Sr$#X?h{XWwx( zmG0>O^yyZ7{2~%lkllhsizOdgbt^IP$KpH^#(EW(=V*l&3IcS4f`VRd_Wsdzu*lE| z3|~YO5ajXdkDjA*+6H{@v=_j;;%F;k5*9iY0j&B}Qv90NmHA2(4DHjCgkX4lfTIL4 zsD(ivY>LKt-2(1n=OEPZSh^@ULBQS#1iX6J?Gvwhrn~0Fv9Og>TUn}@&Vm9@2u8Ae zktxQjn3*#Yi9Wp5SDV3?gUA+qV2~V+#fOZXAf~3JT`2qtFn`{1nZ+)FGK32pjH4N6 z`9xl-g1ZhKE#9&(M3XQQNNi==sKqCVv_>4k`{o(&j3e9S$ZfQ?oQ`a%jsSCu?EHt* zr;aV);lQ`JP_!}9QVVM%@2)6JMW#<-Tn8_-Ll5XQkkhe5mNtQN?sEJujN$HIH~{r= zLxg4D=TlS!Xi(BLA0)ThV|3d+yyg#sIJW3|r^8!1E5gIWr|hl+I{diGK_fzte__~P zWBIj~;+i5|q%pMubG38Q^dYM)g7?{Gd$fc-y?!RN@hL5w+j&a9%4=OyzA8*RW6t^p z&9ykHfMw4c4PnT)9-z&(dChS&NFc;V!hXvXa=!D=uyNX{lvI zdGjHmn7&T|9%bJ%r`kCvZ%|mNrMxj)+S{|PMQ2F2^)%Zf^SHRapiHlg)L$Q}SK^91 z_8pp{rZdyAYnPJr?fbLHv_*h|o|{@r>qmK$!O<^G&UXrg9B)<(0IhJ|Zb+yE%@`iT zDA6we8a)|W6V<3R%d?vvdV)qSztYuXf!2^V+{EsX>51{Vf< zy7n|@lFkwmr&*)xfw(=A^<{FAK}aq7VMO}u<>KO!kSO$hyi@k=Cwp3&7Y-CJ{)lf1^qK~CeU%lRT`BjhV%ZbOKz!p zjYr)C?!HGIKu++*I2gm=F2(YsBurelg7m|B)Jw!!t$Dl`zmD8cfer=Q=%C!R@HMGY+YS{qWu7EUyDDk z@tn~9{m0#gYfo!w?6^n70wQU?e z51NGq-~nXrd}AYqz0$G%hK6Kc5|nS2knP})9RH;n$b}XdkDqMMfGRUDiSy5USR7M_ zhyAoe@;G!InpuW9riGIjY+)hs$4%hZ!+eT{P=*M+?62n_$Zg;Z!nrKXv8cuW1*i$i z(4;RD^*8rKZr7rrp}}Le;ExNy#z(DO=V_Z~jE*3Wdk|vR*%w#!vkbu&kv@13RWDgH zYRpMHFb-UxFaIN1Qv#=LXePT*=^`s!njJ~6Z{GOf?8ToX1OxArQ_33Fc?dKbV?@bA z59wLO2n2HM8+m1=A1Lj!Ffs8Rs@?=O5Xb_76e7&DxZuG<)7EB~xdzq;Zfg^aG{E`6 ze#)J#2bmC82o^Ck*LoHfH?P(su@etJcWwrtMcpQ-_C*k|31S~9*~RIlZEaIL2QAm4(x@amls7oz8Lg4($v&M#=wD7 zMRCd<#{*Gx|3L`Gj|H_DqnR=Yz<|QVytrn{1sXQSfw*l40QlnB&kYWAb=`mPU>TpE z9L9pKp-UJ!$J!4Lp}BNH`H3U=b@xT~2Dn2$K?4-Zc-*yl4>K#Pv}4-~(1EZkEYL{e z3)q@KlBKWz8;v;h@r!^P3uU7)P6N>r!@*_{mo8qc9g4;?#nFZzoB;oD7o#0uTx|Pw zz1`JRk#C36%4LCHQ6iyS)THWG|qk$0J6wNGmg#kkSLMBM#)K0Af~qexgFn1l=iQ>8Ke8Leg0a84Hem)2kGJroJvh? zZ7FV@4!)LK-`K4A`@wytrzAP`hD4)lUWWoKeab(4;QChcTWB|di7+ZIepukM*&A1% z`%8fwiVrlIvzXc+B1sC_G%@>jX=qShCCaS-PKuHrE`H{})=7lZFb4y0j; zv;}b#h$hZYIDW{X+{A&FJGS-onKR&K52GecOH0FPoiLDTZNf5p7y7!_)|EhoO);s2 zI-ab{MlbQQR%sx+U@V(zopBZbMi#h zzfB8Xx9q$m53DA!3Y1d-Ec7-0L`SB>*LZOavqkeiNl&C^Ip-2Yhc+A}w>I{V1lSP53G$6kH&CL>q z4iuqGh@1Ed`x=zzIg`~j~%_skEV@l&v@c1htn3~o4CT!KQy1vl9BZF{9}qC>f@ zowgh91)wEfsDN((dL}R(F~h(VaJHxDKp6ZQe#>nv$7rX4l+fJNG*UB0O-XT6017b| zNH!3s1-{G<+B$f*fgdXA(huh#_?H;tP2&@ge<1)qkh=UQQtt&ujt6itMVteroj=Y2 z-42XPj2CBsJHXy!S>wRl(Tq(qn9bqZhZ`68mj;H0ux2^^tX1F85ZPnHNP#8?PvR2F zGocNgt|0$-P>J9}fbUJtZ$ndq^D!<)%MkXU$SXK`vhnilTO$*b2!lx&^1?v@re_cY zpFakZk7>)@X9aGc$TBOiCmZe{zDyP@8muxLTzEaoN=jmMBa_0NXpq5OBfEfrqYyB3 zbtm=;1vl!3Zy{8h;el6*)4#(|$LSeTAIr*+7Qh{=2J{{MTffa^4un16?qD^?_Q3dY?s-_7Y0V zCzoeRkafvep#=ddHVCMbV7tPm<|b!55ZbvObcF|RM&E53!#N5IAaXL?VKg-tQ~>54 z1LquNF}@tqTB3GKY45)`068k|Z~(CIm^t(@mz*Dhb}k4-Bo-F#097K+BXUj2-T70L0aqz1IFx=D0{d_v!LTxt%*urz1QQ9^u86WU!SLny@@O1x?3M(n zoH}tw#y`PW5FXO(cx<>I$g&}=Y?h5kYD_V)K@4gViqE+O~sb;5`QG*Q5)I=Z@Z z2+hW^H0ALI1VwVcj^gsZm^J0B9ND1>$RY3=kcDAsqm-_H-p&q#f^|>_!R1xGT$Tu8 zB=qwxMLp!T1_AHFOdqDLS4Y_dEDb0LDu_$&?kWg&hc5~&t-xd6C9vsU}9ri>GK{q$;)JPI>$7tp653ismI`}l0=?`&o0&q5O zN`#~ga)P6?WzS$y==<#frh{>f9$+}&c0!ghKF@%QD1$e82^3*#cum_Bpt}aytFZeb z7@sSs*BhdC&BGk?ZY?7dF^Kla`AQp^bM`hZW+s)%b00!qwU#n%Pl;N{C^d1~P7p|v zf5Q4FOE*mZ?W0mF+d|4wtG=zXJEty5w&WgpWkC@Ve#`5PnyZ9NiLfDy{8MN54TF80 z-MO$f&iI_bQh!Zes)1_e!4)nc`GYRT_Ua)+9_4d}(OP!4Da=1om>3z17^a7J%;bGlxb%9Z%L>8or0i%<3auU@q(v^^%N1Qm`x2t2W+>V^g?VZGBKjV67yep}P+ z@7n9%Qms()G>7~4;#LXah+7Zagay8~jQ9sC`lZVZz+OLAlUMDN`o4XB4W7T+gn}&C zk0kbL32$}i(pr5rzE$Bx#0jaFB2VR$L&wJMlH^(Td3V1jQ2+SQLTNL^)_r?l4b9kO z3}jymUK-oC+6!q4`t8@d%Qm%rEV*XK%=nzmjg6ZZ+Kr}uh*Mju$ELj$70kxMEe)(U ze;{UEO}^Pd)h}QCdSU4uj>d|O^~*mVeyqty$fbAHnz)`UU5!l-@9$)D#ccXTJCT~q3ueJqg#?<>f0GgcL)C!TG#`V)H2lJtH5Y=rgKx-G}{z!cAfg7yVE6f3n<4&Xl+K)Aer573l(bOfl zl)>{1P^SOjj{e(Dk9&xOpWupxoEJ?Ym(;LRx;*}IPcJ_rnBX>vR;3Hn-u`b)oSXg_anQP7G&zf{-*~_ABM6-u|-;DzdJQ)IkDPj~%Aak4(>gu!=Eiq4u zFkOjW)oPB^f`A_n9<$davp$HQ936!!Rt`PT#9X)R30s3(mq|^QwJSUM7Jz!bbHrcK zeMg+Ox;iY5`YJZoW^oY@HU7p&L}}wx7Cdy1hnR#L5=Fv%%oqG%au_pa_%3f3SdJ&R zJK8lrEJVQt@@2)^v`d1dlWG3B2mouer5VfghV7^Nqp5L3D_aQM%$j zcy+$H`l=@W7S4ARBV`pd3dDV$P8H#M1K(V^Lb>kzNP8RyoT=<~#+d9X6CAE4NIgK| z3z%{aCWDlO$0(ri+RWd0G-|2s=jT5UH#~GEFddYeO~OTV3gDzC60WjK5r6SOQxbEQ z5_2-JbdeoouJ^#Ye>y&q6Ga#*P(xg98_O+0Sf}BP46tfNDhC(_O2)>lav5mQ5NKwu zbj{XeHDqw0TrZs+-V-@Fvv^s+G_NxTqV-327l6`-_1X~UljjpSg)NI+t#NGwoiHx- z5?x=0J`Q!jVWYz1=05P>GV6hFd6Y}`$TaFESU!+{J9fZj9JY{)+8?Y3WW0)$`Pq)F z9wcx)U9JsE1CjfV#vW8=y9P*5C?Nbp@%$Q=h1N9~F*&jlW4uWe0rd zH&z%PboFL(Nr9>oud+Y;+c(rdD4~Je&teChqQ*&*qXp+FZf*);0hveIKO(-FG6H*p zt3x4&CO`U*N15M^u_1u6V_F5nk=+&^KTs&`kqh((2$Xm3Gm_H~xx0vUT?XCeswi0u zj=KW&5xlNOQ3JsHi<)ptJr#sCa(xFl8)blmP3?6Q4I}4gC%3-TONUVnlzuREEQg)l zU?|yy4-F~GUAQJb;iWG9EPg5@m{fhD+$rWPV1JDL0X`%2t&$A)-~s#l!%QNMUrQaN zzrwPvX)lq-T`D26C_$T#9{1w$a#}9-gZmqTM&I?6V~o6l-9!g&h{Qn#hR|bpXut`g zkMT7`?}jD8fak<#Ta<9<$&1UXEORtesR8=bgL}5?Kq52tEUzGAk(UgZ>2|nbDsn_PnI>s-@f4#6d zQPDNj*V_OmGp}iF&c18}rWi&@Wbet7C*7ugK@X1}4b)a39_ygGHaHTf4K_nR6&g)Qcn>C_Ry z_{?7_K0ZqcF69vwmj0>aaCB6Ac+J_2yGf*Bb=S40j*2k0?!-D-7kb!~6}D-kCGJbuuy%;NH^PlL0j z9lAl3#y1h6x1ItJ;b6+yT~9xFskjup5(y{WQol-Jv4>>&vgpum#_=_si%jyQNx}Z( z!mXnIt&Pd=O%FHm5JIXIdu26q-c&iimi|e|{7w4XE0^@9|NLtiZo-{l+6B)6MKP&7 zPa5F{?yz7rwM`d#3LCAw-DzIc3|Se+hmvTi3l#S2OTDUQp3-CwClTBuKDDGu{^k#F z?WHDB5~%HEd8@syI=U0ZjUTaOhFZ(iaubu} z5}?l%(lnOE&|&51e#B&EeUH$wTA}n(zP3rLsoe?e+ANgvx;m*Oshnfbv)zUdD2`!K z^Wxm)BM{;K<-OR17e#NfZZ-0oW+V57T+8w<$5Dxh)W=y&vSjrx|B zF?E*hh~3CS(+&DDl0KS`4|^Pe$Qs^;F)=Y_28|QRo&Vti7#SGM3Epy@6o3zv-xL10 zlWAX0rs_qjjIgL*ON&Ada&%=fY5^#AcUt&Z*WK75beFb))G8jm_uaz_=`h5cOV$wA zI(yd~#xh}bBp86uMn`!R>OV2iKTNe9WGrDG)0<* zZ!7a`V=%p#^~Og?SQr!l%=~AJJ3oC2-d?je%yozF<;$fD18|E8ksq9x=#Ka4J&>N6 zS#w@})m%E|odIKUK|$M)EA&+_buJW|PEAh!@Vcy~c719)NPAFoybPULnTOIj@m5tK zH+LAQKy%`3`%<9|1DXn_1y~CR2wYDVxH$sTAnBW6K@V?AdZcZqrA04y9l}a*9j2$9 z#M#`IKx(0OJ|pdLc;i zPmhgV6}HTQ?&e8Pb|64fWF0l1#~pcTA)a!74l4oJ0&r1sX2khd;5M^Fjo>=Oap}L4 zM0*ufN#~;*q-ykuP+wgR~D!u}F=7)ps32JD*MUtY)aPIW)*?Ri~KZ5_^N$+)tXj-52XyIGC8Q_CfxH zIK5|gRo<;7r`H|vxJsi`_Lf^01?&`QfU{}eW48~5o$|y!N5;_h^4@E|zlqP*$!0}= zIRB~yGMZD5Yl4&>k|F*%Rf?c(81Qh1!JmnwaB*+_!N?I-_@L&Fc zK?F0L*`0*2AXd6;&#P6s@eJWLSwc(cIr3cJ(G^8fU1_yPT8&5(T;tw64n8Watc!W@ zuqEGm;4V6vAK$0HvMw#d4dlt00_ozCk9U6Np8VqB=9Y)aEe?wwc|-)YO>5wF6IFI%XqM?Tb~hy_V!?~wHO&o3@(wRTUIS);V0U&_)K+7fgmQF|QS z47f$FQF4k*&)HE&wnniYw1J_UQ=;L3eW^#zc_Y8MRQC%JJKTdQnW>2swUpF^si(^J zCpjxBeh<+bUoCZRqkO>riIPAFh6vu+P|Ypkaobb1g|@(<2OT#Gu8qzn(>gfbsG+N@3~QYSR~`1%%q5>3i!FH?+-l- zz4r5Y^sa%C))!ky>r&WzWSS+e=v@0L&VZwgjbv*OBm?yCE0MV4?zWH9SN*csm;$6+MQ0 zA@bzUU+wbVMR?dbUqCx8AKEQ?YEB(|e|7pRLx=sQ@;UF4e(w3ujcj;{rx< zfdLu()j6vB@5!NYL6sG-w$OcJm^-aS6m7v9`R9vK4!I2$ zNmy;6)_>a4 zZdnUdrrS#@Q4Fu83dT+rTNwlwtulUiQ0-=JVbPDl0(9Z%5m4(NeYiL^mr}wy{e5~{M9eYEB5>HUj1`y-hgD0xbHeqKWBXZkCKVAXm8~BS?!@>&UE5rW%{#xp0 z_YG@vk$jBf9rC3n=%4>cp8Qb#@W-LEZf8zA#Z|rokuLw$EeaBa8U=5#nz|e9jX9>= zkVDMdxlYmjxv{04db3X^Gi|K#>Sngwe?PN-{%0wGE5V(*nv;^6bVJD_SA}0*=+wax zKKFhh36?gg?kM(LLZuEtL7qGO)+_Z`rA+ij3Sgnj1bW-B2zF*XND9 z!OIkD3{pZJEF!r`t08R0|Fv%MYFCews965JjQ_Km^R9hQ-~Qjf1s93q!gY$se?6@K z{NbO*igf?uN8$4|u-R1d{BuCc8Ir5ENUe2Mg)YC`*pAYz-cgvul1{Zf<-(wk+@CX3C2>`pe zmR!b#|MeZ${|rhqaQ`OK8|PlKJd|RjEPueUr0-QZl;}jIWajx1Mv+$112T z3o4x{_+{~^?S$h4bEU|u;#%8|{`Zx-?k{hYLr=aGYh2Yj?GJRAd8(9Fy%*0*jH42v zBybZ6)FkFZBqU(W0iF_9 zm|nU#a?E*oYZuu4!@*TSBhRB-+Rv=;G{Sny>0@d8^PU~gnH0B&TU|HJSRiRA72Kj> zCXh<)xj)tNWQMjU?CD-$$*W|V5k2WDB%tQoq|8IHKwQfO%^ChP7=}lB!hcGh3os&v z6X-%?X7Ve@1TSD#`65S=XB9H&y`bzV%??n>>&PQ#wB#fO)Q)=Tp#SmRUi6|cf};{X z7LXjBbv1-4f|+TWg#;c>tuGam9UI}r2n`0D16=}4?85`!<^KQ~20bf=ztHyuJ?q)~ zU&n~E$Hi{LvyNZmbp!J1`Par)?p>2tI#F_KGKI9jFn7O0uIn_v|Mk-JSJEwZ?d)sm z$2(4abF6&-?(m0?FX+>(Q_{j%bItQ)N+P|d4*jBkAhGC)~P+!h5>9 zGwtpa=`W4{EFK~Wq`!J@+wtS=vke~wrK9x5bT#a_pdn})2TtZgrH&vljBOY|6a(4Q zpZCZSzoh`ot#)q;{MEP*K$hbOb-KQzWI~UaRrmBFth&hQ8V55IftrBPm``E0MLoa)|Ng1icw4v~eF83h z8ah@nI0Itgse1NVo;-1)3cWfyxoNMzvllDfk32%HbL`SLO?V4z*%A>G!wFRv*)VVK z-lO{N_|x|+10Tf2jW|{!ljR9N#=77`fLV-#ore2PZ0tdd*nt6H%$cP~>&D)I+I|v- z{7_Oc{%f(=oCWykIxCVrPw8%ri9?%(XE zrL<~DPY(DKMHe|tbuK%I>9g2LB|-ZaD{);mft)WHyc(ACpUF*JxcHKql7V6ifuwLS z+#~uX)3XuBlChlVn?ol9Y3H6;wVQsiGTTs`TTpK@5dN~hK%P&dHp2W3y5ee1c#qEm z_`-DA4f9OA8qf-Wu=zs(0VlKP4t}-Qw>gQ7q~WW<2C!)NQ-?RNUoRrD1!F~2<`;XS zr*)N%dpn+Zwso)SKY=h^(~AQepGgXsGr{E#@5tW=NRN)Y1kD0>Wt03#)0mce|IP-C^*Nt#|N|-JQx0a z-=2QnxYqSWYkjYP)L2J1Gqe8IN+WOjy}x#^C}$ki=MR?;jHY*2Q`r7!VsnkF{p{WM zL6(`Z@?6)R-Z&9JIhI%M9vQcm_q5f<%`Z|g^wIKf8-rle-qKesoBpV=>IEm%t+myvBjUOy@5X!FSIy=8QE6Z@FxcA-?Bqm(R%&)OC9hi{jDp#3hNrsb~nCV066?syg*7e^pa@wYwdm;F)$X5fA`*+1SKb1(4TAX6x$mOel<9(Fhcm>U+Z5G}^ zNMi5ydcLAUB@!Qeg^!Mcva2U7RU)~D-YeGFB>A+f-=?%-zI&=7 z&aRQSnNy5tChn|?3RT~3oX$3V>5OOrdDymMae@ir5=YQT(kaMW=$Z}Fd+)|xBi20o z7yRx~o^oLKdg?tKYICwJVw#af^3J&nvmC!SXv^53X~FAQ8|odM65eEg$d|k2_V3Zk z0#cz-vaF)I;~B!wu^F3R_uh^!tUVfMFV#8kR94> zd8&Nhy-~`_q)7U>_|cV(HPYdcK}E|){l3{9d(}JpI?MV}56UWr(0pH#3a|BumRwPg zyU7=~qNJ(L$U?y*VIJK)vS{A!J@Acb-LuTSdboG#Ps}ZEMz4fR7gyeFElD+;n>^ij z`SrsO(n=OR_kOjo-!uA9VzaA4+GwpvaWzWubo}F^ZgrXz0#|2Gbw;fWGyDFvs{X5L zamIVd{6SV@l3ws7+YX1!U&8}(3jH!&4jrxIM&pkJ4qdLZ^cvrml4h95xbeMskYyKT zqvPw1PP<0SKhf()iWVY&f^z>G2Q`c}Djq#-KL1ZZG#2o~mp{hG5#dTrz=XN;ZCUgg zj65;8-^{>nT<-6ysi`?7yaBEorud*>2L@_U^-@sLK}<^lyEC z{=&;_p;x8F{Z3x$cf=$a^}DEFX?R>tS52{q^-~tQu214UaYtzz{uAOyJ-MiN?_Nh~ zt$SuCH=)7ZlVQ&P?z7K7@{c5}i>xM?KKR^bFTV2k?{)=s4UI9r^RIlt>I=IC5}%aG zGEl*=Ti{xOrSo0`OfXI214$>g)Zf6BaE!-OT?gk7KHvg#MCLg0|7vUr3qf; z_Z~bD%#i`#l^ovG-vrvws7pzoGr_gAZ)2%Jj>rwRGi>i3Lx9wH?zYj={F4oToax{8 zdE)oO*r9PMB{db9OxyME!<@4KTZkg!>FjJ#v(s~kb|3MspA}L{g^wF81Ai3g4fv8>@=kMvaAdhVmF@p zFGoOGn8fBJL>0pNY$x|Qj51N@<3&P+aL)+(i(eS9@l6~F3dN08hO#+~l+#%XchlvT zUkz|mqaxhq#jnc7-}v5q@fx>kFpuN;UX}jYYFi>G0G1`t1 zt|byohvlu*$pI_)zq38x@Zcd6AG%y`ng>1@=AVC$OXFaJn^fb3hsB@Jf1M9WuZ!=B z%CZs$atgLk5yW`;o=zpv%2C!axMX!})hv`-`r^eag^z3R%`wbR-$s+&_SK+HC~)od zHQOT&GS9A9v#s4uBylkx?{OP@a3=Lp+mAi6&v;x{Z=}vE&-aHj9lotuZqRq7VBTY) z^r!~A_KBug@y7>KnN3%kDSZ4Gu8gn0&(YUHI2$;qHDK?J82H*~)qE){&zcM+NU^ zi|FR1y!iaMWT|&@n;YXI(P^!BQ!K~3%z(9qscr%{0kpF-6{559+#n~?;1^FvwnOO{(Rto z__>T-nOw&bt*&ozIKp-FY-91`p*|k^^w<^G5rc(2%VV#{D@yNJjt~vSxCvG2*A{O4 z{WT>gYchZ0eAlJWtF@Hw;O(dkdRz2+`3&Fmj*E8jFHEFQ+xXf<2Xx%} zKB62SbIH+z?%a!AJE-I=UR3B$`}nF|{Bz$j>lgW6U3B=X^#B7znJ_Vh@*p zv(<5@&_$3z&TX|ULqtr{Y}!QTK_HR?+yzwJP`BW=J(GW~5gjvvVDYy~-6-VYl8uw0 zAGBo@yC5wa0#H5f?}PRVtPI$ly)4s0bPu?R7$6NC1ep;mUNYSWGO`U6#n|#y^Q$0W zkfUl)seeTJvecPJ>^ZUvZKv5Av>u8#T8Ux87|kiS9;?a`TjbT(xId?9$d#mE952BZ zjd4DXq(7jup8uG*i*>2T)k@s~tsWE=C@2uyG5cm}|5XJyoCKgcq4fNMq>+2cY;-hf zUwpxAfGxfDeKGL2A+p>L3N%V=9ADa}PW^G3$0-AwM{u?9?V&T;p?pwIE(qo{U@4MG zmd0HNF4@29?4DZC2CW29Yv5?GiredeI*6_YdLeSxQ9AP`^KN9NoB73yFAX!-Ks9&_ zTa3*pi@=!3`N#A55~!eH8<7b;$hku(&i9)~XNu81;7=jB@DEr6f8TuVnO-ChU!i}( z7?kY7r1A`k`F@x)gRa~HX0S6{vwv-S&saldE8qh}=Cseb*+^B`?K-{&gbBXPPrF3v}3NzRm9%<~oGCMAuiggH>I={wfB zoV7Mq5E7YDRu!^5ITzVBFTpTmeD%u@mdl5K{&{kvvwlU;mxiDe!SNw;?F*xFNSLD! zlamhbitv_@(-Qj~7moA|s~(M%QOMV$3`mnF5G!MCramgnZ=GB52~vKmA!|5e@2asz zw|eyGjccCt9pbocvORtL0 z%+zgjPx_o!@ov%MWp+UD=Wns@@AtH8=T(FRt)|`9bx7i$mauadd6B>Sg;H=*SnYj% z8iOx?)d)PA8N}0He3Cy8bCiwxQ{SyNh_a~iBI-{tYeux2>5R{$*|BhDo+CUi6~5-; z;ccRzzT5oC%&Sv3TfG_dOnQfBn2ydNW^$vTMCaD`Ped*Cry2Evt3Gw-2OrFP&g|Xk zxLwiHZ^u@G+~e$rZ(l8GA8R?BV)o+5=gLl*y6tyAil{8jKh}%8Sy7?Ns;E{ZCOW&w zA>gg2eypAI#lU~K08Gt}X$zgX6#Vq&J!o)D;-le7xT+o`dWDNDbHg$R3lBnp(wUXx z5a5GBu{TT>JYhDi3k`cCV`3oHNQC3gdKMg6LaVHbdYaE)8X;ujt^-jEMlUilGSM}~ z^_n-%aGW!4lJU5ZPv66gIel69y3CLv!;QQeL1y7O``LQU!E3xCi=AWfjdpivaIEFikAp z`XQ3I=c1M{Dd#H5J?nadO&(}O_2&-Zd8F%SpaR60#2IomkcB|boL4>!wI~>KgkW&Y zai-!QNc4_jCWfzZdHj(#n+v3!_zUDn8Vu$j(nXFB$|1DfVC<6hrWmHsbfcp*-QPYDgQLABZ<{xwM5{k@oHHnVTVmr>8Gb-Cpf@lEXOqE?c3g>#>DQ z<*yr`pO^VbXqd|%3BN$Ka3grMGCP98s9eS~xucnIGUlLc(5W&m-I>Yr#vBpH?>PDu zxai;4Z5<=yYsc`H=?RBLG*K4@D{JWlqsn|z_vF5eR0{rMSv z=NmT3G_lKeZUmj!>OIzzLw$-zRd$)g3%)D}$je;vkNz^Wt>|%vHf=k9xaGoSpCb9q z4d3@IM5@!ae?9j34HaK^Lk4dwf9~kHh{B-!3De6W7k=JnRS^8XElKSnnQz1}HKEz~u8>B8q{3*KJELY+Tv6r8@bQc`;MbhRhT zy%*h{wi6MK#~NNxyeasiy@&LOEh(pPtHkd8qs%=XB8@qooyL8Bcc1rjzm(I;?`E5>9VhAHl5f50cT5U%`cd7fS`LrI4~MR%QU_1C2nL>)p7 zSIH(p`DXr2FFa{&=09!q?6)Zu$m6kPm6@OaopaRDwI!&^vq0}1xDIz&lIML*thJvEvu4^(lfYEn=sx(5- z$TCE9%`;Gvb7W7MBdZKtLTo2+U6Af$wuL-UP*JG|CgFdk9peN1bU1z^Bis}|-^kl8 z>Ve>TwT4md1GX)Sf?tjx0E1ae2!`0AU@nUF8q(iUieDl#&T zaWCRD@!@e`0I4j;!Q)R6-+j>nR`~$i6%`al`2e-3C&YL^yMwY=#5nh9(8hXsCL+L4 z>5zLLG`zYN(jc6pnIab}FueNZV@CK65eb4Hj|T79R1hO!de)c}A;2#(DwE9-SG zf^!12gJ4d^j1&X3O$TWAs{v&qf`!Ky{P5gBawjXvZ;pU&1JVGzcQ()pLoTaf0SA;C z+$NY4pm{706%-Y{o3@KA9szZE>?*XKkfja#uH*TG;*Rr4F>+oGM(QXruZ4vz?N>H7 zCLda?*CuefHjHeerbrV}`q!Z|;1RcxVwiXC58tK5Wk1SFlFl}kiY^Rrj7eA-jUx?SkE# zXNadx{k$g^XcsQe*C3nsn(O*muRC3;hTXe28FT5eC7tg5A}TJaMnzDO=#=Pi`J;R( zwXcaXy~t8R%Xs_pj@Ny*H_u6A4nEk{r}#IOrmv-De{ew*(!!fVAJ8$!#&@Gsc z;)Tp1Sy6F8{jCJkO3ur`%`M)95~+f8n| z-=`KUq>hsmyF!kBfDyN;+10`SiDGWp9pJ;47xuugL_(a{J_RqiH?e9Jg zbZjH~JOw+vnR?&3NabmIna#l#3@@7|^chTj{Paqs;TL<&);qRDs)`Rj@)XoDN}B}vb)>{3xIVmind{SQ z=eYlVR$4!0s9i)2Q@?XZ};gUIT>*3Ls@-y zISGU{xk?kB=H~tcc0u_0Zf}gZMYEL{exmS^BVRk;WO~ZBU_<3(XKQ~<7;W4S2UG+r zxMDg7S0|)w4h)uq6M|ky)U@D5N(!%x%q~Ki#VmP)KaoRTlgGfj@(XUIeR!brw#N=V zYeft|KJv*+9PS9*K)Z1g@&UkX{u6gaxVfznNdV5DwG(qn?up zV}1QvJo=6M*n36BW21j>lWbPn_jvaX?f%**rfFltL+H9F8Po8r-I1KqiRK)g9KT(P zUOJ2lyzwqjxIn`Kgq{pMgNum+3&&e@C%LCb@gfKQ)4**IVR1sKDk+Kk))Tl8Ta9jQ z2^uiS&hZc*#>BNxc=)|7$AN|82?}PM3$iU!3oGzT!W6L5UfjkHsg~IHpDJd*Z5T9f zw{O_^HR}^`h3iBf7tMtwxj(dTq>e@XU9emV^cx$buDP<;fP($UdzbG_Q)#-nW*^&> z=4u5`{L%5e_%6qDd*{^M{koroy3{HL-rE_+w+b7FYv_<%w=xlYHci;air(Vmh*+fk z@_K7ohV4nmFzzZ#6BoCwl%iSQN8jFF=?n?sk$?KoiQQqMtt{S7V3R`S-hz&}C95R9 z#C;a(r8Mc!LJOlUosTH9=~3#p)9=?!v0ZS^G;0$M+QocQusv7P=DUmSUN$~EeK972 z2L7^_46{hA0Ev*JlqxCpK<>274nZCDP<{V!fwLnAW32<ITX4G8r6K)vzBVm$aIU82)34s;>uett1u1y+ZZT#ge{`5v7va956f;Hj-tokR8BXqS)<` z_7wGrcTG)ZXaca9aqgQXc4w7G2 zc@s>*1D=hZ`{faTo42t60K&Z|PX+*9){n^Bg1ia#7H%xZrd`_=5?84x2WI z+Y9edikDfqz=R(>1!7oF0wU}m#3UkF=x=k$&M z3YOHTR`Qvv(^(N`-{qX?Oj_{nr%edo$E8PXb9WpwxST>b$Zz2LR^x=)BMnzchFpTN zf=7mmRyk|+!O>2?2OVC!QjEHIxu{=Sq&L!q9%qO#)}PWOwl7!L`jx6C%TpA{p8YH_ zow(x?Xuk1BhgjcdQT@FHarJ({e&9*qh=yD*N>(o?`iEDk z?1_Hh+RooYp|07ODV;@|x!kJCnPu{dhOoXmE{7ytoxpuYneELZG>^1Z1)UDH_873+ zgsPu=p(b6;#3Lu9PRUN-Q*ys-64NlTtt^>W{kH0@AI?d=6B0TqTd4`uv>db+5wuJT z>UUy!DX3jvn;ZFHW&kQJOc^axBl?`lVn#E@TqWJ88#j>Ud~Z9Sx&t^LSb7-#!vUAO z$jT`FYun-SI_;l#KBKw(cHzBI;YktnqYu-MST4pbYQka!CS~yZft(7WpR*>fpFckj6b>9;1xSuO z|BTJ~n9`N7Svtx*gG-m_<12|BCFc7x+b6e%7MMECCwM9$GCr@X{+(xTtne{8nNisjC)E&v@Pn~+Y6B9vp#o;5&&?+wmC z42o+~12Yjg-F*1)e`x#eaIE|O?~^37Br=l7UXhSQMoDJ&CKZyscLrs{fzhf^?nV9k+|sp+$FSj^%|5$ z2X8!l`boA&u1Qdpz3vp9mWz5^qJD{G+-j7ThGuSI`?Ty?r{%*`++NNFu_~ehUtc>r zIPs)8{%1?_!m0b-`tvZ zmt)clUaFFWfAZY%)VMvXLN%WKz>tl_)Tqg}Yv)qN>5pS|u768)9_^BYpKj$x9jc_} zGT=5isgdJdR_nNjG%Ir-I*2=Wegi|*Ri);jx%vtPJn$Zpr8rA!F;Ub_|yQkR|F6+c_WEuu?HPtr)&=c_mf;&Dyb2DjizX zHNrdRp$Yke*N`Fv(fC)-GiI?|X9xChoOF^URJUi~id|CDZY74`#Z2KJ*BU>$J?v>X zAXVW-;q~dhW5_wCvm<#t)wazajwbU(pnVZeJXV%NxcXFkZ$P;TYlYh&&byCmlD@+# zYO=O{?|%15!M91vCXd)3Ik-LlX8JkNzBBRO7rqsk?QNYzNb>_&l~Cf7dK=!9vf>q`cC+qHYHhKSl8Cog^dXAx^jD;>nqANzPi(Tgcdyr6|F*W?rA!jt=1py3EY9r3m#!ML z2pSy-kBvPytxg@jUF!xNiPTJc)}{M=p36W!azqeJ%avQtN=72=M`5J_KSqMa0`zGp z@;R?sZ2W)7f5%~Y#ZxBgIK1SSy8M0>k6438S6*Izq5DE*MpoIWqwytlp41ug??i;Z z1{!My>&!;b6c6oE!ZH%jR>{cVqs|Bo-(j$WGqARaUOt{GK}xaMGl78ZzU`}Uo?hy+ z6$k!r+n%L@Aq0$7%1cX2_Zqgmol{<5%5b%xYUsTqzy2j&)fz!!ARweR9{G<^R#jwQ zdi*nPqd9nHoK{!wup^ob7Ca7J__}bk94^|Ne7YVyNOD%tOUJ8?=jNQ8P17Auf{E-+} zmF~JkY~#>3Xe-#KG_D$CFFmsBM}cF~PKzf@J`J4h{$IE8)Y6gs8vgJzlao9(hb=7} z?~RlX4UKPIdL(#{l}z!pN4UYKYi1?F=M|p@(opic?*?O&yUkF~|3pV`QR{oqS*h{!^xQ<11@>mIm#bgb2o(y z0Xez-BskDfbGRif1KSn9kx#P3-$vHK<3JPeGvUKx_7hcjwCpb<7+?X3+1_$cBpZXT zY^Zov5#2gE1JYHQ3C!xn-2k+jS3}CabS+l(f7X_UrmOiOj;dRtk=H|BbM}~Loa9h# z&gL~+N)4b4z7R|$wk z&S>iI^=T0PXcRQ|AnM9r6E+gPkT1JzR+n1BwjQ^4^Yat#a-&^$`JNqIs~nwbVcct$ zVHhF>`;w?xQ_mRLk@em!dHwWUs!6N@gFZFwW4mtJ-`g9l&noa_-@%faS@&dvIkl=c zv>C(9G~zlh3#1Lorqo+jNA$aUOP~u&RzH}Ozv7RNRt>rHKjwU(um4mR6qKIaAQHzG z-3UV(nKT}Kh77)GxFEr`0m~vmw0?4_%Isiy-_)Oqe&$v9I{ezq9IvB5OBlkzZvX(; zR8jS`-Sc}TC1TgknOw5T>U6(CV@W|KV=+s^9ufIBPM)4yn=Y5@okkj$suK0dT{T}j z?ZIptVlYyn(t1*>ek56CAcsIYL7=cJBWz)0VJun`Gw$oV*bgoD=nuxR2!MYzbr?Z7 zvhGBT9C&*ni1;8r_Q(aibNV^OLBO_f-lYbopQ*@gBQmL9aci+CH+P;0Ctl!gs zrBR77Ed+X|(jxn0ON+qcQ5ks9FG9!k)JW+0Ohkr}uC6h47H9J8_jFF$k1wvBbQi~X0+ztm#%Y87EJF>(A;z) z@O}Q-WK&)ps;{TRB0Ntow%QInekvOG%fP}hiaYwEl8?-*Uu%^4*H6uJ)%6EHd*48! zKBsX0hHlnvzrR1855L=0r{sx$_AROBSHfqh;L?Ov#x6!FzB@)4BjLIk+J5!%WZJh| zrkgc5Nf%a6nIAeUV>lk0^SdeW)uFvxPZ-g?4N2h$;}*Pj@)}Ff4Q+avR9l^?OD>Z> zHvWO@^ZhBba-BI&-9OXPQc_ldQorrFe&*b{;WoJqbR+2QEnt#_Pf~m(zIzg_a~*f;DDS~E$Q|$(Xy)h!4#8{SVw&&6m3*OWJ*OQ5xFYSR>}_M34u~f03sH*+cGFPa zpr)fbz`%VrY;!C{28DXuDMO0j5JM~K?Vs{riEpF}@Iu=Oawtw*DE}`)O@IY!QfQE? zM|jjBG#iwwJru(jB4U?fI@%_?+)s3}phWjsy|BCqPttYxV2J=c10F>T)8Il3Uo=>A zd1B!_c)(ss{(Tt?0~i?K_X{u_gkdS#`P_FjFdJlx=EYzTY;eL62hg>%?R+CB@K~H4 z=xESX!-3@ufsrlw5vCPrGfIU1yxJZ#v8QFJ(%y>d9%Pj1G>|V{Ew!SHkMbpWa z8^L{0l|_V+zh&X{0r{-izSVoZKP)Pad3{Qc)s=86K5?3!YjM3#|G!)S#c+#vUbBW) z+^MII>cu2`#pz_0=+cc zr+s(4{Dy(i7)dRbE5DP51pT%O6axGVZ3~4@9UsOw<-?ErcU~102SIDs$wl5nwX<|7 zBs$>Ot-$Qg-gRvVI5CsJ4f$Rh@@69eV1(lG!e2e|r3U{(mLA;P-T^^lr^!j&Eer|0&*6Q>4K<|A#*c8|xU zrtW267_0u6!3QYM9PZ@h4+HkJnBHj?1kOBY9))BLB<_B+UBl+yvYmxXBmic5tG}lF zLHPoPa&|ca#Tcl<&puzP@dTUF?5XYTzA0D{*|Aw>XJx^@G!WDU)Q2XpO@n|ECVOLt zWAAr1aH*8^O?~)2F(N47ZN<&b6pa3fc90ggYn8Nt!=Xt>3U?&m>q zGy?}9`xS^}etbxO4Q3OFR@5X&hj75&x^cs}F7Wb*=?5U`peGUNA^~53IdwT{G5KN| zVciGT*wo%zLOW@H$qE2YpO@-ENBa9X6YO)*okIJ&JlVbTSOw>Qs=CFQ>L(;Ytw&7V z!nX0vYW!iF2R~DUXe;!dmi2zVhw1~n#2{zU)j5xWjO}gPqxgkN!!2%)F~w;Qv}Tb7 zJGnM+OQ&(X5G;}&vJ7=o^^0lb3_swglf$q-|3_M}T?O}tKagfnB#6m9x= zvD4**gzrfXU$2B{`*(ViKVAF%^ud;X^6VJetZS1uM00s}g#XYzCz$VAVP>I{Q4_L# z`v;E*G097pJ0(;%yt&ik1*EQB>d>q|^E&b;tEi}^h<5nSZppqATRl|0PWl-|pVU>T z>^#5ZQ%g?bqYntaFTig$Q+#Ro??n91-vw{q{~|LKaP+#aOwZqVD3DVBk!|MO1lz*7 zzj;RvpOBB0A=eiS6f|1s<~P{BW87Gg`Bd@Wsd^zE`7%FCsb;#elx63RV{)<$w2XJ- zO|2wN%|h7efUh3s(Yw!-=NMof&l-1|)%F8lQ=Vf6pDXL+MTe=9L9ju2M1e3{RJXDD2Kl)#75QsX%svQWtzZqyF*F|#FJTnZQ8QD0rg&6q$Zpg8c6fO)S%U_c?`Lrzcmsdk40|O%$=4 z;HQPd-X>e$0B8k<_QH+w2a^!(d`Ub7<6{`C$<4mlbTH}WIVPC<-@cu$Cjb=@8^X)% zX%b^&=V8$#(Ut|?8VI}4vj=egz%)Hc*c1%tt{VMMXvLVWsi>;P*4>JVN<-zxPUR-> zvcv(ll%Vp#Fs4209lZFl6LS&TYG%bdiCWso$cW&pXkV}^la`R^bdV$<&|JM|U#>=s zuV6n6!|yWode%S;{E$F~Dl-93sK2H%RW;iW_|TulADmR=jjnDYztGGVn^l&q{3L@X z5eD!C5s&4_S@hR_pyJ^l9=_Dh72SA(3Lgdb(DVB&#(>H(avYwS8RQy<)eF$ty|^gL z(!2&OFb*gDh5%=fsc zs3=tRDk4YI#xZPzSPCYT7!RJ}$2bctNDM9ko2jX*I|P(sOqPHuJR!ll%tT4sis=+G zBY1h0m6iV(TwvmXnK}mUI6!7Ei^7*eOss^M`|r%_ocR3tGdy&W+P$)&(sq2T za*N;K4{BHGOK>FOVQCAB^}C^yE48y(;Q369+)$MK>~VZVZtH}>6Wy$Uhu<8<%c>&teO>XK;K*Q-MKuJG+r5qH(;*3vk2H^?gZRA$3D zjU%sZN-qN}Dz2GV1$lpD%&Ghs+^VrMv1{rEW1gT6`wLeL5a(Yk(){zX7dH|Hy=_AW zgaiDuYDyZkr)b^n&Ga(ZZb#kf;JyCrQUI%B;bE3Yc;xK`nGObt8{snxY|-NYGKm8o ze6Rl16rtB)TWm|EuhGyImc&<^SyaCuZzg*k!WBZn-hNoAOG5EUU5Z5Yn@`p|VyGTd z@R403)28@%LFa>>oV86z(5XK$qH&2ATy37F?qXtMqNRNd0VCYoqZ{K=QV>}L+u~(Z z^_T>cn7}BG({|Y4Nor2Gf8SX$6(|+Vx=c+>Kz@n-@@ndz%OmW=)Xehs$9z}CO2^a^v z=-|$vFCK0&9IO9Wm0f2K3FFLP>Sg2VWm`!kD$sody&W%}yuImV4y~@bSsR~Yx$VXq zufHq|iDw765F=bQSp+LHf^Y^RGV}*BZ3s^QYXA$t4dzMk@Vk_~wNC?35I#-B_&w>H z!gdUV*M+W*j??h3UH?+iel7o@G@^-$+}bgE_&v9X#wWK>y2vxXsH1nhqOSiai~yyL z#Yb}7TwIt8j-+2_v-{Gu`iyYNKuryMlby_BS;CHK)Eok78dTjor`T-TMgNF&+{-KJ zs$kN}xnf9q^gH{^v1}3r)r$2&#b`sfKF)1f%Eui2YJ$3dKd8_u46(0$Csh@NO$)gl zVKRn}0UT*EQmq96Pea;R0hw2M&82dK{LaZG9BvfBgo7f&=GqGfj{oyEs0RN2b=T@7xD=%}f-CLj7Y?MZ}Z&N&2w$?{3{7Ezcg#A>^mm^t{K|Z(3hmG>2h?} z&c&-LJ-Dy=jxH$jK>yh|+VeAxpe1Cmld?~;oqj7Ce@-En{nWnwyRx>hh z$|$j(EbO1-j3ex?kqZ^azwa8`tg3LJDYxXmubB9QG0_+Q``3!)65AgC@85oK+`dWZ zpCg6%Ll4u}q^Bud_e!#FOB@;yq~1FaWv#~q2`%$_bzOVt!9pO zZM8XbRgFTGnVt1Sd>Ex*kFA`NU&UOjk8XV3srpi(6My@ZBmetw*EgcHR5$t>gFnn@ z#@p7uHa+L6Z#p0$c_#98-w+)un@S89B}`ZpgLM+gvO|^(-8rO{9&0k^7*hP85479d zVDvwqUeL!#B!oqOw)DK2(edgUzUUfq|W5lI7RWD)252jFe(Z1$qOPV+6$||mw zjwSBSPP|!mP$BGerhxn-y#=90@=am14@emlPTC$-*(TN16)$u3W3$*jlUy#igYJc_gC6l=t*)fg;`DiB_W%GhB7wE$>Bay!O~qT3sLF zHd(GLsC!_E^OItNS*lwie7p&f5a1k?LgB2UDzStSH8Ra$U*3NH3F!qIv!hP^2!~lk zA6U-qAq5FEFqt|qJMP|P^?ipDN-S{gH}HLszPjYpfZ7TVpwUG?8Tfm6i|;TB_w~;g zqJbEx6dL?7_=DS#4S$0Y1vpaKn7Y!9L`f+Ty*~V3ZEp?hxg|S;mx5ePRTP#KO{Wi# zvgNs6QOWb2)2F$r!dba_PPO*BPX22_Zpuq1A_aaW+87Z5Dossx)>mPX3;phZXSX@T zC*1=kFd0XkDF^WYRR5$GP?g}F?WU!5Y2Hzu3@i8hxG!N725CxRPEHKtag_HU;7@cH zpM_<`h(i%l%+R7BLcjz<${D9;_t>c*zz%>DfDI_U*=mfw!{G9u-#!M50f2`6i~(jz zYguUX1^D>BqbE;VFjl>Ua@f*x2%iS>sogTKdz*tpin0eqI59PbkN2lUEdqZ+|AVO- zW`g@cJP{G8`7>RO=*B~qFM>fawpY3zgrTz%h(_?>!`q2ZG7?JUy&vF)Z-q*|YN7Sm zM)mdU|C|U_`}&Q7>%}Z}4qYx2YNNfalk~gUZAB@9bI6lg`DVf3XsTS3+nI{PdB*RT z26;~}E;1~noES?zXj(>+*>q(a1wT`w>Q#xTIEG)2wer_CiBpwUhV9qX zzkE68tJXdLz^vzy-F;5w1ld_w*b&;D=iWbnG64I8edL*BG1NQpL<{nqsv=F zat`W_v#4)CkRalJA>t&=N>)7Yox1~<;EgenAVPEn|67j%GxeC9`X4R%B~$zb^xgcp zy|=1uHBOvDphvbxe}yXy_YAkH@OxGd;7I^=AoS;3z_|C6%=;#QX z5_$npAhUIR^LLaHpk)o5f3fJIqoM#jRgYEWK}`UVrr5mt830TWh+V;m446cWDe>qj zLx;bePg-4FMYsaIn`>RCV%Nr@Fx%Vvuy2a+=Z8%NBjy34p(tUU*ZFHNh0*#pgLxvX zoEjHLVAF&L*0pN{(gHekSPZDyn71K}?Jzb6K09NS@6JVuQ(hQOZJ+%dIj2tQ#H; zu-r#n7^1$M%f#qDjLg+wz$2MD1?RKle(raUK&( zy{u%PN-Un=o06Y|vIQ8uAUzF;Mu@;0PM7Q{~NBYBiXQ_W0RO9>-UcOOkCP@bCdIzX0FJHeN8hDKf-p1pTNPl1zeTD*mld%5SG`d)$BE>CSH1qwKw$YwbI4>IhJ^Khy2ZKciP& zL$`BwVWp~1_=Q_leR0si+s~RqHIhvE==apz z#Hh>VoVtm+%Wu2Whm08-ABT~&qR#nDuQD)0Nd;&TBo_@ewT0S4#JC04?j%uwMQ0n6 zp|pUO2eXpDQ_eyDOPz+MD*NH}19(%8=sTm2uiAQeJgq?-wm3!xyu7@K(ZQD&QW&vC zUxYT(ulBg0VDk}C7_-`f>5SdQCn$*XKS$&Rc`(F2V7UAy4Cg_}hRC2ya*`oi_2wU+ zCzbtQ^&i)0W^2vft>-*i6JnUvF{!Vjm}Ak&{c5OqZ1N}DBh#G66%Gd<-+XcSWd84< zp99_j4En!Fv+ftab=D*$S#VHOhiTDCQ}J0}$_S>}6H%07f5JTBJ`G=28HW&`l6gX2 zFnP#kEollWZran|vts_XH~afqe|g{k=g);%=2%+tn_H*TW-iM8x$Wz1GyOjG)m+0(Jw!N8{CpJ#Nc#mOt$MmS~mNIsY;J+wm!K#rH`y-`Og) zN<}jDVsqhANTGkqX5r+83BLHShA|M zzV&<6WOYrusy0n7dwab5nKZ&(Z@oh)J@EOe=`qT(?Mob_tDRrdK8;y3XueW79sDFn z(bB_mcjT4WHvM~oIfd@0!-DG#yZM`n4GXTC)i?`{1#~86cKuY1zQ!3yA-P{f_P!zK zA)l!)VKz>$Fz$|xi-V)gj7A-KFg{rj(P0!sxgLw*hnk-Wt{)Z>{G@6>3NmnHL3aXP zB97pepGBX30MJB`PVL+dhDKK)h*%JIA=i?kqt9UChq)ZiAwqg=G5uKbk&zRlh+gGA zHY(9crK=VF5jmpK>I_X%$#z%fSP$7sWhQ$2`&^-YC>pKEpGl`|rP?pU5x?6y8inhNJg+4l?vl81$gA=yy;RiA@bKB?#nLA(KvG3dIc6lLsHZDkx{p9 z=`~)wco91Eih=@ldZjY=AP0w^h2>xJ3}VO#gM1>C1DqHD8z3wl5Es`g43x!Pgw2BU zm6%%2=$dG26Cx$3)}tF=yWXuo4{R8BTlBIjMneET!5u&1*b6Gcr6)#2(6$2Y7sN%B z_Q=?NcmP5{cimka(XbYM^TR_&6&hGfXR9DPd{9*Q;DIFp8jp=-@R0|DL$z}!!T4UL zGjRg04T2{g9>GQr{hlBoD(cev^b&R*^yZBqrX9iHYDO7766VB*4}ThSb7dVLAMeKS z1#4wB;TjQX01YL+4|2r_?=v`U;N(}JMNrpF&4MN=#@ZJ!E?_~nXyxK=ERJe?mS5Ob z5CTXYrNaib5tTtI$4a;B+5ZXUm{QK?e5T&X4&*x>mDUycX-9DWfddM zFKn7nE+)Mg%wg`7?RfX#V*F8#_q-nh-W{8W(q$O@DJbCjPVz=m{4SF0nKhj4r&gZ3 zDBo4_pF2N!(l;p3+%H+sJZoqCv5gh?uFa`>yxNoxMjY$z=K5XGbGWLzBAGp_p77nE zW29rcCC}H!WwKSf?s`POn7{w)72SHzPPU2kZKOq+xwf3|d&Z7A>fN;%Vha6gGM9R{ z%1E|t*AUfFQ*O3mT3zm6CZ)@3XbPESd|X1)RFSKO+=Yqr7ncBSBRZIXR9E!Bu=#yY zhptV$r@zsr^j%5++O$j?FAtAEMEiqBXD}XuL=gI+kDGXFPl6 z1ykxe_|@S0uGx#Zu?GUD&fF1wx%aLK)Rm~dk=S=HIr+!FF7)W=9@8`J+08gNSe_m|2222Z4%9yXp%-MHH{V&IiykCKDR7GLy=aP@N>u8I ztEfflDq>(D7AGU!tUs>f$NFC`Ks#O_X3K;l5Ap)R1v`#&ofL*dt%e3XjtlVWn}~i4 zhCW`bZyLas4U{8MY_b!zU(iL>!5jF?^(H_oc$Rpch~zu9{!Ukwpp}432H{rXp>Hly zI6|k8GJrmcaN9AfoJY5f!;z_N0lvd%^jXBL!k51h6$i{k&}aTYy@OQK94Ab!-Jt{{ z`a1NP{%!Dk$LEg;7vWtr^Oz9-K);6Bb}|T=&2{wu^iCf%zV`Z)j__sCF-YltU9)jk z&@8;pdD7;?p$VFvE&85m%aY#$QZ2N<#SVxS#u*o?D_yPR@+*0@{`c9MaHCq#eLgbI zGvpF?JGtvhrq_<=FWfzGa7oLOoz&qMUpK@5Ucg(@`#7bXc~0lT8ztj%stY-1caL2u z+_Zh;h>VS;qGir~i=m*pqjHauHrB8Id~fokHM7FnE-v@Pxr|qN15MEv^9POG8#v6j zt<$obr2VF`icz^!wfg>w&hpXgW<1(Ewa8Tokbe)In z68Bm&n_63s2d?>;nVGFKF5UFLGg5PoMp^dLsWgP7e zsW0Esy~@ANYn7i=y5VQ_Ehb*)dX3y8KV!G)SB1&VzrIbk-jDdS1F{kf@ zV}_2Pn5xeem(86;9a|F&Yhv38ZX~$zZqg!uYjVnwNj-4(@=}69K2VH{ zoi7^4T9K^PjU5S97GYqBO=9;@|MJAb1IghCupq!##cjCccNzTOPd}_~<-WX$Mwswn zfwOnObmcfcLrjAbl9P`@MObDh2AZO5?}cbt1E_*=!Q=CTV`al<^lKCMyw-W$foE*l z!OA4=+At=N39u?I`-2~^PpYX!(GT1gPS|6mIC)9cTVPmgMqXQAG@rp2F) z!BVn>eHUJGB3v)jt7SM>{EU>`$#)t^<-=J#Z#Kyl{L;|=` zxsy5alB8otDuA{CCPO~?1=!S}7Jz8SZ}he@j!q(Z4Rp%$=;FWva`5w8#p#GtK~G>v zM55Lg`qN8kM4~;;R0ymEU{QMu>84c!)5Jf=KUz|jG+tDt(%$bAHA1iSqa!yigAZt5 z0Iatlem&Fhnc$Aj&bl<*@$>vLNNtxrOx!2_GqR;UHCQh=GK(x~tBQ&ag5?6%*>ZzC z*$`&V>o5$HWorYJde*?e3&Tq$jgAF+OOKF^djqdST6#xTo}KX? z>*bw`6|;&P&O7`lW9$yU&~ccdHG+pYaBe@~~iyor0Q z6m>|_JG^)gscRpqa1kDpya2#Ynhs{_w#ShOZ9_PsP23| z@-Ox4`cPYyF3T``HtNofETyK$WA}IzRdJykN&vQ7@ zaIC3Nhv!#yZ=Z{CSPZTj*uWs#%y~6cs2RL_^k4AyjO2?z)kKil|4Oko=1x8BD8Ib4 zfvcv%x_OUrBFdqmDLqddCWJ5(#soypI($;Vj`}7W-f6c_E^uwDNX>`XXR1WhJoXs; zzThl?H=;-K!FvQO>C)#|4gioQfXuQa$)Pq6L;xG)9H?p5VWFDS zM$Pf2+W&C=Zz5w3giZ8}_=+#KGx&yR*$#tRPs|IzzlQ<|_bP@afnRU%TKJAJc&bBa z0&I;46Mdh&01K(VlV>-ozP2pp!Qn3_hZNi}H)g{Z;RdoQl(y@@ceOXg6E%;hbyGZi zZ)E4*?4Fp9#~asa^p{CPF^0o)0ri|3+`rLta(L5=o1)0b0c>v^!jB%>;n}¨=hHL0=e|yo<}MsdFtXX*Y1QNwgkT`e!$<^PBlo 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

?sm4b~A{955<@VSsFO*eY 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͒<%r)*r;{?saw+k+E#5*jn(@^SZv)ZIU| zop;DN`F35e;Ow}sOnTQ4mB+jYd-QxXfBC(Od`03smGvgY_8#)aa}}GipC*Tn)80QtI;pk;(_jRe?st>+SD~+7qYovxgelY-GDtTgQ+}V z;Q9fkl9v;ah50JCUTZPMyIBPb3FdxsHW9o=8a)5pMz(OT%zVRjzEP{*cD=VFJ*AdD z6?T8W4R4-u*+`{G@V160O+*R4&S=gs+%WGPIT$HQJpFmk_Ynzi2DuSqK;I8o>+Sx|IGtz!DMwidZp_TjyO_yeCmOA67k z9hcl#;2uH{RGQtTM=KUqB+}mDZYlfD#HXfOSQ2xEk5Xm1V^GfcL-m8u#?M^&A8yCX zd6}1c-fb6^egE)-V#a3Hjp2o11Xat1a9vu0x{2P3#LrAL?rdBn$bvWVmiWoBUX z`h7+HaXG`OO3!!(YW}^48>SaG=rB2bN_bS(^61&p7Y8F1YOiW@BN;J;S~s&JIahDU zmCh7@GJ3gny+6(7i{~q_i73eh=sp4DJXkMwItRm>QB-7bF8^{eXh16={HFff)-mmq z&hkTxfozOz)T0Uy=>ziA(Nd0#HwRknNV#cNwGRKhCM*vqJ5a|myy@7q<`>TcG` zdw#r|I4C1TgKJ;)xob*cV6!kYqd^3H;Oyloe>HJ&cXaXqv&tMb`6%CT&jYFbq04bJ zRKvq*fFQv`z^AetF+rbRa50oe zF!Bqt>amiKH~nRA6WUa?#Aae!k=QJH2UGfQv^QLjKNciJ34Vek7|wZ6a{OJbZon_a z^X{{~1I^?9TN;{h-2~xX9W(_(6~H(s?I^HMm8bMmBQNA`p^5++g=gLq{Sxd%yy)u$ zzT{!~9NV4QQ=TU*M_%`OH&*Q=Xf(*oVxn(`4FBl!RK;sdb7ZwmAH9+GK%-5AxDZBr zmNjO6X6X6r+28d&Osmmy?n7TXr5n&(hXD138N;GM8@z}h`ag~fY4RRezN6cux9jw$ zLhe_l#X{9jR3_KNimtd^(50fK+VzR#==6bzBWObTsq_e4BKE4p&P#S?8{H;FXD1d0 zUi7@Mm0b7n(ca70oGtAnAZGlo>G%46`Uq!yAs`oGVs0JDqLuR$-#V2G-#`k@KKF0? ze|1K>M+A*rs{wM+b=LA7IMc7C^*0_%~|JobXToa_kp9h5CfB=R_oscEK7OW+G* zGY&mtmNtvvhrw!Rq}+7U4U9|%8a`+;{b!|9KGt#5;nZjN8#Q^RfF$pJH(Aq0&x-b~ zv6WMobe`reY8UEnBvL5*cVWf*XKAv};Fpj^$0 zY4d*y+yDKTFL_rsSpCnXQa)w)(3bz#uh|L;`2X=K((;sh{=XlA?ayt0XvF{dBXt7> zR~BqA1+lYZ6!<&mefj_Vmuvk3;R9|JXxNtYzj0Ap$r(aDohWqcbv4%X|9)&q2w(Oz zXx2Ukc)PQYdH;H8jCelhU=f?A@nm`$zIn?DEQ{LL~ zm^bbMHgtq;5rh!kF6TZI0Tb~SQzkuH+yA+BfnNXhS*z<|I_({cO0s4-Rim$OnCfp) zv~e#?Z~}WMlwhX0&F`IP8pFG*SS4BWtNy+eErFc7tOA1HZ=Cq=M-b@5PoB}9rfM!( zcO(%PG&&E>jg2TB%&4_=YnPBsK4M+Wds{b*pO%)OqM{+lk;GB|Y@JA*&}n}rs`us- z!fw{G`yy_d{>d$i`|oGJ9-{h=_Q4mK{?-=e;!&H^Y8!U_*Bauck)2!IydA1?CT5?vvKC6n{IGs3>i> z%v`=rPrT~V8LW!An)EllZ4>(`$!Qk-4zX_DxSFc(GlS%{_k2wYpLk3c@5`C9U-?AP z;CFtOa9^0?{2)~|7Bgi+u3U)=cfJ;C6x)~Z%;C(|#ObWGb-n6@-%|&JBVN)It~@h~ z*!ZV!{#!|!<=sXi=5H!({qo_|dOmgWaz4+-g)QILUx({5FaF@#SVLbN;k2JNhb*<| zWd@u~S@#7;R>et+Ge?6P%=*eA+*#7D9CQQDg`AIzK|x^I#n|}mhZ*Sk^DE${0B7L2 zaStA9Fi_FgzxKuhQdtbcDNb1!QIGA04=Y$PpsjIYhO{B+gyN{~lE3-A7P7s5X=z`V zqP}*$k<9LND?vdmk_Ih5p+Ycj>tf8WI;*|OC+gWmL4~UUoLU8DggByZ&Fz1H>wu^L z|Bp%e@|QPDiuZt8Ay0TOp#13@F7mWFfUcHQ#xOJrIDd?jVyp_A6jt-K=SHM(EkUny z=ZfF7tUKdH52&N~<%PgYwzCtCIha!Xr$u3o(kixqYN8VDyU_2)V};-;r(&A+#F zTMU+}mbQeb>`5*Qay^zmeX}`N%>CnXSkcd-4#~_YZC)~D$f5M~HLa}MXubW#TWqC* zf58!4WU&9%?o(@8TGI!=d{}wE)ZMlIvLp9q^&jz87osu5p0!7HZRaWTgIjwEsUxbf z@2#zVxRYN<)9rfQDY4EqvHt$>pP%Bd`Zh~VJ?EH_w{VzYBN2P)wrjDqd9v4!Hm{}( zJ(O08>-o@?^>}CRMhTj%4cvdWBu@XvIzSjo0qA8z+t0wX4TBJ{J%Q>e>@kcv=mX0i z%%uSi84mZslM3h*rW~~xq*RW84LO04o3k^h=okdOk57(~<4dyi&o{PoIjW~yc{Zt2 zh&5a*FXAMlgtK+a62U*dnd_QR3+wFsgR|!PVurp(yX;RCcRhN%vS=2lsMe!HT{IW~ z5RyW^$4p+4!V35fL=r-w#YYTBOC?rOUP;*3{D9fQqmaYl#xOUAW8ddbpHPrk%s+xJ zFQ#_*k1v9|fEsg|V`^oJiHWHc>u?_g2A&K@F?yK=@(Mj3a*_%Q3P=i-fC+ng9$~%$ zKG%7lz`_QxS8^2dA&hP?Fj>`}p31>63zoJ*Ks+&~8kPa-g?mB?(E*ngg=52q{a-Oo zL}cu<-lBXmErxE$kfAUs0JAA8+YW!7p61}> z4!P|X&=L&$#iNt*(Y>oI#NYtqVRECO#!}=s6&)2_Xw)Yb{qFRTk5u6fr(VA@*(S|; zm*4juKk+Su^L;3ZnxI=(YkPXhd&5$5%I2#{^4`>fgzMJ5v>F6IwMF5i2V>Sm67 zS=sV(PHGyWZRb`hX6lv=8@hK}7grs2VKeXbyeb#at9Uy8)}(&@(;Fv`KH=Z3*Zgi8 zTMWf?4Y0b~MItsT>dh0cW5~K_Xi)dC%7vyJBt&=v(&;6_4ia`yI>7AE$|88nHg+=H zG3B%}j3wTTjj0O>9f$06d3Wc|?<#q{Xu`zEh)Rnr&N%O<}{ThArrB6?{-}T;GNJ<*;;l8?20QTGj5q2Sa zLoG7IG0;=PW25YoDm~zk?B2b5pU||Bo|2;C7H&CL3_~klHZDEIHiaNQuyuf)!k^6_ zuq}F?f9L#zxlo{gz>pdc)t^8k2x%&yMN3X*%`&RO?ECg@emJn2ntEfj0Q4Sql($o3EM!NnE z$+pQuZRh{$gd6=kN#a%%+QlClGM83lIL)`m!pMlpnfd$=lXB0`AMZ2sNW32`U%ctR ziH`1^%Zjga;jOE=>1|7V&P&YRrjd@Pr-xp?zI33VqH0l5p*3qYgU__WNa)#y4|kaB z-oET^HtBdA|Rb! zWo3`@PEdswKi%n9#1zWK5GE&BNS#3#?q zDp#9Za~IK)nKoM5U3irfbwHVKD*EL@KV9)o0sipYmwuLFGCmf(S`7PFghmbi)9S~G zo)2=Ibb>DY1KQ+$NzSko!yShB1fyF}mZ%8K=uIZ5vn#5$Xo-lvq;o|Hg&Xsz4Ve*SmNFm?D@RJPl$XT z__CDnVmQgI-bz4@cqtYj=KPM;-Y2lo@n3$&FkyEEN9T%S*nw2eEAp>yF&?${AFz_# zKHL|4FKRk{ICtJ8!s3b_2TN~y$_kZ4st$X`G3y<)?#~bCl8gm~=XD-fS&w=+k*G=3 zDdet_r>z}S;rvu=rcBy6fUkzWX!5qZI9tN|NLGeKofxWL9jVjkaY8Y6GeM#eNHP{= zrY2ldt6uT;EaAl$N;Wm={%*z_*JTmQX0%yAk6id`e4M&2s!(RHln@P}5&FZ$!t*W_ zKE6Ytq_tylNN0B#$y7)n&Gg%gGR`i>o}EPKWpdZ|c8?QGar{)foK3~FC~Cpb%i~Cgmf*EL zbP*Fwp~hNFkKcBj>FN3wk;0xml|b9^abm6Pt=^89r<}c`l0p?h-_v;+y5;YDh-!JP z5VIYEG+OHSQ-+zl$WEu1kCvCnHB%8|sXg@p*WUZU8W=JRs>ypnS4 zp@K{~1;rPwccd5d{`{qOuwCeG@t7=cF4vH&lK>43ArW_<(KqEfb2P1WUEMAzsWaH> z!H|DcS<;JFuMz`FP!1vDr9{u8DuLnPKe3BeaPf3;aq;#2fn(=OL-Zk`M7+b$6@hwF z4^r_8iy*~03)3?oaBxL76HXe;kcf*PZDMup{@HPz_wb7of;*U9wjQzGFW$K`cDGe5 ze~yijyjNzBa_LQ=_PtszgH_d zcnA9+thV6TB9o2@jd`r;?>&TMruMK@g09=VC#1^GrNiFtAy0}=b-92wE1Pbx>GGv% z|IM4PUfH(=33r1ZA%l|RK9#{vZ(F=yfjj0CfK?)tVqa;Q!4Gpe7&Fv?3yUEnLLlc4 zg_Dr%LhioIeX(l?@H07j`3*Z%BX9|&zrHIm%NZh!6e%caoe^0A;erXeQfUn1J608# zO$*+ng&VbdW%jzVI!`o3xuq6aFgDZ9g$xcRc%bbv?5~V-Sq-A53|R0ZHkT%!ptmYK zqMs;)`Oju1=%^NI$ZeI6xU!7e zyfU=};dS2RFA$E~WU)R-Z;Gw zwrm;|nlmj^x2zTPXl2THXTd{$j&ZtK^KEp4WGgrLE}{4c?$&%oncqS)h7%UG-#dWi zph3Tz9&;|GjoG+kS$tJF#h50i7sh@V{vg;)FN z#k&cDrQ80nw4<-ev2UZwYB}GfY$t0M^fSHoEDv`#HR(r7X8n~(>YT0`yy>j|Z`MJ- z{-3w^?~u6ThBpshoQkmws=ZJ65@t zYJ0*tV*52Q$%LwF_xKD>H>j3cyEe6ZCMx{P$huKn@i;EcU#!z#7%Dscj(mOO)S}Yss zRWAxw{>tL(Q~Ij=gTauKJ+*u#ZMD>zTXwC5L>H?g>R&U$;k`R3?fv<{u%)3VpK`t* zTiJE>Uv`p7!AZV~y3vl}wb|?dvj+k8U5{R{?duaih~WYk_t@_u<$W@8O%B2twMlpE z{l(m8Mr*qduw+CCU;n3YYOXp{S@EqK+twX*s}BrLs)gq@z` zzTU%JEeU2W85{}s?EGx2{|xosa27a~6*bG2diS4TW$Q!b(x2jGhhl1_KW^PtJ69=L zzwotX`Q+EJ?&4hcF4FHM*FNY57t;N`?1Wbmco5)Yusj5A=_nm^`&2htm%BcIWAh)s!lZFpMs;L3uP=kBiv-JUHAHZP!#9l-^^VL z+rFp0*?7#{d2xA1%&NjU*D2wPJ748ozB*8UxtF&?bnlLOhdoP2-Qw({9NtYhuKoFX z-z?qa6r0i#%kj4M8JUgU#B0y=;W&Gm?QL3EpS$@J&YQYHO?h%ML_n@u) zbyUmBRKvBY@zKJWKk}==QKdF#mR(l7z2D@w$^HpC*LKc5Xlx|vkkN4meW}-~pFOJC zKK-meZ0#q?C!oQ!XRI++{l=&Ay(@iLi2-6c1qC9)!kdWyShFo;2q(>=%}?@gB4!&tca8sf%m;=EE2`e4rIxN7w`)o0`Nt z>xV~9N)7hr1q^i@*g@i!b#y!0CFg9_ayD{2K1KVWc)sl3O)T!^XXTi)7Y%~5Zdj0ni627R2?C=luzQh;;aMcb!I6fU z65I${>)XMfMMMJHwbMvoLK6=9cX&Tc=I)i0kcjx?xUC#wR9ZK)nR1=2j9`5At<9 z0OC~c;37gN_vg6VHw152sW}n?o+O1JKXmSuXr8ajHf0p z_i!znw{2>An(`t6lX*NQ6Aa7C5Y(O#LUd6m{L+Gef0Ywb;E50>CbJ!s;8p^$#{<_Gw3f0fL#D}LBwEO zY$0v;E)fv0*f8ZQ52_!u!aYGPs?=X<=sh=V^71pdw;+DHlP$;Q&5kmKM51`Oft zK4T!SYtoTAv?p9#z@2-HR1jo@70hiE#e5r~n^sk4wuAd~*vUz|2~Qu|HLHSkI0^!h z&%1kfxM~2Hx*8I2Ds12h%*DngkK7=qX*?uM9V{aIkfi4Zc(Z8*C6)u=U)sWVGrl!5 zGp?(1XTbi1y-~sa`;X^$KF!R*drOH9iDs9CJOL8`ZqpvlD{CPuyLFBZUOVBRae#!W z_OD)+tQQ?w*CcHMD|DksVT3*oM(bI5L6g{B3OZeq; zj)f@6B@&Ap{igdZ%-1xC%t+BczsSMIn5amLyZAM!HpXs_uF?7gs5IQNj>0v zgoY;h@LtxWZ6V4pUf)nxOW?9~^<`pmi#$TJ=jM&FQyG_MQ*1X~i2FPtc|B2p|3`dc zU2<}`P;5)eJyvc`1|Gp(StPdy4}B^=6dES~)Hjf!=B+Op7x*$fV3J2t?r0Sgom~*N z{Tr~X$w!0=7N3}W^k%ng5r}@W8uZLo-CYsaJMM73mB0A(x$hGDLE<~D&?gH?^(6Zv z+R=0~jG@;AkJEhUGWa;M*s{6i@Ri@^JU-K4_$RXKxZkdgRP7Rdj|}g}e-p7XXm5F? zXT(Ih;rg{@equ0fDT|H}_#sS1)9B`Mc$Z~n`>Qhn6?*FmUso=3w$O+S*_?N1Ut7LJ z^}RhUJeJi!Ozxq?3;UzaI-3FqWhXB2-!2QEDZKlG_wkk!g8Yo}32s-slM^guD`q2} zvur$glKDJ4iwyDdo}~*bkNS?ja-Wff*dzM7(mHf{8q7@t%>AQ>wjHcGXD-&V)jQ#i z-N$i}^x=HHiUa&K&WWyhf`fuhhWc+0vgwKD-bpkyE_vVb?}Ge787QSRM}#~iuDEQd z`xE!{J^fOX6IJCivsRnVY<2I$cP$m;Z|6l8OuUM)yJ%BbZF?+_F-%SDvjW}eV;VxN z9z}O~MGI2nk5|~3Ca&e(rMvW}ja`8F`qARF@~xyJXHr;9RUlWySNHeQxUo_7i(AtpFqp5%P0!EGz9-$^ zJEk+e@YMdZfFM5+13=#Re#O@shafrYvbzn3$A-={jgzS9Hom(rvWu?bV8H6{OtW;Q zD>@EBzRb^pVt$t~_$%#PI~W<4`<2SEmF;=CeoWbDM{X9GqafgfIf{tw|)10rKG%@BQH;oBdhZJn-4h%2-b**($UF^ zKM3qO`Kb2JDXk`gUQG8!eE!W4iOD;`YU5RhUUG0|iE`>PnP#ueTIRIvii3q?2|E9lTvl%gh*}TqP!ExvwIt^IaWJZ9xl(%%v@|V@t_pqCmTZG4#(> z4s6D^u>ZhzTP&p`SXnO-F~$_ZEY3CXc_IJ3uUa?I>|A>M#EAr<*@+3$`JX2fbO)f) z!3l=$d3ByflVas&cFzD&gZh}`xNi@4E-%sBSmV6C`#VkZft{CKvqOF{P?J^SE^P8* zOp3oF`8uOT$i&H+!MFSp6^#~2fYzTN=%{owQ?K$0`npI}y`SM1-tceq%`=J4gWB5N znTlqD3D@BxfU6`mVMK)T2riquRaGy-Q-xxhU0)9}4#ln=!L4#f8$&7POm7QHN?h?2;PF#!}(5Y9nh1oxBI^0W^H08@Okl4_AEwwMEzAK=hD2Ctay;*1ao zhdrC1pdg6)H7?!UWreQqz z2C5}jIrzk6qbh)y;vc>pCbS~qjwfWSadyJ|?Ddbn%*%#8z6` z$m`et!X1k;fkCbaqAqE1G=eoW54;+aY|p3Pp9AdT<>kd_8#;~v+&@Z+D}ZV5Y5;pA zEok82_6e~8SW~i?M56+QpsU7h6zOev9+b#YRBqtil4DO7dR%crPTf9% zc9~<(8AC!s{*w*^+9;2|4rv#ed zt5ja+V%#J^qktKWrGtN7Zmu)9bSSP+JO4#y=>3SN^WfG~WE)Tm;Nqa$FvUc*?jKYK z_~*Cqz4{9(KCFT00`J|uE8+0;7p7_`x$)hqTAjUITt4CW#|rSq)E7@Hx^)7eBAhOs zx3+2@JZJCEfHkPr@oppKjFMZ6RPZt+7&L`)Lj{LQ zJTE_=GTdu!zJnrh(`C&k@#11)D4|QRoKYwTLs$>(I0j|- z{QPO4gNT8XL)aXa2@m5H40{)TZca!E9xJt@u1LvxdSC|__g)^Sj-Z6^%60kE}Wh!Mu$Xc z1)^J5jy;-~oTTWP;CVvP(<2`cMhuv13ko`8>0zkJBj*}lBCD5(ZVwm!=illAe3kY% zw?suvN^}q`+2c;&z$KnMM7zyZckDSRh!8W3yh6k-3#`0>{QyA>Q8iqQkZ?zKN(E4iSC*Dm-+2 z_U*n!-?|L-e;@DA-ZG#-XXV(1Wvt#@A+Vi~F z_0fIauI5ksJ{=C_C|cU_Z!Y&O9O(Txs*h;dSpDl9^XWTeu!FfzXBT&0-y3bCU2Hd{ zMqj^hJ@a+p_o&phhg5Ou@&W{rBmT3h~?V;(Po)pClzE-MRs9%PE#^mh7oxA&LtD59>>leG?My*c7TJ=l+mc`Yn+) z&?HrVWF{~5=N4}&V#dJN>WHRMp1I~ThZr{1we88yk2Tu9#EG8^-(%MHUcbn7Vea_) z1@WEZg&*k^iAy`YGqepeg`{FlaYQ6P_Vi>8h~$scxO(tfys%iT&OYyDx?RFy)9T8% z{MrxA&WTm!?G*g59CSyE{IBZHAs@f$=Pki3w!%*SvYk3({l4^PAMVbz+(sK3F&;Ns z=sF=X+SgW;aCv`a;{^%pNfE)X=Dl9&DaG8%9a9HO92)8C z7az*)K2v+YMYcA;PPg!h`SCEKM8#(C5tC-4#^DJ4gUdfm96E;lO}_ms@32tY+7#1z z)Jf{3xneZ!_UJ3+XH5sTi|(OP9v$ zhuq|48D4(+nqXqTA^VrU(9ZEkvo_ne-K#qPb>>cijL%q0imcqU*6pQqbNdX%sztAY zx$B!)>|H+DX_bUdDX$LI+lup#wb>?zFMu2&ErJxcF;Jg^zJ}$ggzVo^rDOvioh11f-qvF^ ztNS|(y|UhC^~+T)GOAE-b#@MlkFGWSIBhb$L7;gLB4FXfg}tjt!R&w#^Ty4af6nwC zh}0Ndm-UvH=mdB~v8elGutnhhMgr8c6|*kg#4kg|JOsa@Vl|w&hF&vqOLoP)*T1q; zK+|TwhOKq@hh`6fmZhgDpTvp{@aKOpi=+Vd#zCKYH#M7l`1&R0m>$q5#|3WfJu8=)mj0Kj(pS9k%KnO1=dRl)plN z$XQtpBYsHv%=`W0XMxUOH%66oaplcPU^AX?9)k-9uR)5w6+ zGZ8teO=l2U1EnPDQ@BoXVlzen8Q5D8Xda03w?Q=C@qkN<`{V40?L=oND$hz3H*GRAW{O?ONv+Qt*!krXadyp$fm*# zH4vyTmLjZhnMXx70?QwKBNW}B7J8h6l_Z#)6hk0rD$CyB1fQ%)J^FKTkz2{vl}v6z zD+0O%%FVa<{^k-pe>+X)UUjCU-8NVP4fUP#S;gFe+rEc|B^&FupPAg)KUCpv2@h((p@_yCA4!8L0hSUxtFOo3 z0<+VUVvX5{g#agbbTy+-yzT%?qvTeM*43hlM<(7-@jHl(JiL}rB;q*%OmuiM1J7O* z+pJt%=}AeGeibZ z0UZP1u5#COw`0cLO(1lAk44a z<}^a}P+j0F4B>84D(r{)8wlOJK0F3=Si!*FP#=Psa-8!XpkJ6H0l-B>Qq$+FHpMHb zu^AW`Y!PLG?g0TK=!a0#pg@oCI*(e)6PI_k-$-9S05v35klL>gmvQ^x7Zu&wS9bC+ zOr8MyqIr3FWA7c8CB!T5M3;zW0#CF;n+Fg)DlYUwY)nkOMV8XI0b%^Gal$JDkKPje z5c0I>sU-d@l*X!f2-r754@-fR^R_K7jH#=r{6&Q33ZE&0ApjNQzSzKABa&(77tchj z_o@cHA${6^U*A>~fj^0xm%fMJ=+Wz|;o*3-yrJR7ezD#*8hBO5WPW{eK~`3|_D$l* zkK_F;WHPfZS#PF}A&2f-fz?Ak4~{wQ(r@Mq<_{wnEKBVk9No-jLSo%R3M1)j|t3taz>u7yN~>D0xWw!giS5r?`>myVIQr7f^6@x=M=C3j#W0r5H!IgOG;959+qD* z*REx#hp6=|b*4#UY!dvndK%h)(5nP%az`7tyl>p{BW6yhgjJ$u*kkSYg|f(~@Tif? z;!5_dF$!t?dug*s?Yj>&VS@a@e`QFsrsm9HeXGWa_aYbLsfkUyYw3AB_$Rie&3ON5 zxRpjLbpQCz>v2ZcjNkB*lZ~G3jU+vG)pb3Qf2djZr=ihp=F3#23S~wAT+3ATUM5#5 z1<}FZ<5=RY+}dLv2HxTHgqUHr=S$xL#Z3fz9*xv?o|~k5zO+vUg})TbmC@F0cdl(^ zDNfpVUNKtCP-@!>>9OFx#ZL);j;3^pez;*gCBDjYS*36Kq*7bn#lU1@ZzpIS}jc_CX|3%YRKt;KBZ4U?tNDMt7QUcQ5(xr4tcSs6I3W%hViVU67 z9nvDL2qGda(k-BXiiCjRzvsN)Kj$phIv!$$PIw66C1vD{sxY#r@E`3}VObf&LE-DW z5!#S(T73Q4GVx=rwabs05O@XE&Uap-eeAuz|9oGdCWY4%Sx`A=sq2EqLR~8yWX7p_ zv`LANxZJABQnlIB##eS5h4a>aGEqkde;UGC9XbcS^M93z-C}xz zR&{$=PJWVeOL6OB{%6HZl7f0>SZri|z#})k#`0dS7XO=d**)Z}^c3&qL}aBrZElAp z45}Zdw6td>d_v%fno3)W>1dAL8KGw^HIo%Hlb&p<>n(NE!;kzhw{s)=o=2E-s=G;w zw@J=jUo+>Z7He&K1QQmI+5|5H1x1NL3`(NS-9WEVgH>D8m54;$W~yZ_anjU8Q5vU| z)x}s-J7#pBJY2aG@0zRZj9vQK_v#BAR+Y*$Cg)}XQq6>G{YximzjurTY%(%&whGm1iow43u zaXe{)ZQArb;#YWV{p9x#dz4qc)ez$050S)<9TY@M-EopKaYSGDQeXePGpcgEFk-TU zX=R@JdrJ}Tt*mS61l_OeR;qAvcRnx(UwPnY)t6A9rzh2Ri)9}%<0B^?^-HsP#y;Rd zt-mB$oOVAt4Y@-OD9%8@cj4_P|uuTFP6JU zmsfmMkm<`%G&v$orPb;2r=f}ODGe6UCKLBBG~1JvIHH_3iAhYQ*j2Cgl=RKCU-P?V zzGL=`)1_hY3IbtCJ^gv&nk$Y!k9 zuq0qhes)L~_{{l5O^L^U-AkE^67`FxHz5l7*TXPT(Eo}F-gCNAdgPnm z^biB{n3yEnT$%nB0Z$MQJCphs-Uu=cyJ#7;fZX8@kBs5Fsp_IGa&J6=)hcZyhJ0!@^y~36IgUXGbRDYt6BM& zGgw6I`k#RbV<7kgE@jQRx7faYGMp(q&>or%r!K}#V{{aPb^S(Uj^W*n?nA>tO+y1M z;LCynW>(fHq&J{En5-e_ULc;aOMJx`o&lW6@-Pwh48iWOj)sRX$?=ZJhnIkcZGzdx z(Y`3C2EZ9BpnU-rX@B5dzzf;_{0zwww+e6u#>CKVR*pI93CXq7(2J3OYFb+7)7=-% zV2?3d`|;yP_^99Y^%0>2dc@VrneZ{?bZEn@$ypE5f_61CHQ}1w(-cH{$jOC5Br=447{})yh(Vaj0~kE4 zY5>Qgg<2*&A`Qm>%E>B-9_S;W#HFFp`@k#@RtpD5Dd4fePXkB{^Id_m$uo5N>7gEF z=Ws#Zt;HnbD_1nMv~a^f5@Y101wC?~_1aQ@syu@1Pt}!y|G^;GW0hkUPbQ0HI!K@*4{dy!d za2GK>4Ma*-SGz+`5<+2e@DW;{PO#Og;80~x-T<{N$U^`e0+YO{2;3`8W;MfiA+Bi% z+|@=#j!BFlmlhmkG&F`V8313tI5(SZmt(w>LCFN@HKthet^~>gBn)3(@N$8^_9xp* z2O(1cB0)CtXV$Gw=J!Y8t{tjeY5(@zh7+p=uu-3c5emc~Y9|Wc+A+IvXbS*xT!aS2 zuoD*na~<#=B(=V5$~eIj!^#*FsSe&U$DOy2W-YW!l+t+r(If75_s7esM_$~H5C?*(V8Ub2{&mJOX3N)LHs&WBM`!-c&$u|wyV5!av zZ72RtTDh~GM!@r6I)RP|%tzs~1wR!UO3DZ6*16i^AX{sl3t8g|0;vWZcggK{20S}PcefWks`z{soz;Qu8EM&&@C!?=CMf-Y0z6J zC0@Eig}`~AVLR9xNVRaea+r!eAbyOBqS6lOHOZLmAKKT&b6JQt0p46+$@s%cl6yBEp53x z0=!Xfrm8~)VvVpSKdzhldb8b9I9he={Rf}JuiA-Gh&R|@GH22sg+v-Q%!l_zyMDZ5 zS-XJels$8;B(>_2L+jIL_k&oYm<+EwA@Jp+-)SKWYkXqA@LCAEX2>J1v_6X^_?;AT zr^&=klY#cq0s~r3gk=vsPUv`Jiy`-ew5pd)m}bpt@2!{DZP9e(0wSCow>d8%Xd_jL zi7w_F>p zbn-lh~+8Ny@b zCq2HB#+q=bkdCb{RTFWU0xt~93m@%ULXjGy7F7OleEBCY#4Q-;QDSMpalIs2xW``z^S-XAj_+$le*oG*+iDD0hgph+dNlkh$6 zdr%XxsO!ELw#?VqUOll(=9Sc|x9C3f?97ttF#!&@qd(Wmj)Qn)pYW_RI}_4jI5lIGx_21P|rNM9ZF)DYfj$XsFLeYT2MMW1ZV z{rd8NOJ@Z+t0s9QrKFlvu09?@imi-PKgTsdJ*uzL!T$2&@%Edtnwh9R8nGEFB9j~z z1TxIO@ysNeJMGq3_}>f#X5VWD`yc-#yiBo+>|N*D_VPEkK$bvbj^HclN2Fm{$CcgK z@eg)%WJ5$nMg47ai4^FEF^~0S`4EpxkIvLPfsYup0$iI#Bgxg?+|t@3qkFa{k@uEf z!vrUNts~rK_1A4eTB10kry2zMgKZ7e=C<(@?liQtU-3sB@S?($d#>B2huu?l4S3*g z>?HXwJ%9T9KR{hLC<(iF4UCP`VQ+^Mp0#~Mm^gqjak8>{1p9_@=kd)LZAQTvML|vV5;XW~=>_gZaO*WiLePbji$7I)lL~p`8LeO7= z9>OX>JtIyByd95&%BxV-U|habqQC8=J-@iffI{7VIvz3vp`~!reb*>!;JFEV3%w%M zliYBmVnt7Xzv@!*m)#>M!)!Nh1Z$w45Rj1gw=ckiXmv!0@Rho@fi6#uA6Vn$;=&Gt zJ_2m0!*rH}h=f6T0!_gVXwe|84ir!_?eBK|Cm3GALI+y3@}P3SnT|09hV3H}0)7xQ6EDDM3hKX1_T26TGz+58 zbxlqhK2}_rlLJB#lrapYEjugASq&Bs?e#t^hs08cd_t|$r18aB3fty4moJRlPWDN)D(XCqH`Qj(sP)dh^nm`e?rWRs>3 zp>BAzW;et|FilQR!-D-AhH&_zSfxZC;2|j9I}`y;lv|UL-SC_L*P*RLYEQ* zJi#aU@a+L&86XG%?EpmqpX#47C}t6`Z-T`XW`hS#{hjCwN`}N+Ra18z9Zw$nYUN2w ziikA*kJ|=K648|_!CM8<%6bRi#nXpW1|FY|UJ4s@sI0C|8QP#=*Zi5?2`O;zhlYMz zon!H8s;#k6a7B^EXk1T<^V(hYw6NH1uIV=qY-%c1j`~HmeLmx7vml&h|$f^G6Q#?xS%|>^~~b{3t6!*^tHA=NAr>m81&eV_$mJgQ)EqYA$Q)ESXqz@ot^% zi(l}NjI_vj&LC(<&YWbX@+DOvo;`bDtDlJJdVl0Wx7-uFpPEJyX}k9}`H?ZC=*xec zA2tzwQpWF8UJ_djc^k{jth|yv$Y=3382=}}vgXfb?O~P}B0_uu!*vpTvyCJ!;-fX< z&u{(G_O*>Ohz1eweNC}az|_7%?$gGCfqCoJncNMtY% zd)M>&+r5PvI5gO0x6rr;Zd85}j|m%ZJaa-05ox?)S-fF#osq2SqWKf~`$B^n61_L+ z_-u$+`Qp>KLv=oTGA4rC&vT}%(}+KyST~>J;2>hj5n)WLctzZZFopNKX&1kJYE(as z-i^)pl8YU!n|?W9PTElLG#bp)pum89@{o)+M7~6Sj7RU$MikQXji}s*1ltT?SOP(z zs+0Y_^fg4Wc2zU5~M4WQ^uA8#M(o zJO+iMiKA&jo<^~QjH>#z4UUfPo}R}wti;AAo_`BCAUdkq+Qr$qyrN=zX9ohzVX`eL zDFI2W=L#I+wUw0+%_IZ)ZSU-ohBmrx5@rvV8dbTux;oz=1)Bz-Km&goDAK!73BjTs zO#9(i2rTh$&NUD1xo-jEw1Psobs(tNEdv&sfguBU{A&Y6d>0Wqbt~Wuz#%=+pQTv} z`mEAYm<>PspdXZoQ1T4HAT%CU4K<||RMt=Xp-qPej8DmflgYuvgoOKZg5y_CaJTAt zzBhMagSP_MeuE=03}EKQ#+w?~S)%AkAT2==6058)u=ru@h5$7XC&7y7sp|ES-(5=f zV%ef}T7G`b=|#*@x)UW;zCQf3$QHyJ~HI-k|QbB79vw5c~y)?1g*E}IzJdku_az&-%O4hU$40~}%*`1vpY zOk{%Hz~Nk-{#qGmpgl!$XdYd4M*!vLAbULu3DsXK?15-se}An75V_;109S#@9S~ZG zpabX?)b>`|qYgrP!Vl^$KGDElDP_yW-Tki;j*+{&JM6z;>(u`GH7sSc*bRVM15uT? z01W~13ariyddJ|s2j=PAzMU^!p~+q(o;Bsl3uah=8t?2BivnjSAu;jTjbHJ0)zoQC zKO}SVr4QY0tg&qWoW5Lie_--*CyYs?*ztFdb|fcK)E(h!V#p>}uVTsbr5n5khIS*w z650riUacuAT7v|<`uh6ChZSHronpCPN!gjNy8Or(>@NXmNgs58aZsj$zS&I6Q30as zVMGK>+cy?M$$wqx0kHFK8{K7GT)m6e zm6hs9oFH&2l)2XI44K}y8Tlc%2U3jz#0WKPH~>2Z%pT47{a4)Pd7vG|oU~h;swBwH5kk9cEiZKCpVA*~mJf_FTsESjG!ll_KCF zOK!S7Rql`*MS#652|yZq6iy%B4s)^{S9lCIOVw_J0(*Gy>1SMiS5@&MV_YnZC} zCJOO~*9s?0>2}%9hUr7=!0F?6!!`b6TfH@W^>38W>zYI(5J&fyH<}>Ktly&EL+C(7 zieUNq$ARI~xF0}mh-T7{r^Jn1l=N(|yeqzrk4@XhTuyO2dic6!?%~hdeZ1sRVG5&r z=!-|E$2^j*QHDfJIGYP!9yrd5l7{Q1RvX_fBgOgYju?4@^I`4H9Rney7tBA`GLO1{ zxZ$S?E_nQUP{Sr8d%FtVrmVT)O@h_EZoo*T$c1Nulj1)MsuS`T zD+VV;dhMogAhqqd&i!$=_a>CZU$K~^Ihe%R9z2d25j7VFyw=kT$LAwA+qGk@ge!WM zYq=;a-Iqio=ZnmfU(Xv7V5?8zMVLYTi{(z5$UScf84~{F=k={>TFda zL-;4f)xR^d(en`pN~x0Q#}dShS4-FH7ots@s$+|vCj|j)g`ZTI0=LiDQu=5 z(=q3NJwK(wHc|d5s+lc;SFxU+lVoRNxU@k>KM{Yrt9+RGkB7EEScLt9Psb-e^v@lM{*X7|KmS3G77&zFVZg%nA{?i= zlU6&_AVe!K5Rbyyod2?uB28{|0NLggXx2>4QaQ^q~S6!Gce0#^GBVb~(3kq=>{unq){6JBINeSMM#6OM)2*U!-&`Ymbs->Zc9swU6`<@}V&3L)VmD1s@@EJan&^?17K_=v8q9~ zf`EV*NO?jm-US3*kE->=Pf=$(?2JoD(A3efhJ`U~Cd;SCVR;P8P>xu85xONX7p}@J z?(AIH_SFx8yRI#*oQn?2jiYW7S^+!kZetTVg*Z5G-Tb_ax6ocnm; zewW0(E=`BSGSPyZma?SFE#V)xkem&6nQKDgU)>_*WlA-(SNkpD3U6002^WbTKMJZX zc&nYCp8k&rJ1~*=S6;)j!!tS_1_CT)myOn^F&{O?@hVP!kcdAry~~PlPt2wKrnQ0_ z@}wV*oj0K7U^@7bO&<8{mn_d{ojMM=B4x69St&JmF=SXj{-qLqmV?eS&17 zbE+R!tk~*#+c!^W-bJenKc;zl%}6Jxe6`mlR&7Ncv5e{y$LDV{A0?lUHkW3V;8znDGCkgn zZ4oCG)314M&1i?swHV4*Pqd0+B7aXNAuXYMfA^No_}muqMR(m*R9NS1)E32NHqSpU zXQ4Ui64m2?p@|Af@ksekg5J+)ljzsI2>Jhx%e zEC;_c3PJ`lIHnM+*jy)6RBY*AmG~)EvwW0`l5TN2G9*$C7F?ov+DUG5Gd{bk(>a!O zee>>P-odAY&jsmlzq5xSUdy0r=O5ihsC=j6#Cz@zfNkjMfrpg=;1K=?BI{0^x3hxjjGM29ODi+X@gSTwG3In;#gW?F0(@ zqOk*~0ew9^jJpV|^#N!A3Nx6TV+MX`9Sb>XP3j}Kc!FBS>Y~^`LK~V5kO%-Kx!}B( zl?CeyYcsRckHRAd4$^g=+$v|EokjMbse_f#^e?|k!4sg}!XdviFmm*(#*M*VC=){w z0gm-A&(Je-I453^L~Nb_fDR4Q=g*%pj2+k##Ky-LS->hLxeLP7@`PyR!0G%SWUB#o z6CV@v3N-BiF-?q*gN3>aUMGO_7LBvgBL;B=a#|Oj`T7-@7!qJZ!_WidSoq%$f&2yA zDnOHZQA_s+t_fu(9Ca-1I`A$ZHs`(L5qD^{wW{_gTk9>KxSKmGj> zs+IIe@EQl~*kHBO4wU_>sn?KE1#i;H(Ge0;z8#)Ii{I2FQdf3|(FO(aL+5pna|M0l z-&@~zaQk0PkKLEbg{RbKfYdIybbua34y>6%!Ki6nlmc|;<44UJqhR1N0UP+u8E>Yh zYG$sivZfUTtNx#P3~Y*m05dbHwU7joZ`}Yj0#=~4R-LENq%t!z!vgO8;9!CHtG6qE zFx0QNq;jKg%bnAatKMxvR`103PZRmjG782j-3T4RZ^xGr=f!Skcy9$T>WU^80|<_T zIjgRIkP>)*i84oDcw6W9fdQh2r-d%IQu5fAZ{(T#O*Q5>iDaIfnQ8M{c>}u=xpTc0 zOk3-*EJaXlWysLk>u~(ga=d~`x#ws4d`vKV-&n>?#V~!nQS$@BZ!0|4?f&e#w-53T zqB5Q%|LmhR$kuYjdi`b;<9F{RoflPKJ|7$7aNv4|6}II5=GdjzY~x2fe?LwEH}!#4 zx<56OF6X6nO8l_~0hzV9COT9y106d8LDX{+uNZWBs3+-eaB+_0T`%o>Ev%2N>j%>X z1f`T`A6r>IVGpxM^+Yl0eJheBXMdd9|2y{+hh#D(_0QG$^*=8)ZnUse0)KZL$I3UERP&HX(t&xsN~MvyTl;8xAWIoDZYId;jrwhxTTOWG~!p&$nH! zqPRDrTDfjap6-#jp7N3$tF-g{hC;=1iVz+$!_v;O%uT+pM2`uPOKse!6|-Eo-17j> zmI?p45>wZ#eB_-irQUU1ENACNkGgtRZdBT4SJ%z!BDy4qjIgx^>I8)gN+#^k4f`c| zoUt&~?jOYiu1}&ar})1rbof{+e2t_P#ba-Qcx!JsS@g|yl+%$}U7I!EZmgHCajC3E z)#>TXTV{uppUYcT#=|4wLF7iG12&WRKGIL%k$RF?o;d=_ImEm=by!r zJlA4M){iWV2we&~v>5168h_y?OjIC#a62Z=7 z8zm5WNW%>J)orC%p9Lh&3bcaVa4P~L+X?q&H|jYEa=OF`q(>%93V@CFj8?dxA04mi ztY0Mf#8z3aI9n=j6KMJ1%L_N3e)f>pgowA<$y?eW)Lk4i*K?nJHY6_E92qd?|^V^gvMPq-QaC^orY=FnN zySJCdI{2drWv_q)qf*lr<9CG?d}VrOK(xX@uUCu@u4c&@3T9FK1Rc?b;b9DMf zST!}iS3>qo#H$WkBx8jabGxP9l63A5V4n=ArVO;UKtns0fj~ew8ZIWEYuiKEwRhWJ z5oHij$w49&Bv65$-1;aN2_ifj|0 zlf&S`HOd{((bZNr|JAQz*3I7(X+h)3)2aBm6*whl>lp{X*hCwmmEv zWC61Ed}(#(&J_GmPlN|^06c&zJ5(r?{b|KJJ4tAAF)%wY2Eamd>bSNZV#a4QLJNTW zC)f|^;4)XgEG}w?%Jw)V0s9vv6V&vAx=t>j5O55SEtzPD2ZmfXY-{oESw9j{AqKbeTaghJFeTN}i-WjD; ztkd$Ul<(HlvGXNdfy0O%`IBYcwy;5)ypzAT>wTw~M8BH&x9}yybrV;^uTkq~bNA zRU`|`#lfqUpO3ZUm0q?fXKOS`KE8Hi?oqAKpy@gbt6mG%*X`+SjHMIw?+j(MNrnZq!5HsxE=b^6^$#Z`xfG-q5m1H}OFY?@n zgIT{Oxe>o-n_NzAe3hJ)j6#9MFZbCJS3<5o76p09>XRE58nn}%tWkIgTI0WXE+Gnd zr2oEas}#$g;xw@=oZ_Uf+q-BgubAQTg2~{w_u+y0Yx#GtoSQXR?t6c2jwo&MNM|A= zpvczXsuDd1hMn;Hgu%&4rnViOFUE~TaaZ(|xf~JY{p(VC_TCmp?p$Et#ZR?#x&mAB z>RR`9%1du7w1;ol*agH!Cg9T@-&kUqVkqvfMN}|+RQWgHyMcNRG~L3~UvF-TaHYl(y87?U zZ{MOuDndkGwJyhA$8T*{n#1V)iET&{q6eE(j|Hi39TUGZV3(LH>4S%8Xl!(G1S=|Y zAf;GZ?gj+h%>4ucewZ7JeoEcE=@ReJwn5$P)6&{H;XLei*zb$CFD(aaU(gW>5v@ISZ=P{KumZZ!abwcAy}i;{+|DW36GgpU;o#=} zO<@cAN~p*t1JCP1&?0vsHf!3MzWUH_%0rXH5X zyabC}_|3WTag0p^W~Rv@)h_!DH3Vb12*w~C4+K-Kc}*LNtE+banSMm8h-CwRf$%X4 zsDPpL{R=rf2s#JzFo=GfPF9o&++T-52+Sq@K3sZnbyHPhY+l8X3d`b zwNdesT*u}GUdv}!w9;;M)g(BOVcQ_2SDTkssx~abfLm#EtMc{8WTx3``ZCwo-JBkh z_q;x!`k1^PT;xRSA!0}kp6U=$B1GtaYuDSl-PefhH8OHKUG{RzzvV6ch`U0<(I_cA?eU>NwWq;K440>md`u*qf4DJQ<`MRlWKS-W( z_;qlLZi3q`Vm*&GE^00PH_B z$UMom+`XZ?mGW;lUX-8w&7C5-I?OWwM74Uamx=ThOnU)Z%uO@7d*RvMeXXaFCIPSI2}E%O+pd zcVOH0K2)b+r+XIo+h*eQ#dnglfIaD%qY+Edn|xVRL=9vIAJ%_B{_6PIU&{F4A3eCbZ>7QMM23jhjW(Fov-@H;Z(;VM&iS`uh2gl9g{!Sk$P@)m zL0em9){k-WC$@kI{&@Yp7JN_`ws8y^JW)cWIpi zOPT;u)KgUjBvKLhD?pg|{5j}azBGf5lhY=wwjeDvQShj79$b-NX#&5xHe<^JV*+9_ zK$G7Mss&h(WHEo*@V;YcSZ&_=tU+K>N{XGGT}@ZG6(NIG zd;zr(3&1U4k-%M%izl~3J@MFwr0b4aC6Ctjl9CO`%fndG;H+d(jAZBS?94P;slS0^ z4!p8+0~TR1_e;CuA34tu#)}nbub}rSbch;Q>qWu256lA)HF1HY?B>mEI?IXA(A<6g zY?M8$8m(G4jR95wA~@e`|KaN9=7Nu&*p>!GJ}fIu(sCv47fyS0{FBFrj9@R&c%jzV zG}7S7%K)`mDq=^Lo3-gAS+Q(Q~{z=TvXv>=B$i zcmQ_D{(gRtXaAysOT7e5X9<$x4;s#|Eowor51MRUbknp6mo;qp_{>|XArAfQN4KY| zYoW{9j{g}@Q=RGeFsP8+tgGDnjeRI)x!T9W;!99bCQP~1^aBFK9fx6lcV7BGKU%}+fcRh{7~xGrk+W#D$715=_XMZ|9-xy4 ze<^>dTwiSv&w-{;L_}mh_($iWdBw%s@;!(v6 z#hoK~Ha(5&2&O22xm@L4R z;}||Bz`X|6R#z`5vs6p;7lMGSy5K(t1IyuRJU>Rm*xWqjECN1aMPnw`)|i29$!A0c zc(<4yhZ7rXInCT-?E4|c()#p!U3+``_9)1h0ONus$@XDTcTVK=Q=p>*%ZrT_lK#3n zYHL4vv`*BP*VW0v$PM70NBgdrXU!HBaGHB~;yw*tK!C!7Mx#O6vG%Tn>+~QgJQ5fCS78`CGzJK{fpKNQ{r&T1^)4k|`$=Prm z7RvW5@yh)wRScPVKbH)>Prjg9HU6}Wzp>X9)OGR7@$_K=lBHK(eH^Ri;DRKJ^z{q< z^4sNR{%2cTX`RHWs_70C3`?d?xa2X<(^SM_*Ht+gbMp`go=8fQg&A2CGj>N0gIYA+<|jY>Z0*KNge@8$7MAVE zF!B^t+|6)f_TLaH2k9G-ceZAyl&wUxLYw@qb9$X!-XtmoUm9gW#G`iQHCtrO$>Y zs;bJnmLTM%{*drJhFGVu>9ScZ94i!!sCQ4v32jL#X@X(yw(lhbQg<)|OONZG+{1}N zzc}+-+m`yj7p2^o;=B02|2@#W9Q`~?MIN7f&4`3JTXOx>pzJUXnF>)4s63SyFGR7~ z&G1r1=L8V(@&wCRTjYa(DR)B*C_3py2lB}{djm{mUgWy}@QziD4;~&N#zu-pt`jw2 zYbF{m3CljYOYp}a|IP=G4)27wqDf7-IY0qe#OQdiUAhUZ;4&-`TDm zt1C$KN+kmEsB856O~o_M(sx8!B;O9HZt3bGrxM_pb8>L7cp(!A%sVi7XoqGJvsV26 zDf{{Jm!Rs*%F2TAr5270aq(#zfewI}FGI@c%43?KVG{=tIv}S|^IOdeC(+^#)Kk2n zFmJCjMbYZx5Bw}rPC@DELS3X zN0To>;}3d~5AWZ@hHVAoDgx#f1Q7xdK@Bq!97upx_yV?pv8CbQ`01CX2VHpQBj{(r z+)PMl|JltwpzWBnxSN`r&p|N_g#{XB`*(N>^8moxKfuEy{4zs^qz}8}L#PWNa!@uX zmI1{ipt<-3aK6KVXw~uHSLg9!(HU`?<2H!{>@I;p2uB}K0Ds1Zwaap(et@e3G5}+6 z8F}({1%kX;M}7md6gZ{QaMS{4A9(s9T32(y-3`be7$pYC279jXcRbI@+3}SE)al~W z%^x_pxS-{L;Ib|`E9vua2$FB}E}7eXv>bkU58xxX+#pWIR20)j*mKr^H!VbxTDhp% z*w{cn06xG*-k@~3{$J9UwzM3x&lg*e5=YiZV1ZdISn;AU|3|DlX_0YKx}2^0D&Z z^M*Pv*XcTiNs@&&K^I*NL(ciGXXsTB_rRp$ytA0E5V;3E8!RrO8G_17OYZ`WlJM~= zG!mF3bx0QgJ0V&4V7lheDJc#DC|I%6J^{)ut=uDNHdA<=u)2WnFA#V-6~HeMIw_+9 zT^LO#Tu>rQ$JS8Ny|K=rXa-lYqE&K zPMk{Yny|z*mN6~~O9WL`&(`E}I+>(z?}OqQkH!ZQNiTVdrhf&ny0SYy8Hl!+JtSjd zCzbu4@)~!%am84v{7JW`y5e15r~93Un(d7<>}ISyPV&PqFE#yr$+3}Ha$v&`TCBxl;SG-r{E2|u(mf;o=f;xW6YKgx+ta!of(?`KK5nu^mWSE z>B2muo!6QfjR$PfyQl;>ebijZh$+36DmFz3{mqPut!~I!I7*G}l#8_UqArCQJvrbq zy_;d@plGw$^ZezI=}&wtvSc6aYAUtr&47yTTP$Tc=m@3A$J{BJQSz-<(_g=QqSB>T zQZbel%|7rh&tu8h&83CFH7^rYYmsv8iOGSHt?)0K#Kgnw2q#ZQikg*7nW;3c`26LJ z%!2*Q!SIP_)eC!_h5OO#4$r+#fPI*K`Gv1K!fRloD~{M;QI7d;u-aKKd;2i2rAQ!J zq``bVLg=uP^jRyThy$r$&A=t5 zxv#;ZC{^O_o8WyAmOtiFV3iA2n5_C=Q&Q;F4=uA>zHDW{M&e!Y1{#AE= z#$+VdZ`;QGiVX)vjJ;CmG_sA=d!7wT(Z1G!Yk`H&Pfbj*p)NuGDCSk)<49B8KhAn< zZ|{(5#_Gt->6y57j_$U!vq?r`-)X29{FWK1B(xgYQDtK1+;gd@NG#X6PAnJ|Ce54Z zP|bw4N_Cs$#`uT96bxdK5TwcE-a6o4GOgw!Cv<=Mim<{o*{^~?5wvL-qG=z6&gdIZ z+~521a-}reqfqkexwc{|-S+mjaq#*bE(d$GV%&G0fmzC^TwR4*1^@+!2!(of?%Np%}emNin0#I z1(@03q)K9Sf!_j-f#$o?sGA>#XhxWp;r#7QB?FkZ>;| zl7dvcfwq2c4$(4bHVuNu>0jntQ+UEqLMS?3= zu0pU8^Z~ia$&GLYpl2An4hn`gcpuOv0r)Xao^T(w@vs*A_s#?MTZz*9@4;&08NH-F z)DhdacxJ%62%5GGz<{VAKZZ)ODAQq9gL!Y@FpMz*27|=Wv=1Bm&_jUrunJU05hG*v zWGH2($SAldP+>w2+Tkx^rccoGg4r_o-~b4td~bVuRF^8z`Ve4fqdF@YutUWBdEk9D zd4XXg#8Ed-lfB@ZR4uA^`Y2RmRX3Jo74_uFBADYr37!;+rB=OlOYUSn8Lo0ky-Sl9 zkT{?y!sH9IU5wujf(bZuGYyWRXLC#Jnij-#lRTp|Nb_-;+Kw|T8UBE?PM9R=rxX|K%xG;zn%z&JdV&< zK-&V$k~1)T!|)x!f9JD!_a=O2mx6!D0FxS&)sXb&0G*&nj~6roz9!az8&p#9Fr@tb zz+DH_7)+GzVhSaG0rV`!N)Yt{z`={Gtk>`%K&v_S1{jW*EjOSTpt4%LI_%mkCN6%w zJ}TG`YvV-dr%T^ITYClkZRqN}!Mhot5`cA10bYc z*Fv%?G70D=kx$j#co2Qd^_#Cm4t8&pEwXNT_3c#HV4KL^ z>vj@L=VMCj?Lm*(BcYqS=jVd2cnAoVO-F-eL$`nK9c1pD?fjOl3%_^t+mGZ0D@DS6 z?+)hPd8ytTJm?Rqp&T4Y76ZzXsY-o2UwZy2u@g4EG}dv^p}V>h7tc*BHVQct1U_kt zWgR+M4-Ct8HGERi%eY%UDWR0`YiHlgsk~k?3~7hrx<2UeIq$pS*<18mDngtJQNJ=X@C3+034|ka*}H zI_b(jR@wY*gqxFv%r3xNoBu{Ed4Go9SUvTS`zXce zz9dM5+;84o`In;6(Imqm#kg(%lyA^uUCO$LqN8&~3F@+xtcEs8rH%si^Kbc!7B zHK-031^Ahxs;TdlF^+q&(n`)3P?N%yQ#5%uexJH#xF>BYP*VFjf!edF8?#N6{R2a} zg&Y)7Ja}R^+YbHY^gma37`wA;-JKMW_q<(wk0eTx%(hs6>=*UDhm-buKYx)v(*4q- zhRrO#Vam|O+*;Dt+!V$c=E5O7thhiP3&)?=)w|7Y%yDYxQiCjfEns z^g!xPq1GJNmFACo$3c<_N!mr~8UkBWPXCdL-LlPS`reHwbHioH#BXjPUtfEpuO z;Qrq6A|@nf%jIkjn4dN}uMTG&EvkT01LG70nn`YM z?wcI$)rPgHtQAlTYq(NzB70%Vhcg=tpDOWcIO1SYs;=gqP_NJX#jd`|vYQDXYF75h z=6`1gjILMql)@;U<+v<9{MfaVU-ay4lu@pRsv)+?%FCIU7gtmL?O(7q8Z~bwfPmjF zI#Nkh#%{}}A|6NZb{d<9Yu@3W-b|-Zwb(?Wnv9!@Blfx^4RPLu!4ZLUYVE`=(nkheJHOOXe2-g` zC~43dwqkV_RY2n{8j<4nn$s)hd3LE(ffi zuMy#;MmnPU?dprm%7Ufy#>e$@wbS$RAahBvU=(DdN(GSTX*9>v0iXjojS}84;5}!Fa?TRV9v-umcsx!(iWjMV?~&u( zxA12G^#aQ16I*Bvdj%3;eG+ooWK}ov{(THXJyj=$PU`4D-)~Yk?R599>gjla?iLL0 zuFa6PcKy^HsR*I!e;2Q5)1l~7P3mF=$_)4XQC&O9;T8$e2BETl< z>-A`PHS|*U=_a>*)wK=adG+v!q0&&730+`0Sv+B6a$+=T!*T-)O_dtM{zYyT8uPKll_pw5Xbb_Oj)a z!pOt~^0B5|pK5m*h(Q=exF9%EpRpMY5#mLXL@UugqzNR4-5&H2xMh`0Q5@46C+$V^ z>u`O>2%bFZ?gow4kK=&takf*ZSa~L0eSJkeLm;RYCf*M0C&p7!RaHHZB*npo@8gFh zGRFbfL&D0#OeO#pbRf1uvnpcpoN@onUqZ02+Jjm~)GeNUX-@Ig=I8`%a9Q;bpd0FrcWuX9i zgHh<^gZRuK55v=^A?S~0cSmTCbEe)M`jc$?#T1e$#uOr1Zl{<~ZM&7QAE{?Zmm3%r z#09kNdcK#)&jmnT115gZR_g;5X&47ixeMF$AtBfpd9)^rKBDjQcnUvX22G>fEAI zj49=;Qb047V^T|D z)l*iV(2u-nN+?t<$WE+Whd?ZUFxJ=KpES_n1&ijX7nK0HaW>{!!F%o=uf;+j{`^1Q z-a0I*wGAJ2E8>740}j$kHwZ{b%Lqt!r*ue(fHX=T^0nwM?vDer?~UID(1W+RkMX21zYe#F%A>9DY1g=DE$Z}T-fijXW*5g`^GI}*|f%k zMt6NGpwr43Z!a(k$!Dugu~$DdZPCDV&Y$?|v_6WmfVO{ee~+9YQDsbgTFlR7-&u_H zNHW;$CG-|!pOHo<5gm}guJT}rsQkeRHlsac8qnaqajOW_Li)WcvuD}hb!m^*t zD{c9=zGNOyxq<`<0&@cPdsvp1mRcXGv3-`XAALj2AbT>=fD6xTl@#$)?0tI@j8sRp zQ`-?-YM{U4&I#td*nKL06n^|;`N@pY zESMMb;^XIkXykcErmv4qYbfjYOeB$4>nLCU-3nI%pJ%2-me`~IK25aJN8)6!cu8y$ z1vfUon(X>>(f4I4V>pt6IFMMDoGuDahJiGQ&>;Ey8{TyRkuurWV{d>!VbIS*@b3}e zU~pb=2mjBf7Ng$s>)QYM@xp~c;{RO%_79(NP~yN^`@hdeQo@UR|N8;Hwg}vRu-EwC zkBiLz>(Ejc;K};m$7qQ5+2#NJaN$C^E%M{V|NBQ>{^j9}zo-5c}bbxNFsac1a+6e)(at>h2y1 zGr~;CBCoBxxPY%LeMY|1-!8@Soop{3JEbN$z5ag=^6cFBccA1M{J!5kcnJ)ia)w|ldW$G6f_zPNYXKS?HI|oSJ=X3~CWIhC`}gzlytKFb zqwC%Kq}OKif96Qb-yFWPcQiU;zqiMvT^`f5&qI(EZ~Nj-5iy!+%qcat3)+)KbPyU1bXrFV1L>32alF;vik^W-ENiXz^X(#X; z*@5T;rM|kZPr!N7)_zRQ(#zKgu^+%d0e1V-M!}qcr=W=pwL8j^MF3G8oQt2; z!S?5sgPtlS^(2)-U`j$>+K?ki4FUaxc`AIk=k5(SWu?j~KD%Az6FccQR?c&Oh(gCP z^ZD2tThb8LC>}H)8WYu)K>Z_+S@LP)jPt0PaOH#vn>D4TY_x*Rd6KhE%dDx1$@bCl zUI^NP+#K5Y(oZ?Xt2s7>f)3`!;GqE4Wi5*yu{5GL5IEeP<(Qr-YAULX)YPNi z%`w$4J4a+>WZ*2=8c%`-Zyk`E2Dbiy_CWlmDu7p*oOx3dXq=Rl31y6bEZtW}^;UuH z3e=yHPD_9_wvvNziS4}iu@Nv!Xpx{Cl2=kX*%LiICLzF8%@$)r1iCsKeYNqEFjZHN z=j;V2QZQr7X5v2f=AV2umc6jy*5v%Ha4GHge8_CCIOgZhr&-PNd)0|l_4{rqF-{dy zFJAcz9*!)Cu>Di297YpM?cSmGJ6(FZn1kV2^+wV`ow0{L^H6GRo1P3EjdF)wS~K26 zrMN|yd-Un!4ap@0wb)o=)3D@|hZ=4dHnO&=d)LwS_~KL3xc`*eM$hcF@~z~u@0dM! z!%bW3rGI|`hil}H${n6*Pwmu~@AalDnqpHUt}j)2&&FUR0~m_b@6s%NOFIRX=5(V2OOZ>fm6r03D# z2S_u}#SFk3r^*thhJYqgg2j)U5UIrp%*74MzJiCM{Oh{Bx3_P{-|2r&A6C;C@&wm1 zL&tIOjUFjgii=(nPkbdOLolL^Plh;(xIo!}Fr?@etxkid)PC)K^bw=)>RK&2KV`3TJcK%PKq{sV^Pbk9iV4!v`{*NXu7 zv<9c>RH~1zB3S@9a&dBs`JdeV&$2w2-HUi=7R6^r9i+aI$6>?1)(8?D0zwRYP(i@gF0P?N5B{FFmmLeOIB>Zr;0d_tBQ6q=Qm){#2B=ex{y4^Sp zreM--8suPIQmW+tf^Ln=J%Vx?OJ&tHc@bu6KR z#u*huFz?@oE%zT-NHL4gyNeGiqhuA3kJ4#ROt{D0YC$7k$x&=NQ^TFN;7Z3+`6KvjzHQ; zNX~PwWK1D}-*fk2)Lw>jb6RGm7*PD>j)CSg>(WjLM)|8S))G}|r47C-mfPm-Eb}C; zJ4w5ja@0W%efA7R!pO}znDEj`^J&buo%VZC#_adUTN16xW-9ddp=cE8qruvfPgE$y zO$ppwZh7X6vqI{@aqxZvoCHf^so#)85Xrzg-5Se=&bdN+YdO%A_Z0&4`B zgG-&4QcDz!>R{^Twmqef_mD0aK^Ghc1d>N^V0e}O!a@n$A;>kl?U(g=bs@*<>A&Uv z$EDh++uyUU6Q(`>`|do5IAm|uWAea`IrbnNT&f38Xc3kC&E>kjJ6f+9uLmOO^S*X8HCLXvXv)dV*%Jj|CPZ0}_dEYq zvVWE~iy{sgV!C$gIEVDcB3DR1m5L81zX#sFd)-$rh7t29PFXqTyn5lEQ_G)=m*e7F zO;da-igu&ciMxzkwN`bX6Pr^gJTMC;j*d5Q-P;@eW`r1{tW$f9;u)?yDztn0vv55wzDZYMyBcIPXSCN6X#;L=T3JoQ5AZ_quNF z{CFsAQvCEu*GA))SAE5LT)pP$9$`z*Xyr*-18p&j(uu`}RYQ6CG}&+4NlTxE`^W5G zm6Ne}oj$}Dm15;b=UO1l*uyxJSGlIO%0hUfM2o#lXHw7(GT>)`F$?=vQCZnEY-gk- zAOBW|7Ox%t)lp3PCl4|~90X0%{>i!(z2XIw=L}KSP_rm3&(*ei%DfVA1rCrm!}5XM z@3=Xhb?^|#sM9oxV;71GA8#-4UDV?bF^s2*L*O*CM?(~zk&pR8jTHcLx z>LmpYj1*4WsowCF-OrNFo)I_`M{Z$xl}~B>C}JRDOm;%7ar0A;HSD4_s87>g?)6n~ztH%vZ0t7) z_@}+96gNNTD^IMN+P>D(^4i-cO_VWSGDa>#gtoH1=raQS!iqJtvyW)*!xy~2CH3DT zj6CG&&C>WiYnh#YJLhDT+vDT&r5hoiuDs=M`fH?xsht3#R{mqoB~;JxNJHtAdQ%Da z%QppVQPlo(YpT6y%*pN;+rnRFJIprTQq&fwd%5J|o5!w{q+>OSR=)%e_5G~2LAINC<(?KfKu2Y-sM!(mj@rV4AQwFY= zaTGy`D+G$uE~RU|3(4&p)^0q}oRtnrPa%e9X8J2;nU;lFpZ~gdNA%{6630Ye z-3g>#^?-@S3Q=U0OyPR6S-qc{M&_IYu9~0X?slx=pSLf&HBhpx&s;_}!cOS_Oa^Zb z8Bdm3v^|~z&DQSVeIgmMdN9CNDFkNzd)Si8CP2{3#(TA4ddB3!Mqo3HYhjfD-4&2T zau?1DStCHU3?c?|Z*T7sUQS>X0u4ggWAmkkvqY6I6rX8nX<%K`53bRZ9M_Z)k1w}H z&$v``C?PE@>~ii&o1hq>c}>CNBE}<*Lf)qf#b}{I@KPAt81$K|ST!-6f7qlMB67*Y z!mi?JNKxk4f3+Qzw2FyM$aig3cLwwg#uoV+;MZa$Z)`jb#jJ_%UI&NUK;Q>!9HiC&pM<+&oQjN02r>tu7ka?{!b>b0OCkhR z7_uc)6%`Gk&%>&NM@La)WKXBv$FanQp`jk&)dDe86b`K=D*(FgQy`B3p>sgun6v9K zhfXPk-`wLN2SxA*h|wxw3;7U~fHm0$stTN=8tW)5Mo=fg0tCDPtc67aqwzd#fm}?i z>%~dIyWrac)Er3HTXYj4#BGnGyARPe6vn@2w>(-vjsqoK=D^B=mp^#-0Be*e1Pu!_ zv?thvnv&n@Po5lL$&WA}1IyNAh2-pPf8gs5WD39ADo}-!@IU#`9f4w6wnnxqM=V= zbmLkW*1)z8iVXnN8^LCSE9Hq~+7y%n@OQE4d{C2->B}Loh$RHry#*E}XaYSN*M513 zVuyL6$Do?Vu)>InTkq{dP(Z-<-m;wj$e?-(b|J8~g^0Q&_>I8(!3mN|v4ZLrOR@bM z|KMgCWIZm*n~`^}xnaLSpdifOAh7#xFtYBS!1f4m=0$ zz`tpoHwS*0GTt;JwV@Lm91+~G4S@q$*G2BlhUv|kt z`ln~aew~EwA4n*uiQo2-PV2Ot`>;;uOmo`!Cclk?AE$%HMl2|WETFz6=K8?lbqV3s zZvTbu_4a5M%Aj9yiwOCAt~uonu1l$?=E~~&F$vW8@s5G_JkcXnN69Vrry(?%TC0fW zrY?!qvW1PKv9RDb+gH#`ft%OmpQY+-m-+ZwSmkFt#rZ@-C>0qKe;&EBH}r)c_m;_H z*NHAB(^Qe(%sE^Yb|(VAw*$PJS=W18O7M1Vt`xfU@c2j%f0SQ3Z0_dP5xN{<*d`Sp z%vklhk4C?6q<5=~5QoW?hm&x}VU5T8u)XzzFlAG^y8266X=H+>CGXqUA{iyZ!kme! zk;c>kP6&Bg&aJy!%Z$UQlu*l;1R{cr-A+<5I6p69d?OM&4(^QV;cI(^TsGAiGPQy(Xx4KHEykLN+~2gPQ$aY(AQBor6dy(*Yrj)~FM0tbDWdcd28bl%wov z&UmeKoF9hsy6dZnqt)X1I@6?`imUv%`Rq@BH|=axn0kKOUb^iq=$rZ8!KOd%Y0T|E zGmqPDz7NY|zuj_}^yu&UaF|YJsjYlKQp$zc^&Uvxyb=Fd((Ip6vC_dekeM?B*&eYZq-u*{!r(vA^H@urHe zJBSEUmF@3k0s+VGH}#^OCk!9t%<5=PvrC@(=BvWf-Kpvngx4cO_f(lK z!Aysu-QDVc_AE7b+fQSD2i!W@*V9~lL2V|?O0tCe5g~BkO3D)Mxc*7Tu*q8h%_=D= zDMEw|u>=BjFp_DPxE}_6(@w!^Fc7~|;+PLKY-|WFu@=bZ*m_0-vz{PLcbQGoE>?KKXCGJ=<~AKjifo8)+|J zo}bU-d+_kwZe?b8`9A3vcA=FOD;37Td+5N;1x8&te0;fkT3~A^RrH0T^f4~?shAOd z)MFok42TDVskT>kY!dr@#GY6zH;PToTx2Nd%iWj*Pcg8<08$)~(u+sdP&ji~^~1C~ zl+Yh$cVge8aHJCQu(U*2th_+j05THR2zE}+#*OlZo1Q=mrC3_u(h(@J`CAabA1ctLJg0Hn7)zxT}SC18*c=o|o%1b`xR zWi@>Ob>KT|WoVfBltEB&6*mZ|;;=nqbB$jcvfJ!~^A89WVRza+J{21PJ>@xw!@R)z z8QMO2E}--QDhN()X%iC$`7>Y7PLCl`VXW?nO>Eb7yBh?!vv7LAQ-oR9np#VyQ`!!S~FzoudY$a_AL6+E5Ckdx$E*V9=v*XqSP^fPqk>j1C|Cj~vHxLD`KWvs_e^U;QPr;c58=?ja|B6S00A=*}hH-$3d8_l_G5E3cr9?1N6PRYUJh!7y9LS`fAFIUMuwV! zSjDO3^cRR#;F-I2jRl1dj_p_*DW8_ z857LO2JrTI2oJwDcS@u!tKi`Vt3@X^EFDA;%OhXtW+sN_dFMXXcg37b+wf!&u3?Qe{DI!Ikq)C5E*j>3W~ zI#whwP{~{?DL#Kaa9+IWwe@i83v@Rb%I}|M?ek2`Td5L^s8TNwx48cnxawj77C5G( zh8@KeD#8AeIX8M>vs8D)*8Ch59Z}ET^=nSziem2HXL|NTtiq4DoXRC=tN595yzZl$ zSTY@S7=n#vcI{N}rgYX=y^JFJY;Dg`uNJR_>{1NTzIp3*36D7&wS}cNYCFwdY4DC* zoN9gTy7d=pEsh+#kdCb-lRH7i^b}bZA6B*31eFKkwB|@{qimz@^c4r{eJ+js?AL~1 zaC`qH;LX>krYu3)$^s#I3Kg#~-4El%YAR2rDW;P`B~*+W8kP@(2^BCg#)%#5;`jV+QD#FgZ)j4 zoaxpg#NK{r(4p$qo8FPZi)cGQSj=UatN7233HWL{yCBMI*&OoU&)(TqPvWN z1%(^^oMTATfM*B+m*1<;4P|Yw^Y?;LS}s;ba@cmrK5fpWCmsGw)t*gx^D2n#V*DXyOGH`}b& z9f3FtMQ`&bTNK?Qkc+|9ZWGo;w_l%SXuyXaz##?yeiTF@r2oi>)7$;#)EesPv^@QB+OM&b_O!J5h)iEIx_RFMx~w%Va=P%wFx<4nB#vA z1m0L`;rW9++I8Czd1d&}A2JS&mCg0+8w6f0oT z7LNP@3A`T|H^GqL7SF?vuumkXz}y0Id;CFC4RSzHvo@T_4h^WMAx8;T&r>a#>Lev7 z&Ug*%Nk3tY)nSP#2KUfmk^Hw3Qr)5nfP}W1|I?g{DXI70Fs=E0lb& z%>k^GjzCD~S3VFsKWaG_2cb9M#$Yv|qN+-UV+QOB?Dh!@stI%-Q1(;OF(TUFN32^P z@H^lF{b2Y2J1{gG*v?E5K+6Hjs3(9GiwzVO7EU?{GU@g4 z5DXjy(i*+RP0=ZFY*3p!ItC6~gWDP(v^_w_ft?`(iOp+=i*=lB76)n=3LiXQq2Pc} z_noP_J0jfBU~yxE4mly$sHCK~yIU;E(rugG|Z-HaR6Q zyWt*_aT%bpF6tN>HULtM?Y}f#RDhTdY;9QbeW6aE510E~LMtJ0c7C>Vj@1i-D1CWp z2?j9$xs{tjBG$M`2A|bupnne%6=i|jO&y5bm|+eFeQaJHW5e_r+|VX0!Mf$F{<{M_ z85zlA8mQ#7G-2p0v0n!KGB8?!#t}|Q4{&3cSzwz^2qgUwq@)q`7+iNENI8Iqg*C^8 zy+twMJ;*-c%K%Zk1DF*A;S$zE{|nvm#MD$HG?ZBI7akONf^uB5vs7%rFb%#*UN|9m zID{o7O;H`E$43?5F%BWJ*yjW06uDvykl%q-obD}j-SXM2gh;i;r6r&W!n}Jp90fPu)h_BI}5E(b-?)R<_hCFsD#oc%16H0Ce*m;TVAD5-m4!vL z`eH&Ekt#1$wSBfB+O?*Q$W3`xYxyXo{kqEFo!nanfA6j}v=ubPyo%0c4XuCeeDdPv zV}lg#k|Rg4skdblsqOPNAI9C4dEHb*B)M{EGtzI)_NOG8mp;P(wc!vsBYz|QL36Rh zADJbuTk$=c{HISe8}i(@EE*gIKN?m?8Fs})#JGnfelQP#vb-qBlVLDSCbQ*%%9{YTJ z7OzWh;mWz$(Mx2OhV*&zu>hT*)P|W_y)H;97@)gm#Y=(+9~%-MRzlEa$lqaaugx9J z`l4Z)de^5QBZ)4It9ke416oDp{ETm#I9Vn#(>{J*S`~JTIU+6esVj5=Qv9o3Cm1F7 zlT|S=F1=I8Eiz4nQJ}T^>)pDiH9AfuyG3@5B@-;fd84WvrEa%HhBH48o1vMP49dCW z`d*9x=Xf^T*60a_DLY@1(}UEKyz7^W1&{0%G8s-}%%+_t2mioy9w)g};o}OyrGHW|QHaOApdH+1Gm&Om{E^wNgbQ zzrvnGRw~8ezNWf5??m~M+SyWZ>$%LV7PUK$K4q%t35`QMQey@09l0V7)A~!rS`!<2 zzCSCv$$M2%Y@!{B zIIq|Bl5Ng(lij!9hIR%jIc?fEJ+ds+yYy})>7eL#Z@4sTxK_1`@f}OT`(Ag~=D5cG zc!%chxf_>iRwyWcc=O0;DuPD9P^BS^TLhZUK|l)v;^;4{c1ItX)?oR>dU?VM1@yVwdv0{WVAcw$#}Ewh4$uz8 zqJ`HQMY1u}USPffrRh(omg^Bu!G#JiHpo3lM&+)a1cHYYLomp36~P+t0hufk-CT0O z^oakWIWgUP&fGV`26%LhtXDDAuk%NgOG8^tvXkm$gjmdFJ81Fgte6F)5h<2wWZ}V! z5h-yWucrF5bAy@69*mLrl^=MY1;+u&85G^H_Q2`^L20ZO7XqgKUY5d+e85q0CcpRl zHz)Y8qBLZ`$z0S!&{d32g8GCd+mqgvZ>2BoIsqrN5{sp1p7imBDsCR5HOxU zuv1OLObSj4l9W{H%-}GvO8Y~C8&D+x*aKe@=};+o&|4?{1i`P2D-jGo5Om-@28L$X zxLmvR6f3W>Vr|&yZVEXWE%KZAY*kZtu~~>EGXcRDhH5}DA%Y1PkeQS3X#-l>=yd>7 z70`{n12K4GW1}4H%auPMs>5oghl0G4N2~-hK(9Ooj1&7CX!Hw({Ce15Cq+-7JoxyK z=O^f%VFVVPrw5`iK2Yl;5t)FVK}`gTYXwO%$Qfa_n zv8Zaf3a2d`3XeM`0Gl5Y*g@Gb~c+RC(HAe#LY2fSISjEV|a39ry^;?0Hf-Vmw z112ocHs=8p4HKrOxlpPkGDTV0&ih2-((0oyx2y##TG*K$pxvuD4LPwhP&z&x5dnc@ z%p2GyouGDr?VIL-V^Ur>+^K2UlRuMy$GfiX5p0fLV%hKyr0VDaIF)_92RH=y7ewK( zV$IgebVQ-H!0w2yVMcIcfCU4n9gz6}P|f1C2&F|pH2}L)6KMV*)r!~3<*9+e1e_5h zYhs06t6^>j%l)$f=YCKyW&`0uj?w{OJ}`R}j`0B?_S-*vRR7}wz&GvFMf22XR}We? zuoM6Q0RY_!&NZmS+Ib-Nc;!Go0q0+y-H(950g{jG&euo}{uHg?Fke|Z7ou-+E})>x(^R+c{^aLYD)U47dUX0FP?5+| zEXX2*)SFTzjlL$(RKPfvSN5=vm?A-GKAQ9RKDbUTf2<)jxf(4V=xcf&tlvK1PZil) z-9f39Y??A&+b$i~%=I|GV{J@*DDD}f>X&!Zg8coZO&@z&5#PzhexDF{snE-&M69Qn z+CHEQ!&AG>O)VM@fss%cIewCybgin{0nzxhTB=(gY9W3|4e(x$? zr$@8i^~Dt?t7esy<|yIg;`IByy-oMs2y%C$FFzLgsj9>6j?U)#+CB5TwMUCodv#G& zrxtTb4~MQrDG5m6tOBy*w>#9f2}a1}2?T7UWlNt+A>363JQN3MDh}VFTp=cm{)Lob zWO^biBk^#5aoXT*w(l5OM83X&&^0A)R~+^x-=MJD}!uruVDy{&Km zP5={waAZVu^p@woL~tA}0xVQuhEF5}dOpBMV5DF&zeXRNl$=~<+TsuIE-bOlNo;d^ zDNV2vJ2z~708K7sbSL;7fVfr)X~aDaYXLu~1OXz_PcbA+*EDy57Zp~wEu6Q5Uv4

L>EXg9d& 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 z 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

vtcAS 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 >pJ-^RE&aIwkf73W+D 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 K?K&;{=l=$cFc_Tx diff --git a/installer/nsg/install_pngs/007.png b/installer/nsg/install_pngs/007.png deleted file mode 100644 index b2e3c735e04f661a12dfb405870897d9224a8167..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 352683 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 K+?)YV? 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%0D{{OP<1OJwBGth~nWj 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 {rXVH5E1QL0EFjB#nBgK6fryKtI$>+2aHX6 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

phu(6{?B9l?~Ee;CvBF zK_$C?ZEI{Vu)cetIFahq*jVs#^0L5(0B^W!UVKb7uxbA=qo0s{C+J*BQ zJXOxBUfMGoJeIt5iI)s~IxkirJIZB~Ab%s$A2K;yzoKR{VWsRk0XaCI(4l`7sezsEyhyMaBUX5qetVwmUYsA>2jUEmb;CH)@6l&*X(HF|+Yrvd8J0Tr6c6=H>83K? zZOi&>6q)a=(&wOlk*`xoOs^lL*L3P1)|{oSPC7GCyOsq?R&c)1M#Sw>#$ zxCp{FmagTNZf=Ws%}OM0MZ;fe!f`dT+Uo!P3$Lh2YV~dVUNP_*f4#U;Po0Q}-nzM^ z+uPfrNMO2fDq*xFOp<1XLdpTIecBB$HY?Ur7IN@7u&g9qzAipwJ(f+sTz!vQb`d{t zV2ILwQNxnEk*I1H)-1H$+>_`5-zss*#)T7beYz^@(d+_^dqQ%;OBkn#+`Hc>e8&tV z0TS{%lUsz~kjk+LE_Hrc`H#6)EYf#u@NhnCz*Ahd;N4UUDQxG1DaZpve$en~Jrh)h zIhYug@qXV$I{CXMHj&=w`rOMMS2{|va$$!W=;Y2`J9;<>86I_gya(-c3KJKOa*I=$k|}I1Z`0+vC79W&bT+z?g3mW;o>e7NJvKJ zu~64H6e&H_GT_2~^gSgffaJlbb?i3hZT>(I8!aujT9&`ae^p9>@|$3+;Y5rE9T!FJ zGqvf>`7+SXT0BU&2Z9h01NA8+8bvOvZ~sp#_SwOB*clCmKlgCV z;EstY;!ui~HgoTXW_>3ik;$#;x0Gj~1aHnqoC0~kj-7$)tGMtzxxp9G{)ql=I;VRE ziPL$c0Ui0bmH1fG6>Je^znen%S3a)0j-u~65v6*>3h=&4puBc~Xiz}SN_5Jq5X<(5%d_ceUo6K7es3Q zorg^ibvAlm)Q>ej(gON~>X;>6?WT@BK||BXwYz~!r)+tQ{2?u<&!T(+cLPB!t0_W( z7Tl5x=2r$%}(`V@!O=k;QYZx2PO2}FA1-93S^s8RZ~B&YFb)fK zmLDFpsfkYVim1}Bdy7gy--?$jwJVil2LTngV?h2WRQjs{@-r5NNYv|oz20!q>KQz; zK6o0R%D9oAIFz#vBz0d!JuNCNsGxlt(L5WF6?j<|3Q z37LM}cd>E+$ys8LxSy@hWIeNmsPBEHe1m@9kt5>yL8*+nY|TsL{W!Dl#``||iF{Y*jNywCFMf=_ zPG5rvXhy(xkz(XH_um8rcp_$n?MYo4uk)gGQOMMO8Rsa*UqOYDaG3k?J7OHvho2$D zdLoz9BRcGTIVNS1v{YO+>Fi{Ma+7Q6lOi*Hj7bJfOD~Eyo+h7ts-K#9GQT`#W4r}6 z9Pipqodq64o3ybvN`O}E(3hs)!@gR-jX~9ZGNuMVISy$J0cd^Ug8uQ2z9!PJ z^YOou?@?d+!~=MRs^Kp+=CmZEEn%{RPntV*J`2_RqfEs8WmK&MI>DrIYW%g(aR#6f zw)IiOePRE#ufM3;#)4mP&ZGXh=P~>PuoJhGM4vi=MT4ikXXNtpC=^d(VZ%8Eb z6grNCH-|3zV!_;j%W7;B<`_b$aZYZ{fa*=z)g zzVzp)b%PKIWoVxT{Fd;wK6MvdN&9oL)yS_Od3754-4u70lCIOb~!T*5bU5~kQ?%uJR=G-p0%rA1;)6m~(Rw&J{pi^h*R z?eY6AhocI5D2IZ4B(7kM9uq4?sJXX!5IWlAf^$(vCT*$rE*JR!Qy={hF|+!A7ykI4jwifz z$nSaJ6{CRP=gPo6PsB{p683UR0Q$uRL)Kbr*qbqAudwnfqZ zs+vJxL?D21eNASkY6lN&8)Xs;*h~xSA8eGSIYaA35=3k55PVbH%p5k!v_PUo!H199 z5>8N}-11(^;F{GnLpQK4F{zg^!@KIEi?OwP!g@c&@Q?_O!7qs8T_`dZd+LR<8V|D?o*YLwT0AYq|xIS*D>Bf&x39v zQ=DL4nX6TzvnBLm_0A&k-rd8F-HbA4IkK~@pt8h^!hN#mxn5^ipqNa{To!AWxVz!4}941N&Ed$6m)Mc?W2$(x0Zt`j9?p#l!J z-9Xw1jtQ+}B*L_uKaet($(5W3ng}ind?)~91}u136z8<8XQ>`ojvaj(Q2?fiRrqQ| zh|u>P_ZF3|YmG%%C-|92?~g`Z8imU#`}>`*JI?=%11-MDT`CXfrG0 ziHIG)v-mqc7j1dhUuCe?e_qu+9gWdq4<~z${_c4M;A5fmA8=8fkEz2kHYN?G}|=SsOdmhp~!={rwc4lUhgD~@a;yO4PMWAhpN zQF0sK7sy7$_B%QZ^Vij)tS2CA>bU-^!Ab~^O8kmJ?^_d29?9zZ!jZuki^377zJiBB zb(@ROFeb7azGSk%jrDrrQ(Ubq$0C%wRx=Tma=hbzNuFx7^6l@xnYLRTyYQW39ydnD z4uW=Ogf2tmkppU2b$u9%#5CdDcO?;6T9Ifp-2njH#Q-g$jY#35^)4sE%e03K?`Z|m z6RHeA{!*FPw9hgR4ZHQ%Q+)GeaDn)AVP%6#hr|PR0EPq^<$pIMj+NFwLZO0#6av* zXMQti7rsWvQXHq5#<@q>7ZK?Lmv#!E+igPN4*HFMJzKju5UxMkl9YS1%;K(3=H-V# zF=wlb%>~*@=T>YNpoj5AHpon*t~jgz4VF`9+V?NHlFMJmj1xpsi>>|OAN8dyT7A$PA?>7ZFP(he~n{`FldhE3HMvH z_&L;;N!kNQ%aUlp4Fah=Y^o0fa$8EEB7-(%ya#^oLqtL&gM-DXqNX;xAqZC2Rwdf! z8g&kRrBVuAucfoRa)aRAC|A;_QNE`hR8X#ui7xLU<_l?g)dFn+1vxNR79S6rm`cSd zUdEW|WBo94U~chaNNrCWo1Yc|W2D_l%p8rcgJi-!Y<>fI!hjreFrJ2$v~L52C(8zG zqC_?eLkX7)g}q3+4#+?n^QH<4)L4MDrfhirRo}qR*jbMtMxs#hN}Xg48yD+EcjiO( zv9{zPi=@Uj?bcSmU!o*qqx;U9ve*l&>GInCZnhWbLAND-=KJ2VpyaH{i%FbU*W}!t*d2JwQoQyvOTc`#5-mEtP0VQL?ZZbzU?R6tPo6cu zsRTz_1|rmKhsQiZ$kHrG-UNdhoz8nC*WLO>>v8^qLOSkoT=Nq(UP~E|94_LSjQ%4Z z>B|6*WEjxg&YOiSWGD%9tB7VX%_fg=y-&o42^#~Sq@rWuXx;TmLGCxaobsJ}IlvAt zF&~>|eDt)tqKc83Dwz*!&1+d^`&9H^+)H%6i0wtR4)Cp+IJ}Ng?+7;li#S>0< z5NKBFXvSVpoz#ShrA?k$fF5lP|L8GlW(N~Cvp^<34%hGjdGwa(>e$MaZm;jqqqp9n z*WY|hZ@m4Ou6M~P?yCsD$4qpoT2XEvAJVJm6bx2b^|RH!DRGoMV4B=)iWBjRnFxT3 zyX#Jws7A)jBonXU(UWQGXwocdKiTpJ_3J;-;8A%p<3oy%g|?3ku3k^vEq&;mZ!8DYPsqO4e+Q2FfmaxTOIT zOjp(Foko&Jngq^p@fi||;#5YajsG39Fe#nR4fYUay)gNt|_y~t%u0iQY4 zpNWdkr>}w8Eox*M4w4bcWFb=9VSCGbfAptR9=%LdcMj-vtC1;d`L5}e6do`__a5X< zBIvCOeKLD+Z44qzJQpX)CXVfw8dHRy-A@f4q`1wWNZ!@7WbzBzCjBgnT{jH*DIGHg z4_Xvnl*U0fWO41NP+FuPi98V?7UgI6%u_qX#|EU>RVR#yPD~dP=fhWknN954t?;o9 zJ0ma=N5-zLbE2IkW4pL$;FCPJ2wJpWJ9|};_9Xcf#gJ#{@@BY+w6AuEP;4`aN)`vU zN}Nqa8Wr^((Tr0_{Z>3Y#Cx@{wK%XDp*FU%sr_J&-ffEeFOasHIxE z?iAzLW;_~Wbl|z3?3aL_G$c3wfV0Y!KQpW~{(yFZZ5l;siRCxr4KGBvFPwe`p8z|A|uGdWBy8>i(KEU(BKIWO0VM5bVB$=mpo=cYlnvP z&aLHWQ<%BQT|=HPq4ACC>3Rbs6Ahq$23L{*cAZ_%$`*X+B(_S6;L1Lxcmp3}4;HC9 zCoU}UWM1wbRbnqPblsY@gRTD1elkntASzy<5-6T^2kEP5I=_iqtVPvjfkTb#eX-s7 zzPL!TO>)smRX+F5cuZovv}Lf+);ChjB#ie?0RCux%-nQ0;o)&C7|;U;lV1?7=!ZR- zhI(M0&G;N(p%f8oH}Vh+s%!JKiiykO@&=7F>3Cu@P&MG%?Q}k+lBqnz~bLhSR|82dpnA+(HX8@YmQ#bTCr3-MI=c*4+Njt5=kL5 zQ@MFeFa6=ag8W;$_NQJ?CPzi-yCVc2f%rJ1E-w54VeWW}rBfSNB}J`vD3Htr^?WoQ zq*<*NDd-~p9eACM!=xuFsC}e*+J0FOU=5+r6fHl#64w_zj_cNDCQ4KvWLSe*9w$k3 zW2M_#_)0>OShX*k_9-Z2ls_$c7rWPg?EzFeBwPfx8v_YSOz-Ha=LumkW^OWDo`@lC zEb5nsJA4pMn>9b{mNZBVKN%{58$Q$Z+TM&&dfr$kkxZq-UHDSS0*5$_<51;ZmHVa=V3?O`6B$S)>2kQP;0 zIp|T|U}g2k)0|nEZO0S_4+vjZwgh#;M{{K$+?@9h)h9+bEm|{=Ig1x-zJP4c!22Y( z<8L~>byH={Tgpd62aTqrnN9|Cn%tE4nXh7Ei|3RLLi;Z?AE#)+@hvc?{Kv-p6~B|D z%@pu1nx3+YBqkg0b6oIXJ1|p?rf3FxxLB7hIxcGNqeEbipPGJai$%{qWeIN(cwqf( z-;s9{BH;Ry8684YASq@z*n^t7Bp!;%rKw{0ub zZz_FV>LEHJ-47Ayr3wZ^xmtF;(&qJc2Dejuk2~mtnSao)>SD@*p`G7eJLuN{n;8YTwU8Kv}hq#7QtP#Q5yhwSbg zDh9?P&*A|AO4>IwSkJKl0Iox0;&@i`YE3Hz!lq}*ddaf@kk%BAr{7RFA6}J%5A1>ZM<)*I&_1g8GkK+_ZqGaG=Dk^gw-V8Za;-!H_sy zV6Q~SlXV#!qOJaxkT*_=3w^TD7xf&X!ZVl^(I46kN(v?kQpbbn0eHeQl&8HsDCbuZU?;?-XsMoLXk!NgY$NJ8L2^*DUp3Q-}V+z1JY$ zby^tra@)e;b7l@s(Nn|mV_pmTH0yYh)kOo=D*>d8l|*iAscbpJJP~vmxFY} zOasNhzFJTmIU#$=gsNL0yGLXbVNib;^#Z8S%&iu^OV1i1vLKxr<#GvFGE=Q?$afa* zk`_fe?VXI56xZ{Ks6U(-XTxyMMUF5R$tvS%BCqYFlBhi-v0bOhGYUv!H9~m)CwP+K z+1uAz4UU7S&&~QorZO!PRoQ4`JJ@7vsXI*YMslE<7wpZzhdVcdRxGG!FoT#f+bthA z6On}nMzHXwd3ppM3S$yFEn!bYP=jaJ^7 zOy8>&=2C?^HX#J~i~Uik?G0(1>p(F6g=sLO3mtM5-tI+)JAui+dIH>j2Q#q{^tPJ2 z09IA|c&aXaM&U7P%Eg#>Lu0%2rcwwlyKVIuh3AQar+HgY_?RtrQ!SSNh8_gFXnwGHbKW9Q>bS*aPsC zGxm#XTjOWcXEu*0TUneaqN~#{qM_R?{$JJ&psXHw&|7Lf*LbH7NAA%It`e-SL{eH9 zOE&R#h)Gv=wd0Qt)vIy}m}=Dve%N9d83m6DH-MQXNVxTKV2u0VmW;-tPg*KX_bu85 z`aJsP=9AetfjONhWMel)aGw!n~1GL{aMQHd^Q#!Mvd__6I%L_5(id;qFIRlh0} z-ET^6%?Pqm?y(akDN|@sHsA@9C`S@_FTS^zD^a<0F@$4;G#41L4%MoISv+RyGoBL9 zZVvb)V1r5A4$XLDB7(kbnooT~Z<;iiX1Rr0nbt@yTJc_PftJF1Mp-KYWt($a7?ep@ zBm4C5Db3X)^;^ngIX+un){=t=(iUiI+KoAE&SZ_-pV78wd#vzN98~+Ff$udRJEEU+ zA9AexxIv?shX51)a?A6)8BfxUVODfM{Llo%`3#*MX6QG)UTaASKjrcZ*=OYmEtuQs zt4n&^9s_IM`~Vj4$|?@VH!r!~&#YqAV{8`XEEe&LC%d*6@Iv}}c?hDvO%)uP0So8f zC&!Rl+`3S@Nb)gSv-7TJbDq;LmRJVIP9`EsK8mobYdgBLCtdA@ve(uk{~P&AUk~Rd zsDZ(?`-jX|6{RZsrNs{0IdW>i?>0T_V%TKFi7&G{ZO4d`yFsABM(s4V`{MvTZW0ty zx81t)2W?Ja?8fX!p+J3@WXR+D+&TOX+gMs4B^3nv&H)`{E&M#4kq}mu6%rd5aSWuruGOzH*asU6tlr2m5pct<+kXb|uhf_mMRHwh*f zCdKrU&MoQVPkXiS^{Wfz+99U2O&fOSJn5-*IKzLGnerk#87*-*WQ{ruzOy36Q<@HY z`?Wld)&&|WGoiunmj2rIGXB`l>9B%=zxT^dFOvc)+qLl|>yH9~Z1+9^mD8y{oTCz7 z70-7GJjhyNH0qfPOmf;`g$)wr5)tOOu)ETJD8Xc>+A^ecKgeR15U)XxH1Rq}0e7Ec z$^eY6&p^_mxz4G`$t|*BL8O+B^-Z%ew+OY6s5*vr^QvdDYao15#zJRd9QfzG&b;PKzD~(4v zMo1jt+~M9h`V?=3V#reWN_bmF529U_r93+(M}>7W?04qAda^*wn%j_~Uz*jkqbu{fxo%tjSYJ=ONAi*!)SIK?hMc+rAA)cBEIe5D>2Ou3G1i<*-l2w%k7NLa&X=Gqa zjy@V+!oMhR#m`N#m9ZD1at$W5CVDkNT2ZTpf1r+MPXClYYMcUt!8-s3dZiNY=q@gD}VD->}^yYwl|4xCr3e$HqEB{Jh;nasY z!71Aes83D-GvXc3@#+QJ&7;=l*5NBJT(T)CI+P_agP<93F^=2%9a62$8&Wp$6xL;WgfP}L`S?FZeo!* zlEuUqP4S|3={Y$ERgJqVL=yTRac1=lup?rvZO6VI)G3kTF-u+d9B1z3pOi z=OKHa0I-|Yb9wyA4w>}zMPEklG-Z&6UKNXEB2V^^Z9Wh?rO943toX?W$`#M5ZzavP zU%tTN)-7g7<3axt_>BoaimCCxxm81rteg_BcCoLW^Wg6xAWZNEL8&Xy0w>Zg{X}&G z<9s=yIu((0TWgGH&2YNH8k+@atAEws(iV&s&z)Z0N0SD|lT#z|id~ zQE<*EoHC98*TAPaxX-yeQwBFyUv-Nv^Gq;XfO-R*OFEL%%6vwk1vJUrZnaG41ApUj z<6POH?oKE6zkY5Ie*w?oP$D^NN7?>clHJNK3_+*+yp1V$C3vW6Wp*Ey4_#@<_b$ai zUuev~)M31!Md%QNnYE*b(8s``4mu&{6Nxqs>*Gm@h|e3<>jwU9D)R4-+Q80 z3=V|HR`y`QTdFGbYNUlno!oleYWp0OCSvy5%T8VrJj(>%e zmh0%GPoR62Z;ieml~EAPlqM>3XQDW*udTFkj_z5q5!6HH&hFO$6Wpl7I%ngU=^=~e zncPl9QEAhH-D5skRck7VuH9+=4;)OFx}8uMMqfl}sfT>b=r{$+cv>&rM6KH*>$fu8 zUI*r5%5ZFUJ)M!Q;nbo(ytea8b&BM1DG_@<_Z+&|X(c&=^4+x|y3ZTc7t~hyS8X-E zk4SZJ2qT3UyVQ7nzR!IWG$?Bv$s$nocR?nkIXvZ3Ha2?lp*8k3(ZS`4RIH8JM96@_ z8xZTuj6p%f2haG7XkJu)P)40Wg+3yt*9;=C+W~}x9VT&!JEtfu>P4NBz-;P@X^l~H zKrdrLkA_RO1wR_Ob7ZEEuWM8cb%1}hZoTRMrS8q+bvvp8(N*6$cgVdNAxr@@kPs4( zNkjx2#E77RjX&jyG)_NsL10z@j17xqR5Obv_^&?2_Py{0wg4Y z5Filly}9=cU-ch*SJj$p)jsDoSjoBH-c_sCTD59TReKuL&Xy#?lFo&4DG>y|hRIym ziqAo2ap3RT3$lL_q!tOrEj7Xj)Nxwct4<{qj28#rtOQ~m7xBu`P{MJB$I0IzmK1(f z3Jxm>NA0Pvy4sx(6033tffdvgmWbo#UOXYN3~TPDdXCR>P~>)l=(NqLUqVj|&kN_~ zJ{31w6v=q|Df$ZF{5QQAYO2?gYk)uconM0g^n}OQycP!2lz=2yO}{C@OJ4MBc>Z&r zftdpUfM598r@$Y+@ZvrImFlK|2RQd$A5Rs}I7R25W9Ym)q{4#)~Uh(=04zk`$5 zqYGT$@VxR2TqlL^oz01gp5M)EBl7BX2!2Jqa-IQlo*qz13S7S=ZbE{->wV{KlgRF-EJId68Spuk!Db?;g?bGpvipo;*8bxP( zQ#?R1QThtW#Qtl2J>EHeM=1$B*2&Hg{*M$*H&Ihc)g{iSbDWs|ieIU11sysDNVBh= z@uc=bzygcNQNEzfRh?0!XZR|<90K;HiPWUlRoQc&A~R-?KOMx0l^Z~Cj-}l&ySHND z;W>r}Zw#90YI_6=oJCQe*M#oW!TX6xzTP7?wNeIk7rL&+}hlf z`dNmFYEH-&l)sYKlT^9ZgYd=jAqx4QPIRES;Fg`w)KRmR`3gOk#=#KF`owM0Eu2;Y z`WQvPpc72%DTh;?uEt}`G7764vUq!cJ6`GK$T^c^*)BE+iu~!Fpjz-LnG-(E1}tWV znw->Q)L$&j*#Hv8XYr_WQM${k27MgdJ(MG24k<%a!mbJgs+bg_ECoU&WWnZ{shmR*=#LOjF-fj$9-~J)|tw zGhX>(Ln;}j3*?0Wp7T=L*k%T4<9i9~R;SQw4T3(JNM%U-H9qvk zboAFAn}pNmQTn)4eLB)%gqrQ`Qf#G76;g!9NYUhE(NdZ)47vM|!7ux+Ri#`#{ERlU z-v&QmUqrZg?sEGJbLf!3XKL~t^|^+@8`F=i!oz9l-%trSk-EqcLrvtzLNOvG9oEoc zBuBnrwJ9hlwW+mmLjnBD@;^fKai`mmD@qrY6Dj=r@# zK5v?ivb7-MSKZQ8U3%ZfFuC|hym6E@kWG~5_x@^7?-?zbon$5xiZLRnf~yxpZEo$) z5~$S5!h;D#U0>^>#BJqjt1rt8ulkGMhNu7V_rm}G;$MV+dfThv?Qi)jc&N=63MZ3AVf*=2p?}WGg{U5`%|NL@z#h*PNzW4!`8LTjl*C1}u z&5+Q}9M2%oAI{{}1O!8u#Uy&k&cO{ zSA4`A7srqFMhlp}Q>i;dBiWHv93>B-C0p>L7!vw-=7qogEhWhH&F>yIhgMMxYkI;$ zRNB!#V`F)ehrC)H!5N%Pv3X~gc2z+xy?C_|Iu@use7WJ-4f6(Rh2q-R3W?f z7hV`CvZWB;`&SYqozrutRYyljE zy#adN9D~<&i97I#$kFQ6&%-^x)&6q1g3a_nS(*0Ht90~cThBDB8ddxxPZP9ps(41^ zi0k#SYQ4-20%?Z0cmXxBO@anT!)v+14eWDUdinMzeFOaJ^Irg$-s^twGe7+-`1N1@ zDL8s4!|iW(TX@0qehQxVtG@~N{-XQA<(EGKUh~SA!uIhS0RZ6nzxWeyhdZ1PU-^|^ z1CM|FH^8@j%cJ4&;X|->?8C6$J_Y~)+s8iwyIY?j^iCc75S)MB8Srb*{jc!LzxrEn z@B7>rF2DRM;We-PbJ+Uq^#G9JXa4ICz(cu;n$w~O!(zreGz=oeeMUB zU;dTw>R0?JocinsHL2Q#ZK*x)IZuNJ-v2WA#&7<1c*Ivc0uHWD!SB83Cjs(SmHGF4 z$JfK_Ui(^j_#?g+{^6}}gy;XlQ{ktd`91J+|MzdeBOm#-aQ=B`!1sUGqao*Q$dKV# z&wMg`*_Yl2zWqDDA0F_4FM+>!=^w-Izxdg3?%5lNk#;qzoYsv2)nlNfr$iGGu*-44 zQPnF%sj*YfJm-U(J%cn#XtOW_!5GPb zu~=hryJOGaqoehvF9lLJPc0qb2@~N}sZ<`rA|R)&oL1zB1|P`0iJ6bVlui@(0W_S3yk>VQS*Me7BUH(EkP(buK zt#Q!wSpj~CTKD3Y%^h(KVLmt*g{Kzh+*1*zt7EX-w0vcr(nH(}Z&8qE*=&h$xl-WU z2n5&WMS{JRgbzgQ(0S8KBvDE7baV(xPNZAEizHU34$0Q>A#xn+oa0OEMoz$e0O{z@ zANTm=%QYH)m0`cRff<0EkTO;{=ZTDum%tqa*65T=Rv4BHN*^7*dQ+~K)7Do0^iTgB zu6pYguyN?N@QT0wN7&llfiJrBPH^7YXF<*YuY1*>!KXf(;a@+w0WW>km9RQ+Cfw$> z=fR`D`eE?&r#}PU_x|_74If*>&-}OFgaZdQr%%##%6aFW4LN6c?JNHjZvN~FK63LR z_={Ix4I2l~hO^E(3g7m`{|Ha{p&x~}Uv(vX>eylU?LYh*_~3`Ghetl_-jLTj008ip zFZ(OF;tj8X183YG^5Ki%X+Qpac-hNe11W97dFP%pmZMJiEfZ@>Q>aKm7d{!wnz#47~6quY!v&z6f6Y|GW_X?hUVi z58QYw{L%k-Ej;ug4~A36KLTf+eH6a+TfPpS@EbIGtRU7v-xoT6}<*HGTf zkxLc1(o(hoR?u8W5n|a8SQ`stBY3mhpnYzs7 zUOGV3q)#oRR3Vral+Y&9EHbhokoZ+C#Hnd)JQ?e-#=%V1dmX)!*t&$x(~OF{0ZZkj z)p8qif-~=W+R7qh+UHU>?K77+=o7E;ob)Dd?V;i4E3i7}-mS7d3vGRKdH=#ff3$L67NV))d( z&_a68XCbs6*wZ|uN~e@8WZ8)eE`G+NxvAj998LF}v5>BKVG-e1#of-;`5=|PocqO- zFDD+vshNi2GWzU{(zE?;xhPOR6G#L306k|Mc5kD_C8s+;L!3tc5nr)yy8wg5=2L#f zK)|uRzXbFut++I!b;q#tani$lMqvO8?87(Q02>F-hSkPlfCTW_&mMos?XHk>hIhXGN;q)lg^)H5!F#X00ZyIT z0(~;*CyuVU_Bwdm)&B@rUwsw)uPfdLSG?&ec*U##7EauH5H7j+LO5_>6W;RouM=2a zcc(kU?)K{e0O0-azZNzQod>G}XU@Z4!J)G*gbW$3`M~w?wyWPY;s5)q;EFfB9bWmG zzk?IE9wJh2SKI(z_t&q7=luN75_&)Sqdy8~o_Qt!0Nnj9&bWhmv7@L9ySCj1ra2a3 z@s457xmA5fVFt)6t(%X7@R70!ukB3QxtLO!8E&*CSznboU%_i>y0kFy{Z$MkNL%iV zE`D)MLzVQAl$Rh5Irc<&q%n_PkEdr2K9{tt)W$|-o6v@33kIg!P8C=q05bUXc@*s_ z&zo!@UQS#zrSaP@slswS=l&d@T1i!ennvH}ZrUw_8Y6P1HeOk4p+ve`gSNF&^N65F#u;itd2DIjleO|0~&YzEqk1mq~jEd1#NnxprD+EyOTrIiA!RD>7 zp=-g!8n45LQ>x2QyCg?PENqcEjd>9X5a>lf+6m4p#*4M{$F$lUdQ2u$b4S-hubUsA zGae<)FD8vxn-TpvwG z>iBYov^qe*Q`&&t-5q%Rlb#9>e!yMfVPF1b@YL^r5_UZ7dQ?axg!7o?C$QuYU9w1ctu9LUa#Sszx^5Tp!?nq9`?{Lg{MCG3Gjpe z`3dkf-|&5K;^Y>Tg&>}|n?L<&c=!8mfP+WxT*q?Dae%YVJ|KA5IB;eg(<(t)Z9?9; zwbEW~9w8D~uh)<^&xG=#wv<)?dAB>(gGcY8>qSQPQ1~eSge;6NVpze-znT-UHL#0M zPJeqPLC(y;X*$mkAn@BNYK}@gy2K~h^%6mZBka{WiNeCQhtB1aq@-XO>+7;T2VHHZ z%o#8dCi1hVV$i^8Rf3*LY00U!(9yc45LmA>Vh@%jf&zZjU^7g2IHl=DS2I5Qb7>vEtZLk3iYbvJjxE9wk%ZVBFnEK6jya=N zCx1SRePGC#J@m1v)c}EoL|+G0F;F7QVgmj?q=CCNY2P@v;+n9o^|N-vU_U5ORHOE10$u5nxtu2(P=NY7~@fc4r~xu z#I184D4U}vz7g_l%{qL;ra47gw_dN|EmvO;Z+h#y;RP@J9k}+rZ-@I{dOlqH!RsNV z1oyq~ec^rAPj?Mmde4jC(7}VX@3CVi;OukSt@r2O_B=S_@S%o#cMVs*<3@P%KfDiK z@LRtN*S_Z}xX&f$!F#XyAnfey!lNGbD0tl!*Fb$*LtB{6Zxp3*beh2RdJS)V=f~j9 zZ~J5T^%wmCT=VX?!WUh9Cj7&@J_(6mJui-ZwK*^DXK%RLgtFoB!4KUC>-8EQ^q>d9 zJKlE+008cE$MfNWJKhf7^Pcw-bed{uYk~KF;D$EVH{Ad!Z9p<#;-^u_v@Q~7$immc zOPzEAsCN(mfza?I1w_KRCKk58*b0j#j!Efh^~cLZUiA9u8{S$e;kF~Qp@PQZtwj-i z7s&pOm{(~dOmlisZ_&53rh}K;gg_GKtKCCI{U?@T1}d%Y@8SLFNDWG{)v!Mg6ppPAl&rvkHG8x=N0h0=RFTDx%kd-$;B7KfBWel zhVAVg$m=yg2DtLA?|>&f;hW)%Lz{5;&_Q_CPy7I^*VD7z_rLGG;K|?p1h~iDzi`I= zpCF|bT=$_5z|Eih82sKJ{RKStxzB~K`|1b71$VkV+~?l+glGKl_rRr>T%;y9{2dGT zyU#u0$=~&OxcH*G!UcE!Linb~Ke6K9-8n|hOclM9*Qaj&47~i+e+$oj?sMTj_qr?G zzp-}~d2Phufz*4SqB`rA|(cvI7Gcmc~^=u^o!V{5xb=P^CqJqmwYfKDnvBYJk0-{LA)}b)Asgg)Qmc&OqNmE1LPF>K z>kVDpJ-JbM`2sw8no$dWY*l|`D1pYq-U`Ac^u^$vpD)`fk!*~PX|V`2IiidL-Nu;+ z{9Xtndf1mqbtUQ<7cJ(IZw~PB8h#R&VvN%18+7|J8PnV`KdKgdd9{nHao4QY+f?3z z9oDy!`y9z-9)7VbCS8<9!P#>iLoa3QjW>XcpfFx0YZhyP)+Hn`l-ydiYRz0!f2`A^ zacmEM8ZX2Jnxd<7(_$ao9W3A|7RMQ>+iP+BnIR9!1@g)DG__c`kZm4SD8IZ|8@ZZ2 zd-@)1N>)P@fw!ce;fD;n=`uQn>VG?%sA?M#slGnf*c-fqKtYoenWUzR%C246%)lsU z=3&reX9)J#vE%T_M|>GP?J3_0XC6HYANx)GeEg z?gTgAd<(q!%B$gHH@+X%yE}3iY<712;4B8MWm_Qa*W=iQ}Agrm6R4Dy1p=$I5MPY&&f+_e2w`TT8Yb6{97bo^wj z#BVjZn@(0mugr0{%)BIkloOpg#z;=B!6v~anq+<1%;t(P%(q^w1w^lZ<1m5pM$-_E ze(}8rt=^{u&PmP$Ouc=A*;_Q(ja(QB6)h(4R~Je@wY)IDo%q{V8XMSRBkG&Q*Ijyi zfTT-6P{PIS6xT)ZG3r_ElLVCBo0m+Rx&L!Hv9V4K#ea-wfI~a^?OZBOj%)xR7%c&{ z2O312m1D0kIAwnNm}Xx&L>k4MQk7ABMzYZtJ#&Ad>s^Yb_idxq=mDLAssI-JY(+S{ zsdpXpbkS}uKsD2CBJFqE*J4q0baaGCEuGFBKttB*Bc2O~yPRD}oy`(94b(&#Ll zHCh!?bwV8+dsPzi4jSvd3yfweH~>{T+4Do5eN~3D?_4Xc{^%8 zR7Id>Nf9|ouzl>qusV1)Y#chb)@|MLLD)RwcCcCp$3F_YJICO{3-1o^c>7gw*}d)!Hy=9)tIZ>G`@h2O&Iw4X190H*?O^M~ zN8r$zUjS*f30o&V3VG)w^`u(tZ_&nG=e7gcp%6j)CY#+Z7);lL4=QXT04#9yV7r@4$^H5%#sw__4 zat)-k35U+Q8xhf|&t4CC_Y@pH_ns4(ybD{$J`B4jKLr^wY#clX4xV)(q>btEij$xE z7uY;}etlO$&O318<5$5M=ieVz8;2nQz|P4}!q%}H;K*$s04V{ibB68XH^R=zn_#`W z1!=Vj8wbyULuXz%y&r;48+cbMgZ0)qvJ>$rhP!x3%Ao_V+=6X)>)49|FH;MNBNaY$ z1`YpQy9iHJF4BY~ryZ2meSoYoD62uoJJD79!`zwY)k_qNVGW8nO=6R+h9n2tK z3cyS&AqLxGVoXCgX}zEU$UTGRIV$NqziU7)Cu7_>5Ta>xta>ud^KgH87Nuzl%SnoD z$DnpE9xChg%Xstp_TGh&6=8PZwJJA}xA0{HBnXf6w;YE|xAwuE#>Q+9IVfuat8q)m zI?>G7A9pTHcr)w;|y-!kplIOFdU4EUd}ttj?Y^{Po3zXY_P74iz{vO+%t zdCiDF6WoCe>hJviC>jC-I70s1n0wRC`a0Q%Z#qQmGCJR3`n2MUf9^`i>ox40_!OMH z=|k2AL1&>;t9bI=hp&$pcnKlTBFk@gK_?UG2j2gS;He zy$fIem`B0kb1s3j+9dt!PxY9Iv^GQX7=&7K^%?=nEM#Oa?$7 z5lu6Tb9$23X#6~^;6$x_u1u4aq&c1XbE}Jw&xSEKTIbF!N_@^0wXkWHdhUU>1>yX! zo~}8z(H&FYZv5q^WviA$Qj`o5s+X^L5o5IQnQZp@mMCTWE6d8=5_*B3q5HY%lZ{eZ zXon%Eji!JkRJ%bdN*lu}JTtNCQXU8IaoCJD;-j?+=e*UE1Dz7CgL&iBOT#uBFHf5c5Z4EN&u}AfF)*?7s%138e2e( zEIi^(8jvp1If*S(G9>Tb;>@;Vdy`-v>IdbbwuKi}K7C_p;^SGk=0DT_dfz=Q?chtnusN&r}%BoJ_TCJOxVMe6n#!FkgT927&U3!DyD$hsW`g zBuiqESpYTkS;CQ;M4=J@9u9q}Tk6Hy!dbv# z*^SVp=Nbo-uE+3N%~4+3h$s7!uU(Or=HX{r(RT>4puow$L_~YK9!K(GHAm!|@ks!T zG;kl6#JJjLu9{w08W}ITR~V0B5&SS63$^Ap0#Y8>mV}?e{hfJq>zFUaFYz?zHA~(T z^s;sYn`=PN^W>(KrkB^&d1k4Mfj>4|*srai%tcG2XMV6Kl>As#b#us6SG$tIpMPa) zW8w9R5ssBSLLHq`H^K(DS2|zyVG#7LA>wk}H{TfykiHSlXsx1oVBdh-k&mkzBbk@v z)PxmNie+ozN^J$Pghhn4xGBCYYV;9vV!W;ON+<79fxA3bwRg2dnYu3jWEgcSCL z+I)=6w~dv5)>!jG;VE`mN@T7nPBclJsV>11W7;H_kC%q{H0ddWNo~qB>{^y!^T0eE zA;Yc0!%ID7i?=ZwEt0|%vn!Lp=tyqTmx}QYf*vs?DH8o@sqvEJvzS4VWi^m`+P;lz zF+fpgc;j2Hf`@#?*TeSle}lZ<21p4u4jhF8M=zMM@Zh9Os;E(DSI~r><5B`24tfmD z6j)2jBjr-wD4ls94Oy)!20ndSveilUWPe1va+Lt)M6m8WqO2e{a+0$Q(`zx+##H~M zu9g~!7%v&NrWcJFlDGh>t8|sQE;%2MS8@R881a$ke-ekKICwspP#e}QyJ&4PrE9ni zT!oI>Y}uWxYnw3%G+C3rs=LwVcL6W;-q-R%DuV_Xnb-de^SGx0xeU#&xn$j8&lodNVSusGRbxT_CBXfoPtR==UG z6)mMjnLDXML@l328B(Y<%bSjyj^<2ZTHMO07ssm8G1=;8z5;WCG$rRLI`C&OL}DkRoA6xyhsn55v=|d5NoU&kMx}E z=XG^$iIN)ea8D&*6r-^ERLoxrqm{YF&>4I6sg_$-#yT@btWJZ@sxE3+Y)8epgTV1H;?sm)^!-ojAPB(`xew96IOjD*i0$@G?=K1pZFvc9`ZwpSo2HS8lP*dUfqMC-GU@OSI6OwBg~rwS7O7W9(%mWXAG<-zYJh zu0d$~$O6E~soN=)O9-bsyGomzzz2OP=|8?OF>-gm#02k>j#)-_P}}ksm;~q^bhu*3 zotMgW%DC7B2m6Io>#>}*mb)7>YIf6oLVz2mc}RWS;=HI&pc|P7X6JKU!|7BWR8{+H96s$6j4@m?gw;6fy($KgoD@Xa6Aua=ZW6oGUa=z@*wGtwf*q6mthAbrA3)*!|^5OY=3#eXp-Yx zMre4jJ^NZJbK7oeYo_8}W;4B{d*NJ&J17SH&_a@OS;uwWMjTJj$GHiEt9401qpjl5 z!XcHH2F@O}_=n;v@MUq*f5}*99XU}(wYKC!I{nqh8gvYRTTGN*UUNF;ic2`tCfgOLzG;}Wrj{mFR$Cd-VwtY*X`Zk4 z3JFIS%iIzW4VLLF5@~{H=-Au*PU#%R!Su+r2^^=AqJna!O{pb-obfEG#H~@du!EmD zjrGW#Kan&+U4trC*}sY{>_wD`!4pw^pL41qKVpT$uk(Wr4I`MHMIV10+FZbExKd znipugQy+C7bhELtS7b_{EWSDrRx~yu+KOTHXGT5G>NC)=X-Ru9N<4DS>Ib&O@d(d>6YmrS8 z1%j}s6MY1YDhc9CK^T25o%hEiqH2-CfRuj5bz0H;g@;zQ^-ZSSEbep;4RF>~_uqu!A_2#CiTIC|}ZUK`%E6N6e<`k21 zsApM{t-Qx17zKB7hUjB?Xuh31j==aX(RKd90>6`7Patq9%Ft;qiD8*MmIRdRto)6l z5)a}EJ0Y!;7%IPNtSJ!A<~S zN5Pz!OL=EapGOOhm$r;*BIt}eOrFE@tQ8+v^2CsP8UMn<6ex?iF+fdSuxQBDK<^`> z{ULh}XlZ<@j+x=I8B!_noNM@zONq7AxNfr21M5Vmf%#R(FIL)&<_h(TWP7sJxPyQ@ z5N+|GDW0-OT$=ldz)2OiDYL^Dw(*n*t_q8gYLA(l>|@Osx;r8kga7vJJ2`A;*cE-CVBW|#O)jlb80D6xe%INK=X#VU49B19t_8&;tdGR&GS{`YcUX*!&nuQq?*SkA;U61v`s@#7C8o7v zrK@9Um+d1KCvhejfSzG#S{=An45Xe>AsOX%?C@HFCPow>l?Z3+eR z^S7$p7S3n&>zUQrxM@UUlgY!COW5hhzZRkN)t6P#MS#boj?3!R;z2gV=EXN|4@p@l zSjC(7{qR>E9UqzBQrk&!<3*0IzUWi)IWAu#y0fGk;-S7UA);GMr}kPZ8yYoBE*kZY z%_Kli5qM+ZeuvE|`5c+QcsA7$U;Q&b6KwO;Pq-j9{AkwUW}MLh5FZ4yk0v!hI=n?t8DS-Ou9PJGLKm<9Mtjaf}Op zY780~pGPn)N#JD+E~aFCNzMMe+!#;Gr0Dwttf3_z(jPfkszo@F*4k);OGV`?Kv(Nx z5|WD!oE6#2uK;~i9m(WGU3dzVaa&Um(W*BfPs?~U*%qaLQs8Rf`XsAl!Vr!Vf9)<@K(UlAB`$X1^193yC%dt`kv<+#t|a5iMKfm6$rnFglpMai%pNiEcNc9B@nLyeYUv z=AMDP4Om+%U`JA3wqz;`<0tV!&HW21{M{GA*LW5zKRbqJq#$Q_Q|n06Vgdj-DXFSa zcf{m(=0}F$>TS|zml9bUty&$`p!Qwf6kx{VQ_@Q4U)g5yt1uvZP;)eX8p!7nJJtCg zYzNWBiu&0)NzpCVXF&x9VX*BmmCw$|moMtNZ1LbSx~x4}bG(2~wY6T~Bx> zTUhkJ4&RBR3XkDZi6o_N6eIQxqqE29Qv)dRd2*9HG4lMbgq1!l!mFmU61GKVd(6Z7 zjB}6@Cpwtr$Pm@!^PM6b3H|_h>s1_DWHF-T zf&h5h@Tta`-f_4%xd69KNHDt4BG|E?21|Q!dZNuHEe*%O!c8zKCKu7;+S&4*_KV>h zddeNgR_3Q-A-Tx-B#7x;RHS`~{H2~*kq(;7g*(>fl4%Oc8-Ss)bwSbHk}w#4R)kA8 zbU1X&EED>AoYn6j;4u~e$SobFrz|e2sVSxHJ9Pq67cH$%`;-K&tEm+kWASVyA|)yt z%Avv^5Wh->kPBXr^B`o0a1w<}1iX%eIkHAfs(Kt)wi0f}s;M)205k?QEm@Gs@dpKg`?gL^g>N_0Jd6IE}0x7OqV*??| zup&K*hBZB42`1LzI&$A*=L!pQawNw}4W(f`6{BLJN`yz1NNZ-t&C>csdk=}m4;uyH zu&?7Lb-TJCwjy{1)O`d0W_a}w^8{41$Uio$IN9ip$7#nCV){;{azuntTjlean%>}6 zF9us|VK}_-m$+w-ljnDxKqm0mex>&5JmjLl^>ex?CZkX6N=`X%eQ(fUEn5u~b!vFb zjF!ZH7iOP|%OaZu7%q=cX}(w|7L~Z)TE3ND>{72j_pupGbUF-K=a`^p4D z=P_FQVfP6DkTP0IGd$cKd7Q}}*k(D`GfzqO(Z^xTiX*^m9IL`|2Z!S8?31;bQ|~Si zRaWlBfYETxA!@71@XPsd9^CF2s`;H7W*F|kW`sIx33T2iWb2T5dkdP_B5GvDDOslQ zF1>nzrzXLOie=*sRxg?qaMVNGB^p1fMTXqMd*SH~m)L6Ypr@OY zt2)GzYi3kcR<8buj;u=W$nCeLQ9Q35DJ5yc?rE;094d~-Oib_}Zc2mt++R3o=~Wz` z)QgCgNr>imOh?X+c;aVZ4JO|mWz*uU!((p@)0v-X%6aJ;giD>&QYpv)03ZNKL_t(E z;a`$J@`$HqZ3Bh!tYEJvS%Jw6&Q7%^s67AiixKuQngte5G*M||;QUOBA5LpdE;1g8 z?(4Z}_vqQGxu=P11URevfOfQ0+2Bx{z=+MCKgCMjTC!=w?X6gHkv83v;Nma?bbKV8 zP4oPPr%%Z%>M>v3P#K#g97i=_Ssuxu>4bH+)V0#UH?|Ut3OHC4d?^1}K1i!D?>1oX z64K(!!f7c-XZC=9_5ODy}LpLNPZp~ zcpD3^R;NE`d6lLv$5Kg1xDi*=3I%bI>FnoyWkSDgg6DITL0L5Oqz~lk3($R2y!i3` zKxceznHf*$7-0x)I#}l3nTMV>(5s9|Ir8z#Hau1jE+RX8hu3t5~-$&`yN;~!;h&hbgru^ofUrr#eUl{ZEHxSJ!3r7e~#<+ zfKTPeHRx!9;nmtqa>I{AWMt8^eYY=3;b`Tt7gcCm`y`yHeNbmJHLE8n16WrG`}pg) z@zpt1_Oky}JM|Umja!HU&|>vZ_Bb`Vi8ya7k3q3dNz84{;ZTblZ3u@gd9B3wXgN$~ zy++2O&0+H;shtv7N@0&a#k6hc)6@7c#yuM?oKa7Rsp#vVBELM$gs5V*I4Ux z^$JTU9bZ^-;|{E#8<_8e4kn+HY2%2v`~YHfDRI8zuND?(gMcm!n)Uhx3v3Z@NgOEX zTJqDtId4g7c{u5uFei2{PECAPgPvg0R1UR}jGYUV@l&cub<&_EOGeC10;nz6IrQ|R z!lX4Sw6q04q9BM5f8LbIHIqclhDOi&44>?+GNn{yrC~gwp4&85*l!yfo;^+8dVexB z>>SHyKovflEOR{%jWHSZqDv}-D35q$r3}Z#$@+%)@ZeB(Ll zJc%I_-eV7?J|mT3iNG=eEH(1ea7Nm){k;|m*X76~#*FIug( zAlOUk@kw>o?~S}b4bx66>)|K9DLXQ!4LH`8M{Mk^rp{st$ACHvzmIyvpD&7*gbvIX zx#D2ujAeu-TkO!V=zX4WK}qCi!YoibFI;v%uzqhd&*Y=XcGXk4&CoSmLzU`F_-LI)1oD?C342$!>kb>Z6)>*B)}4A&6s77 zYk5~|_xJC$7L-AXlF+|S@^94G!`EH3hLZ*-12XU>13u3CF;Oo!?&RxfD&6G!sC!OPVnp%hB;K@{$NB&-SpF1e_mhjQ7x3 zN;X12+RuYS{wYVkJI(uA(Ae=V$#&;{id#`X$+{4Iq>;|D_D+ToqR}# zS7R5|@gkQFm*ZuryQzJwP90>5?08JlbCT0ghCf5 zWUb#uik|H$MW&h>go{PHBO_H^w{1?C9XZ~l^7~w@7>&QGu zb;N1IS4cy8i0daWY&}Jj7_?suh|mTK28hTBRaZq{$NR+@(o>g=l!RRA8!63lddo94 zS*meRldpZZi>9s#;nRVPIk&X3>PGjh(#HV0H+5el2xiK$fxb6-;qV zJP|6eCenzO%|4TVzH3fS+SgRP?|fUh8AvEV5nu&tv7yF~nC8)`D;>$5W-&ZOwJ_@; z6*4W|?NN?mZ#3$~7sC$CYM#8K4s87TqRSwlq+@kjoDt~7?=jhb(<9o$ctu-Sv1sqM zmM}pyLP~rIDx_&YT0M?!S|1%neGQt*lb7pB*c73Qi4SN4ww2IpMJGo%K!frmat4W< zomzC5FP^LL5~nO*Y@eK$h)h|J4#+~akvMUzQn}LSI08OPae2-o2E54L*xBKr6TcEW zw;78EIVAK(XYN!roScRF06p#}r?nX$ODGlRL(_`(DwnKu=}cFrSjlCgK_YP_3E)*h zc@~yfy+y@R#CRsl-`j2XWG$M|75K*CG_LyDAeTi){(<6)*usIB5{p~CPZ?JtaH;0+ zvS$)Ai*1jZ*%UC!=aY)97dNjCCzRZ31u>g}tS#~5Aw$KTV-K6tk=~>2;7IZ`l8f~4 zL`%uv6T5xhbVOJKVxB6CGpSj9_H6)<8(vRgrQ$1`na8q%B@!wZ3+G7`j>q#As6Axd_vRo zH@2r^%%gDnxt(SKq~qXVwQodsEGOD)bBd6&<*cI%I^3EVXT@#?CpMr9@*aYc>t&;_ zH>SBCI&3*Xt+JUCQ`whk_1&S{%o$@2Ub`-WADxRAae+&xzlOH_h^Dub^r>5BmwHmv zwsjUVs}M(Y;s*=IIl`l5Gdi{^y^=>}8+ME%+drlG@rH)6=gU}fGU^l5_<#e4Rs8YF z4T9l7N{{qMW=E4I4K7t%sY5pToFTkiMa|c1=mdFPso-193oV&Nt%$+Ssl&X0Sk1NA ziJH|Dq&WM8g+N!9P<>!}7`{+%5JHP-}5-L5tn zH$qO+pKgZg$Ur?dOsME>8DUr?X05d8cC0%atl)z#qL)?ccXEO`@bXNoEjK&xdkMZhXLSmY#ThM@k#9uCYvV3tXLn79rI9 z;9dxMME3$e(&hwzuA^P^wQa{nq7&2gI+&;gUW~iEU>}mn4Kt-Fci$s|*aZ5iXhNm| zIhjBA1TC|Xw2XYx=Mo(|i^-9ze7rU^RaOkyOGzMJ0OATr5tzf48);_J?%IVD5K}=u zYC40j6l5c^ldwEL?z5W=eQ=ye!e{v39=pZp82f!rr!ssc+?K5`Ws6RSzR_Ifq~@Mv zQzb%2+HKF$`v7Cbo@G?$jtMix^fiYy2&nP}a#Sk$=_o^ew~4%G%V3!x2nrVum%4n! z88-%u&(8(r27$NsBedn)%M-x#Orn~IYbCj3S6LC28g5pX0qSQn=JSLasVVcVfz+bu z{L5cmHBG>(!2<@DPlS|z%_OJFkSFOF4_ge*p!(HSVR}+RS2xu) z1~VA^K)gsgb=t*IG!BuhDo?RwE~~Kh%jRr|g-7IggjeVk+#mI6blAMBPKP7Z3Y(nL z7uhwpdtx*EP6u`8s?491pU~@sd`ncfjiKnp1DNSC`Q^FNM=!O2kgGEC=v!7M-}Rv- zmW9v8X@>qSgPkCnIS5KdI`)G@X!vR>P8$-m-T!)3wwyyf&!}p7;-bYdjknWLP8Z%$ z+L=`|-QbTDaNQuNu%EUk#nX%GibPGIh(&tD4n|?;bSx=m0(u>GmwIZB>$Hr>kyZjF z!KMA{3m0b}FD2@Y-H?Cv`~Jd&kFt&kho4-y%m%pu9Z>ryvNrR1Ydowi+q$E2TGAqc zYG7>mod%+p#A81V6rjo7JrCHHPN4oh_=I4w+mKrzs#SshW;$Ni*U?bvg3}l_JijvG zQu~Va1P+D~I+`er2C9~*Bld!nw!szT%fQ89 z%YIgDv50qZn)AXtvl;Yhu{lm`As552U+&Vrdee+E)0z-g6 zxl!j=;-Ei$HV>{U_veV%Ytc0k)Q~gcbsle`8Np-#^6Zb(wQxC_*kSmI zaw78-`5>uXI^!KfHdrkAe?Aw>cVxSX57RuC_LjKHf_`rE+JQ`rE`Eujo5L*0xU>3> z#KJ>+BgjI!;(6RSp9%e|P3xW(V@1Xx+2UA3%iiaFbl#8xs@=61qHNw6)x1|dym$?=ys6^wp-s5&z3u|%opS_Mt0ai0l;-*X zV>yq1;R}_W-3%YN{u6M`b)ROV2++nw{Yru?8Kyn6EQF^yhVqNrs`AN1u%N7tv%Ddf zkm7tPI^v3+R%#qOon^CJr3yd-QWyD3BBm0+i7PPC;hV)v4Nn?M-=xs$JsKj5m$2FI zf42&InJK}?<&#K@nIA|JP8QY#iov?>S_Hcu16nbX$WY&@ji&=-E=<=+6;}_PJ=z_3 zlkSubH7^?rURWz3Dhe@t%-eed9ZhsR9;5vYe(Erlyg+dP zC7x#hl{BB`{<3IP4%k-bTedFDHOE{=p^T+UU>cWI)N?$ z`If!xD-)L}Em7(sU9*c$F?5wz(`S+VMMQZ+Lf0%xoAIK#c9KDRFM`G>Umzw@%T1Lk zfIb@r*O|3ZhE<%kb&mBJom1W{Nkt|XAyd2fIQaV)EL+t}OALm?t7hum;Yyu|&@H+t z?6n}sAn%Y+pGhjrj;CegUAzt+51tQ-oO}2**pbD>NLDdexL-gDO}>Juu#gdN7KgN6 zddmn;4#JT}1jYT1vDp?nOObcs|&K`m@vb_-4I^*j!69`|r|IqK@WzN1T;Dff#P`J&@5jx`xXxoj8^$&_ie#DXJ~g1$oW?3_9? z-meo%QNn`wr$6du1ozPiCb716-6fHq_)=hlk{tpiNxPnzvu(j9-&n-KhVvD-L(G!s z8DUK1bjcSZMx0^J&H}KVIzInx<0u+X>KzWM^Y2Cky(|PLvm7BU z@&=|I_03@eMg0OsZw>-z(Xr%e#_udEz27K(3 zCzPd&m2%AIYBEdMaw2)pHx+6+mea&anp=9c9V!s}F zZfV6KSbdfHI)4sog@V1gqAij#@c~-8fcs`;E;3*g_S!m;MH7pPPU{2p<@!)sK%Rs@qy(hvRN< zCCq)FM!sijx+5W*b}EWN#lrbO;OdH$7NV!c09Bzh|7FCDXd%P4aE-~P+|xI-fEd88 z7e@|aj4^%YZ-ifFhqj&6{3U}Su?gqCxX)@9TF^8o1nsYJ$@@4P51qPFQbB9d><%x;n182U%;{0vI-X=FP`(*v~mLSfu~rc(m3o}lYrW%96~;DUo=cL)_&;f z7B|^sER@36NCLp4hXAo%k{1#3=fP&URe~?P^KIZ^4}Unk`fvWhxmy2!sLby zg~vVaad4k|-q8>Pb`|ZXD>p2vp&WSDi{znC4JirOyoXo~C~fm>CHJkbwUXB_T5^y?{du@sbca-I!&SHU|B? zJtk4Cr0G!7hC$9poc=%wzj1kAi21ZU1vOEjdE%hGHD-{MU%VNYExP^~ve5K!b|5V+ z8=W^B-oHnv@UcI%dY06WY(2R_fZqiEhpbYa0@>8VYc=)c*NZ)yS<;BO(b2&UGDe zkqmB1VKu5gf^b1RaorzOBWuLuNZm0AAVxe<%N+IX7-{({Umei7(zUCHHty3)DIa>m z7r+e2022VqI0=ltp@}@F!J-5W165E?aW%}u2V1xsmHBz-yRWO z&12*OEnhW>yNWiGIYis_XBsy41=o6kfbL35Nfp4v8hVU3V7|SAWmD?#=1F(Kpp|kP ztD0?K>NGU9aTgAZ5LpK?7D#LmL;*8m;4_n1#9n6ST>gSQqtRH>>}5&9z;VTGd>A#E zU(PpEHg;T19*;c%v~N~oW1au_)fA4qH&k4|Z~Nbpn4D`3g3B(+=6Rf4+$EaRcXd!3 zqrXx(+P9@CYuLYf0*&gH$KUjC&)&j1tbrv?RqJAmg>9PaGamIpefZ`Nk&h6Z>Un@k zIP3zDEt&z;>zA6Wd++t(7)4I2_3-(|_oQgqipkKRKWJ7Yi<-xW=^#5n?hhc*FAJrH z47*I#v!t?3jkg-+yI_%@v1M&A-T5*BiUMsMz8NYA`Z|OdGLR%DBqXRNB%-hUy7`j* z02Pqw0X;S3%qLW(kGYh#h^N@L;zZXiWZ<^q){uQW#BTy)_b;PS7!9A5tVcfjuUsn7Wx z1Vz_24{XBA-gG^D^`jmImtA~&3wkLP+LuokJ0d0c*_KosIf}a)6_trvC9e6RU$In= z6%=41rEFp~I?T8QaVL==83A3&+8h~SKgTB{Fk_a!U0aQ3GdRDyn<w%f#lC9)|VL&2-? zGL>Fe;4%E)W7%(iuB277drK+BF7O6q*{-QY^$L`{RQLa6jaiL%%VnIkjXmeqT<|N7FD-o*g&+ux1*sr z0t=Q)sr;%NF4p3*!ECHnaJ$=_0bllzhr*w|;*B%d=k^YQ%>(U~5!Za|R=E6;kA&Ob z<`5h@xG~p`8crv*?+PDHyqgD8n7TmA9L7SDjiFIkJtdT^nq}Cc8bYz?9wMG6M_fLE zsjg>|DpWT9`0bF@!s6Ms~V`=g8}DN*uT zqA6rP0jVDa>6uco1GNtW#FyYSTJ>H?g{D^wou}9IBRV@ks|;BgdP2Zr8ep%<6DGVr zI_6C^olA>isb&7X>IUkybnr#R5jreqTT%N}LGlX=(|{^$E9wOGlngnj2f`0WdWirK zqh*lq+oRP>b!v>A5x$B)8sUL;k9GwJ>OI}4i6KTMvhfgM!-GzJLUf+@xA?glFkJ}J zXJEaJT;hz(jTM`|_7OdO&XcO{y+Wey(kWQbudfn>d|K8s3>)MbouK!`OE;TD?(6RA~44b7C?Qj&&KK1avR4MXzu~mR z;gYoRCP?#VH}NO_<+U|@&u9xqGg-fPzI zV;$pzQ39Ap-eiB!kRoF8=<uGNa4I7dsNtKRY_wpKtpRQ z&De)1o6#oT9$h*l5a^q)(76-6DSFF%I{e{VvFnhko@+ub#J2pVfY?u^G6r zJ8^tc^T_+*4`opwG4KNgKehy93r?%IF7}TNTXCWZrbSmO&nUJs(c}~+E-ty~PVmS_ zeidBxPuIc8(!aIEY3h zX!;{2qh~1?nAv5q<;-;2Md{6rgq*HOC#8X%EDh>r>EOU*Bjh}NI4v=lPRQvA^NbQ7 zo{-TDpe;(cY!n@hk0A=#v(E_1G~a$4^Ky&$D+0`{K5leCCZkM2#FfB;J->y!{n}?& zdA&_|SxKNt)AG9kI+|}Hb7$eFZTuy3p%FSj`g^xnM}D!S-nGUR2E(>Z;+TYzShU$Z z={Bg%nR|DhXxQ6*J_5J$!g9X@J~Z;dVJ7q%nl^H{pzmWXnZfOcG<)82^kE~3)uzzj z*s|&s{Zt-BQS%zN^e;g+#C)4TFoassvFWGnGJI35QenK3247SC9#t;j~swYFS`u>>DrIM(X-Bl-Q6{8Y#xBS-2R*n6Q{47bM_H9 zb@C(t0Ib&lM>p4S!JY36hYlZvqel+F@e|v!llkl^4YNdSuCr1~#$CCL=YWMa2I6l6 zM)a{K?Tn|^kSe^N$E_|Ya0m(Z19_?|U(b|_cYdfjd%3X!$$CXr`2c4^9cBk2Y{Jbv z1bbqGGF)so=Q?p^*j~|1J}F0K6R@0MS?PT8GkZizRVaxh-m5>}WbqN59S)~ZbSJ%h zJj$onP_(2&H8W8n4m?`M@kq9~jwngQz8BB$L`nL(?G@{qi5oI;8~K&`HlXbaa*NA| zR2WT-1thkF2HpSqHI}ylD|~$lh`|65vPC=fdDyGiWsYNGSJEU1(Z;GsLB2w&Eb;B* zvXL6V=sTuF7jJo_To}kMgiW+6vDd4>;d8(frFuXS+PJ&Q*8JM@o{eMCeR0`uotprt zTonDN@?Z#y4R>fHLWNYyV{MGds8{o!<#EGys1jF=0x4rvi;-aP{L`8764ZzN5`_b# zf&1N=V-55WeX-rEUE}tmY!KMQ$w4MiPY0B6c&pa3#gNO8E7h4dD7ALG;)@r~c=Ij{eK>j6 zcmZRecpQA$U}bUE=8qyXhi`6;QvQ2cgh`8TBQ*3;#78b7vYQNM1FQ3U69Vllz+ zhD*p@MD?{3>jWAdr_3M_+#*NG@fX?eAI>&J1-A-^9W3!{8 zFia9tXGB_Z3aPOmoT;QDTIY&mC${0OSH1-@AOtX9Q$vm=8J75qb@Fk>#Rvpfhv9{m2r4@ zbhLTamBe5tJ&o!nLrxZFJb@|1Gdk~bY^1&nDjV3k`qe9m9{M>%t*m=z^-+q%I`zrv z4VF&uz>W0JAkbKk&BJIZT=*%Hy`aeRzu1$Eq~p^`V!AexNC|8U3hu8ZyYje=l}gE; zuRyXE&*eG*O`j9ad&0%Z#{fFXKb0lc+ES=UxO)BM30z4q&&Y9 zrn6`wZ5-5cT0rZIk793%pMDd9mhEy;14;`Iet>LK8;_yWqS~4xZfn!nBt}D?LRm)r zhStZ@0iKA;e%On))K6-%%b^gJ>`^5|RFUe1foz~CGC5p<(>cXVL0n$e+3VE&`03t(W5gRB{(7N_mB9A z(|-WxCvUn5jve2ITTkuK@z4WwZa614zxtD6Y{tnc&%CHu^K9sKjTK6aIBH5?gU$%K zrdKX4;%{bJvkH|pM^5aPe?08U9+jy%CU#<_MC3@&GVl}TG{=&vsWfQo7Rf|@>|%|( zkWTZhXMF2PhbP~V1z`Iyl2)ltiatZmb#owBIE_Ac7&uTnV=H~l*o4M{V458S{HI2* z;+-QM&LM=3;V5d`5s9_c8jww4bx`Syj=|x{%3}o%KzGH6;AB%3CW(_CnvC#}ti=*7 zr6bAGT#M`RujK>y%t@R&eU77@=`i@HfBXH&Y@=9QovC0P0a)mrq_sDp-QRh}YD{-q`kb(}+LF1;(E5G-%wB`D*7hgb8W{Zibo=UA* zuC!WfDBL1>JuRj?!TuxT)NMeB%3_dbdzQIKWEyi!UsPmR0@{^liB1-cyXH@2Yd}Wk zLb)^6Na=u$!&PC{EpVv(_xGAXza=g=_Fw+68J({0!ZX8@4o#7A2Ddj5j71S+%#br zNnfI;1(GK}F6z?BN;C{ml^<4a{>-g#<=d}^TXxQbJDh(OoZMQ&dOeRkXMnt(1f8M& zu8Hv|NOK0Ej(PgWohIfWQvx`0_yF8;>?9n&`DXa%_uZrqGL*yUls%F2o)xa{7UQ%4 z1C`zRAOS)so{El*p8`#6$+{^CQqmJu=d8h@dZnLK;p782<52m?Pqnh^(`;SH-pliJM@YWVjJtzCQr_(W;#B$06mO^iCyo>jKWnmEw@$bD_(wV=|P7ti~exYaLN@= z(Hi@a=r}(zVlP4C3#-Kqy5L-VW#3?~t3Qj{J={?q9D2i#PV}O*-;LgP6{NeD`B$pw zV=X=(1MeVU>@3J<>9;5C1&3T`X-Qy`*0o9~=S+G(n>zvi@lSjP((${&Wsmw=xaOnB z;j<^U0U*zYl>u^YxB8dG|9aXu$h*6c^DgA|?(})Y^%`>Ch4p$3kar;Ou3t{I=Xrg1Hb=K^(AB~n*l00QZk_C(F_p)xSG#5Aw$Uv^_ft6_D zLYG@Lj8{JrS^6pH+^1a4?j##st;I@A)e6fu50$0;ld8{s zT09ArNci|eNgTtjr^J$mg-7KVWLk=AZMuHJ}J@^8?~`<{|W%P`6oGg=37dh zARgu1m(?sQeW{hAnXaBm;PID$ADS7xK>%0|RR%&gQ!@2)tUk8TMZSK2b|oY~s+>GO zR*H;zF)R0R^mwLsxklzmqc8ISVSm`4Gx;>?Ht&HA0+xhkF2+E2jX$N}M;Ste$Ah&} zFAv8`-i590Z8)&0d=XORY5tJQMuG$zKo|UFLm|)q=gDC58rF3~VQ1P@SWn06^$auj zNs!^tp#uQ032D7Xen0X?W|iUkuNKDa8ZC|1Gxmx*L+8-~!>H9hw9;JExU^WwU@+Ly zVLC!BVO{P3=?!gjO@diRlF7DSnYth$nGG9`(gCrqYlnkp{=o|`u5n$CQgUwn>|iK5 z5tO^NbQ9@Z3eYx-HMfO|qJxP*DGs{Vg;dz5enCkxPfQh( zntcRPApxhwG`%JRU6hvIdB$1h2MdL6TXGM=xzfgh$Eh9LWh5zz-~p&B%TQ8`+oMTZ ze>SNMG+mYbC~Sivax@mO%CA>XY}au-GEnvI=ek230yrZ>&fJ zlROidV-3E=m;e)XjK$|z)LEyJi?}>XEPAd40**2lDON0xj3~9{kvZ>*mjQGXC2N2f zey^@|5r1Pi#ETZSB9aM*CjhYDU$LA|3nmeLDFr`ZGD2s>{u&2dA8HN1+pf!wHov|d z=;NPCcY^?<=AY3H6~#eoJ8vQ>B`pdx&$<%_rTwS~29nJ6Hk{hphRs!QgJ4NX3DU+U zOdARZpl%!hz@6s`;=#|+klcQe2UXqGUwu9dV&=wgSGLf$xbZM&JUv7OUDrx0n zAuV)L(&lz8Qs**F^7AQv2^oW(4i~qpg~BioCS&E7D?AM^SxOYOwnB9v%GW6WiTy_5 zbJMFa=cIHl^#mlg(;_Nu@Bj}0Kl7v~us`@-@bFTGbFeV*q zL=H~P=tNg5k8?)I=9W?(%8U)Xkz&*CqZx9Rtx3pB6RtfU*m_HrC!h(f4qu!N1ZrDd zfL*J&@nmF^Kx4#UMqNnv{Fy)!uir!1?|#L=QjJ)xozTnrRAWIGCzf17^)-XD6G1(t zw}be4XB)OoZNuiq83xShEh()4RteH-10YZ4cjU~Y0ITUS2F&XW0Q&fqk)wN_OKa#3 zjuLd|6TH+UG_+VUvOXBiYsy>bTp9*qrw>nwJpJl2>Euos@FERIRJ0ff5$V@wwC*zD z1(U%+<76jT+7j23t@5%|XnLDVf5_8a1Aqf8axPw=;(^m}+RHo%xd?dfeYN^xAJJzRVhfiSx$t=h*0}ZHt9WFYn_ydp%E_ zah8T0NlD{Xp8#^pGQTp3^2I}ma&U3Kf*!Q z^n)7K@;ITcTRKJOAsM_W(Ge?kAITs#pnoQ~F3k!~nk^%?kn2*?{q}q$(8cGLyIj8) zvy;7i{?4PQ&NCaU*MFpV0g;1c3F>lS>d~C1*iEQD=mVVM`vJj0puS;kp~r}%0!7gN zY92;=c|K%VKGIkPKKV{E%ic(Z!&6dyqaBO&#|p>BYWWGjgx^7s$U!u9m=$KRUdyms ztF7h?fEOmV8}F3~ZoRVw+dDgO;K*K+`u!`L8ws|ycOdU>L1Lz#O>yh|mkn!sbau;3 z5z1v|F&%6!hB8iJcmw^YvKol>M3&1AYto)xB*&t#b!DkZQf1Gpun8;c7)*^GG4vra zY2}(;&7gXP?zJazrx7yZ-dVqM5Ru{AK}-vBd-i( z@uW*yK(6_}kuVcox+LYO89f;tal9D5CxntKBmjPAlI@lDTDbfMaPk$5 z#!@#!j|hb!WL@ROUFG^7%ERI#-0LC-^+%tP%~1H|Txz<^=yTgQpB=VS^&&6~F?5S| zIQDchJnWENJ?SlmY-4S$qw~lTcUF(9Ge5>t7fMY`#|yD5mkG!&RCFHa(+r?)V(ERm z1SBnBy^Q8>)9t|NYdSUd48fiWpzCQ`!g*iSfGTOESWK=;1+in3p?Vz$5mP z+m@#W#+ceq+o7LsXP%lErrdtIbHS_3DKh{GyfqevRM>xc;;-=9;?QXj~yfX4Uy@e$bRrR_eTxVSX`h^Q`+iug1To?PPbM2Tp1g8y&b<&aJ>T6r30tSO z;lRded%mlXar>r4B<2 zaP*K*mmmI=M*Qd5SQl9be-s`7Cz4~E9)oZRs==4T>Fzt~7cB2{T6j{+v`aB|6I4g` z!<$M4e2&m%XT`e+6ynkA)4?Ttowl2!$besj@}cK0oL6o;ol-$l{j)O%52uwY_<2R8 zrC#zcJZb2;{U-_!bIzC~wK(s*W(9Riw``)&D7O#!3BqBC+XLv>jTcgEqE?+m#4aU3 z+59Q*Kd*mc3~~LXuhvyKf~J@U#WLX^oTh6PR32p!gmAE|N|G_aK<*xfz>CvV+?&5iwV zG5b?CH#cB=dkgZ`si{J>A8HJmy7a;=0LTR@QvUiyhUpA4ln+Vp3Gc~H*@P&@B1#gV z`tADB`SOC3B0GH-g)-#e)z=V-Qmc+?!5GA(mM6oAkpSHtO7s4o4EoXUij+iIPc-y1 z$}Sabo7Ys&@h?sR=-yy&mBy|Zhs|2&qvV)EKeFp})M77v(WKT>Y-5!eakoSgIeOjZ zH267za+HRjoRk{NH0eCV6CRTlt@|Jvp8!YXA?J3jB?}xrz(NR|3xm^JwoDEX25JlQ zdDvfK8iRdG5~b+0exZZX(yz+yktYw^zM)FwN4DvNeADnelGV$~T8bSQ7=7p9(CO8+ z7F{&frGRBcyED#5ZP8-8@z%VWoAzLAE_X^vZT7HHl-Pe3nf>uZ4qpkT`E>(Of$iDP z=`Y+%tEu@yLGio@|MERKJBL;jG1K-$uDmF6TfRFB;_;bbJS^Ky!0svtWzum_K@0cO z2EeVNU;6JLD2eBcdx-BYF9x<={B$=_N0NSz`1HHp-hxvnPkdf&5FFT8!HJVwu-@IC zD#*?pfA1#dyDX)X+A2$;hZ!aVt+(8*)n0Uq1=X1GThei1+&Zr)&L>H3TQX;CtS;iA z!DXdBBAOt2wX{uwP9ha9vOj-GP0St2+c7l`aa|h=YDcGZV^B*d@e_Bmzc3%_>WxA! z3gjkm2ZyKgN#nnDqFuSw%S{)e#~F|b$H^NkFUw*CVAa4E8D8B>23|4+Xr$nSG(J(QP|%x_C5;v_4a( zpsyW!$(%K5v<)u;(hT00ib6XX^}T)yR{(8zZS!t)uDo%p6eBF^__aV3y@PoFdv4fm#b%D zz(D7^IB3~FDMvO=pI;6^H4Yi6daIFxSJ(p2Y6_6=H8O(=#f}7GgWQuMr-*53{dtHM zb429MuhH3w49!E5=j0?)h!fi{#u6qqInm!1sPvWF1Bu|I+9l}p@QV@{mo|wZaWp>r zS6Zi1lGfIT6C4>^mo){S7ej(`7;4{3;XE1qVZYId*#s@GbLlSOeIL81Z6o{@hv#%j zFkC|}x68Y(3624-Te;dA_vr#(nKs8y1))2gY z0Yh2!Heq%i&o_w0JXD&QiYYuQI165B6R#_-idqd6Ou2Wq;nc}3*!VoUgJ5$b!PeF` z~%?kG^Z)2+)%0Sd-0sP!qQBy?NuY~8}`l;>7b{N{UU&iA|7e#mXyfDQCSPgQgxTqwFJiYPt#$AMN zZG^IkO8TV$-ySuzmQM)Gl?r0@+b=s?YCA2x5^M&Kpi#v=xa z7}x%6plC}C1yEm+=v}e|=EEcg3JL)-) zg=?(GbL|FlF2{nRQz~K+(HJ$H{bVwRH1}8ctCj}HeJ<*Tk7#QUbdK1UEBe!>6g?mc zEmr4Xk3|^Oej^9a$2N+C9}nQS#OsYW0cf`#?`cg>*bQJKFVYKAW+KtnGHtp&9#|W; zRs1bt(VZkk4ukEq-AOyq)H&+NYgpBA9L&|c@~FH=cvR?1QcG-R#BQ~yvA0s@PWwW- z%|q+y>I^%bxYl@|6-o4o%ErK{GhpC1w&RNHHa7{3xlnCO!euDmPnw}pnl79PJ}rB+ zxl&ni8_D7*#79lUQo=?eo54ik5rh6)lXe0@sls4^w?r}9d;kWlNLQ?RwQJ-;n-l-d0+ ztCV1Km0)XUx4naal}Q}2*GfwHDAFQt`8OOgIqnF`RP1=DO91pO?Jl%eEt6r}DVlF>I@NP}6+7t%4qcC(xxd)%mO46>jQ&j|Au7QjH2}olNCFptA$JYL=HKLl zyB_j8DUI4QG$f3TClH$7i{OmVW2MHb5dxU^UcdcWg!o01SyHPAI6uZPid^jF?x3wz zc9FhnMdU0VZkAyv2?D|3ZVq%x{i2N`W1FI0l!Vsq6_B%9os>V>B@fA4LGAH1Tr@l* zcE@88LxW#vH^jpry!$5fsNbP)EV3ykt6>kthzp_5;Y$Az>gJH{fsnK*L}$dLUT^+R z8oVs(1_2?V#)c8W3=|bDswCr?qBZ=&^s?UFhV?qb z*3KF>KM&qRu(^?7XE(!oognX?Qe|cb`uT|HS`(^%y+iY#D6QvHTbu_=I`fpvuG%vhCEIQAUyhFFc5p ziJ%q+vL2|yuD|s5@#B}o3yU251dz{Y$JkUqnocC>j^T8~Xp@fN&xy}P zVE(E@#aN;g^(MuB2jw~hKI&5%{#+eq-&=NR;#lWiu!-$VQ$OAlh||`+k~LZkThkui zU1Cm%{*}-Mhwaw{3SpV?6(N?{}ZmLJvKZ zkhC5wR7)ucD5!kUl0yRW0S%%FA0UAcl#ASoUjzY7jL_JK5V?q#c!?4qDu{&|6VMhU zS}r2gmbTIpYDx=~p2OMaoW0+@-~aT(T64~E`HeBhT+g#l>)iYQK5NZ6<``p+Ic{^V zoA%9|k_{RP`xR1$nCB-cw{-NbE3~zFI6_tn$f1EogO4JZ@0rV7Htxg_NndDw6n}{c zZ3n;=ZzCF?NpB5ZiH?NQ^x@zrc9v?WFz7XZD8Ten2KEV6L!EmESu&wBwb!qR8qup) zuj%PlnV1i@ym)b=SMR?j;zsTL_i5L`9=DiyIgNHv%jf+lO$NubwBXJKbeDml%14{$ zsDY!-X+$G{nFDj}$GF)%O5XxQD$2a6pPbk6+&Q8bpcf1Ie$%zQn%u_(Id%cWY~+|t zf<~Udxg?cM*q*u$Nn7j`(H4>T(0Y7d4$Wb=T4Yb zIOb^OTb`Smm-xM*UCLSE8k~$YU1yUnJ&DR+*e0p7sS$FqJ4a-XZRLiomWst`bxpLx zmrODUNC3>y+u;oz1Kt%{q<2^4hGb+XjWv3?_8Ou)4vu7UTDpxSX6wvo*lG-nMYe)n z;Z$+O#uzgHTC{~ZZ{wc;v_5xEhL=pH=sQdw`3@Q<3l?V~Pr@&3GtC6=h#TXu_yB$; zLL16mQ>6v;BbF8J?up7MP(GCOEes>=1Kb77XCCcpqvD9?I;Y*74^|BZ6blx`5{u71 zvKVrb)LE*z4W#p8?EbV>(Gr41Ym7apF+4jLON8G*^L;SeXeuA9o`tkrdQDO0n^ zGViv)P$+_`&QjI!-ZN`S=|V{#Czxf}tGat2!);Hhl3T#_O#vU#w{h)qrujgf7n?(s zQvQ0#f=)C=(C;n!++Dybp)DuxfwaMjqf!D_l=(KlVOQm^0KY9W4A(IvX-uebVMR%2 zC3W3C{BY~Ku~DGET$elxV~TIdX9qp&fv!h<$);`FTXoh6`2mfRbbS->_Bvrv05d_ zSiE5cXxOM?K#=KSh+vWtrsvo6?yL9c_QPO<;Eksnz5DKa)SlnFoZDsHWb$%6QeYn| z$V!{}1|!(X^I?$^DIeszQpor=j?Mc_mfm#_`iRj0(zr!&p&^NJbRr;}o zx1Y}V0w6raJJXVDCG!Xb;sl0e@ox%hdMSB)K;sRs<+uuvK=%W%aieD!^+PM<&`Eom znGlPz(p+wt&Z8jx7O^j8kLujo3lrDu|`1M3gmIL3myz+Y7h z?1FIht+Dg3fi#j2YaL(Ff<8B61i>}ajC?x6q&<1mr3Wc(6V~@HSa%rjEq!9GOg3;= z%&CM<@PJIU%F;#kTp^9GB4+gn?)+m)HUS{_GslVG{JT&P>< ze%!)vHLrPOm0JZaB3aZX4~l7Q+7b$c31G#2`j}*aWBynncw9Wn_ja5DQ7S`#OpaOQ zK=F>jq9EJP!DG~mAlm&zRjtd+bo>VawH^-f8x1SU;GibrV0CHgF5V$NG_1Qk%QYs$ zI|!zaz7ixfHaT}V!2mQW#s>V;`gNj6lX554-hV~!zWW~Ce(2v(NKZGWcV4|u&+osY zk~$UiK;Gom=$WWJlUNPH9{y;*)#{0?Z_VqDdV|@r7zYZ2KkF)6y_6jqO9Y2r(}yYm zZj9~(iKJZ#%tYDt#cb#sb$*bkU!D_}Zb3A23@teIyGhn0{yY?Ro*IP-oZ(y*{iM6% zWSvTLM@k)K48I)63#AdrVsr3=1D!bfO<)?3(1Ei#RyKKWMvxGI?+m!3M5YE`kZXAf z`olU<>8c*67vTh5#g^bf(`7jkU(R6CHgko$k)NR3H&>2slNi1(Yuj^}`={ZM+Cb4} z3YDYGi0<0b97Gmja9gz$h2(6rirT~Ws2`0(plnXY=DdSNL8d?==%v9oguKwxkZ365wQS_k;8eKo zdJy$6t?{`AneR_Ro7E7#+PNr}Xw4rIsg@`|sKvJQ4gzfOI~td#^bAh`CsN+g>cLQp zx!iPC&(HMy-aGW_)q8aNVep;98#BFn^&UOH`W&_P8p60a6!O!e7H6%^jR7|~$1e&= zO|*pX*>gPE7T_t)xmK+ifsCjdizP(I?%g0(P58iy5#B3{n9h=`h2u|Tuyjjue5OaZ zXDf$!J_9co^#>LZ6pj4ZR5!Bsuoz5C=JTS-9V%$x=GLoqf@w6|soSRGcVJdGIQZ?Q zo&N`OFEja6$1PKjfSIt>q>G2Q0P`3o7x(&HG-vFachH5Efq#K-U+?vXhaTgs??RVL z>{m?G-E}Pb$^OY;P;*H>b^uj4W9#JDS?y0lt2qk9A$j*?z9h(O2gI@2VQ>Nh1bbca+Bsu*?qz zs8H0h(bpYitgOc=~n-a=S6T^Xff%e)UBeu6DE_2=?tfDn3`x)or!(wm*b2|~=v2mse8Gc?ReGkW<097S_cho#UwF|le_;>j z+U2|%Its7hO@{)F`0hnT>>uNqu}s;qGsS_}dkK7HjM7>MQ+^rLHGQ)^^G){DdEZeC z7mK)AR*jISC=O3Q=pm3d0dn8W7iW|r;`)8Q_m|Q$%N-z_8(q?i_?cfR63A_8(J;)L z{nVA^&YBU$8NL{!U7N7!hs4f%?--~QbHUW%MPm`ZzM}xw;UFC(7Pbe0-GwESch#e? z_?LvYuq}qgW-BB=3yRJqMEoP)fnY)1wi<%?UGGUmRC?hye8k`ox;3h-%*ADXdq*&y z@OcR1L@H7AY->mnAWMnGoR^eezxy`5`_8*REbbt9x-q@`&U^Iy-aF)0^K94lhvT}j zaAD_F`Qu9PYohjC@c6yl^fmK=GJYz3mcB!lIXd&)wSe=V*-Jgx^c=!5;Oo@}P52Du ziZ)S}xVrOHVCi0;0P!hO_#OyH4+qmGc3SbMhXUE!(LueKABYo~bfZLkVF~2YujcIT zJU=fht2Wa)=}Uh(VZe+Zc#^!V18|Gu^f^H%tAfeafY$k%Hu-RLwv4K zz+fK8Xv8?9xn@i#RE_2WOOa1|MC5Ve9w%&uBj%uT*H+4$mT5JW_K3~gB&vcwi5Ro# z5x_a*i8n|3h?$BpS&tu&>54zq6h;kJ9M|XGVn?-7yYNch#_job=Hsh_i?VQAkMwLB zDZ=)KBih8QqVudJAGN z5ZM`Ck>j!FW+6AE>H8y$$_OP+IXg_`o`-e)WE*&uSrRB5ljHa>is$z}M{mFV59r0y z)^y#(`oh2&_y$<7XbQZl{qJHZcX2ca~;;aYH@+#C@3FQn?TJp^dt6D zq^2&GgY*;;>0E|b+pDRV#1Qd@VR*V-*U`k};g`?0(zicZh<(K=;H?6pQxWo$n01>x zN#7(((_ng1VeGuJk(cAJrsOD$ZcME;1yqR;5|YhW57!i7sbgRDVbj(?E4DCGfWe`R zffQKRbxR`CK{G4eS-buXoL!Nd?Uu>VMi>-7_U3b>=15GMf&DV#Uxc*9Rl~PL^3cs5 z3J``4_~M*J{ki5tXRg#_iclv>hR#rRX=}id4td#0MNT)Hza@hJpD7{_lCR87V`Nzu z{k+Ol!COy756rj%@F6;Gz^)QHIAEuBDk?76k3Vym&yG1a$#60uZX!_y%H3>komt2T zWI^J%DuX@~@r~a5^xvaT{lE_pGtr0MeEK1I)ZmR5H~Q$0e2bo6Khr<>2cM=_pZ>c< zbUS%Dw70b`%=523)ARYM!gl`ovh8K4yA&4MmA@2o&IJnj%hIL+d0r}Ip$i>3M42eG z)Lqv3i0>h4+*P^Ey=hF5LF*)URe;@&mYGR4I_?e8pPCF%3I5{QojM@RxrMTi2nNX@ z5!K%x5u@<>v2bb_NuaOEqQwYr=o8Bto6_DumlEtsD8ma8MbBJEC_)D6oNN%Pm3S#pAR?7NgxepJ z>}8KH6~peO(&ThMG69NlrLO4$e(%lf!XXn~x@o78Q(d8Tcr}<%CR<%>OOA%CU z!5~qm*0vLdyG?Qi2j~mE(kg}&?g6%NUI>?%G1gsLW*sqT$m+)dhgSJk6yp?8DhRs z-|;(0_jd`1RQ`(2mYgRG{^e8^vg=Web;KU4Tb0vSEHgYz7?|Zo#)%|-{I~i>TLbVc z=R-o35-!mX$Sh@7;jYXJPt|E8F8&U$AVw)|tNDnVFeRG~Oe_^h{4CvmjbP$pb&fB_ z!3SSDblQ1L5JK8~#oalk*hZ%tr%{yJlVcor#K8lvAK69(mk|o~_9G z7Hv+ObA9L=@F^l&AmU9zA{JpyyOJ~Im}FG17X%xv1(45(iqK1^Gnl552@|buA#fW6 z9NyxyScKUC;I|W{>rhsJg@vgv4BW*(T(bx zJJcw9oWZHy(QP5kRo)(!EY6Ti_&P5*4YvJ*M6@rs(%ZKqyr!SX(U9MQ{MZoTc)GG~nr+Ti3#7w6V!RCz92XwBC-52jcVh zM#MK_o^LyE?U|Tg3@nPnz>uc|aPo={BN_O*7B{D;fYTma+Vre|d81%n5lsYxqkb;Z%yu}e%NFaBzrmPqc71s~`z8C2J=c=M zX7h{Mdg;-v;c0eV@AYE>#bWcqewItdRxQ;8kIy>j?lF|-pht5qlXS1VDYKHLSC}|T zXEAPD&k#oEKHfk0C_WcJy}g=`guL-|0p&#lN;3?nYiULBJukt@kM#(6i7|2JvPlO5 z;Id|)jdF2v!Y!gwY>x%L!YzIi})Q1n|hs=9b6~uCipfxvIOsJ7~pbIH9#jk z?unw#4x=bnVsoBDq-g62-GIlqBb&iIR?7As?jClM_YhQG9ppC%C`+ouG+5azXNdi& zy*1e;!ZLetY=C6RK7ZWo%mzusf>lkHsXgoDo=-YM;7Tq%XwSui9uj+kGO_JrWg_M$ zz$%|$J`LtHen%npW9kZln}&B7bCb#L^DcgNgJV1xwchxpVVe@?ruu z#Ju4Q;mB+Kq&dx=$XT;FmC%~!{&Y}Jz?*1nZM8W7d`PG1Kspbz4^9Nc4}a*mLWUq3&n!&G()`ATOiXh_#jbIr=Gca zOQ_`_OUe>h0JauOb?IR+g}R?ml#?|4RfBWk+@Bogl{6o@ieCXC6GMo#7-voWF#;!SsT1LqUu;`jhL?JE}W zC48l7vy9CW%_~LVVOm}nm^=baQ!K0#;iCqRdVe$sPefBU`G;l_JWBi(Whk<`g4NTM zqk*>JVjy8wu zR$lj|b`fAvN?9P^g(a{fo5idrc|+E(nr`G{uZ2auKJ*mR&2W&URFo{FP|y9Rxl7Y$q61V%uB3WY%+Ohycjy zU(#PtTeBQNgnXKB+AEM|(z>Gm82(Y0Lv@h0L_WBd$#{)ma3=1*Q)rR*1U#9SbYjcF zx|tQ^<#)C?GR*QbgfnFA%wgQzEKu{UON7H-GmmZiWPD}u-0Bg!E3GnriCwvBG%nUJ zk(bBCf~;aMs@q)CNCKpd-B09pYw1X2m53ihftM6+B&BN1C=vpp$i&>ya?&9uJb^P# zZ%vijD2N9$o=GySrZg5b{}s1r?j zwcU=wt0Fdh&ktfOxJeg|KhWQHGS!nOBSuMO-ZFmTfG@VReCk{7Cn5wL==)&TI%u3Y zI97P_fr98ztcVA|DXDo8{d#`JF(%BgkY}PJerkCH?qfQqEyVP@13jVT!|IBhq+}*q zB{=3?B%{UJXvR-4r-p@=`tnH=aiO_Eb7^@3{1j+A6xQ_YV-ZU~9#cF^fi7Y(hWx!^1bKuSW|Z z%=ziKgKH%kwDT938?RB16cb9c_fYBuC z`q1n*RnAP1=r8TaAe5=n4lPR)xbBkrE+?~MGg>Qhu~i3Y@X4Hq&D=FAqBWe^9IG8V z4mG?hiA!t%ejorXJ`^EpWxPS_DvD%sCQ78IGRE9n0Q18Ln`^2Rx&0n@%^=pjbv&}7 zYzD=K@1gEnk0XR#)u5mW>1ZJJ4rwRm#79BaVP_mFD{sN-8`(>;#16&cZLT;+mjY(Yr_T6)Y z%^lnDg;~vLV2TAV9m)z7kytPiowaHcyfNXBObn7a(A79euvSwlH*lJ3O-dCGwc~lW^RSfcPKVXvG|$^pp>yx^L2p_A4we2#+?%Nl?igezuZ9;onJ2i(~8GOxH& z3z9!rDvSOr(h6)PYSp%!9{>O#07*naRFlmsdTkAomeaj~Y^_#y=ti)+MALK#C;V4Q zVfq}7%VL)?4=!_BuD*b|*sIa;KD0|H ztJ-JQ7&eSSLVCS+UqM-2b)|+%lqbd1_4c;+{5p!>%n7R?`jKE*(5lRK&W&k0tWwIvx*$YgivD zQ`*t(O*^ntlXS%z9c3@~@^*ADic zIBNC1x{)O4B2fkKJLX<^2Z8A=uY~mZY5-33$8m8(@ywYbJJ?#e!47>h=fa}^{gX7l z@WUUb@A#{KDx^R}^z;6OUq#>gZU2uzWIPtge=?(f@3ViK-uvA5(;FZDv7_JN&v37; z#nlibU@9vx*NJ9EFc#eMy!<>UHG}52w7<8+W$pC}!;&8AzumWff zu);{OSSBIiq~D|XNbsZQEW%B@AWn(42WaS`ok%|7)@)nYpMghOk<1!ro5a{m+Y%>_ ze@G3819*gv1PHvZ8{kNdzlnc!2j=;FIcC5jNA()B)-EAKshdl`kn3Em9hp;FIhTzh z@12fbs*PCa2hGk&(ANzV!3jGydT{OT{A3)?$8Pw-gcqxR<4L9~FCk3J4lZEZov)Ga z1vvqH?+lVvm6PN_E2Vx}KyCuAY6bWB|7!05sz_FSm#cOEjpgMx2&xII&U{UU23#-R z3&kWlTFpS>#HN0*(`eo*ATaSdG=J`$cj@Q;f?q-JeCj)ii0C)|#@|RE``E|mYrpnu z2i$jl=XVl+=qJ$a#fRzTn{U#)uUI5OuVtcQ>3f?gYRlP2 z(z9c<<$xKoXH9LokI7pTJ*hWEIm=GzIFt`4cI~luysSjZS~G@PMgr{Bs6u_ahwN!} zG{y=P++nKlQkW>ILQeN<ASPRZICt%*<@30vR1#O}H zdwjen@w5z;GZ~tGv0*-~oLw@$O1biO?y_ghfHUHD?iMa=3} z7sw~4oA6PBCBh0`Ncxux?hd5+PS<*_)W(7$_XRW)S@5|{{|t5S6WMlg&Z>)E`FK?- zKVeZ~f!XipDT^Zo7O(jn`2b}dE)kP)XVAh>03FKZWF()9y8L|yLE~jN6?H^&ZmSos zf-FsgK`KsXE>Q6KeXY@Z0$%4~3wSE{nv8b?H~25wN11HY-!B7HS=>PYl0oT!Q1<13 zQMs1vMxupjYxsGuo}|l!E9bo*JwHFww|@I~)AReUh=}P^pK0{j&%I0E`t5&{-u%Lk z(Kq~e|1o{jpZga2>YwzL^iTiHe~kX%fBMJh@BE#=Pru@q{X+Wj|L~X5=RWr?edGW9 z=jpfoJHMYk`?+_h5z#OD=RZ!r>0kRL^bdX6kEOTY{w)2uzwlS+SN(>6chLTVFZeM1 zf&cJ#($D|7KbwB=lb@!4`*;2!`i5`(CdaelXgXm!Wy7+QWqTp1#9;kM&B^F@eKz9K$JYRANe=~^Dt>v&i33{LyY#Wz zSZOQ-T>SxzQjmzcUN0Q-5qqZKMwa!ww84Crv$QaK1o*MjlLaFO6zF@{&vK4G(`QqT z$X#kXtm@7JuE!AudKq>_$%P*qv`2?JlgPzlyRXnyLMA6NS<$oRFI>fdZ-f=s8ftS5 z;JjMOnT!B8^M&LQe)4j~XPj_)GuON~x-NydXN6xPYG5nA%c}XsREU*DD_krhTj@<_ zzedLPU{Gtp&A)@kHpSx3gjeAI-hGU|a5d;tZ&gFtoNW1#0C_8cc>D?}Q$C%Tt$3QUTS z%Qqr?hbV6^K18=SK1_UjK}0uVzR~T)hl%(`zx|I=3n~@iCUu{^Ce$Q|Nig#*Xiqj_aC62{8N4! zef;A;oBq_F{G;^Fr@muQ{58M&U!`yU=D$Gy%+LP$^xyo~|0VtI-}Rg5!*BY#60Er$ z@+n*qcJrC5CbyN`=Tk=Gk2|EDJ=VP%6Z6e0k?SkY`^&z}3nUup08fm$oym~|2&Bed z#+{azn&5RuJ-uiKjS!HX0s{^WPXXJyhHfo3v|uO86<>La_AR@3GU8EOrVaOGafpDj=rzT?P>c&ar0;HEq&4^X z2LG-kOt)6K!HY{WkP<(@7fTeyJ%r2nH?eHJs&Kg$#Q9vgIKOX{EsX<}I^TMjtCfN{ zle$s4YfXTZSSLEi83n%;DF$^5Z;KZRF?(RKzUV>qJSst9%3_0{h((kxhphz%L?oQH z(H;4xZd306z!w6UiuOD(P~t2n_A?4~8iC^e_ka9P&^P^=Z#do=@Zq0Gf9y~E1^RP; z{#)n=Kl2uS+b3Sr*Z>(mz^r{-Wv~PkL7S(5)_=1OA=xYupg(23l=m*pSKNF9U4Yt!SoPX+# z!k2QMsMGXYzDF2#M;9a?w|n5wR~*ZR12Q(bGE1qaZu5_-G3ILIETUdESfFgwS0lWo zhwAP`nYF8lt^9{Xs*d_(qn9-L zj*fyFlBe2)sL|QdnJ0UrH_(aULqfK@Dk^4 zL4o>|k*>UoRr}@~erls(bLQ}e>_G5jZnK)tjJjPJgh{Vk&R!>O} zDxi95UO}X-(eXV0jk;oC&blh!&W(i_|95=nchS>ZUrbML{0Jgq`l_$|a{Ag||2ya> zebrB(4}a(_dgF~3^z!8!^!nW&r0@Eh-$UQ>m;M_4rN8tS>3{j=zf9lszx|)|KYrsk z)8{_-)}a3F-~Kn~#mg_IH$L@h6rlaM z)4&z4Nit+$;)TkKrhbiAzayYx7V*{&-l^?|t3r-vPwisT5Q5Y}yAd|wvHoH{&z1As z@fwhRX>VjAd38ex%-1`Zo9wT)GUS zk$SefN#snV`XJuAK6ILGs#Uim7PN_Cz0*tt?UHqjBm361ediTNfR5NEj}V+mzGI@h zhR#dQir&CQK{My15Rl6SaHw>uLjTdFGQO-11%NxyQ4$YW=sv-Z<>}g3Ln!q82wn4PZfL z!!O?B(`Bl=#tHmdO4?-V0CxWlf}ODnKDk@S(w~W0Gktt)K5otO>W5S?5NIPJdR8+K z%T_8bazI3{U%#f?(_2JD^wyg%=nwxFUq|2jz28T_oZ~p`O&|5DD56{ofbbIk3#R1=5j5`S8 z(OpYQBU{k{ioqWuFD;7ax#`kq!m?^CB$F_5@@z`;FD3_K*FMULEN%rHfMDeV3y@ut z&oo&$Ic{MXk;-qB?ckNvJ2e>%!3!)ylEU!ict>IFBbx5xyD*K*F#QAGy(7faD11J^ z>5r$onLnN06u4U>7V}HMhl0{l8e-NAwSJ*sUMzaY&3F$?+SVKwoU~n}9 zU*c<3Iz5$hZ1i2^^2oLOSa+|^dw(b2$@Dm@LRjjW8uu-f^3>}1GByV;+j!2O)L#90 z(+BHmwbuEe&b9wh*Mp)gjI=+6*L?@%~||K(r) zW%Q#z`VsnVzx8YBZ+yom=#%e!gue94f12Xu`MJ^m@qhgQ{d>RnkI>KgdH*u~xQ~4) zef5ujlb)a7-x*v1?z)4JU!|EUXy&IUR{k(x1k0Yk4=WJFDF*9-Nzjpo3@t; zcc|AK@72XgHaCH;-fNF4o_2gaKd~GcTD8^AcIm#a;}~RIUkjlbCPxiSPa{dVcS%6|=6y*Jt;o$ z2^$(lV&j<@9dCRP2mfj~n&pMrw)T$)Hk_ z6(bsWw4yiUdrnEspZc5)=C=(vbNe9E`P2hnbV`wqbi`M1F7Qub75k?Xv*@#e>qmtb zXn}rPY=wA=2=1PX-C{Gu&ldxcPfDB!obm#mdx}DTo=BT%y&hfusqzB`W@1(w|JE=r z@w@g6qwcTF8Ya&PKtC{__nLhpUUaOP-J`dCXJe|!!oW*2Zt5T}gAJJv#Gw0HyVL7{ z4b5n|Fa<%rdFjKeh(XR4&oipe^s|FplVE16jxr>%o`-r`)yf^L#+PWw1i9IU1%~bX zP+GOj>6(G!v$^JjqSmg77~0lu*f4VMg+6g-PdnoIsK3W)cp&(=eUlZ%(aM-;=w+G2 z#s&fVIZIK`#g~Fw<~4mF9@eI(&4MxRS1mIwZRh&eCXIWDKl#Z|({KJQUr%56bzet~ zZuGam@9)!p^q>3@`tAS5uOcF%w?Fea`uNxU6ZGqU%`c`e{DLo_@BYO9OTYFv{8swJ zCq6+R{^(D2+(le(m2Gea=PT!g7DyJ)S%iRdP#7aUU$#=tmq%BOv;^Nm-XV$9SUMmgpTlYj3?|IIxwu-~onNSS0b_BKd#v6}q( z;JH%vlD2x+Y~evc8-fYwq%2qGsp-QcCyX#}rgu3XE8;|Ale|bX@L@YkTn<%?A5|9eb%1cHRzBg3L50l- ztL}ce7x6}bznCu_kBho$7d=`3rDNHdX}efOgb*=9BRq8<+no5E>C1c>3S?AeQ;rt) zDZT}gX3kUiOGbvr7!zK_1(};4-Z^_$1^=vn>n}F?kzYD0{_^kmS%MHE%DUP)J&?#Q zU(6NM^~ny?LsqZ{aF*C(49z6p?#$ih!DH;N-u_$EUcW;xzvwH8i0GY9ejB}b`B8fF zLqBHpeeazgpjU5y4-pgHZZGM@TOXx&-~L|!EW-YG{R&K-UvmwwXbIdu4ta99WB9-@lIg)kN7$i4J$HUDUi z3wC+!EM}J7U2Au48yb%M2_Ge@G1aQA75eU$?e2HdyPH;Jb-qX{!jkkslbEhZcIjJV3*>B@ zwS|l zh@$bJtP#MOIgccxaFuO|4I2~teM^_cMg$S5%T|gBbi6>l!%Mn3kmz1BV2k2L#x!5d zwd}t$$b6}6*;ahn4Ye!eSA~h(^Sh)a?RoT%5g1PbhyY|;Ox{@_3Zzdv@$yk6k`Jq+ zhJh%atpZI~T5fuTNQ5iw0_{Z3cTGVg3xPEJG>E8sIQ?onW(D^``fGuU3v5)_ChZ`-TEvhM=cGbX63rZ1 z_Kl5bEfB-@uX42vR_e2SSzJkmO{8uNy96&OEI88>sZZ*d`l5tGOj$C(=5QvPnwEtt z@*VcAmU9Krh~ziV$CnV=_@QS0qxL+MC{Ot-EvFPj{_pR!Sjcxphv~Sxf@Lr3fTGc4 zbPT5sbiw0L9}S0ntyO%fMW)A%bJZj{;`9EM<{Eldk#HBdQzpgu(x7$zItBE!@tO2*-H{98<@qs8T7BrUSiTGuPnY{j!2r}_ z&)KcTf#If&7=2p8(EE*=kQ}g?r7Ci8e68qdu*B^=?C3E)fK@QpptN)h1>9Tw1pN@% zkgv530O$p4_D$az%Xe(_^o>GGesR9;Q7zL?jhdobVi0NQfKJ7eD3JL{+F27(-?S%- zXB?s(oI?ByipDlaGZujrJPAy=&|p4|xvcf6Utou0p6l26Ao;CI#^e2d8PmF_FM4p4 z!I7z`?~@%}0{`%MIUyn-_i&=KvN+D~+8%0?h~5w&b0&TNE{*gQm*pJdIpNb5=Tvgx z31u;^@j~|nY*nwvOXAm{AvsgP2EoEX8(zIk8*Wg7*bltYo}8NsxBEgXga$E}GmL{3 z6CEP6W9eIt=SIP~OE6VOUoV@u(fDJP*5jW0O8KYd%`Tq%Y@6rd*c7-C{?w6$ukBR% znl5Qeqo@;5eENoJ{F$XB$XNwr9Xhr1vOHAzW4BP``iW&fsM0+2C8Oinvel!d6&01 zkfV)#qB8v5Vp5UXciQhL-FGNo|2Ue~TGZ5!lR#D_(`7YN3cu^0#Tt1y&n4d&Da$n- z$rl|$vP!Xe;m}O~C8$9DfqYt4oISAra38W=fm-0J6xvp!GK>jC7(OStsO$n zd(ztk)@Tz#i9i#-3v`fwu?=zc1VZ!?b6O2tlG6m%u|%%LnOv5p{MCQ~e2 zg008ML_lda;Z|lD5!W`BsYYx*HD6^$IcH3y){INh!SR~3R$hj^;*{VfjV;Ez?9i$1 z_ei{|@t|EJ+0-mG@$WcnMCrDnWvrn}21ke;WFOJ8WF`X5kSmS7D_5bo#7NS;JD_HU zD!h&pO(K&32QG*3yjths=W+q;$TuAzbyC_>j&B976VYG!!ltE+QJ7?)>FRvCREByd zccl3!mRy?{+loA0sygGUh|Po{+mvn=eNZLh9Wet!2Hvt<7nl>Kn*>>rNo>@;4WFUo zNe3z{m#+-e6&-6_XaaDKhXU?(>%De*wG7!Z(lGe7$e-{U2GN1G@tcsE+;=z&-~d3=`om?vyc@(ga>IQ(p0f=G=|{3V%kJ zaAyRaWkT6mt>757y}hdJ;og$Xg^VjTzoIRJy=k(+yUP~Idr<|foQ7~pkT2K%Beo~A zBiHRko~vH0yE&ea43HnI&au8DXRq&qw_z$r-DB406aSpcD*k)p9KyBNgQ$LKZHdkK zb?@83ZptndVJ69#6$|@>WRH>SSbGVJnfdS$0kn~l_%S=&(3j=>fenIWQJvs|3LZKN zT&nZ)5Mxz7QoBf&0CiO2I19W2<89hxZI0;s}{Sy|Ot?Ota_l-(v%#V_N z)yK$QzTYL=NBpG2s*HK1%k2K-LV0*nnd>Gd`@nmIO^L8=W1W%oo_ zt_cm-RM3(hlQ%X8*VnZB{H@7rJ|tfKxKRm#+d$m6lHxlE_HMhlnm*v&DV%=QI9hXI z19U`p21ZY#J1D6vnnsq^1f38Zf^`Nd6%iO48iSm)Mk5Ku7guB&VRo2v8TyryQCMl-*o5z16dZ7PEcgIG~m=6!i?RItqPUscr0FuSWZ*f7OF~3y6A0`@SC4 zjvYj>h7%cAX(37?ao3?~WulR^-gC!8;uo-tuf(2Qjy+#S3v1OI`U@Xd^EqJ;dZi?r z*B0ec-*Tj&DR?_NW7u*5Hwf4!oRfEM;*qYs*<;H7Yx6dIztB^nE6c0cM_g0I)LK5U zg0gLPjV%K9#jxx%9x*Pb_zMD3B+W`};U6F_o^ksQc~_&Fs~3LqeAT_Pw0pTYtcanl z7e8}&mPLLB*RJ7P;>DI8+qrx5G@x9tJ!r9C8^D zBy(t$XyL@C02|3zAML>zesw3qF4l^43Swc#GDMNOLL*>qqLXiR4pK-rXF1_UbUgjQ zI>GQMVV@sMY~}5>koxrEo;Lk3v(h(zE?SkT32r2^ z(uV4ODxJ-fGt-5iLY%DSVtZd11HvVC20B_$O+>Z#dJKQp0u~E?Te{q4nJeHMRh1aw z$R3oGVI6pScyF%9PQVQayH#G1_b%`L2Hc=a#8FNmLTKw*)UM)BVO(m?VdGZu`2^)E zg)sCx8ekq7AZc;~qltf4Nm^asD8z|pK?FFPyl1qs`IDts_sA%X{&M&m8e zh$y==DGR-j)tR)Blg+ZNs@ddpHC~zZDA=hXdIVU{YK1lLBu#$%mecy7XNn? zL%z8=sgxko-W{5lzw9gCS-*XPqLYcS_o54d_#|c>sY&u1+hKpWe!*rq;D*YaWVIsW=gdn53 z0+Yc8lka^-kDkswsA%fBt?lE>=DSKrC|sMGoy|tZQCx2#pDcBO7B&b@?^AH% z%RFyYI`SZ6a?9;8D-~+uMD9xW6=;KeP1uij{vdd>4)T6m!alv$pPX)!XfS!ehv z%3yIZ$TI**(pi}Qi`$O-4qVHoESAkGWaWu=Bli`6(5e?Yb~O=eG&eb8rkleKnEa;ASrq?~&yBg;~*xsTnGD7W1$X>zSCE8L&?%_RESivL<5{f%jjh zI5B-r&SV7(W{^GjjC_Pt5H|f*T799SJv-nPqQZI9pPBu zw{lRzc=q{P4NKcoH=-3E0$IS4_Xa!e3s5{*{|8g2?FNE z7rO zseu{dK&(fMStg6+5I>u9peKqfqScnBcsR?cxmqH9z6@QM*ciQ*Z&L&)qs-q>aL))~W6Q>_+LP^&&)}ErO0UonTgW;q>vJ_*Gh|sEa?oP;H;8iNvnHqthEtP|bsWl!( zKQFi!0s+gD2<}=49eo*(U6E#^6MHI%Nt1j(Kvs}}ig{oj&cr%G$R-o1D_I#GquU;z zU4F2yg5!w*&QWk*s8b@PWD!>`#ubhM784ibTxvaai`yiMHYp;Zwh%&Z1f1B-;y?U? z!Z^4ih3`75aQ2YOM11ThF`IOc&(5uHt2|GH@~!(1utmcxu13d}{&{?u7&~xu9-)m$ z4Y~N1aW0qKoeK?hoK36kiH^N9O>-;puEtT-nl^mdnHoCMwX48;R6DF9WEeB(ifCmL z+6)qH*=!9l#fXcad)qGarrHqhw0DZTn@ACQxe1XVTaf4AH|GkI&HZb(g?8m?*; z4XbPY$LruY6XW!{Xh$l~BLhif?Hs{E_uYK+(%zam_h2BibaWCsv5&bV^!)T!?I$Y* z{o!*i`H7fsbZiV9cb>47p-=P9Svcanux))uYui#P*37Ye{v<*J?#t&kXV}rn#6SdB50GRDcE~X4m2g z9^n)jdP}uq#Bn_mkC2~;S=RonO|7wM`osWmo%X(0bVe|LXMJ<d;T7+~sqs!%yTR5#Ekq zX?-y6?A<{izQY+Yre%F!GM1M{^Rsy}c%YOhFN-z9IAGA!lS13@EI66{(CRfR2!@j! zw^K9ynn(g1ygu)1L46S(joS2888J>jjczBy4`ftUB@+OpSP+~#%oDi063IlLNRyrm zi-3Zg=)@h^SEToV%o1}jccTwOo>g6w71|YIyomoO0A&+Ad0Ah$3G^oIq@~oa)Aw|} z5yP8rJj#SfqW77jSKdtG*pgqRoyKMW^Aq*Wfl02Nx0p8&P!_svMEevrTD!EJzWjU#G%G+1br=na&i{yTjVMoJVZE`cHBgoSI(0z zWPQEQ=o&Z(ff{?%ndpY8m-NSnLI?arZR^5{BdcqyT0DoRZkLkXzzNYrD)V=oN?Q4x zb(Od1M?_1dsY6cn#>S{FIl2&o3=a#IW3H~~IphH1)JrZ8S?A|!f6#CEhhPJ4$xh~x zJGJTqF!#t`TjL%UM7JX2eO_m5I{8dR7s#Vnt+#b$RNUt~^}Vv(X+CcD#OK$yac{w! zKs5RmkqDnm)Gq@xFb50TfxV;Jy$ymW!-ahKWUSP8r>UVvSeYW4JwyU9G9^8ZDPsLh zRb=|{klIY9;dh+$qGiw!%FIqN*eIUGfQwE(mf#+Ds20*q32yxX>{p^qVE#reV_A4F zmyk@7sbwFY_%nQ{L<^i1tdZ@)8qP-43UpE?$tTSEktUmx6Gd0iaZF-&x}}~&09(Ci zZW5eG0M9!8d=|9?bS(7*gp|;GfDfWD9na}K5#L_WT+p}U%0JC*5~)*;qwzHm5rm{~ z;~U*>H)`$0f%p0SW3!-~)NK^U;L86+iM3r|^tV$aGxI@C)dx^?{6eusOD0R~$IP=x z!3xu>rP;u7p;$L#{rjS{q1*U+vH_k1!-eL3ix8X2<;$ZA?NzMpK5mXjKF+eH8`0QX zFszqfLPzD}do>18R12_6=p7$r7wQr20=gz~>f9#f?6Ue@Fr3jZDPmEhXEtCIx`E1E zR0qB@&Vt>bHs==-8txmoS0>NLq!F|GaJdzqjNt(vO^@Od&XLW#};^rc*Bl5E7OsykfT%<8^SCivstGGB}m>@ri8V_@7^ljRB%Y+@1-){BaUyrCqD! zum|1O=L^vIxKrWjRYo z#h(VYQW9%#A?dY7DsbTtvM)T?Yr$MP%yX}9 zZNT}3h(;GD+)4e}NiV3k6k|<;Xl{5n`})Sfzy=E(r~+D+_;JmkwOQaMnZ_mOZq5=d z#@6w!9193!K~YwN9vy*mTP!^@W1{1V{u@O6;;im>3+TnU4W-3cH{#n{ z^!)rjwdeQfc=Vuz#Ec2CvnLzrul2ZCuref$`FV@{Eqnfpu^(evC|NQo+D z3GghRB|T;2`x{0D>}1DNZ{qLLnxPSxOFYW>^H(O*%aTc;-%_6)3lbaKM+}TkI=rA- zlvHN9JIWCUv)g~Y`!;uPsNqC(z?c*Jn?4p>-0Zc)=`A9DLC3oR z-Y24GvRhhS3xrb~pXCH&hC?=$tx-(Jw|#aqO;H&#fz2*yXvJ5T#oH^&L| zzS8-Up<7$Y>?YHpDy{vt={bTpKsIs#%uM2|7c}Nayw2znZ!x^l2j^6}j&ztv;Cv%=8th)N1FUDhHr9nYo%?u#MLFy$_H&k#{hySr0I+}5ucM7W$6N5> z>6*gp0Nay_NT#6*zaH_3VqDIfth==Nvk7hWHTr~U5?llLN0bG!9#b6{TGXv6seClm zC13>Rv&^JU4+h5tqNR*LBiwtsN6;XHgr|tvCIPm`BGoDG3gS~*d|uGTWLj>b?->c8 zqA!spzk>j7QA#HI^#NAkaY-Ol69|4w#}s>QCcoygiinqp+M(h7-!>+?+tl(!*}bz; zkSPGY0X)aV!V+1wHwjBX%yJU71?WH~C5Q}2;75Pf)yv^=%B|i z-aZc4)9T1}VgOH5&O^#P+oZ$BBZ(A8&Yjde>nS|_V-GOMfZV4qHNGZ}m{{CdDV@;~ zHCT60d?MVQ8B3EueqRV4&R^L++_|3ghJ!GV1O+~6O|M~-Qj`%y{N+BUAvGa^+E%^VDVnjjSg@_f`e7d=+RUo^62F~gpi(M%k|-{?}2 z;R&bI*2XVQUrwbyo<7x4mbZ2<+2a+!vS@Ac9I2X1cM}NzH3-^|Yt~TU(g2W*X&bh9 zsYDto<8x#uroQWD@?f|1J++(BMK2qkg^|m972F-0QC!MM!gf^Gjky_~*5l~imUEoG zT-yd#tvj|?JZhU4^&BtmQ2;3xqf-U<4ja{=b9rq)8MEl%tZ48O&4Q_y{z-@fQ?0~+ zEXL_<8RKvIFdV+so8zD923A7Gz7H3hKg>G)BH2Rr1LJAVtG|Q5esA5qNnMsH^qNyo zpn^oaiN6XORFLiX5a9*Y7Qt0*)VI@LR(Naf03hz47dxfP`p$)r?Isyxnp13#yS@nN z-s$vu%Gl4O+xEUbNk5k|w70d}KiLMz35CT&MlVz}jt!O`xEbhIM5U_C$PUfoz@=9W z)LkpB3D?l1*6IU_61bE!VkZe-i;xU`+^ z^+qFXY3mlH(V5L3jr`t_Ov8u}co zRcNX6Zk?gluW}(pB<~gwKa4@K3Mz1bplExUXBPe@ zoxmDxSk9|CS$;UQ^6iQE_L7JhemTIz z=l~}eSm>PD{6;W_-{(fe&vfG_YELhzJ-;G)em$u6euHd)-i5CPC-q_meBp7153F?7 zf|#|gyNB%5jnzhtZwTTYCPZEjE5-;;-QqXxNq(fJlrPU!c=pC0U3<=#hKAd6GKB#p zE$>0B&+l=(n!ZX8yoQULdi<=-1(Th+dd3+m1BM+^!vQ@&UgK-sYD=Uq4_R$4$l3fG zXcJ`R9^TMmsDclzFpGUlpA5KD=2vxhiIfQEr&V66jXmG7KCkj-2Me(W?OWG4R%K#Y z;gfj;k^W*y%$JG=&Zh2}A6$ZW5CHzR0QQ2y7F^n;k_<681DVxW1WQ;yuAVwKBJdc4 zDbkSTGrS}JZu76-dW*AmjT71N=+|*bMEx7(OviW6xe!l#a1#MId{*A{gwMKHN@iU8 zDt5k539496F-LHtqB00{D^qds@(T%F%z`>z{>eJ|YQ1EU)~_z^%!!bdQm+VDvJu`^ z6Em>L-{Ud@xbK9&9^N;CrsS6Nvi2bu#6vWL1})qisOTSPKQ{-M`S2-1BekxGJ3$6E z^ga~e_oYHb;aF^cTTR8;W*CkEfJzXw^$x-@RHIJH?ow)}tp}D7i zpl35DRk7ELIY?x1A*oI_2n4af0YDN=gfG1!=(UHG3#6lhO%g6?B={x*1E;h`RauVZ zt!N!XPq~>6VKOmlfIS)BEN(dra&;_@<0KOu+6u_8+=yZX z73mBMc&yj9pHkt#_ry3d$EUx!AgYtH5uhr=%esbFq*urS`S26nZf_m|S8NUtN#E3z z>rufdqYnejt)OJoZC-RIYQlG!J;B-xPlz%QsI9uZ>zfrY zpl}M}NSs4#6f18)sS$}sE;ki%_X6iGJQ0GNt*J*)Y+S(Hr4L7TA>-nKxmp&GL9@2csXSx9vBE$L zP8!W%R$$7_rF3mq0*Cr#x;?!?#5d}94j>PsWzMQ4q_Y(WGbVkJpHUi{1W#`cF7cws zYvmOY`rD|sdN0b3dHF4JQzRXP0IkS}?CR@t5Ot$?{gbi}IK&6P)z9 zyiknJF5oB)>#E~3rFhrcIJ-x;#sWrVJ(Cr(I)d_ytHoV{B2{sTPMpc6_~D>VAOnkh zgug?VFY@m@HGo|-mT*OG3AK(z`RDyHH0*unr)E3Ij!&+SVfsKZ=PDSlm9j~GEi>+= z+9?;}bmj$|BU=T~>7VO5z135yfteFn9;S?5;k;%@&_hJ0bq-qqV*J z`JgcX%tA3M%^o5{M$XmJ@Cr6kYXy3osCH>&QlN?`=0v^eoD+U68atMDn*!yt>ipzF ze-r)0dcOI-@EjkSao5XDSHL|JjW_F$H|&pXn7Ih(fmnV%%SE3g4;N$^B;d*gT~fhoxD?%__M61sRd-toKIgs|sEp{C*r*cF2(d_<}=zDi_uhhB2i@ zeLVtYcoD$|WX{FHj$Aaq_vu*oe$DA|LH6r%v&yUxr8?A#?x$ zAOJ~3K~zuTr(>ZoZa0>jFk(j4BEwuC<>TAy| z-gKDK*o)jDD=|rBWFqDl#J4xpV+A&ldp}}7R?Cz#v#VnhJ8Wt+>{j!3JPyUTH>tHo z+};aQ_U&SI#mEu*(j1{dkiqQYv|TwkMx~mxMc`Jc`y@Sf@l+~)C5M0nOaWfnUXk?) z>Meb!k$h>(E&&8P#LMR%x__&R{2(F8HWvJDO%HbJ>z8mLzDes^*QF3gRbG*-I9PlH zczf)o8O!$O3?oDlS}s5|R8d!hN_pf%?H%&W;1qbI=g3F-2qae&j)O5Du+>nSA7IZG zYKI+Je_`><+$8fWna`J#o%NOV$zl(|1S%bu>O6md{v5C|?ZMJ*Q+yp#u1_L_G+~hF z@#t21h9rBwU!>U!=V&t(;YGIN9HywZ>#{|0aO;k4gFro#JrecfpyzNMacky!ko&HB z!l~m;e^Hs;za#<-o*Z}D<<}qHNc!wFzpEy^o;+CF%7=N=l zu*1@Pob@vHhjvGkPvzuU>F!Z?JMUWQ$$;U%yN@np$ZMp8LJ81VzX6ca*ne!N!u~&4k_P@HYOrEZplC7`aD@4Akxk%|Y9P%3UZW?>6xPiIAOdq}0spgH}(Tj6KgW*3!H6uy-|94A6|VoxEB9ul-KI5v(rGg`sAxAT6PQq~S?` z0ArQ^aQ>p0+2#UbM~%}mqnvdw@;l}ms0fXqSCJz*jtMAv0MNM*Hj{I90JvaeUpxOjMG7Z2l%6L0QTS5%Y=QK`(h(*`^Sp-{=P2jDt4yu1=FvP! zdbpBY&ObN!ctGr0vZHR;8G{|X%c};iG4md)+LDUZl16k~jTD;=OpS=&AbxsDx2Kon zO~THwpMz1^+S_)#R;OZcoIw7w(G%IWN{80e)=A*NfBjxzLalw4@tR zYv&zBFI4Z85SrZWX+u^YwSNHR14SRm2-;ZCbzQl^LjjbiJDD}`)+3J@thcAhwe%@q zn;;Qui7jw0xy1{W1);Dx*(5P}jn9D|T((UO#n(+gddVkT>2XnNBUi)Q3a+JWj#pP4 z26>~p&AF~1mq2~wOCjYO?qvri<>yh{&AG(#7%C-nOOk~A_QTLAWZW!d*&-P0k!+u0 zS?|TTqwSK~>4FuR6z4NBz`NgyU*??=6eR4qBoA^b7zWf=`6A>gA>j7V=RQIH)9aIs zwDb-FnY7tO&@rc6m|N6XOI~5%lg@|TL5)1(WvqqSwe}5xIsT?d&*S4L-_TocgABNi z59>jKA1uJti-L}XTkCP;u+Sy}=ESINz3}b0ihq+E1Fw%O{~cfdN9}W*?O+21x=iIv zj{e8y!*R_KjdMZbX+S%Tn_A!JVXRHX~4|q zs=RjlaHzf-=PKy}uxw$fi5u91YCQ&XO*9Gy*d;z$mnH3}vCZbYRIq>`8~f6E3V5{Z z7!(;7JgPjeSo6c~%;qbalRccao~v* zD6{att9T^p$tlhr1;~OsqA@34J{w&cMkan>GmT_mv=ro0rY8rD^h;Vae1BJZlklvm zXrB1I&Hqh09%1P31rRi>LX2sj%0HX@t4*3b;&V~00{_{gJrlRr#C$t$(|&ph^?AM= zQP%K42luZ8l+QVMn9t;QLN?K#h{kQ%`E?uX7!6}PrA4j-)bi26&bag;H*qcp-tTCM zL&=^YN1F`N3|Jm8f=5XEbE=(L+$W!%sI?eKLAU_ju(@fE!^_lwpU;u#m@&=oS9zXxhQ{WweF%U zzi0o29}DT226Y&JdncFV&fIwVEaYFt6fv{>*106OsUI7mL&XG89+6HqaP-J>3^|h% zoPc>WrUJyvEiP(3A}OFH?k*;ref9`=r;p#7zMPJTas|{VSI_Cqky<;#oP*xHJoaJ9qEeV=kN za=rtAZ*QK9*XQw8=FShBuH0WZ75E$f&IU6ZeZRsf`FY*GHKO+Vz;t^-{PdFe_GD3v zU#TzM(8qoVE#5!S4cv;w1Z7KChLG(~0M;rU2wrT1GUOY3hyJJZ#2svUX1GX?lA`b$ zNP*7Dc^?1>1|4&>6lQx;GW7sZ$nAJ6hX`xk^n3F0D2+Q}wB>N+f|IhqQ>l=q0S^De zw-=^&cLHYuw2_xQx=epMwE@RQA~*&jnwOSj{nZ7<=V9PXiJYUx3SYat+dRRPW@Wm; z!br;WIBZA~zyY$rmX|C6XJdS))fD?}65TmsvF7Xh-&Rfx>D>*G!xzS*<#x)lA&7hU zn!RFqSzFnm!KZ%@?s9X%=R9Kl2S5}i&pY3#0F_daMI3YUo<0v-(n!aPC{_g|{8z}+ z72jbvxYMM1rPT+Fc6Uu>ZBJsk=xw@kyNgf}AE>@Cj&LsG@eWJhQNtL^8p;`ZT*5mD zrW3QfD;eTS@Q((;03yJ_D?UWK_`zyr0{uz6G?G)u>aL0vXB$^?J3l4G$utb)MCiHw zxZ`<zD}(hO+DysL)+O_SblTdD==MbR*(U6ZWNom!;b5A+G?so)C1q<@<`sA&AHSn&U>{c`w!Ycy^&n>sT<{dl>i&{(Hvthn zy(Ho{$lrm&+Q_tg@2|c-&t3PE5mEfE5o~3B7Ms?Ebi0Mq8w9)bzvT| zVp-bSEXok!D%2yZ*<&Y)bp&DP9EbeRbRl7A5lQS)$-Le8vRog4XUHeD=)wa(fbMw^_Z{+Zm7}AV1 zmqQ4zmJyyAcD9+42*h-HH?1G~-PA(6 z4O~1o1R4>w+i34((am&i;4t#PDCt`N|;y<)G~-z^Hg8RI=xoLs8q8y6%N}?sn>g*G4)JjcP1~ zSQLW3nyGx|GUCJ zy)Ae*+1$-^jC+TdT?i|qesDonQCQ1Vf1{-Ob`sEAobh&lPGMy?LDl8Oy6v_)T>}35xyzQ<;?0Us2IjJ6xH6}1tw(}Ewl_z1uDvJfgOiZnv3-l3$o5t10c0L2H zhdF0$8DkauGC4k|*;~5?rFPPIdP(i}V(5k4AR?OgL%=ja$dHjDne%r3JI2>aW@8bQ zirk>#mY8rD!g;)ZVQPXO504{+Ew9yG(crN0Nk39_mmY0v;zJ-++R|ql)H1oKCUDSj zZ`AL`u_>EBvzm(tTu5X%I0o1;cZFGTof{D!V;}VnRV3RrnV7eIoB{G#by4g^5^2G2 z<+;MlLN2y>nNVklDgraf*X2^pKaPT|D~eYSDQQyq@clE)!sP*FzNnx}SLlYHfyPAS zi=xF(z>uwj3@9uroTUj&eRA&bP`Y4;Z5n0O9h%KZ8!GrLbD$6Rf8-zN%b7j`oZCXK z>K!;cS&KbiH96$*v))><=N0}1R(1zlrDjnN({@~TReI$0yL{E+)Sa$jyM!+(lzwqZ zzmH8Nu*eA4*p9_vU#exWl0!d>>hmE2Tevh4TPSMLWBa%SU-G_SHC+gFpqtukIYt*@ z1}5Y{ZGv--aYv~dVKIYDXk1A(ZO7)ojaqw6av9ykzk)rplxZ{g2)<{#A58XLi3ww` zr|Gdx+>MU+643eXgs0KJi@d)>K|U4jbZsHOXyaz+pvZT<`$0D&RrTiNzq6^>MFChR zIOA>?mZ}0j?C480=*wQuTe$N&Oo;)2@s0szq%seIJyF;61DjuHrOF9!DrwE*xXt9e)C! zqN&o@n1_v=e~Vs`Rk`u}z}fSVr8Sc%Q$oYXoYmzs0(K)XsP>(j0fYnnZdJ+^>2 z!Lh&%c6TPe?A9u=%y^kdbpqDb|#=G0YBfVXAJ`CiD~faxE=%C^YXwjG^My3~>PP!#LN#XDc(ykF(OkCRcN~Xm7-cpF zLwi21w4a+DOr+l0Z{yKwhN3lUG#hhVRS$BZaHml(iXWC@ju;V1l8S3K$YmZdlG3Ce zX?pzpU7#S+7lz5kCYv!b0c3sN`6~>D`BvA9r5~E0Ahq;)Nf)_XP5tnRDTqk#lxde}O;0bt{?k3z_!vSMZQ^5$z@vy&a*ks!H zZ_8$k?fQJn+f!wnBC3w}(Tnh2P`<+Ecwvafb}KLT3h4*+!kZsom)iLx@gq#sA*?zl zAYYhU)nZ33+Ed9gm~Azx`1c~xN={iUSs2N=;S^x@x7e@ns=0(3{Q!T<;wVqOHYWdy z30ps~)N6F*Ympqtx4&pm3z9n@^e91dnwi55w=bi{6f_Lj4WZx0`Cy z*JvxWC+xhs890Unt$m(dZ$4a|F1h!br4@PKP@!a9U&!9iYEr-`&Tp0R|T8m!iBagweQ6W!2jW>R;+HIn%9 z3Dj32HZNB;EjurPC~;^`<_8<-9VWcuVqLB|phbVpxo_fq{ulGh-C(M!j`ZZmZL zqA_UC)(}J7iV2m5hBvd5pY07Ry@q9<@70 zDs`q9#Ft%6O7{xZU_3AfK-A_-yw2+@H69AAb`W{dNyqtpxjzt?1{<(C;q7EShG-5} zh9?zI(AO?@R3Hn#S%)eyKL2^>9+!VPJkfn5vQ6WAdAC!e&WpYgm6?%GZ{~6b6mKht zUD@Yv_y^lJRkjWPT8N7y(w9@8NE5IUz;_UIrzI7&qkwVN$!8Ibna?Y*$HHF;*{xvb zC0gd*m&YaoN;W;$av>hby!Gk!oU0%!7uth0^lC*g_T;u^x9ku>De99}*=&F(r#l_Bb4wfxU z|CiOjvA4_W|3B*9bWLs?R}=)4>FRUuy#KSFZaYJmAKb7LRD}DRK~*}mkN^l`BPEfa zhBYA>D#0nA>@1eMxFQ& zifEj05Fk6am-?6;sfQZCl<%BsMZaq{@SIe1<6yY1Ctt^twjxV)`!NYbvGPN`9s#}r z>}keXE^)xymX#{5`amhgT}fBw-!8wg^=(W)W6xt}0)N7Fip)Wy<9;s906)sRGFPl} zl94^M1D=>iC_&5i-8h2sFfjXYuH*5OUJDIF%CtudlxDK-;lkX$E6)a6OY|l{zB{eyvgHGjhsVm!+NVjDd-A z&$Ih(+I;;2E@NHYf7b?uv(de++1NWtrzeTEX$x6Yi%avn)F)OX&$d1S$ORA`CuUpo zB-rPHC%)knC@*RyIT1CEst1M1O0_-Rvi@RO(mIbxWbLc8oz~zhAkp-qh)X*tXO2#u zeb&az>K~@|N?rf;0@D72V5PI_k;jeYZx1=*z8~_wI7kjG&pOXxe=`nS zCOA939G*;;49u&vXiZ7zidSVvBU*-e;ns(%>nTr3>=<|%VCjaoQ{X8poAM|MjjZSb!0*29z~GH9r^bd1IBKp* zqBdCQHxb-#|HRG%z$L$WUUvtYvaGz)d)vD8toEk6EmqjKJ_PU=^e}qvsE6u6d4bHH#a4#WDpY?qhi#*U>yJU7W863(UTC z>Lz(y=<=2O+|5U6-q2vqY4pl_MKZ;0<#$*lHRkVakvICykmb%a!WYn*yeM!TeNbIl zYaN~pxd$ElPF+$wpj8Mau+xpq|GwL|MWsy3Xxm*DZ6cMK7&z%4r~gU%ju==iO}@lJ zrt{;sDoDqJh!w)m`qe9`ALi_0LW&*Lc?6|_t*-ENhBNjR-gq##-MtMM5qN(&C7;)* z)`?m3nK;3RA5)*rLhdRJdd^^J?>N~N_Q`UpcP9c`xrglPWD0nz^D+Hza|PSp^nB47 z06y4Tu!?<%^LtOZ-ALl7s-NW01m!h6!8lXvA;cFS#Ih~;tA+b z@%7tpT&pqi%?-I3BAXUyM>uftGMye#SLd-QU(**tttI5*2D*W;Ex<_k3xLTp7chuuFIxTbYf+j=x`<%q>Olbu1<>(;1UYs>s1WaH+UhL z*a>b)3?DTWw@I0;j*n7ey)^fX1VlFw5g^=2o{%wu@{q=l^(^Ce+6i0y6OByS`8H5! zaD=VI#Z_qfa8}I4V*q8R=|dyGCHwhFQyTL(wC64rN`H8%VXyA)JW>W6^yFU#R&Ev# zzpp0+s<{&HC#l^---1e`F7p=|(amj(d~)GmdIk;`!$)ozbZ!bg<+epzKCNzW$yXp_ z*uST*%D#7C%4Xl5xW2RnOP|zv#<2&ij4rPFpF?dWz3K2k3%v_H>yzy|{#4Ne9Lt668MY@)Z@w ziISIN4qPB+@rd0X11i5IJJ8Zo+4f{}ms|tLS?mO?zuDuCiMsMyx4*>O1EUOfL63ot z8ZTRUI=Z|r$>ES`3E?uLh|hklUB%{rarOP8>3lm))ZZfb0=j<;M!R^4AD#TRE9I(m z6Kibd0yxU~H2;AshuE)g^F$2wOk&^pFHlMu6|_9>UEi)iOm zGj8SBjgK%C;(JLqr#N=BG6>Ve0O6b*C=hyzyrp#;l%&|AOJ~3K~(AAgc|>~%UiCW}g-=`gePEvSG zEJ`FTQ0u>B$a4lWH#%HTs9d<7gU9u=u}zVkO#yO_2K|y`#Cl>D_g)HR2AN3wC^fFj zVg(Js{!eMrOEEO^NJA#a#m6ju{T*$2om7!n_UQm>3-%3yj)1?6=#iY)4QD8}-dg-< z$LC>pXr4N99BdJ5$JL**@!^b2U#+}s^2=)>qcfJu3Ym7>kegGSIQlZP&}DJMc!b-Y zc$?yy50tk6f-HR1)4U>ni?;`aaX5-Odfg5laXtYim!9_q!3wiW7lIZNfPBOpyU)R) zT(7_B6yh%+_%Wq@Kygd+&t<_E#HR(7d<$C&VfG#M5QTZF@pY zJcF{x_&Ez;yKR#YE`aL`0KXS6*ar16Xz?vy>#)dw6}CDV6bJ?%5su@fYD;{{qU1hx{WLs9jzSlz zR+!@3)YkrMW(Vd;;#0@u)I)atSg$t0Z18QW%6i&L*SYs#Zo^U#Ph2)C;P_*{P;y+h zKyBt$UjJGOHiY~2zPC-UoFnyn0` ze>y@^2^5J7@cIS#^-udn0D>C`UO+V77+@i|Ms`%?5rN0!!8>)`iZg%^TWPFS)^a$Hn*f zI<#1&^uw(sPP{=se8W%6Y7pm=x-!&dD&d zen!}KJzNz$;st>g7UfCj0S1l|->ijUaX(+MW3QZ^yNdy;^Yxn;1dhppa753TvL`we z@6)ovOq3}ftTI}DA#po*PZoJXc1-n5;hNAeT^E`aM7 z;MczZukXeM-9Y!oXZ72nJ=wjPfc4azhW#ncb@Q8Q z5Q_N8wFKwCwLtgIeFRjKBW{D&UB|I|%O`sJtf!(P&iq9aH8yQ2Pr$>u_wZ5lk+qmylR<$_I*eyJo^fWkqqInZ+{B-#!r-l!EUgt`{^|*}Sj9gUj$)qrjeEmru ze$rZ)-s)@48JPp{?#kK$t5VPS2JO*b4fvp3WMHmK+tA=ohJ+7IoT-C`PljT-P5ZpG zKEB;vE+ENcPFrseOw*?PGdy#!-zz)0UGnhTnm&Q&$Pm_`aO>iL+}WqmoM27rFgXZ7 zF-h#ActW(VYINZuSg(G&7#!ma8`ZRb1RjJnX&G~f_EfirXL=VvzBNVyt{lB0EVQGq zQrszxUY?V*!jwUj><0lZmrXUn8TJy$L8_es(-tu7Nw08$0C1uIwNA)%z#(51U00Wr zcTqx#v+znuT<>Y>*TMj7K$E{W^G2qETy_gt4ME@_|8v+hpm(wW(khPt(4XXA-)-zp zH?&DCX3ziteUZT=c1Nt*3!RzqRssP5T-TbQNb&l?49(G4GlAFf5|Aq70m&;u>i&_d z*%pq2I*Gd*L5ih=M!IK{* zvGfD*$X*bN2j zYf2iy-)`{{vXqQP`P6WBG0wuI6h5&xuTfUnPV4(amx%oUCqzf%AE9y9(ICu z)1jGv8o#%Hy9A*(Nz@N7@ZTb_USgrQ`0Q7kHkkeE~j@Bv8Zuc|1V~1Lx{7w!d{amYtYN%oH zqrS}CUG^t~ij+kz6|XT04}W#Jf)&G)1U6b7`jf@y^47;r+LGK&ifzkigzK8wiSsxQ z*j($nQ2yW{Eo2!K4>R~}1bq4Ph{fsO@qc72M{nS;pz*}qZULF@vA z3+xN|ye7@9!kn~ElwN&D;=0F)9NQzipr2}M{o&Maho5B+dds=BM#ZS{&4y~>>--mYpzyGH4EMV8koXnME)bzxU;1vXh~al!sw(%xz)EM-0+tk9zzFcn;~q*3&UekuAp6a_o7G^Xg^XHr&v1dIB`dNbI*{;fZIs`lz<(#mGDoCL-8{*DdSEKeyTwBsV)L;#0!`@)Cq*rJ57Cf1uV1O&* zbztuf$#?@Td3H{YqK(x*k-6$iv79CKf>0ArBn4Og{(FKh&SWjis>g!LK7yu;);TYp{FAPHdeO^UcB+Jy`M z;?nV8qj0Xuvjyh+f48a)qNA@7*SFtZm=jzZ#E}rZHZeVxsu}d*(mwzMy#4{iZy;y` z@+I7^Y0Qq-b%s@RiqiY2HQB5et3*I>)7^CY>!0??fqXs5k?*3v0@@e>D*;sktGZ`c z_PQj&5>s(_a|<6vW`y?X#@u_)h8hHDi+mPhKZ0hDFAch@UE{Z22<@e32oqB0ZDQV1 zT%(?Jp0K8;eRKVBJKh^o<-y zpPnrAhmL+)&#&o;w3I!F;FxN%jX7PpfYwXRtB0dDjSxrHZAB3Mo^`5bW#^NH_-CMX z$*8H@O|#$dt=noO4$H%q(0BM{3FKV87q?CQoht(-a_1qyz8SI5TS~KXxf;GbVeRq+ z+rxiU!2J|`&cIYXbH$WN8u4ayz6;DeH$zgY3~55tn?h z>>T~s2tS`Lj}^iBc|n4nR-%AR$@%;mCTp??Toq=F_~TWu(2v6SmR(%hBj7^EzJh^?e#U z3yfrQ2C0$gA}k{zF-IV^-BCn+)l(r|RYc6OV*>Oy9ljzm0U)f;TcS!26R(7Lkna2! zfCuo_horutnr7b>bG=aYBnf4RJ^>~DC-tjXKv$X0A#9ROTrw(dyoy}ZMvOMPzdk?#FtuQFe zhjj%6rpVvX8xC5&pON*>C+LA;A|IjT)pl3QEt}Avx0-U!VU?b~TawKN`1XWO#+ESS z%X&WqJJ5_Hqa8WjiB9W0*j`&Ib&@|+e9pTTqQ_?KpEn|>3+^rk{y7@}gijuJ0z~yq z8Y;Of>v9Kq8z&HO;&y&yq=AFkHnPc#ZF#foI;JKpD zOLqP)WWIM3*M#o;H^8&5eGRvyb{t{NxMwy94$BlFWaWUyrl@Z5Irry*MoXK_-JsG5 zW|UWe)Ek{yb=VbhC?@qn*3)2E?TLc_+P2A|YTUv*(;N0VNTgEuy^U_r0J1!x1dSso&-R^aE6e-w(ouawElII6&zS3C$lW6 z`hiFF9Y8kEhfX=pF~1i*iLYEk>p#P&;P*sSjsVz#hn;uUlzeJoK{WW6yG`((g=NPn zD7&AGsfy0TB&mPJZLg6^PV4E?_zhEqGpN|G zNeG-jp;wx>2*0+Uq<9D%4~AFApNKWQz;#ggcirXed18_VlzyL%49)Uq10sJAWPF90m*< zUffPz){shs_OM)M2YXK9@mK|%PYeL~mzU$3ECFlO;bzs&wLtPJRd=fv zE;@{Aarq3idii61rK(Kg?n(8T)f>a? z93>^UJ1qxACl+Zi0p^+m%x#JVNtyOeo@x0rXHsMKO$js-oqT$U4gvLn{&oFQ zt>e=mPQB6A@)lRYJ?<-#wr*e7M|HrRrm0#D_6SIQ*8=QsBi%2?=!jf9oYsfc_^5&k z*RGLi>u^{w?4U0@J+Rs(sh?_Q0j9kwQ`kG?GWLCuhl9RnJe=_4C#LQgyoR*KlhPl@ z)q!jLgKszRZ*(rk%0v-3oFFkg^lTpmE$KDLs#4zT&FmM~UsmW;%P4r+O@oVXE%>3+|xE2ixP$z|h6xJLZTycPN~Whr!W8_9tJWiX8v?Y$oP& zF~+VdRnR)b&k@>ohQu{>KJ{YWlc(ZD0C3BGW5QawO6Oh#xTHfzH1t#A)mHayn;QVX z+M5ttrRj2@7sS$!>q&kGTX7=MG-Z>lWM=ecgf#t6KW%|R#lNSs>(~qIPZ`tcj>>?9 z0#{;ZKw|uI+3Nb?>RW>^#pl~?KB%u%lhp5HaQNVtsxCdXW#;X+zBjRV&6lcbx^v52 zz_a^VmLnr(3HXu~VvqZMXN=SG{;W0f{DbE4l$AG7ICD9x zz$}O?mEgqHW7*3Z#GX2rJGR1?j{4~+(=!~ykhvsgx#!PD>yY}mjn9oP7vU1)<2yD41XXj^ zcLI!WJfob~U~7PpbKKgQk^Ls+QxiI(M%m_MFx68Ha>AZj5reW>b>DCA@Q6>Rkbpwp`&6H7y|sC#9l%QnI(w%BW|d{n zw`5NcpX&_&JjiZ%;mDAS-;nnBa7G~Fg7Uv!K={a{RdOEUU;G%4|A?n|8gfu~rw&g{ zBP5SPN{^nazw7ccM@w+F$a=mI7XY|nxLUGQPPZnOtO1D&aDo;uJ)6E|C($PHzZ~#&)mx))z5tM|Ga&G)QUCZQuwO`_b3A|1 z6cJ?+?Bva?t96=`>=|wZXm1gqc#FW3`$9)>(~Dh-G&7JlS>APD4=(ol=r~Gqq1x}( z*CY0=MDL$o1cfYhvLh+JV@;d7oIU1apoUxMJc1mu3HbbESo+CO>MJ~C-AX-ovtzBQ z+r13hj`RzT;|>1O1Od+8f%RM6r;>X$vSs+n@cNErzUm0Inmc^W*U7X~cHSaj1MHzV z6_wPlyPAD~NtGmG0Y>+-qNtxRIZ1w-9PnntDjF9rx5onkDRkQDh}A6S+|rPtHAB=- zH>Mcc%gtk8?hX59P18Bvi_zrF;BMwpr}wwtZl-Ag@dIC92P=HCsP0C+!d#1-ML==g zjc^hx!mV@kt0uWN3uJmR=>&Ycm+d{f<>n}U0e=1JSH`*nya29-VCQmv@2@oo!bH@y zcpWH$K3GVdw3zH+(}5cRuePp&Vab^myMMY*8*d$W;Mf5-8g%S&L)S8r-Q1-(=p5@} z7XaKq^dm$X4>(GUC2V

)6uZML0%PbZZ8VJ-VVO^h25?gbXO6Co)@&!_1)#w15Ux3J_1=f9P{t#?BC4T5R*dPKOXwMH3!HJUecM@B9ofS5#HM;ali9Umt6IYXwJ zxl9KqDjdy4>u7SL`8`XegHnNAycx3@`WRC^oUit`8p2}4@r++j8WGg-b~+(I!0QWe zye8vjuO#%x;wFe!=;o)1oJcbrdH14nBD8Po2>|g6+M;|3V6zOme;{9^&VGSEO$^t4 z{kKXNbmg(q=0n5AUYq=&`wz4U9(DgytOz6`KpcfWbN(!IK7_wP=%mr{XwD9}S$dH8 zimm+}L6lqgVA3t;9pX<*+a{4K4u*q`Cq$kGnYGo@Uw{+D8(rUuhwN$i08ESu`o9kZ zaF8%$*!4ZX?2f+^U!h}bH2n4QrPxw;74fnjduv6c3mZG~%JGgz?E)Aj0hg0Vk@7 z7pND@HOKN+pj84Xv4{Zc_1`E1l!*D@Xva(B#<@K8vAtKwOm&bkN28-gVO>1NtDNBobX!}x1L_TfMbp$%7?@g5Ov%R^W1u&NpfcB04^{b^M!^sI}$E=EI4)#O}<1yQ- z49yB-fC=#W#wLjvO6mCLmZ!G22-eyH9H%8q_a6YFzIK62EabuSzKe68o-C{yA)@%M z^6nn=wOaby1NRSTp?Up-&e0h-{T3g^wq3DR0&$9-6ppecjoFfP?$5O>9~8Th(>NaA4s=oXGzA zxm&Y3b}4|nhC+hhgp!id85Hit@gzS*LAM^be9zMe_K{~JT(DC>+^^(j~-*SSxqiHqnU^$sCWQ?_BjPjoTfZwP0KU&;#i+e&W8TzICG`LnpbQ`*(LgmGW#cAg8FC z+~^CVe|cd9b+GgDC=We8dmz7Gzo&Pp#g3DVC*fDSrgCnx^I#sLo%7~N%}M*-h0oDS z^F$OshoEEC% z3Rx4kJuTqt;Z()3E3GF@g5)5nCutSIjAl1=jgN;JzWu^GUgE};$Mh+gelr|6LvZa3 zWhbLkb!bk& zV0-;utR;X0Hek^cRjK>{03ZNKL_t(--ne)cNgJ;O?JWX- zZ4?c3|Fmrny|oBPpRR#6=X5n?w^$qZm6lwsy^b{tRyPN?xpnH7ex|R=h_dq>5xRi>EY(YvQY7Y@Za4J}oHVkQhx}SXcehTqm=s7$>aP z)#d@zY(cOW(9rWR* zY}UoO`gw8D$Dvzm9^up-p~S6_mQZZl zv?gluMEPe{ahqGy?49u#Pf19f1FXD`SSdbh&+BS@9v{z=8$B`8n9?t60ASybK9X(3 zfx5;ffA+?V4vK<<#hNU*v(Xa*dSNTg@3Hz1@wkaRztO3WLG%0;oy&rcFz$(75E%xe zsi7S~E{}CiVK%4f|i^iDf;Gnsg2zQ2;`Jjjxijolqu!+h6FyzDMjUlx480~jn4;|trzfh&? zcOt#ZY)jelOhR=1RKh8HIAYClH(#EZ9+wG|`U9@~7<>*HWOIa#Csg2|xu& zSf`yxAC2v;fa?qJ`UQyJ^KF5-XF9Z}+HezV|4`jl0HUtb@CIak>8xCky+!HX}e4DI1?n2ll(T9j~#|RA7o&7vIlOl z`gh1(I}r@eQo{<01j*uc^(0+!MO^P44Z1}XaE_ODmqg_zoX^rbjdGbIfmnyYbf~09 z^a8qn_t+udB#;Td90pjfqeQ*pufvObTui28Jq3FGhT$>t_*|7eQ#ifAAw?S|3+&7!20xT8fG ztG||`*9U*w7Iv9}EGhB=mft@j8;SFAldH&94rRvghcuAsk&8lHNOB=3>GE^5cY(yX zFPsB-cbu%ZL^r-&PnmkQ^Z>U0!M^J7crmJBW~5n1m7m z+T(2w+%2FAfi4PrFjj20CC*E*OSBXB!=4dZtdiPt4}z9 z_kFYagh8uWr{*-j@#dN?Px|Mda(R8B5pPk=-44Sv|0Q+JFsV5+r46G2!Qnz^KJ|LB zy4ulhf~`~HUE>~wxKvh?_LYfM@M31l^QkAat7(D^1CndRFOVO40Eo{Dh~_lTd~v3) z`KLH-XQLV}?UwtsM+pFiyz4_*OlPwT-_#~Me*E%|W`~GTALl%*6YsbPhgIGUwa+QNlfaDM3iqb+f5 z%wWg$vsBL;@VwJMe#`UnTKmEQ(wi`8DL*DXl1CYxSQkm@zo*@rYWsVj<6E_Mo%^m5 zE1Kst{I5OhS#hE|DTVC6Rmyt{7S|+I6=sqktHrrYIc;$Zp5f7@(Vn_=91S^@ON7S* z^9hYd=&9|n1THB-<47lqr=+$OJ%6!7isB3az<)#s%RbVH)0h(f)yIu6K61duc4G>` zt-LJ=12ror`qArDHY*aK-%b$B2TfkIsz47ooOvE@;u+D1Yh~36i5^5Y{@u|jL%)WX z%a-;@1$pHr5??_4K>?>EluY4Eec)tn(*6Yc%?)B?y%W$nb6@MRTUQaQz-OX>DyBiU|kS=~DC98$?Wp z+NVMXyAuGk?EvpTo%>*tKs9R|#d?wjmDJ)=UgzB*B)kBjZ)tej$pLHWTg)Pv71h$@ z&r5X=5|6`VB1^3I;FpeZJPqof0CfMI91S%PHs#@{@Vqc<{G1n`!v>y;Ab?HE68=(E zC^f~6xJqzdoim-~W)V=_v33Xr-pCxN`av6?%&1Isb(uXOvub(_=oVqi>1IIO^X866 zQo5vWRofr8oVUTonWzRYh~hH0Lzy#UpavM#sPVwT3|2gkuntl29)hbpy7%Ak&hxFx zBI!Xbd+0GQ>HZQ2383w>)R$V!X~{W&aOiqDfWeLCnyCJyGp4ZZ;?M?N$)VWzqN}Xo zi)RD-Mx<*H7)sk72^5NkuC+2AiwLL&Sj3bsAU-qhF&x0SEN2sd;m#Vxd9aw>JLr{+ z*jVM7ZCvVY0a<=Ng;%KgTh&t++~h~|M4bZ!gzrw}r}abNOaMWyOER0#`<7mS^bI(N zx+r*D!>x!HfmA@)yWI+i%^K=J;?_YAqtnZ7pYlF$0~y%{&3OzQed(*%u)xOPaa%%Q z@#Gi3#j*5wul)>)ZLx_e!kmoaA76>d0bB!d=_)Kvw7oY~1lrmcuHFxvt8mzcS;iI4 zutbJV+-vJDoPKhAzy7p^FRy?40*w7F7c&uP#FN8dFz`unr2%mT?4MK6{ku&NBwAMk zr&T3ZP7}*_Kuf=!`;=|j)}3SG>|*QFz?witm^RbiK)V6v#O5O{coVGPZGyuB4n^E? zuaogk$?18!-Q=;1;t4%za8o-{8+_8AQJqYF4@n(EkvE9M;?=L?+_C0=2OPjm*4(t- zq93>e0@Dg~n&J}{N7K{Uv@8baUX)SQA?QbD`_EPEO$9a8ZNIrXS0$O^eiI7l5JrqR z#B1tQk$4JJH=up7x^3;S$1OVWC;jrgOUqiGuO+eSjs9cd(k>{o|5OG% z90gdM`|5g_qK|EvhPSxV{VWu@GuCE>!m5t0j_Ov@(mp5zy!umo0J+#-Hh5*%^}>o_ z`)t*@&=Mjb`~rd(%z=bvm(lRC_QVq}K)M699FqhD>w^R|Jv}}(;Iy)S)#H_cH#Q9| zn?SaJk{QDh^#uwQR@7H^juV}u1WGoDvuKXj4jA>dHDvLz#4W3|hE!$~cr1>O@0saQ zCz6*r>!v>~x8=Nhd3@Nultp9~Qq>>jgwn#R(vg}V+^;`?0B~LZ0N_^}96H@d46%=X zBd{%FIn#v5CI|k^$$=ayf~fHA-=<2zH$l?z?92KEf~$W%@&yQA{TD$@eDfzq1bw1_ zddz89`qnJ{4XEz-@0-c}uaEcyq#lwr>9FQ3@q{0MH84rNc>UrVd^oL`!RC{LABzul zB0Gv4sY=A>hJV-J*5GdsDEY+A8ekU&ItZCU8!pn!L8qL$@&?Ep6u=9V#PhCC`3{-G zY?%yiKhV3dxQ}bJnwT8+d{lJI!&T&a_(%AsbDz_E9Zgl9%Yb+pj}FaZNLhv+(T#o8c7mW0V?@Kmde2f8PsD)gtc8r%@%(i; zr{!8y3XbfU|I)bc3vJ8VaQfjZ?7ZxF)#MBCy5RWn;H2k~ARxHvvLu4G+rb4Fe)YPq z;qKy=IJk*kb8#_V#HvoT@_%beo>wqJT890Ev&RpUt_0ky|$7*yLqBa% zH~YZ;=G1-v0l0rmShnOoK|F>}rMD8S9PP^CL0JF*ya29mxcXwXawiOe`CXRS1%Lo( zya7W%W7XaJ<*A4gm)BJzBGfSa;52Yxzk)QFj?UXB3%edlF`f2fUlQdN` zoV8Z?T2A&9BYXuqjQ^2to=c-j)4Nxoq7%psP99eQcuE)ZChx?_cnUM_JoO+!U40q7 z_YEs_qrv+Fo+&u~1ax+Sj437%n_Pxso(i)2&fb1Zoaf?Ts&81X zv8@P_f0QM1Gd?v6xrV^gF`Xw@jcewoRU$g?cW7uHn&t9}eNcqJ23r*KFh=FOuAjfW zpI8Ph`1YLEIF@}b*OyZ5Fia^~XKK3xA|UvI`$ceF|IESo+Ii!tW>{X&s;%u>iSG7U z0li!c#aLnXe3eE!@3!Ad);C2fhK~RMW38BP5TBrpr~TQhnZ$<#<6m9=THn)O-*8>u z0MsW6?mvN#%Y^yj+VHZK>&ydSDz$slai>NbOX`d zUl(qY;~z~lHu=8`o#`I8X{Ki#?#L+xQqSB)9h^ZLTJ3LFuKv3Ak1TIE5t5ZS<-{i_qqlux%QvgO>7GAiv(e_BeMsboQ(2oPZH zL;>2@v9ocg6%YAA-ak6nBKz0XGX7MM>Nf0OZlA*3um7|yQ?I^5iueY7gLAMWn5_ug z+Ev@Bpl@SHKR~bF!p~^CKZ`<{9R3zqj$P2I0uT-?5WfJwezhHEMkRW6AI-e#CNttv z?YkzcyEl6w=Z5jw+jac|aQ$j;pxl2z?9`INd77MI^73XoH(p6DJrBkqPX)u?oz9h3 z!2;g%gx;^Qa@>b<=XPDB>4NL~KQVCFL4nq;iyngb#K7>B^Aj_oF?~Rm+fLL4vH_5e zb=GeiQs!0nXAnvIIs7;Db4QN{gK>K98&YyrbjpVPRpUm+;?;?u?r(QkWNpVb9?H;a zeC1OaPd=&+Q~ipMFyo+&{TrNWA%fUu7+wc;_y)mDT={tPBtLT?Tm4)TKP0qGR7YZ! zmFYA`Lk)9w=oZ3;{qgN+Q@*=Wg;nMy3FhkL^D-N@5wxujQ@O;Apw25|f0KZe3=-6I z|Fq90@S1HLG)IEE##%Vh%~EPGYt2KxlpPIzEkp^Tr;SW+klz~v8v=U$VL`8Z0rssg zv9^T+9UNEQ8NGOQ&2SUEw^X8(SeNvUXaAX-r)@#?8-!8foUKm_M1^Srj#4<8etii% zPN%nd>w98Af)Nn^H?>)h#!AqvE>mR`DA6T!Qyyil_krsH@^))g^pe5fIm>pZz z;syBi4}4wUK(9Y=-@jSisywRYGYO)!y-)(}UW`Zkd`@)Gi1@VAD+0%VWvpQd$z1q^ z37#8q9P1U^ZL`t>RbKK0__UZ?PltGl3Y;KQXTyC@k#6vAN#p2>e?{I%S74x%$W88JXJT?maoYJ8!vK^N+KG68esQ->*LaywS4#>skA7Wmh z#yhGh+N_CJ3nAw(&OIn7P>H(SqtqICny;gN5=kA+K0ko$=!r_c9`gG+Y)geFv81JP#i=!%BP~Q#_F1X+u;uZEi zJ_(4em_8P7*V`3ld4*R7=b!Ce%T*#z^S-thh{+(vo-nmtH*T;`)KFCCaMBl2N*w)8 zj{`^tvS`a73(TtIvWy`On7jO;Kvc0%-kzvDTVr5wL}MYfz-bRgG5KToIX5y(3i==% zA#kk?dVHvrO2&o0DhH`UjSogy7r!Ra008=d`{&z9YGP?Z4!rG&5wWo3$E|~yB5?v?xBY{#Hg95 zr7WBINp;T+Mzx4iho?1HAA0?0unxT_@vxCW=;NH*xhW`!j54yXl#LQKy>;`Owm4Y79zJ`?hx`oy7by)N zp*Pf8l94!BuL|T9CV2vo@z?dF+Y5Q`V(W$i!2B-Gnc(zg!gOW0Ic{roQg{ejVWQtx#G6mi#a_KTi5x@FxPUPHt&Dx^A>;^8P-?n3l|K!9+4 z{R^(||EKv{qCPRu7YdxaUm)x|=`m3(#I)Z33gN7@-$0 z*PdJ{1J?E|Ig0pm_|X0$z>{gu+)2VE8%6x=dS1d;aSE8+jQ&-EIDKxwm$_~Ogij~c z1xNQ>u>Lfrx9)`NIKBaz+u^oLWQ;;*;0C&4-H5nHBe@;Y_2W3{JE*^NFYxaOGyNo2 zHKFq7;&B<_yd<@!8(mxen7#00lK3ZL}XIspkU;q9K7VNbm0@M8ta?JDLW?ml=2fVl}~ zFN=s-#f5j)_4yv3DzwGKuKpxzj~ajg?jL~b7wa26#B9<jp|u%I#6P7$l8KQQjH_n5jkne!ee^f6<9=L)RcuEo7r3T`Jg9V883V- zm1}tspWA(=iA6R}p{#e+itKub*SS$qQQ^(v>JdpEwl_=87kKeF(~+cFZ&&(EW0rUB z^^9?z!>l=nDhIgUQuvyLGP05HKEbV@4{%5$5_O7*mmWyCaD1d9q(wRH1g z%Ylr_ixi5as^r7>gPE23wgdVvQO|@7AAJvglh<)Y@61JCD6KQcZ4P}z$ln5r6kJSxJ{ee` z&;fbJbIdo;>kkn8fw{{-pBx-J4h+6%_#bS&M|c0F8`_|;e+ps=PrVYPH!TU<$A0!% z>LK1LGADBpmz2AmZN)FRzW)V)znb!}HwFv|m1Wpec=R}TU{5h-h`u?*SXm-Kb8_?w|Go` zK5g#XehHglHLkUae!|xcoO<|optGV_Vt?sY9G>hV9;CMryu0`vo1IESs+7A2hl3{Y z^BmhuYDouJA0oeAsCpKEJ)Du8*N9@R6qigC@P$1Ra3=_C6@s-bpg+Vo0U1MajF+A# zbSMw={BTEvDJCw2z8DqL31PgxK(6PHN=Yib>V%cK->IYH>Gbw}hclST=GFiBxA!ss z1ORaV0AAP@W+XX{BL-mrCMov9z2FIXNyV<)5xib901!!b*+znWoO!W&Qjr#&Zi(-^62&vTc! zXFA56vv!V?@$@9!PExR*w;%vsaDDwx`<#Pr0C-Kc4Dopr2c6I5r;+74%h$BCQJP@$ ztO0CatjJAp15%qLfbahUFh5awI5^pq)?BSrD1#jh72rhzfob)y1qex^`Cdep7$pr- z2i%k8Qeb#Q5Xf5)w>>5HL8!`jajbcN&aSQHg&!Xx30Al{1U4Ol`>-7URV zY4^M1p3Ie6j}G&5J1<9_aX8VKr^TFCMh15xj0Z$1-(F#PZ7kl{)={v7s~GOSMb(0@vh=N{a_Uk4{ z=o}`B+ii=R?^+*21vAlYOF;Pw`sf@D(y<=o8sgoJ`x0bpd@N1~vGIEoXELU@Ht`;K z!wwPS9akpJLD~n7$pLJW1LSNWc+B;rV=)i_5T3q+qH)P=rF}B;>f15?4`69K-91$g ztOYWDLbcKUC}Bev*~FnzPVm0kg-HBfUf_N;cubl3mMQABIPIIr&-0YW&=f>_X@7tb zo!1y`c6P?y!u>=TQSO$tWn2mJRe&J&anKfNX>xg+SfJ~c7SSun$KXy29D*9mR7&@D z?c;6T7zpw)Wr#=T7as#(*K{U0p_0aG1Seo}@dOdi_1`931JmxYXhmFNl}q@NW2yuG z1GLF525cBV2TFB%9tUz_AVSvn#}-NN8DTe7pZ4cx0wT|Mt419b9P`U_jQ|3^a@80h zxr0HM9Z{b`N-HSCQUVxf$nj?mUpsKCH`uhr6_O%kBEzbJUM-BO9?G46Im?W5Sx z+B*90e*FQUA1BRl7%t#8sk-B+BqdyZRWOO6?$H>L%b%}@!sVua`J{m)z{Ruw&Re4xNrdLk z+TIDozT4wR(+!gbQ7Zs1vEeT1 zmDv<@$vz*TSle+dnfbPNhC&NnU;q(gHKeOTiOEkS>oNn)Y595LjvoyDP((+=r+sn_ zx3DSKhdu#H&Wd2V!Fb#b5hT$703ZNKL_t)CNS|WSBU|m`8N)N(T)B9SPL@x2yw~Hg z6BzvRpD;H!Q}$!xdsO)E&gnUH#idVp%r^k}_%})1!?y@^M)01T52pL6IxeZTMHI$lS?)DYb*bcg%zFp8y_W{0j(38yl2CBotQ?3c&&9KUVosix~?@aU|w;f z|7J-qqnNr!3Ij0})1y_7f8Yz)3%-{e<;(hf{_HH@TQZHt(NYztU<2B?xQxi&o|&_< z`<(HZGMOt&_Cr|W+kl9eB0>a!WG9RfWIIe%Z-OA{C9P8M$*{a3=t7(($<)2VVHV_{ z!01Z{x6>?S2jgWb`D!it<;VHgKY;iP#^iusc43X1=EdemPH)| z5Okl~*Y0JNRW=?puiXU31wASQfVnUm!TtIV+pB#OjNN`>H|ICy4oO{hOcaoB@)G!z z2raoxewI|iAaxY8nBeDFU%m#fr#D(b#^2=4okDQ>v>Oc(0NvJ}wU|UVqwE`J(qx`# zr>l*0cAhs_;-?H3sj9s##H;FghAHA+)_sFO^`_Rp$Q8Su66LR}53!QCt{Y*#IojMH z>f4T$XRz^$7~SwNdHNH0VkMg4dD~Y%J}uw316y-YBf+N^OexqvQM^Hx?%_o1Fj=xJ*5q+tj9)eTV_yvA(%XVCV zdk2e-<$2}ij9}tj_luf8LDTX5k?KmWL99(uac_))PpCg{Qhi)|LYW0@t;GG28nq%lCTeAuL74PX$leb-;;Aip2IVA1%HudZ&U_}nQ@ z6!qIq12A_x{A4o3$1c>Vtyf7sWL;ly@jh`70lj_$-9LcP2jYB_(5XIF4#O$>LrQ+l z8FIMobYzWR&~__$!Ts|en5&8dMl~Yjh8D^X(Vg0X(aebgx&DFfml==<)Y}6(nOA>I z=8;^z6X{n55>kJ#eu13_skKfhMm6!a#n@_71K)gp{w=*)TQM$5+~miK`(h4>c@)`l z?l%~3!WlIg;BZ!qJzjK)fO&eJv5iR)14?sd`($um+$FvYb(=tSQLf{9@($(ACb1Aj zHCtdjzL{fJrD12yM9t zu6W8>0j2mnPWX|8^Pu?QO69?uc3g#bHO$9of&1ag;=rpC6dBY=BcG1$p)IxS8`-~S zd{`b*Oi==tGOYDX(~h`~a4jC@JiZQEE-*ee{WUi~>O^GmCyKvlo4jksh)b@(gBI`3 zo+tPVD)O|ZZjtD)YDakDWaNIXZa6-QY3o)!b=}#P+$LOJLt3Qd?8(6LlDm>9_XD?2 zlL>-lKq5~SLdcBLV=ECinFCSNtPDlzk#T8MTImn~FK4e( z&$(;}xbcL{MLzDb!-G?>YK&ZG=Vt<5)tn{)!A*bQzJE7Veb4m)I`tGhbiS5OS9A~y zVz~MuiU9EX4fOgC5WPggr$2T%1^;~%kFns|%+l80=EEY#t*Nz0eNi&<^@W||tgGMH z0Fc(Z7T2Spa6EGqcC6hj=hW*DpGyE*tG*AnG+xX0X1I0KpmooaDn~7Pmb^;1Di|c9 z8SIAtYP~fPmC&)lg#Y$H02@6IZ@-&8Yahh`Q9!Q0T_>)OP+~F}1Mk`0ImkYn10(Tt z0)Z7N!F>ImPPPObzvGXb`$l`gUx^?<-}Rc$a}8fr-&nNqM)#|&#c=&AT_p4KuTcjvhzcI?Rr$w5gh3vEq7Ecmy6j8OPUG#w0@r!ipz6u585?%bX8Bt9tox~{IzX? zRVUmUceK+l)B3{PK*TD-<*?6^m|(!-(aVke5Cr0I+VE&%-MbZ@v{|7jB!bkg}%paMTp>ZeCw>tH?i@7ml) zG`KFafjl?LHQZlsD6Id0h^@^rN9Srnfb+X^iqYUE2ArrR9U`^)-*{6XlP$W3?A%MT zv=-_zUX=9xV{3~ukO@RF7fYO{wvMrdBTTMkB`=;^z?1O@z06uKbB`~ zbn)W}jc;(JA*|;(DfGkI(aL6079)iUs*dWD1-|)(@tsF+H%O~@gLWN62@ueNT-$r}V|Oiq04&J8~yTCRJTC5ExZ!xaVVoThYbIswae0Px`I z-{k}7MF~F#UNY&;n8|a(^*5SiGe1N(un$kE2*S55Z zyfqq)dD9xc@2djnfl7C^Tk)R0HFA{DR{X#Tp$}!!45E;GHmLeUmhZ{R4qof#Qs)Pq zBY~giR!25*BEzf>cXrG9)dp$1GBR%kSkYpO$D}zC@9U`4D{F87lDVh)=EWM+>^U?% z6j;{*@(wp`IPAjkYm8H*lN~gdB}9#9gMqeKPF9r_}vz0=R#-1QXj9gL9n%E>DtdEO9O^u>1@@JHD$eLP+-y z(CgoAhXpM+rayUesGuLR086KL6+;{v29Jn+QVZ5M#KCOB`Xlc;nB(zvGYX0kuO2?G zw}NY@J&CubSJ(J!85hrxH7nxSO2jI+Fw!Jo2QY&2>J;%A$10?%X}@rL1Ij=n%M%#| zTu1eTt4wBbn)cSHDFG)@i8Uz|e98oD(C}xF6leLYb5MMOi;67RH`17dn9@XYEA+95)9Z-5vg* z0s*vjpe#2r7aDivS_YJ!Rw3MABCY|a6CaPP+0~JEu;=A?xXkA>p82Msqoy{WjjZ~H zIrCBh&$A_Pq7S8lHme``bFD`pZ0dY4NLje|sCa{#O1-LNeEc?62K6eqPs+`d)vKFa zLtqagv$pSg6In5w@pCgBU@txxgr2#@xD93%Aus5x>fF@ZfMR@OSnYy^lMfag^c5KT zh+gHMz>q9B5*+|oPdTE*iv9F-5vC{Ett>w;FDbTk zVBUH%ct%3VkwLBiAyC^f;PryBSRjnI2@r)YI=wA<60dyl^On5Q{bFlRq+V@sR&GO# zh_wJ$>6#PW8OPudg-`Tr1%bfmf~9NxgQM$vV11ErNxV4mtNp_L19<&v+CT*65tokq zi3r64hWePG<0gw!FicC&RMWg~!VMur*1EQkE0v!(BLuUu%svWm>W7`6f%2b;_8Fk~ zi4i3Md&UUG7k6E%AzWr=-N*q<6%D_W-WVh|zw_ULD2kJNb)9ud5C}cAv1{L(1UDdj zwOtMRqOUd30*#W^fQPM~Y;TrHacrUXJy-kn6GDOn0n5J;Aba1va>s~C z0Bec&&9W%9{%seDHMWrHt3+PYA2v4M2uE3@?evf&zUZAgQ$(U%9~jXK=3fBqdWPGpc}B~*~kJMwcJNu|!g0001%Ki=iv!j*UZ|N9-BF!8XyZfG!0 zGw$mnZC%?RyT19b>(1c~UEJXd`C2zTzqlRmB?VUaTW|ETOx^G}^LIEjD+gB&Uee{2 z(MA_FILU8Q9vJ=yVL)W0J!d3<2f`zJ%5%`u`ja~+FI7dFUaS~h%d_qNxRzN|2-z6PsL9RPtY>!b_ zZm|0a&0W73bj*f%taobX_>u(q6pXnnp8Skc5{;5^vgs1DnV>nUP_?ua!h6vz|22DxKf-UUOysU^|(+e*%KL4-m%f;ly-1<37^m z(3Ex@0_YyrW&9TM{#pUVE9DWS{&~O;(EZaE7rcK1+->)P`~KaYyxu*^BwafifV;LfRJZOx_)rziLG>%Vf7snt&*US8d)_YwJ z+g6l)AYzKReQy)%X;G1zBY?Gf4SRboq_jumJqBHZH-QmxhdX)LYUjO+HaE2mLPNi z0r0E6UJuAWXAATv!6SY@1||hEXIfXOPsTRgBUMgTo>8+;j9gdr!s640@y;w1{e=8# zBM7UCv%bg36UJ7w;24L_dg18Nj*|i*>6RIe-nAEtK)1xtLF|Rxdi9G|Ga~5SN_Ol- zMaV&A($*~2?*L^}qcWvy)uC;rcWu4(_$I)H&NY3O%H-jI%>k`7b4&efE&T*HLc|F6TrgDj37=*0^9`rL7BJkmK&c=?h z9>{d}AQ13LI>6n8SF*JYymp03F#kn^bUeZ12iQ_1f8Z72XpX-{+oR4IEWI z%C~a{WUKo!aV8%U7bA+PNs0nj`#3diC-UU3ck&pTm2DD}49+aU1IR{v$yL&WQ_jP) zKxw*$VCrD(nzE$DYLM<->56pdW7k3kR4*~5SUVjS4cMrOKR}V7rQ1AzQeHIttUSa(DRX+i<8>7#jSy!0W-j$M8~3tWMeJ+XAoTTcwt z+cc@Sqr26)eg!}Pop}U)`;9YzqWzjvuL4=X&eHDF-z_Z#- zjAwOusE{}l^gAHfJZw}*r8COmEn)kO`Gh6f+&RXpcTWbloHIz;(0r#AasKoK(6rxr zfFNMj(97QnB={rpW%PxDZ@Xe;P^q{cb>0{&%1@wcDaJe6;_Z%B8>iJ_x3itS{;>%H z0AP~@PEzi$V#k=D&Vcy(?LD#I@>1nNnUkYM(MuOR-sT;aA(Qm^TJ=koaWG$a^V`e8 zIf<+p=oJvMs{3TTz+9<@H#jQrse-jTBzn{18K*oG+L4L(V^^2n>s!M}8HqPoKZQtV zG;UD7Au=jjlLXLrzF;5yOAZTZ_3Ri_Sq|?Pg_B^}>3)?+OTVwR(LOEs@r!v*g_*g| z8I~6)%pe=oPy5wU^V7y19#%|e@O%K6)psb5GAhc%x!<1x)vZ})P0mZd51HKXp8$ch!z2Ax_cnb%>Q7@4Z}!!)%?*sa zMqY6zCf?4<7qVzND8715Bft1=W29dm{X%9fAXRpn$DTfdH_p%EWE4{Z6gotn2%gM= zOXjn78Q?;FQnBYxI!KS7N_3D7BFrBciPxKEtp2vZfk zi;`49C;hbGED`1s;XeIsH|pdXd9MxU?k<0Oe<7a&KH9g~f5S0L-1<+#i|U(o+0{K$WpGvGlm9k}mX`KjEn;X-p(cQqLJ z|CQ|s{N3r)`>}6VNk1^&xWIw6CxG>Z%aopRzRTaAS;w9y2c!MeKP?FS)n+1RGmR3~ zs}s}NslG+vZ6qwc_)cp)hqU*99!1@aoSBJL`PWa)7a(5kvlmZi^hBVFX_jf&i%r8( zLt@0P422#razTDeeS!qdX9lYDX&WoB&&v0(QCoR?5tO96ZM?&W!mL@c??gr?MKC6Q z!so+|M17DM1sv+kdUNy(N7%UL;9f;&}gy|cg^O56~gPplP&1M2( z!g$wOI9xJI0Ei+euZOtHptk4W*h!vRf`L;WPE$q=r)n^HaN-%O<$?($Jws{G85#x8 zsz(%_Nsaaw9koO$w{27J%~cMOoko?o!nKY*o}<(4i0Dq@=oMB-gVTUHlauW750?dx&^va1ShftC z$)|`BnY#F8U_7K850*B$OUlBopNkMwCspXg6Xk{ELfkI6>ak_=jHj~bFgbflyjmjh z_4H=HhnVweg6#x>P}&oL5C9R0wFySpjL~>1B;A#+%ac1XO+d5;?h22OUpBDl-dLed zuRj?wlW9{dWGGs^&!_rnOiZ|DsVGLP%#FcQ7dQc|)zLL;Viu(60M*g{p=?0g#V;oe z7?mB72%&R<)?YYlx-k|vgF6Z4UVC!;IR`%pwKkj#fJCvWOko*2-BX!-k#@7D-8$Lr2lI^=RQLWC`0^TRJpOcvH6goy9?`xs@D^wk zvvm(4var!E3X1(bnWh8I?Y*;vP=|{5hvw)W>Pe&j&%xiEM8iSAyP3?RYw&F65N0BJDA1$g)xs72QT)VZ z;!y@K$HQy{d+t$|3hjmQcB;QT)e8%4CVkU&Y3#`1eNVUd<=jV@l5TSfJXO~-NpP77 z0fg}U-(5-Ntik_r9Z0Cig9GY47iV4%S#W-Vp+Oww^Qm^oMymktL4 z0bpvFC|M$r#zlwPOEw&5CA^Gk-Nc4@}-j0aD3rB6)5F$Bvfnk zN2*gntaQsVR`w9++{QBv2PQ@hDGjujLSZ$8C}xWu27V$o*hco>*qlvXxb8?pt|`^~f3 ze?&-SMRs=i7;J3n)%{-NSAsg9Xg=xHDKhjq-zk&^J{2UfJW-q#praXA{D(yUAxY>@!ZqPNW~`T=b#HPKOBO^yO9%ES5B zb5oX|o=t8BMMz#WG9`6&*A|s`#->>G4TS}4InQUKn}lYeT>jQ)!3kAOVnfkQy?Egz z!>Y#Ga$`P`sK5S8!&S0*)^L?}uFLFSwq4qMQ-N++k~vRFHUFHh?N-OwCf&w|nK1=4 z)iV0?H1yg|Q3R*(sl#0bW}fN=hw<%!`;D!S^=YrH&gRJMoOk$%fuO$OqBjcz2Z#DX z37me7?s0Ip1&r(1A$_gh4u2C#v&uY4bt?JcIEQpTNlb2~qJL}*i3m_OQ1#xJYyi0X{^#~>0UgAS*d10PUMJ4V8uI;` z+~a-}9D^&QLYNAb!qyvOStMjK;?_#NO={5N4%p;1r1s>%8-R!w8j;1yo5wY;9;dUW zY_#z5vXAkInd*09ohTKBjpYv)y1elUVd3~K_zEnkNCMuWL zfD7$Y2gbRf1&wQm-v?Krx1&w|FyFwNKbdmwaS)4YBD+SQIyyiQUp|L1t+H5ex(Bi? z0JGC{|4%czFk##J;S>HG$H8tR#NV^&4&fD9%$#!1^_fQ|OK})0as@9m)hl7GjPq@1 zAB8K29+I*houowk6mTCEabk`kp9UP^(aWHlyDQoWELE;jE0!}V`^>A2tdV=1&60`{ zB5|O*#MO>!?DJG@*y7{uOcxJLAM%VWM8S8cVf^q7YtjWo#SPK8sL%m}JCHue-DzO5 zvf*p2?=*{Keo&*)I6eP^g1pvKTO;?=5oCU0Z^s*&LRT~1f|V<(eXc(ND&^7=^pe7d za1_B|sNbwbRH-Q zfbl6^A3zeqY!e=dHyHnH?m?^}vAN#hRr5N*2;O?XW>F7h>toZoC z{mO0DZOb=3>6uNvZLYYZ>FJEM;4P3j@NXw&QV1XRRB+|?BgmvJ$Ak|D`;$Pk6}(qK zB=!fy{3tu(=Cl3>YXIsDLA&HW297w( z!8HRtL&=Gbv)j9?x#@MX5>KphZjj;SsxhqD4h6Rgp=P8&q)G|@vl#Z5J^4arK)rEk&*AXDDpoS z56oz&WDuirS^Z$5Mob7w9o@IUk&QDkLiB2j7v4W0_heT*BEWjZ4q0N%=_y4COlz%O z+`qd<1iEja*Kamyv2CCaqMl8=hAd+fME5W!=ojQq=VQ~)oB{J#$SxUjIinPmag$QS zgx7~a{7}jFi3tm1)3!wHdiU8xPT30nN(nOmL6vY-euSrho1);)D(PvL}~8+4x2I3ZomKaLT77<}*f znXLchX4xrz7F0g7Y^ajk6rj82?MFYSqtKAW=*E5^%FCShe?G=Axp^waP0(bnc=een z#c(C#8pV(F_#d8Z-R9c-8aii%WWt7Q#Xt?r1VNZIg-Un^A!~NVz@Zi;wUudsjbSoO zV~mj3EkOUwAosc(BQ6b8+2D%+3 z4paw38k=)37?+*}RRsTaxQr)xH!U%m%wF$%?rADqVE3nZ&Pn3&d0!cho_= z|6_ftd~ucULeK6UchIKbINyZnz$Bs0OoAkr&pIhn=p^p7Z%^yxub z7~}HG=&-egV8FjtV^dbJOGBzKWj5a2^U!YWVqTKvjCobiR-r6ZD44J#{13=>`-uU{ ze+n?C$_`+R2sj;^kd8ITiIL%D!U-u%@9Kh1OeDjVVLuwp+&94W4S;QhcG~f(uOfi^ z8GvD+{m8Z22b~~;4_lO|AD8+4@=AnwxIc({VqYS@9mv;|C6IGtZ&3@HWYCh+l5^1_L3dCO7zi3*{k1Ox;(&`p5w0>CeT z>kEcHU@rk;kwoUC&0=h_|8TC*-rGjhJ|z$fs6GMJvAlh(`<6sA!KrAoD=zB!1tTwF z?C@~lUoE54E{_!7lCNwZRR|E3=Ftoy9uFlF>tE28%?0j~s`C{Lf2S(;SVf$X!bD|u zU1zF~%?M#G{$ucDygD)263C~r@0cpQMZx1w>Hdjs9{0&Z`OJ7(CY#EM!RvI0;VI139oxMG< zq=4$0(;Q1x{OCcEDplqtBSkL9LE(utOV~2kNnl>+nW~^U@VJo!$oT2IbHja$L}%q) zU-?)$A`$WGs5&2Zn6avBjy2oIa6(PK0!{SOC%W331c+lHL%w(jEg7jGvJSXi5fHop z=mq(rXkPnHK>9*0zf!VO&xp$4_38AHrX)zlU2#>IRDMO_61xRvq>e$_ogGV7lOe5G z+`$nWPi{CD^p(Gl;(8f$O5*TBvh0c_WJFb?;oN!$Nd3Nx$8iWea(-?-fLcPjxCzRI z)g!$+7@VZBPhYq3B}N$yV4Q&RFkg?WX-AS4wxIlu#a-pfb1=JsWzLg#d2eFXftHs; zc-~9**#`$Xho}f(&24VMla#Nc2nd8E-b$8oDB#+~PA!sk1vu&ELo#oGu*%0Z zE29e7#u)zeX47(-Jh;?r-{QjCE~%SHzirb@aTjdrxbj%^ViRv`dY zJ}_TQyg1re>xXR?I1}3PuOe`O^uN@=?ch2`(dm?gi9-F#f$>Gh?&9{7HWS8L? zT*CQK{PvD*2l|vnUAfDGNg;8xa_}a#ap@ASGNUb%j@95LJ6cuX27()i`sS;6P5A-h zGI3go#1aMp(A~CMom-r0+tgyJrDI!g&=9+^rScgEzd_{PQ@)a`O_U6s;8*TW`-uux zmrN8K&_8uQF$*hJjt7_q<_z)6*;wm1X=%qx%qVaW`!{1jLUMP@-v)a+0w^@z6!8Xe zaRr1ib{c$?h0YM16|7as^hhV$r#a`Ljw;f5fujm8PsjQ5+}^oRv~b1eyW*`>ryQ!1 z2m9)o1JSoO`A3Q04ZTX$X=YxX<@`oU`Rw_2X8XkFc*D~1ol1KV%nZZ{i7ga(m_?Lc zs)HpHIf)|G@@zxub!u~1+2jsfU?!S&S__R&gcnE;V6Z*nEBQiS76ADeir z;ld>=o0ip*@wUBW#8HG>eyV|!Ck)CvxCP%1ug1o9{mx~DG5Zm0`BryS|L^H^BeGW+ zBAbNPaGR>>ZJt}#&5!XnD`lYq+k6P>eb;TPa4^O7R&94AwN6tVpA`(7A3>Jjrq@a; zZs{5jpuam~o#cGGP4Q~LyuFh_pyz4XtYfnzHDjh?Z(hj^FUlgqJ_=f<2Bac)XV-_g zpM2?FXKt$MG5rC{iw~hAf?BjPvIJjomc70+ z(%IF2BboDZ6`lRhBKD&1sf$otIbA1<^;&hAdz$tPLjs%RJ`MFo$#Z=egThi9Oo60U zZ2lWKC_DmlmC*EES{vK>^FG~!D>{Bd`STbmoH5&+343JsoQ-!{t%I17>Gl zc01dt_B|f=2gQOmJMz=ujy_Bc?F&-c=4PzODgU%e#N`qlj`Yuct=OY4{oKrxXZwaO zob*El96!yjniw~ESc^4r&PyJ^EyM*VWg+VInG@GHsD#J`ll1YHE@d1(VkDWP3;4P# zr1*5n$QG>TeWDZ7-9sJ_2pul(Z5xK1J$GVIxrsz7>C^l}r#S1sl2IuBlH7%OfydzZ z28ACFSq|QEC4BwJFoAw81C+FEO-vNlZCDtIYV~PAA``7HHNmN;b0~G0|6%7#=y9lk7YBv87)5^D5eF=ayadmzt`PZuf z>rd`B_8Q&CFNN=FKW}~VO_%vvG+?H&XkZrvqOk1<78(h~*2>V#rRruxzR)VUdL43g z`CT#f+B3MC_RYJc5s}AfdHWs+JYDL2Eui|3{_Vux4?IxKm?%byT+4k6d0=jG)!U|v zxj25id)It_)Oyeug+_Ai#q4vH^WG(friiPC$z9c}S(@ooH(|hVwqyYMnAZS=&KNvi z&Imzzv+7?}WzWZPuWqn6ZW`%FhGzbHy@z?l7iCkU>d7T8v2+;R#8BQpbiqu&r{kIc z4k;F`7PF40bii2^1LN8IWK|%w^@G~!ZlB!PW_#w^+{e0HaLI5MvDss=vBtVq$+l#E z{}qFanplBzb4w-*OS;_2-`$&=$PgT!D8s?vg`qbsQ1?QNjiZjIZf zKZb(XOCokW671%nKM6Mc#Bjkr?193GB>R9W6P*N`NAFb-YMzRgJ;qP{ zSyoW--C%lK1tzgXv{@jqY13g)X`1K&J*yr|_Ef(a0Au2^<3bk2y`sFYuNrh$%k+c^ zGzxwy^xL48&Dk{xn65jlZ<^n54@3Y_u_@%nj|6xg!T(;5^7gSrTI?YdZZZ_|0j`qSZn z{`nXV2TH?N^rw>*cpkevqk7pjMEz8zov#cG`(Bnd$tk{*F9qni1wqo2iZG~;+Jbp0 zow-xdsj4gKrM7$V7Nw~k5YZoE{!Q209q(F#Bk54-SjIn(f13Js<({IFP_M8m^1nTs$Umyw%Ws_z zg=|OUEj8L#!rj*frU1QS$qBAZzT1AH3P^qguI=?|*30rvU$x6309<9)kNezPEtO%- zIqnt=$dn@L3DZe`@-BLPRTRfQ1$u)w^XQP62OV_47HyF-@@?~(^^sxmg*thD!0YhS zteHpiWt)0d8WcRCpYlWQfCw)ZHIQZ3P-K!$%NH@d+hN1s10ZytS#)Dg((Pg>avh0! zy+HXec_@ox9VwjQcB8NZ{t7bDLuZ?Roj|V<^60Bj5pVGkD{*p+pNo8w9_r7+HJ`Q& z{-sCSWVuq&f+_pw|P#m@n zwfbml_K9SUp}YT14_Ul41yxOwn2h#5v3ul1HsFG&l;4CV+p+yFD4LR%u(|MzD zGsN!pGj*9M@qf|MggppwBet_KK~`P%MZsQjtP72aMxd}{X^AQ9Kp2C)dG2pnaRnyw z9(Umz;%_=QA~fc}7%FX%Fs8cRW!0HqH|~G#1rCp!X?#O|JQ6PV@%2slvFY3Ee(!sH zXZ~SUP|lI{shZFqbL{_A*+?`(rXdN<&n%C9A1li;di3-w^=+=0C~n%7jgO4I1C!xX z41^+O?9G=4hR)b)Os};`;B46K(8cSJuI&LH`H0>N)A6VGMBufF;!MoLo(Y2ib*It){|F%cI+IA#>Fws)S zw16n|Xae-(2A)oA#PMhF2+vm!|}4l(DpZYu_2CA$H=)ltnf3&67+Tw?)cnM z_XDL=EnGAZ=?Jd5N6twTDb4_coAyN2XAVOnbR7)MxGb>>^y{Fm4l9=0GIj0*4=2S? zZ5pi=jj;=@?FN>I4~0-baINStiM)y4Kg6$Juw@3erss%LxmU2B(9n%8rJcKk{`{fq zX$PgutR<}4h1UxFNZfVI-OmG;?1O-TLsnmmahP4Jgrdqab^n&DF!l}w)~+&S8_zwz ze$!Wj?p$FfQ^c3X;RDfO*p?1mXG}{Pnf=K?Ga`Wn0Rsx>QN3C)`L%boc$o{0M)dyG zjz4E(j$tyN+aaQAb3_zSJ`ac2V~6Vz{%98hgZ9Fw2_I-a#=)aMB~tb!fE^Q-pX({D z7tc3Tdc2&bD47q@xLvt>RmS*^Hq9PaSIzNxUH^I9zW*}__4GOgFP4DgfTyB~x2oeg zLuEqp23;ZtHRm<|y;`YSIY!MBUS!sN>qM>h+{pBa%9$OhB&>`vAhUz#XELxG#!j`! z4u9U#M{0gbd}7a61Z`=xr`u98ig&Egi^Q%SytBEpZKKvxW3ZIiztX}f{YZUxt*sBo zwz%TqtSy5h6K>41Z_w1Z8hB<5DWN*DwU20y`1J9{MJT!i9}s=uVm746kKTyR(%#x& z36cP}u!Z*TnjjD|k95pPvGRI&ptYeJ^CaiU#ix=)OLNiO8=_QS&h#r3SCbj2obT|3t{6f z@%Uujy_>x5p57#LYljuh`Dr~@*6{3F4eGBWu#sh2XB-Jp{b-kl%rVv@t!q4*#~WDF zyyC@&U$0wK>JB@t`y`CobrgEe(>s(Y_l_9f6%b6XyoLzh-(6t+DMmd<*6~%OKFJvR z=Vh75HuGt-c=N9Pr%uVEV=_sDona^hKY@OD7D%vWi`^e?ivAX|78+A<*}pdp{kDJl z$p%d+67f%{W#(pY6&7a?BTxr>Jl9eg-?~4Zp8Eif0;@*s0jo3#ARh&}&_NrSarZO+ z2$BXF_kt=Msetms$8Fo#FT`q&oyi5&-p`D0%!Up(ne{p8 z)R8o*Q{|bBK}o8pXmQi9t0Brb&L%iRRMBxw8UV;l7lR@1ssP0wNfrOl=e00|Ru}zU zY5|?G-~2Y>9S86egoUb#jzg?-$59z7jrhakVDg@K=R@+4;IH_XV1ZD-agi}%{Oe}# zhptCLwLa)20DwV{z3}`l`StfE9&LrL_fbXeMT2qoU%#3#j`t7lJxZgpT*ppBcrC2s7qV+LOx!!x*cyAYN-Pk@uupV%Biv+Cn3qh(&3;B!3VwZ|y%;V*R1XvIRW?ics^L!wn zbO56vv@bn=f?efxV6C}7ZaCavxOZ(mHDCQg20y3d$dxtxP1vegXFb^ut1>iuuWUgvEbV2GOlAnb|4Eh1iJOSBV8IDU`(ny z9)RKBPs0_S*9oO%WhBk={&T%{kSgTYi7+a59+}{O5QrDF$ zWfK+>mA+l7#1w+`!Y!t(b?vC;PQ*R9;Fd(RRi;cRuI8T>_@Ho6=p?liut>vMb{ zV^YKQFQmpxY-iK8pycQ9rYGW0Y<*TGoYwNlqTc2X;_9DF`?sDb;CnBQxlo{k5cBQ(CTufa}|df*RxVZ0@Nd z)Cc=|!accpY=Ve!7F>XAd1Kg9=3F4FBl8Cf5EVCz7ir=L;|kmCoLUvEs|6FLdsD$t z7bVj-K3>0L?w4sAEZ&E=@1p3rVK^(4e0>HSDMVJrJT$s>|FQn1d!gXsZO-r3 z-TT_$A$!%jmmK7L78TBE!a~q5{}M2fz0>TEpUj`US`DWW zW-pyZo?E&5=a9Jhfxocq2Uk_0cFo-BJ>sI*;Y=g?ut~Uu!0g=tr*_fcV3gMsRD38m z+!dm)%y(~>Etve`Ya?{i%{~za3X^<1 zb%^Nz&;$OCfq8g1j z9G4US<4rs;Cy{_!K%z0d*OwBib~SYBc!lwSc$N?sxMo}~AI5}x{cEo)q=aCYV5{vu zlQQ-8mR<8!r;G)$gD7fp?P3TWR){-x+-_(k3uPf-bqT5eS_9or6c|}}P7wgcKAV0A zI1+pMMK*G}8828lY6lKas0j@AMw{o;DY zAc;77BGC^_Z)oXc69$7QKKsNfeKJm19x*okUOa8`PMv`nTLTcDm_ThC>_J)mpx|M> zT^EKNjyn5+_H^UH?=lgi(V{UVk6Z4;&P@ymYD!V3hd-kU@`5nz0xmxt?OqPMgXw`M zLmtUVoDxs_EMXdr@9a^M)PN{4*reFGRaJEU5J`gLwZ)iF`M&U%U`v7BFFtJ`&_wT7 z4&Hz6&k9-thk05C{0c-jH1F@aGmXc_e=d4stL?!Gbhh7A%!9!bS`c** zYVh%q*t6l-?YNKMTkdMU^Hw@lAmV;ShhtSTvdZNB56S!I9`iWf(N(GqE*)S=_!^Z; zXEwc)Sk?2;^KkX+Mc1c&KY4n9;o%+fME>)p*>4Ql1#_wC`tQfxu9zL5S{WJ42=tLs{Ksw6iUQFj=OU2toG7#rEn~rxur| zeastb@<-Jq`=ttgh;N10r_)p9u-v8^2P3)h|DV`b~nNV z7snxiV}uoYE)320x_80wt`Jn89%pTOh#hezuiW5ITq#cyPhnj=8TVc7Od3~DbXC7K zB}xn~ld?9Y;eu9SmJfWbQM)_%x1v+-w4rA?70=t!0r5pMEf`nyW`)=Jlu!Cy+YF*i zkFA+e-yQMHyH>#*!^3p}{pC3JPJG$M{el2dG*&3-?<60)AnAG@OWyy!YJ*pEM4xI5 zV{E{}$-1JPeJ?sxZN&Usm!AYj(THDHE}a+o=aKua{J zKU8j0!_m0ypB914W1Mv1oyCj!GeS?%z1Yla;|L(ru)3Ht1n0_jTO`8o zMpX6@F!?d;<&kTnkN-CC6@K^lfhDV_@)`OMpwQ8l|3K}O&mSSf_!5|j0~&$T#-zcz z+|_})(y$e50^$Q`Z}NQC3pq*MqOUIBay}n3vd48F*e4i+hlA?PegP9tRyI`u&Nn4PDfnQBK&cz~HKg8U{ZdSv}jYgjmK4*33lwi%7=n2ctgg znK$wHjd4Wo7YAgy!!Elb+1%7b^(Y{@*=wwu6!rA^c&LJj);rxk`1CkiW6$O%3*Q1_ z)_v8#4!5YX@m_kyLx<0VopVRtX_SoRcoo1WFZvz(1D;k&&FeJ@=um+kjrKQBnQYPM zv;hw9ki%72hy1q=PsMjOkBN4VfXo0qF}-{D{2XpG?aZFH?-fZvf5Vb>Y3qY=>GJWI zXU_IL@A;YMNo&bJB=UcV_}AqZ!{=9Z{}K{Fd#@{MPVlXdsg@(Bb=MbF{=r0Kk_Le`RI%{joZ82&0*58a26SX8_e!(iy6( zeRJvPtFm0S);>4p%~#a%FzGH){hc{9vBIp|+r4ki(Vcz|5O4%w%T{Gm3q-B%I@}Ha zehSkIhHm)ySrUy;18PYz9Dlauy6pX&~3dmErEC&?l1f_^iN$py7o6$|wlb41jbVoWdMF*onU zrR85L`fIw(uSqDD;EXe)iiA3xJqEGzS zdFy>W5ux|bzv=b+|Do&YkslDx4P96RYfT8d_)^by7g)5ChC$oelwo6!VexQ1;YITa zxO{APu&ZC;EXuwSr#ICj_xGK77p$YLBI-JG8mmU{ED4Ft7`eK5BV!Zh`fzF^ds{*G zCw%1?A)5}iGr;3jBmi|lioa>9JBo2=b8UmBs?!>VPZilmOW^qr$V<_QC)4~GGDrT2 zy@)jzM%S)Ou8JqVAS9+kQ=NwKI&N{%$V?bc2<_&I!k3~37fm=_(YPz_*hnDSm}58vfzj0{d4UQJ^th$qW2Gx{LuXJ`*`$L z94a6NJ74w72NJHY{Q>T?;l+9WlZQX)*!T;)c)l52ysUsUcFlG&%3EnV`x2zRuF@nZ zDyUX}0?L#=EV;cM!a>s3w>)bmE`3}%LAGZ?KnC!+`jc-lmqUB?R79Ao;t!O*1WX*r zwH>A$;29abQ6a)wA@=|3zTM&~(=F^Wvb8fEp&h7W@4XNRS;QQ&$!e%l| z>}>~ZlVH(lN`OOcj47zqk|ca2SuakhKN)fx-xT{V>``+NC0_9UL-O;_{3^@rSrCPq zcS@N>tWBC)qkY$~WmEo%Ee+R{-8zz;m}C9bnQQNb^=U(7llEGN`k+|u${wPX4g-6b zEJS3;;%X+&_GbY!j4cv#Nyjd8bnS2fTfT`&g7HNBt=mJa83;|XzO$*P`(r6O`n9kl zn|BamyOqrkcBf|2^=K)Yt%gMv>mNG2B=g9?(w{YCX^>W-CF!bv(3<3kduNa)mesqG}sM|>!@Jgrx3Ky8%Q?77i;r%cm6ck(+ zMkhE+&jS{`_s@Ch37|349(HZ)S2|d_Wa_W7<9dy@n5!#Zv&nnbD|~%3yWEgy3S$Gq zEbsA&3?vC+j+!4I`J~a1(y2GajXyz~!#I7!EqnWfqQpBXF@LL8uubOR<<1%O4av#k z4_roqX$wD7N9h|}TXi#87jwQSUUZ6z(}s=2&hAYx775S;$tgecR3?sXoYuP-Ybixo za&*qd_<&j)XS}SgBp)uk$|TW-$;{%V?$KdT2CG9SP-2t!|B}eh`mF3d#9;$d9sxt{ zL!+-6@~rsrQbFO{3HWhl8c%e|``@G&mC^icYMni7+r>u3)MY=B#kt1SEwWPN)S^9% z+M_N$9cqPeE8R~L3y!B-biTs20n*LG80>M_xoL>q|}mzl|+qg2=9Sv z?{kfAi#+$lBas7GAZYL5iJ_@gT_fki9zpf2jCM^-Ox-o~N;n6T@4 z3j2hGT{2P3Y4Mndf9Rv=?8jG3Y1_JJ4AeD9`}&JX^__g_vCw%T7EA&t4P0&6@Kj^b zqpI(cc?w?*C>(Mv#$`sT`OM_+gwM7+`y*l!b8JqL6`}Xf|B}d?n13znJO`cCBA@n2cIj68 z#W^p1f;%ULo*kPk>Z7QQiTD>06UooN*Pb`Za(oQ4uN0OMWUpKF0lc)xYt{pm>51HcnTEo|68-P~K6#g}(irVbQbKLHL8Gy;5p2~5f zFReII^#jCJ(b8qSy4<3i2N_48kq*RL!0YKP1vx#;D13t8h>wtTXrlWT>tat~XM5}I zHmY?nJDnYj4iE{mpAgL_U{kKZhz9hz2{YAI(sANDv@TwOH^XFjCd=k-vranw3kW+- zvN9JC!QIm@N>}WoMvw{cM{WRoM!D+GCw)a+o<7{CT@a}F7uK>G(y$46;MEAvma#2# zcp(dlhN2}z<5d*DtmF3{UmG%ebIIn+64rcc!G9WLSssw}@f2!je?+cCg8~gCqGiU0 zbMg*164kREvFxao91G^!t^a2W zp$K6D*6|!unDG8{-(4pn9(%~p{mPggyT#MgDK;Pby=#q#hFzwQ&S(~mMfX#zA!{&S=JOym_k zb>2fQxFU+X#TI?$k-pUNl8KcH#t3MKbk85c>&yE>`i49Jj*~&sQ?F!aMEf!7|1?S| z9P%Ofn9GvD-Ajmed2w@nU%4Lhy!0yqa`+tKlU2(Qa_qC04z2|l6Pu$EV4?g=*U7MF z?J}m~wXi^N8Hn!pL=)apOI z&8l`rO2VZONQi!YAYmaSg8JM_R1*F@-znRv0LLQ1otRW8GaNeLGm8ML4{=GLpIKgW z-KODUjudTKJY1J$l@Xac<)htP#0aknn9Remr~6^=bisM59Vz>@RjoycSZ)`F7C#N^ zujWF+>(zpG`uBvWXbPU5-LE6mS3!gQET0NglB9Y~v24a$Ue<-+^<2x0=EZ1ybwZX8 zGhGtX&d|7@G#K<5bGq7e9Gr0~7ah3+0y+MKWI|(}o`(pUy&;kJ&wuVuA2JdDnqU5i zySzA(!RBh~kCb@GJ>yEZeF}FU=tc7L56RDejD2ghPXSmXGu{>c4gHkt6Io#SKQbv> zI_=6wVOtA|4xu|%HEbC1#o{j&?S0XPudb#p-2B0`*)&S=qFJR<6gGu z23b4EY?&tWAWmqqgS+~HeLQZO^(^lIm!%mRA9uv~VUFx4?m^-6G;Ap<0oR}KVg_}m zIkzlawVr%|(b6Ovb|~`Ve0U7(TAb}DA%L3{LV_lVNUX)sq8SKdTukC(+sxRGplOlT z$C`@e&EZOSfCPid{))dJl_{}4&g}1Sv>1KN^A3K^QP**e*|KP2z}(6t}MuesR9=_B8YPCJkO}r&M?xByNKumBB4Vg(eRLy|j zPUYOO|Ld^TC;y!NreSNJxj=)GDR-h!4LKKPANe?0?^!*9j_)Yg!pOb?jv^T1b>w~L zx2Wzq)X-(^H@^%y){X$IB8Nzavv}`}6Udb@fK^*wjI>B}>el_4Q+!OTQ2hht3h~m8 zlR=ML{b!;)?L7eaih=&jMRa$Swed*(Hbz?)- z9_Mm8`Kn*UokIu)A43(sWyu5LQIF6#6jj|PwEAC^>^kgVo@hRYz8Gf?`=EFu?O!{6 z%rDyt4hJs|>y~=4K;|`nY_5d>&f=KGrHXjoc#3i)TIC-X#vHd)>FZ9u(AbR4z?e3<@z68`}TZ8_?3`{vwqxHc{# zRD2{3zh_8?=x1_o5O_ebgJ)s!(Ri%N?yrHqpgL^G_3Cv88JZ_qAkgIkw}vJp#In!u z17vlYI`WtUn4gC#KW$6az0tgBDw-S)%xw*LgP>1LFx3u{kEgsR)g`WjJ`!XBpT??YbW~ z9%s&?I7NxW6?LA-ls(6Z1E0vm=;Gcy_~h1ZL@2K8Qr=8Pe*DpiPVrdT-<9-Uexc%5 z$@9=Sm7lf^=(@3MXAi&2Kcw8$lboY<~PT zj0FdEYyTBxxu&D}rD#T;RzTs14t1Lj!nRMx{%i4E-!}T1*CN6H+{rZ)zkZW``fFpR z+n{Il^)tNaOBI5kcwX@Kjqe4G>&b!l&;OAAW=QnUcsHNpjy%q!{~OS-yW#9xB-#9U zS}@Sfk@cXAXu+jA+HSVd#i;OXUYi%>TpZU!Q$?(uHvank2cHIr?WTyryg1)%TiQ7|ss)IIcLT9U2)t0GNl5Y!{Y8BigrTtsyuVD>J+&b3ny3^J?}L zqlB$Wo=?>c#15yXD^^z6LmbdR=1nsk`^c2hm`el5z^)q=rl^H#0xL-O&P0e5}ADYKzUaVg@a2RBw!eev;t=z`wF zT~Bmt3T`4yl)G8I=+38vg;Y;9fN?Xg4%s>;p&%D>1oUnKxh^3C=X)l^WNwqPtq;jM zS&b=!G*l1QvF~8OS7{v9OkI|YlXe=x{p5g3lzWDD`@_x!@akTcO5{)i?GW5QLVi*qPmHwuqAnJN5>n15fx_WoazKmSX5 z(Gy!9aavB>(MkP`!Loy-&UV2&v*iWNLXC9Go_EfD^Zb$Ix}ulOZj7F3`v|n@jE;~r zVt^wZ$_+i)M=3ef`Y|GgS#$ogBDV1xFomxRoj{|FjnUm1M0^FoC3DB2{`iuHSU-V z9Bno(5iv*^O+;veE0+LM29)bwT&wuFW&;KQP{~_iV(d!qPrwLNOG5G2GGjlW&BZy)O zo`_yVuYVEqFA{o_{QM8e&wq9ugM5AFr*2qT-4X4tf|-Drm;VA6kXEgsNpQP+%-3`V zh0*_if|@t)wyU*~Sj6V%uY&lKgwas%8)P4L5H>?R9rAhHEgaue=y7#X|03vRHQ$8q zT2(2|PSBjmy8|;{ut90F<#!DcJD?o* zEF|2F%V27u@>5*56EuiB$Ho1SODD~q{s{1Q>vCCww5LBexH7nWH6Kxrf5c8~(PebM zm+DgyOCJJM&MXcUR|;sD;<&^hLTX|E>eG_w{hqV?%wS-a#|D}*PSt`=(#BlgBqf7C zvqG9Jn`r}t#votnIMyOF-deoJ562R-|)0~HZZ#6IB z5Gh}3*4Wyh7E%n4uFQb;B3oC=Hn!<0uX!q(}#>fJzsP+)}=MY zznavZAb+O2-vjGC37l=4Z>u7CG-MR)PlNmy#?|lXHJ#jc@^*-WIxU`&{|Oc>LU$;2 zKEdN3kEmv=MdOQ=FP7b-`01ViJL~&UM~SGN3uHVU=+x@-q2SFX%w67mlP-c?y;Xi3 z=g#7ueNx*!JqEZ*A>^ecTpQ$OjbaKIO%hTL%!Ut!BR)$NwY$q1B~}@(46iTeb*?&b z;D(N&N=^+7)t4W=J$PK z2Jawc+HhiQQ;PM~IUpVf$G8AaAH+MrWq4Q%W+~bhAme18dSX{TxV;j}@6Eu!GnbXY zVBHrctwg$aYcWFAog9LB$`(UPf00ndnFY@=g{y;yz~1Pb+Al#5o--nHvxCCMdD(yC=8b~wS z)nTqNRwiewyiX1A6@zHjx-$U;lnzVZz(fuvUkN)VfJSVU^v2c0aE8gv%1SXLR^Rc( z;9B58r3j+skl}YpZ$W>Aux*tx9ybO{d3vAo5LF#56xf#wLY+I{H7F8J-%vGv`9|1KyxbmHYGd%7+MVTJ?q~Bc39CO0oeQAD@=+pee zvf>DNh_=tCceZ6mFHw_;%s-@(LN72g?wnTZ-r|%!sGZc$j52*}Hiwi6#gUyt|v7 znIJoH)=YqJ53B8DVYVrxvRS<0aIHHy4t+lF>=h++X1cx=+yZM%Ou|~C!1E)HeW%|- z_ly6%dR-QcQ@-bJ(O`{TcK5Q;mta3ErYmMW(*;Z?&RLym2K6_*A$%H3GM`kv@s$=W ztE=T`|AOr*@-ryA^-xCSEO8XBN=&PlfZ4ILVMee!2EaD8f;Nl59a{YuuVze^vv(X> zsq@F6BQ94|j@uLcUD^qsUlSpc|B%Q(B(LA};(y(V`)?v1d#}8SB-;%F)3Y%2>qY#F zZ@qex$PdZ;553?2(A>*qi)&#NcaD;eBYzmg`^cjzWS#39UuULT#miNxsv+R02R>?R z8I_WQ&mbY-O+Tbr1Q23Ru{^PVDRGAd9^V$UYl|wIZyN;Z(`~@J9S>XHFJGd4PtruS zd}@Dopm3`I5T|jcv=HvyJRXO>-KLF>C79eb2hf?t2vQ#~6>8&Knc>>&0ak!)UVO`r zARCg)-1<<&gS)2lp9agg3R}T{JO!TUFsQFxf%>Y`sRs@V1itxoz{>Av2Ly~SOu(+q ztaON;1?b*93+Mv-`8GMwdu;DPCkq3k54kZ$k3V-pLuqHAOG`vHWfT=X9RL6z07*na zRCx&Q>j?(ac$~mHK%2REga(h8Gg4P88N^FVJw2a|zGq6|n#M~)D{Go_KNr4CG19n+{tK(2-x-q4II`aH>E+H2U>{0kV%q|aAG zmGt$UL080DL$rEub#nxSO!|(@keU5Tz=j1O3XfQ(4RB%R&IUhdNwDOL(O_KjXM6?c zDz-e$uOuX#^dZq_KmUFChY;PjH*W816GQsv`oh8MH}UIVMEoMAU$;74uTv4x^;*>( zxac^6=Yr!)QM&peL?rSidjE&sKYxhmPbcC`K}#nytH)scz_$WX4c^%_`6+wjz2BF# zX0nRsSY?f6toZxb;D|F+KaJcI)Co<+-UvHmYEsc}&U@8-VJq7H4V8NUXxQq}>&T;6 z!1_o}YMOhrX=?TbE+U4UnydAZ0c*gfgWkT{l8hdRe*uk*?ACrg8I&ViZk0XSgoluH zEbp(l!%8wvD5Jk9H_LkQz;X1N)B_e42u2`n%xt)`hcYIj%m@u^qjtFKuL4z#Ipb~U zhyU!;2qFVt!^1dU_1Wt_n{QUat^m#&F>jW*VBp3i(|x%m$^yQ+!vGI582T(Gvopl( z_?WT|C}ck_0A&2bs_QsugxJimGnv&sUzyjBk(9SYsea@z?82q)yF{}?4^-IJ%?n>N zY#q;f*U^}@s&yEHA}|YMyez?JAvc|s09Ji)kjxd&kv5oh7|FKKJ1?&s9E)r*6Pd(b zRbqg|VdHy#3lY(q=DF{;VC&~lBAc+|qrf#sZt$>v_n5qm*5F6z#`t;>y|263EVMpi z0nO8i5+(3rN}pPoG!Bvy72gcNsMoV7p+x?W{QM#M`S-m?z;7b{MMPsWLtfKs$VKiM z%$A18n@E0$=!f1vKP30l2u}lGEeYRT>qO=kw}2*C$w`I>M3@?XmzM@9kGeN68b}{T z#Ca^<2yVB;PXxGuF}IaVc#b&_yt7w6A-(;??OT`xIl%HV6&8i?3Uv=qFIyW^Qmn#Zaz;xiS;x7%$D zJ`<7oTGK&~Pt_adW(AThZq%0(9f_U^ykcOJ!w@Xb6P)0%`gf!plC;t{yHNh6cm7_X zvSE(ir0)t=dxhJp`i?#kT>&={vD}G}P<*l7(>vAQ!*h zCkB4?Z{3**NXK!X+rHQ|d2^$WoMxQVT7_Ws6-x|bw@(umW4Fc%(e>H8_bMBAib*|& zPvefeN5z;yKMy)A5gFToFzogFmUHT#qRqbUfWJsY)?Nd8$i@mU_a$8NIK~Qn0S3yg zbO^K9+E+ch*;HJ9(Br?3MSe(repU~dN$5rTD;P$#>3zu#ZNN%no{YIWd{L^^p=@2r zzffXp$Ezu@`h6T+ZlIi-4O`G?Sv85{-Dd??IT04p!>TS~1k96^jmj9gppdO8UUKq? zGdq*REOpadY^iJXH(=tzjl9_m|@?mQi{|L7Owly-&+IpJA2Z)1tp3O#$A=rRC;h3W_@Xq9I zJY$lqH?tjqD32>i4$8Bg7noQ3FnuWm#YZ}On6EacwI}ECbr$wczMy9L6xkyO0Bl0@)Lxw&5UJB;T#JT zR{iuOY2b!cfbAY8amwOqJl8&Se$iP({08(4SrIx^SI>fjEAjuC>byb%N4@K^P+P#O zKkIS*EbxH<=WB)kly>5WzYKenCvlZ_fWW&$R9{T~K1IzV_P22k9ipiS=s3Xpwi-^x z;O~Ho7XJ$V(~@UUr(!lZ|4S?cQjvg_^;5|owkrabf>l%RWB96){iOkXj5hC__nWq+ zxZjNUCw_O&qXisM*lV~K8Gx@CtaK$r)*ZCdAC2H`u?0DHbD!1GF3s|?PQx>i;9w%n zj4yT<9n+o}2v-}X;qdM|I73F}PZqAM&ZOr!7oqi@!9Jj?`2|oW6rc@wj9%C3s?@GQ z)nho0p&zn}fU1};;&?(Q6Ftk@sy3~6vuqvqT`U=3a60Cn4leyXK%?6vT`mJ|tZH1;6LOuj7shE!M`kUCdwEF$z zfW|AJ*{x>${_yJ+1|eG>p?;op=5GVg-@#TIP`S}eh36J-Lg`NG4`kC&>xY#;gQ<^y zo0b}zG&Wn8u0Q)d1H)>{+83A8GPhBJ($%R+e?z`2Hq{~P-<`Dw6p}7`tKawayE;Xi zuLuUkh^XF%_pc7n9+{W*701hrv*O?QZ}0sa)>ylkqmjzz^EUINec)z&ls|l7V5b-! z;sWM%*r3u}GWFt4R_s;o6%pC)FBP#pw+$DB1p;v=I6f~JHSvXHwYJa-@q_IBBoSR0 zn)vTfOKXma`@P$^WH{_8Eo5^4Ei@`xrG4;iP#D-GSVFfO3>KeJtog*aS$vfkmX3cT zevtKp>jI9JPBW^Q%#9k_CVdQYn)IuxWrb|c>eP;7b(KwEX`g}@Tpa6U^bc`c3OXlB z<=&qAxZz%>D+ADRkSZV;&V~7Y@7zDqeezD`cY+JNIPe!>d@dI7Jx$ukG1@FNDwbl} z_gm7EzXcm=Nq@qnkX1%qji|o0F!L8cIIM&0p|IFtG10bj>G_9H_-1!$%#zQ-?vvJE ztgHO(@u#N)TRCm+qtZw7InHkTyK%93&3)Y*H<9<6Sj7VivCzhhrAEW5{#bg523I3(=j}vDFqp!+T=OmQ*}Hm~MS_gtT*xKLNM9{~E^&@9Ief zRq*7hZG4>$*5{SuXw*kVOxJz{ORsxA%Im&Q{2staxD@tp_ziR7)Yj}j;LG(j#_s(& zR89wV;FAdADKu&NDljHI8_AZbsm8-;o5% z%4+BGTV+~?pd93yJDY;%r8TO1IcnSmXC~rbMEvX8ot<7P3GO&{JRg@nTdkPY0{UzQ zbQeQViJsq2=Z?}GUUEY<{IR*1E^KZRlO}&-LF71&tL%$+599NFy!3vQ&*7BiB=@{D zxSc}>771W;KhwXvRFT7}evNr!`=wJf?Ee?|bq1!LL?22*!a-80InUanIuGc^Jkavk zlWQ#OXtKeW>B~spzNLX(`}jO5LlIHdzP7-)bR^j9u0=W=FFgkBU!NyXX-nQ!@O;Fp zUQhQNa`NY`LcwpTma;o+@|EcVoIJQZ;O{{mwKq@?XSPM2%|Cyh^9`wd8n!@S^Rusu zE7o!T8Lkry_yj>dZgo?~r$OUw2%`GwlVcZvO$rxrA-?W(n=QpR2?Xa=G=}4i$Q*Lb zQT)1jN27sip}a9r^K>#A$?)(_f-pjhbyQ+7r6OyQpgr#ru-t=yxMMLu@)GclD+XPG zds7dF4JHa$+6R+dN7jy&qBjo1stn+J+}amgy&o{{YTXM`u4>D6&3EF}UlDo4@!Jw` zGGgM9^xwbNSlsiG{SN$2Yvy;|@8t&K`Svl4JHd z;*HS*zL@BGP8D1{r54isNXrC!fFQT z@mh^7MElr}YM0ffRQWE&B@y?O#|FO2=?r*0U(}xnE%f zO$c><+lMEp(O1mQxzAfnlULUIL|+hAbnRR|#;^HZ+}pulK96lg9s7t!lCG5@~s^2_Fxj_Zp{^h}SEtxz+Nb#>Sg zjTDr*;9MWuFxtPM7UaXvilN@TPtmBR`eB4M1bTOX+8NEQmiTo)HNwRAXZm=^Yczkk z#ew_ydWc~7gx7i+g|7C!p*@v}Q?z#T;Oq>V&mggt&bCEDcO_s4tmzr^1$VaU{zqE> z>6H04zO=oak?3{3-57skp>1ea*rOvI{|c(gyI?*ZJ|R2gXrGq)M%uc(d>h+wUr>V$ zk|$j|HtE>uyJ#MxY|+W0VEu>i6`dC%y;!W9KASZtc4^jy@i1<0L&nn^6&>F0`p~bR zOC}%lOqan}mYuBld_Iys$2*Q|IUE!`DW6%^%Nek-Kg>8Vy2hk5U4co$9tRP4f?$31 zTm^XZBn&}Ne%sck3LdvE;G`oCgS$nJXnMF}%&SH=?S?~_`&;iML~mX)u>1~c_iuHl zT-1-K`7^r#`lt+`VG{7ZZkEXmhI5s46M3%YG+J!OrdKpROKGcR8Z*i?&nc@DcNnX% zfuyo`J`ugI=|p7R6pXlbJgrLplu^mHsh@-E%zri!A<@=5anOuH)T@&iI~uPsCtm7l zfwi-VPuS*%*u>3GWOwu?Au0(Z5_*ZyTYgAqt-7agax0?f78L|N$gaPcUfgv}DAY`12vi1?T86L?)q4;@kM2(Dsko~;ssnE0f8|59WXqQ#d0p+%txL?Gl4SiPdRx?qu z7YZf0-T<$tPay<{;)?BaOC54;b_!9h5vl@U>FL99*@<57!4A1kSTlp>0nbN|1!Hn; z`wH6RZP$KR`%r;RTq&VzR-$(-taZh&2!AqvJUXtuR0Z|1?h7mJVV_Ox8!1 zTl+Iv<-1Ugrig{ee0Kd4@F-UQ8Fxs7pSZvf)q3mc62VOykgmw!opGIjCJ#pR zX`P_#u3H9l=!8R`h%0Ji<~Z!;euAoKJg;BZ-F^Obo%7XN=zV{u9gP%NSudxC5Xt+V z-0$lOzwW0LeiQR6#Ly-_9yw!6r|U{Nn@A5T=ZB4Sq{pT+XgB|yGe`f#{P?{K)G+2c zXBP#oCm&uEH^NemHW+NzKW&dpeIeodZ?HE9pCg`6VEBf8tfh**0YiV}jy>{h{j{j;V-GPu5Rd2swL7~++2l-NRfWD@{K*cGM7->t zL?YcnB*VlpZDweciY4Js+59@>ke0+a@X7m?lZ!@B@WGtO!ON1tk;Qw>9V8|D*XuVCuSfBj@3}{-?q?Z>W?k$lf!=v}b@0Qw zcb|&+U~P*_t~0bd0FDhUOJO&oUK<#-MEw!Rxn(dG#7D7yB0;W{^;#?tQ|Ef(;YGCo z4)g*o68KN)wZU6;Xyu=#l^sS&gVd5!oGmzXZI)0Mv^m-mC_yyUdSQ4M$adLIBf=@EV9^kdvYCJYP zbteckL5Ob&XT9FFGY^0m=L$98I!9j$#GDn^_C>pltd5jATo=5yJN-B3Lb6iPZYlNL z4lN>|VS&J3zy@e(Wa5TQ40rxsT`Lo8x59=b3&rkqkyk8{bAw8)-ORl{ik*oFrL(a6;es{-ZZ`_Ix&!m6)2J9#l7s&5J+=jI z41rjE$6T^ITP;@p!$kBV8cz(&NAwi+kWF@a?%M9^&11QrQXpb_(Q7UgtZ|Mv2{Nb9 z;M2)5%1_)_?8XmGUf(*iFomT>WRE6bbiLQ~u`|Th8rENZ5hqRXs_|}wDQxM_*X7@r ziMyUGPy~4JejWk=!WP6`ZrQ^w4~-yKz%kL`cvqCir;5L}HD#BuKXK4;Iv-}N0g~YQ zA(a;NGmQelC-AU$-EkCn_z>!ZCAg*zx^5!*5q#3~O-!lnpgZijz!Tnz=ovl1c_`NC zZ$5yn^X%4tKtjX8=g5L7coM@j>S#}1#u;tZxW~%`6P-2?%RIKSP)S^l0t1*6c`WU@ zKv3)Fnf7*t+{uV*yl|63$gG5egzWkTV`iE^Gy0q)ii3J?@^{X_#8#@mXFT`48|EV> zk{!TGTr4)Ug4H3{$M=1<&|iMQ34E-T1nde%lI3X|zj+4%L1%nUVLk!6fm0uLBy@rG zs}O#c9ni5x_(jaW?u`O(9>$9Z>>3eKc#K?(up^})ukK{H7Yaz|O-yfo5%X`twnH%p z?Mmkw=FL7drbptr1r+v4cm$uDeg|w&Jr@V&5b|6sS;2&Ul^}5lZQ5WG7Y-)G@l*BO zydOct(~TQ<>fp;$XVMjp^DiFXHkvts!q*>|z1ijo`OgCei|;Wr37(X>>t)Szt@^C9 zdvReuaZaU)MElE!c_n^{>N^RvX6@A7{&!(X;A7J5xOBGwSZR0V+l!JaIR=CD)nQ2O zGVnct&3yu>k3OA(d4m48BK61to|g~2g7&@JcrTBBa`Uc?P`#Q0K@^TWFZQ%DTnugW z@)iJm&XK~h4<`J0;Hw9O1p*?O?;7j0+h$0kX8ge5U~pYK39@U*zFE-0-F4Jed?ls^ zLI^WaTW;dzX0nmo^?-?h_>%(iB{BEMHrU>B(+8kdy+-LU_;-0C;wpAeNL-RY0*CLc zB@kxrrfHc-0D&n7+RhX7lrn}Srn8p)dvSnXHsQ}F4u*NRKM8HONkTP;-o*4X_Z8vS zFCzYRz~FvhC-ss?q4|Y|Elz}p&2iN4%gNdqt1uo-vR#UydNHf{ka8>xoMd>1kza@g z)Fzr%T$W9tsSkDoDYQt?;C&cI?FgY^_WW*8pC-rEV!Ggtk@;^0u^MjSv<;(c z_l(+uR?n+QRcY!6Kgfm;8Of?|gZ~>snD^B*(9qeBlVbWWu>nakI{EtyczKAoXMv{- zOQQwLxgJ`_^8j`P4{^_qi9Khcv5Kl?xT2jzhR1OZ-R@`5|H|ZEI7%O){pT9a*t2Hb zv43PcX&EW@QXpg}`OeK6v`*u5T1CEer1srB6(WV)D4D`}T;l)>1ZdSzQi%(^sxRV< z0UGA=are%`o!Yz0@n_#T(bPn+T%>_Cp0O@Y@vyc zMc`H6_IauoU5me;aBA0*q-Iw~k%N|BH+joS3S@NulGL>M%tXA~pP&Gv9u5a`G9e-_ zaEd#xSky_@v5UzYj1d;%+eYS0_>fJWGelMZ9|XY1q`whtci^))V4D_98!T2gO88RW z=hsKN^=!T_^JILlp7{5D%`Fb>K-|gQFP})P zLm~5I0^C?I3nQbmmfK(wzwXx}Qsc9Mq`r!SO*#XIup0Zq`HYDJ77MJk+9=H<9R@Ru zZ{3)i|1Dn5VoSi#&fgl(N3=F!iS;Wy%J!A~V1s`NBDc%Dn4(HAnmNga;JkhWnKo9n1191vE22wO%a=|Q*z{SZo9SRpiN}UMj#Ka^ zqU+A;uUjLoH~gO&Hc zptYb;&rhItf=;Q4j+us@SZC^%qWI!+wZ-SP;Ut|02sIEvT}UQxf!Y>X#x z`JrJUF^gWdp8&3<^aQT@c0Zt;q z%3_2uZNz=vZ&j|dhoxk!*EX>)wS+P)a@QJTpUXHaNpeN<^2N1NG*!KjpseG_VS%{V z2xxfadS8RRhJ*!zkq$2#g%uSk<64bX7fDRFmlFd6GlO3p_$Hx;xtrKAnK9b%Pc|_8 zZA>#0w5JvB#Kb3@UCr{LMr?)udssPs(e zQllg4ICR8UR)p+azw8wgy?$Sh=cRBq2vuz>t&a!y#+L^oX@1orox9luOk_T38RvS} zI?wX%XY6zptzzTC-FagsMAaL%0;;}WB={hD?0)VLWt94Qz?&RbxW5eAl63addBnzF z;=K>G?e0Ra8%@3UJcMQ-a+$Wc%Y0Vvpz}CP$u1tD?Q@g(Kt^-Fh?kJuAJH!A9Gbmj z*fd5KTqv`VALK-aALZ$H&T7UB9W(i&2Tb@pw+D+U967K2QwPi>rVc>KiO;O*np zYzqq^XY0J%8cbl~*-aKNdv!&XVsaU@@!ckwlN%ShJWv#Uh@|ocdKBt;flQ0Orc(>g z^Dc?(!s0Mci4l$C6QFW6iZwHUOX8(jBtZR@3=F*H>;M2D07*naRE*X0y!Rka?EtdF zq;BLj5_I>fcNDfW--`o6*T#PXhsv@hYL%^p`G)O-RzXqf7dV%_^Q=MZw%pTGec=E? zCqs++gl5?~ss^)4NUfcWSp1Ay3zE6iP8D~|UB5VR{|FO_6pPabQ8r94iup0;=6wPN zLC>&jQBa_rHrh8A3I1ZxsU2iDqWUU3bp~X_OGMgtM&IsXw}_CiVc4nKHgxK-tj_F$ z_HOL1e+&r{n}ken-A8Z)P5V0{;EzaRTHgt(JMm1H`7cexjw`0x5^Yf!XJBV+Hg8cl zTGNTK8}wDD1F6{c$>EHtdkjYC1?Gp~J`5P_dKK&SJF!M@pBv4p9n{00A2o=)#bFUw zJM%rDKYX;$?)!+$_Gjo}{P^!s^P67Vc*>j6ALFi7~|oZ>Y{T0g;-rFU5D znS!8zSIkWrn`&}ULV(86wZ&Sv`g)4ORTOs=)z{Ym_;B?UOyE|IH<0t>KZ{ELMuggX z`+iG{W`lR{UNo@KVZ6(sg+yQbIY z3u`@(dhq?!THxUYD~#s3e}&T15ho?;x~!zDyw$gd!R5Dva~jXg!n^_wzJL!=SH#qd zB`q+0sd=p7zuQ(@034`&2peSg?88&yh?B2!?clS-`}?+}0Z)?7uMhklVHu%M^QCd& zn@Tf==3UO6`KsYz?Q6WB!|02I%yUm8sUhi!YIl+-TWtETm9!n;wR63}V=%=9 zEGOuE|DRe@M0WXTfV(Byj#h6FodsJrZnXjT7MeQJQq3%2%o`R@5P(L>=o&{T^uhnq zi1{^nbN7pB?2&5b%@$lAb>JGOUfz7n9fVGw88d(qypaIvS17&%HdzpL+>zDZ5Wwe} z)Z8D(57M&&qUGgKw0q9$P^o7pnsU1)*L9}Y4c`9-{9@nLFWY?tzTZ(iOl|KXiFPx-7*_@)A)8~cA| zX%cZ4A7kwp!ag)`TpXXz?iLhCR~M7Wpu4xnY@PX~100Wpu=_accO;AVPW%GyPDu0+ zX+9567{c)E{q-AvF<>Z9p!C`S%@SiwRLyl1yBzU!Cades$WA_@8=dmxYokFqkV$}( zJ~QdBChS|b4J&LJTk~qOsTs;cm4<)8Ck1U#k5AfXkOVH6m~EGkNS+`VZqDH=1Kw3- z>^ZQ$GmTiRO%e*_4I5=*n}Nn)C8E;|7l%7OV}KlV3%rxQ`(ct&@Dt z^NfSj;0)j@b~XJ981yoKJE_k}VQmKxwJed?K{8X}FxANsjJZq7OK#)uBQmRoj9X``=D)&T|jL?(U(w#$t|knscj$h zs|8w$jv*f5>o;aN9~p|(XJ@!w;?9Xa>clR}D*L)Yi?hS}hU^!OWp0 zE>BS0h1ILDkZhJy9_EqfuyT`}LxC~)--A6Naqp&n=8H9^6D~TH)S#NWCwYUzH{y{H zmmB~K`I}@pDuIW)R=DOjMoMEW@}ZTu9k!3-fIqFB*Uun*#ej*-o2K{ zct^0DU^h&KlaGD%eh88vB(1OY%g+qmGodg+X*_N(G{0D&gW`&hs&k@Gk~YRSsn>CI zd2n2n;?zQ0lDOv+8>`pCQPW(0G_fBIEy}0sAjOmJ0-v5n6qTaEW$D z=L_QzCRslPKi7z6?V{Uh9$GoGRIVS4qf`V72Ap{JRxi6&myH{oi>R;b#say{l!SL! z9L;9MYEmzf?&!su$xP@C9%-LfFQ|r~2-#B%2ZoS%5V3gk8gY((Z1t z!fQ{u_>nmWk;sW@&}iTsO6-B~ibFbWM3u3-lvmDK*g`G@{?=EuZW4R;a$fd(5SZZJ zLk#tdiJgMBO5z=-5ZlK+bEZ@7S;$pd~ugmMf{!-+K(6MZ!d|QpB|iOkPuv&q$eNKog~K=!(3pSTgWp5BSW$NOMC^Q zb!!PZI>x7}tCdD+t>xD*Q2WHI*^X%3aW-Ul(U{q?-F=i%Q|`J*BkT5q=kfFa;w3iG zQ_H)L)mw2T7>pFsIp^%)Sg@NW`Ldt$F;5i$$V+iD>tBsAhhu{wKW@GsUr0_*AqdO! z(02@Cj+XA~@eJ`7`_Voi2F029+YUZmIZmA>DBn0ci~VI1j=RPu;B6mSodz|>La??h zMv3(R7Noj!a}NzW?VPQ?^QFIjl|yC2a{Gu?qPA-WrBVJ&AzY?$BF-d-L44=hl@M$t zSz>jbxv1+%vORUthNWA|B%^gPre8*pP+s!|(9sH>{hvqF0Vf1^J;l;A6MBy10OF?> zC_kA_p-M&;4{CmF5l4fj?H#I>P0v(E0?*CyKI}{-xfx&JUBgcZk$z>jY?oy} zgxQWSam6x&(fMgORa4tn8+v45g{f^V-@zyfyF0^*yR=s3`u2Q$G7TFPWMb@%h{9_p zOo;n4L0s+^Ba{jJ7|D}2)NBx*#c6vZzVAfLnOz%m>hS^mo@b|9OV`8>byL5oecr=+MaM|L%{gJZ<=;Gmdb`upY7;r#$23c*fC`T|4a2b zw4l?0N6rTC7T{ZDGiKP_y_@y3TVQG3kNksnp<+S*#GbAe@l%&MqS?db8D%qn9aG-+ zU26(<<^+`6&q$mY;!k1B*UZjgUMlWT-}UdC#`8r!1~_9o5Fyddy=YjJXGQJdNPNlk z^IeWO{M!eh`vpB8w;;RoTpb^c?m4}dd2c4G6$YhV&)>hlS_AC#n71-1!<3J#%u(fC zf?UK<$t6>Jd($H?hq}FkP*o}0#RT*&3Ku=%Tei_+P$HO!K#$_ zHH`u6j_a-GBi-*wR zX;I;zF$MTyF6P_AK^mRGO)#4|Cro_tjw=M`=vw>rp8+uz1E=(x|% zD>)Wr`Ms$dDOaEJ^NgUf{A#p&J%}|=CfU`my5F#2133nneq=NRS1h2H)R?<0V~iHDA*7KGNbju;>U;&Xg)FXk?pAv_X&WrrcG_ zQrG-xpPht36^^v7KQLbUb8!DHkoivmjwv8Ge8Uq(@aLUdxU+HLczQyn@_`wR!ZbXy z{fg;h5gkp))kKBy+QdfjeWs8oir!Sj@YhVUN7h7q@G9v?5)4nkRq#|O8U9`H_$_0_ znGs1qd9c!KW>)^bboe^3eo8wuYvC#4&_+yF6@&$XR4C0jf4r-#{r*@k7+(WO=b;PQ zGMF)(vwu1)!2LyujdwQZ$v~cm)E4JcTQqO*lHv|#g*M1FP$aj(pu>6{N0MKA;M%vS zckWoy_egWxI_8;a)^-5v3M`#kbSR{L+7$!Odx5|t;rF=DyQyQsCHGxakdPBq6;ReZ zi;Es03!YYwoT3y%OYT~q(61UKbEiix!O*b);$pmIBv0&zUstgud8_{CcVbdy6Kq&> zdsX8zRkI{qn|S1Hg=oHpFzR@Gx3K1uSYoSNev=>}g;L=UK-}MXOMDH#j8fGn`XHp! z%(RgdPK$KW*P{I`z9qYLj=uPDMk|T#;#5;Kff#|gt?7OUTOvt?dswGlsXNSd+HlVE z+BWD=b36Gr-pb)w~TdtB%S3WcvJ@MWgq<74}Z5t5%MG24H zra6z+7M^$uh~%sA_wt+z(u{6f3&$|6r01BQeReDg&KhKTYbbexdvhw;Sb>mjS?EdqM#l)-jYf>+Iy z%H19~re-mlqlRPYT(3;V%nJ16rPxhbQ^HzNpA09;o<2J36{9*w$KIP(&>Js{#eT&> zYocyizcbXliA}J$%XwvQ2HdqGxsXUTVuzS~N72G23m5eV?0FJ9hud5VN`l zsdwYJQdsKe#JD_#2EWZ1FPB4;##f=IP57xb9WQ;7z$f*(bH5bhBV}G~Ww`>m<0mmY z=f$O-$RC40a(UTPL)Q&{bDmwrCWQz5HisG6z9;5rtSx=?`qA-61iji|kVb8`yU)*)p6uv*mh}yp*yW+{+t*-N{Spsw^V$pv0zCTTk=4gpQsnvE~opOZdVd= z;tN}5n24C~xBa&p7Yr<5a(nMp+Eozg>T3!g_E9JL3+lQtj5UU_PAn%h`kYQVOG&3z zn|^3M+p9@=nNYP4fo_k|zzzP#KC3&HJ0~QxJ~IL+cpXK%x#}PUIUaL*w`V`Y{&qrj z20DqA@VEXWRVNR5hW?J$i^j)Xek11jh>^{<@*;&)BSkagx@UDWf#vahnG`;CF= zlkFr*M+h~ruF;`UW%~`=y(KY{2)^wIZjhx@?UDH_L%5HPdoQU~@o(&TOYcwB=P>zi zq1?qVPgyt~%q)rB%=b-P+@li!kb z1!i`FHt-1oU0ZYk5Ykp`C*!AREc4*PS%!xRxbC%fbX1c`G#@+nAbTa+MPIwNu&K<% z!SH%AFq?_T{;z8z8~rrXWyof>14$&J;p|yH%83KsdRaWLE*gj4tgs@iujPSWolt?R zlF`u>mO!;0#n4tyuF4kHeaPGG>##X1wf!ONu}wd%+o(9**O|v^F7f?jf%cNEK`SZ6-AP$qzPmx%POxJ%7XTSa1zULxRO9OSv_y) z>Z*xTb#v5L^8T(Z*a0VJi17!OO2R=>X#_CZ;9ws8td&qw(0uyJ+(Q^11cogwQQ`pL zaMpYP5>LHT%@djNf19tzx*r|=7b-Vm?=7s}f0FtjcH+#v{-!C>6Q%vRKs;J2j6^|c z1zozu^)Or;`dPBPxs=7iyXR+vdGdEvo_@MbrCjD}tmh1KwveO%I_|Vl&Ho7fbQm6u zvh-GV2Ja&tngJ(j(@h~1Z?{28ZlaZZ#ZQCzp0cfy;#ZT*2+gO|Hf7OI{5F^kIZS4V zC!0*P9-IY+-|>|>X_ZpPwjt)OJ0wn2`!;8DZQPK^KZmy>GoGI|B%5SAno9>RISzfE zc`WijZmeb_f89kBp|^;wP^r`ccU6*FF8oS&{B;(=SBa*}ggDl9CZG+55n#TV923tm zXMPdWFCzLyOuy*GzliuZ5&tIUU&QqL{`*BVKXF;z^*JliGe-xEqs&Uy$>PKpPX!z& zC_uhTVcdCZ_1#p4dJ37>=Cr-KjztYWL+ZJWe-8N!jj-rB61@KP=9F8Q1X zd{rP**@&Xq7N+n02*CowPcC6iEk0|rv*nrJ2fP3?;j9#9?r}u^0KE8PzyuoDQyUdT z@De`ui3Rqr&CoU?&50xf8h6)1XpKK;6|egFwx3IBddGQs89NX!6=KWpH4s+~uw ze#+uA&A2@Jx(z*8LnwT$#Pft30$aAx8@J~{&0dA;;@aHoowz_84bfZu)e(_5IrDeg zpUlqo0O~H6HI8Y-y2{hC9=d9P5h^!mu=yoh28$oX69fxiD&r1xorAmNgUE5s`$-=< z>tM{;*^Pit!Bfs4x-@jsTO_CzalW#kSo=nAm*Kq>$!-jrPyoinPUd?2*9Cv^Ca#Lc z`&~$9u2_}tz`qcP@h-UD)_jX&WH+XQZ13ra<@JT`A-nZHj}JyzhH5BT1LZYyaTl)z zZ*gOrW&6JtF(l*kULFrP-)w10OBryNo??-c2BmI_t@4`3fRX#Bq zM?V7p&9wjsUu7Wwg!AW^|C?IUqy(9gx|k}FqgA*z;Ra-F*p&8E;!`UaJV!6Qd;$lr zHlD9}?s1#Z+Qr~o+z%$~2aAq@gXvIF*16$fclo2m(Otu8vF*zc!p;qEQ0aF45z3y7 zfL}34DL;9Kzkm=>GKEY)PY7%BY`|jWvJ+(yltZ|vtl-J7;0D0E(>e}vXG5-gf0ZX(j)R3;+||zHb`f%5ODo*K-&$$43|vWd zHx$YYlMe<|wltQnD}c$BMRgs!q)Y_^tk2LwK$WR%{ew6@#^r?ZofAJbW7>GawiUg6 zfp8^TdL>}PoO;EB$D#n=PYCdO0^m{=y;M5>-Zbt)*e7b1hR-MI=E4E1MT6v_vm!d< za1`%l+Xb@92#7s)hBz*A75J>;28NJYnkSuPn9aN=DB%{WNoSnzB!g4P`a3(7*ID}# zSfdt9UkA{fejL~~%iHr@%G0cc?ZG$GS%+&9ipdwdYPMZwcQam}@~dz>K>>B6gQ`2g zc20h0A!DO=$25AW{s9-3k?)4?jtncOkwth8U@%odJ=08$R{ZsWqMnSeBPHPIzXp#; zCY~wC01L#1cbT2-GIkSH@p9BS$H&oLRTfYFdBlzv?ZhunxZ0I+;+Pu>3s~E}VzAK5 zO}p7amrUR}<+fo!$wJ9fJYO!fAGFu?ym}{hV!OPjbvkmbGN}~|iYiJc=K&8sG|Z)& zWXOeFVxaY40+n)SB^7q;WevSkjXl0MGq?4`{Wcj!5C39B0xrWeCZRe4eqHxAxRNlh z9}Af8t_Ts^w86AO_OrQ25svdqfY1}jCBKH3X(rxJ&?nf}Xe9keE?=(emrB@~h_x2I zufKcj?8b6K-}KD0e?mecZz8#HjMs~!!&tq7Ei^v z9Zw%mVN>O#Y*%S^vgeoa;!K4Y<1uX$qv{u%{PmLjc3G#2F?PUTN<_DsbVE00QnC|@ zHw?Z)$5o$Ap4qY07Dc&-kaqPG|Cz*ksub71kAt#4QJgcs`BnTW4eyV-7P-eX`@gn& zAz%FkK{Xc%a?+XJ*Oy27u5n*l1n>n_?e{y?@czX{owKBOzwPHHuYn-2IC;e&iskZ^ zXxIFlo;}S%8k9{Seqs%%^9i0s^3V_|_FzlRepfiGJ`qS9^Uc&%c(Dr=nygjUGRwCE&TSL+zcte=~JGYp5uBTD2( znJKJ*sD5#%KdMH3%meLBKxy>sPFwVlY5BEO0vkb4i|o>6M=z5x*wXXvvl`3$X@QxX zyYM3t#S5L1$a;9c3z`aOSdqbP_f9P4?( z6d$is^^BB_{B9kdkJ8^Z)Sy`JmCD_IJs@^#HL%%@P(_-s9YCH~iF z_LpGECdL~NnMeW{WVIpC-eyQ8ylAji6y<4DJOOQ)ENpxy79!b&G*+tZy;Rgi@g zg#C$|Su222#(orsx})BAXM?6}WN4C?O{Q~mHr@w+#N+I=51eA?$IK)$lD{lb?ZI$R zYpW{HO3xrgH?Vl0oJ%NbFepJUmmYZQ!VTQ0!n)=usb{k1uN4cg_A`mIo6PmP+7(I+ z`Eje5VcQm%9t)B1&}mm^WRQ9xF4aMyxL~KQso|5XAA`Iufb%5dVO_^z$`~&uyDHYj zt;@0Hwl+@jJ>~YglkuzkG9l|J0ik#86ckEiEvi0Pt9u^_%?)jaeG%fDCotbqrp8g2 zQB52KoIw|WexM#qrJYbT-9_ z@~p=PeCY{s3ZY%}d3bBuE@OWI3bZ)hLv7TBjUn?X!rz#sDLip8e+_xvBo*VS3GT=W zRzYn}`l#ba2~9uJ=8-G3KOJu4^l3-(K&pY)dR{A+vY-s@mTwmvFp+*l zW&6Mn+LZ8Emyzx~LWEI+E=JIXUg<0UB7WUwdTxSwC*rrQK}mdM;?ok%`FNUO+_AnE zB>8o3Y?$^CUwi0Q0KXqbaN>NNMk&fdjSpNS5ecnNhR*h5QHSOnOQ(U@3ucqVfe#1` zdz>6>QlG!%l;QVyU>$F2zc8H^K8eQRNRd!b)yI?hYilWSOT0wmeO1PsroX3V>1J?l zH(=F=dsJmT-?z#jw?E^r5lKA*Gr2Rr@(jK?eJ=Q>1R9ncJ-)&2?*Mx8s4Uk3(YEkW zw)0JRs0A)oyLw(cagfsM(*NeI+7m1X^4Wg2Ftfd0a}uZQ&)J_eR>K-##%~4iy~d+K zQa$~Ofsd1}-;Cp$nRVCPFS@9tu@t3jFsA?jAOJ~3K~x65AM8ae?hNu9lA4}#3}?Uz z&_s;_fTwVRYlVFhMp9h8Q!d2Lr3bS=R@Y(97(WnrA5WyA^ec%O6gmqm84;!z5&w($ z^{;C|U^+aZc}LfWJ0kAd{ciZU&U`Nz{2$`iZ;Dg$bgYsTSH?s0-e~PoVr@j(cIAFm zW`2gHVnx8U0!A`q7c3-NXJuRVG;Hsp{RmvR!%y8ELprNV_tC8r*B1p?>Ckv;z&`6P z>fay>TWKEJ+zkm1^)(alcwe8cCzs4-hIaP(*27wPs%HtqKEdr{+LRdmC)1g0{WkZK zOe=@dFJ)+-j^5DW`{a6XVLhSXStyfrS9i#LcnWrH{5a_IF#TE}7IC+JSte1L+5mzo zCNkf(R2@Cl$IpQ+Z=qxb_J*2-EA(WvQsQ3&?C0AQKgd3+Ig%4Y7|vNk$n>!a?kt%8IPwE1=Zm27v&Ge~Ud_{s7>U*9`YQ(j33_^H63i#eVQV8RC6q|n!!!9)A8JLg#^U|~<6 z7A(nXr{z1V^13r4s$Ss1kmUYc)RCLw0&Wg`6xrTWk(YwCB&#D#2zQV!LOfoWg^1ln zE>G_ktR5{!0@%~2g4SzH(NhLU8*@@KXu2Q0eq`)SqoH{EO!1a~XrM$fg`q&6!jZ?|9K0vE9F7@UQ+zATj2H835bP zkjylILWVMEiK}MBIWs5}Oj`l1vxOj=sz=4(yklZk#{8Ma2FqbXH9*_uy+_YzEW zCszMO*1iN&#u;A=1+oV77)+(<@9w!d>i8uXAAk7k{t1<}*s$ycxi)^<>G3mWQuQzP z&QI(N_H!uOFIVTt;Je_B$of}xJ%$X~Ov1kq>5i|&&)!zIeE#CFfX8RLK|n^keS_aJvA_fU956e z6PrOUH#k%lY;|D$8y%7q=L%lGuDklL7m3~_UMB{czW&>N4Brm7t+tl zg=irZ!c*T{4)q$F^M(B$6izSjah}RWapa9)7?$p^p^7FoJpK3}S zSyClffd+pi3ut`52N?V3dnl|U@ZoLUg*;n=cr6s!bQgx2eOIhK!Vg-szfjj8i=XLx zioa_=cY^+}55ys9SE=pGY->!aT7bxl!X|}aJ)(~-z-`b92cUg^hS>)s;~YJZwyw+w z4)BC|F~yL>^~0tzN}@3ONGx*PtiH!CM9hswUbv8`>uYH9bn-3!^uJl1a4kg6LUz0H zwRNTCH;1)5`>j7>$9^V^bQ{&NY1w2;aAIBcX++lSWX->c_=o7NH-J0n%+S=Kw`{Ro zvdMHrCqHY}Z6V7de1BP8M|FWN^7uDoBu%{mQ+7WCl@1sr4p9l3@Dk1v?=Q7DAhBO2 zrWG=;TqsQT83AqoUTRMg)KgYE>-2r%aunx?z9KHl-*#gLHcLr%7ahZ_ev zBsnhv%IVnce#Q@!`~eIF`8wB$);=1NXkX^nQ5;%5&7M(`5WXBewF#B=sRCytWQ&f+ zo;>3Ev*RT-HYYYx>r-$1(dBPQtn970(vR)MH|FBges5ZxB7H21otMHL^y4J{;AB2a zJbZ51k~k)fSwVYktPC0*lYn+i4V_d-$6<5qw8igRsJbS6f?y3oXPSTJgtlKcm=xKa zt6!i%-vdEEIUVig430d6^Ue{{FR6l-Bd;)@ru=(h4(81zgbhEo#RF)LCx5RSm;PI1 z|1Wp%+HO0lssZAx+UNiO-afSk{UKhsydat}>+EjUGuKFr0wQuTi2-fL<4VeEbzEw3 zfN9<(9Rt)X)|J4010E8CpZ*E6_7~A#=4patC>?L+OqUJ=zi2)>IgFG|MTqR@^sI&| zQ79iPt|}%N)Ms1_7Y=%TR~{t$zNPvz9O<*I!LBO8Gt$qdmVfEw+YHtjFDHzr8h!+g!D?<#=Hu{<7QR4ILi3D-JXVQ6 zhXe2G@B^nJMJ+Ef*+O;Q-{@j-1nHS_3^|r z4$;vPJJ6V2Tx4^auP9?2aRLs&ujB)J%glD4LaFNqs@cpRQC%WO@KuDD$gXW%rQx9- zg$q`u9wK2vKTP#<6k(p6%>BfOq@N$W)`zH}?r;2n|J~dRHz{0`90l}<@XpHNI?9lC z0phqb{=(%j{Lj#!VC)uF(!u)F*OP~kWF=Xt@S3}~)J)_*RQ_<<)`>>!6zVS```$E*)A9bCnuH8Q+KOstXfz$Su5Zw<{R!sc z1*374TT^;VyyjIBf~iYP{>vG>vdqy}(R3!EHmRa*-#Fy`<2UEhi5b=0(Abu?NK@mx%Z z7A0gGoBXq>p{DRq6^G3Nu>YaHRL4-%y9%_jMteSDt10KpC0o`vb+l#a%!()qjW9&q z#{SloA9vW}oqIb)Xsv=C=*+VpQ-F61iT~KPtR^Z6K7oQqc2tUs0uoCa>z8SKT$Y5z6=A)=ztT)5bvxk*1hI8S#!|ufT7@ zLVcMcsZ|!)@;#T!BO#=F5Xg`vuJsQ5xb)D2vNp99N zt3Qd_le`FA{d$|v^z!owGpU3!51;t>JfWPif6%D!C-Llw@OLm)NGnA>WCszWpE3D# zTkJ}sP?>Hh7!$K9Qc_|YU#gkGiSHlC~@4~ zSAnG5%fc)QG@2U;Vok{A`S=|)JO4g<54t`k6J8`(Jg9A_0$hDNvtVN^)FPjZ>skXx zYt?EK%j*#K`+kL`sZztWwiVRhJ$mV?fHS<@%8!?Fj z26XupF((E59HbInF2sLZG?Rx4!eW7)8o;{ z_E%5N9EKVlbS9q{ZiATsADaW~gJzD9=v&5>oENN%Q1w}1hu7i{0H(ad{#AnnScz#p zbKmd6y95Z$HJ-XkKR@&&>Ae%SdH0!Fn=N&8gV7nPOVu4btAl)S$b%!rx-FBagS%= z8r`>ZQ*H9=F{VQz`gEfs}OshMm;oG??dp&K1X}G(aa4BdBLm178dAYcr0z1htl4>R|<}5mlUA86{7k zEc#P=)q1<7vkjgk%7qqiqA8hH66;C$jKOlD47!k|A|QS_oG>s@_!G{ zB>}o8Ktip*ZqWXw)~E0av9zws*EOP*PM-t6H@(GtdZ`pT*)%&($ zBxGPck+ZXb(#B(JVuZ%@ypEzG!~H_R8n!m|*MgLZ{~yfN1Ah~EkOX!?l^BCDU3!`A z-Iy{`b*<^J0roh^mItI1nkly?>CrPUxB2DPWilp%fwv@{JH%2!lvR0;YA*vn>NqAo zQ><(T=F4h{mhaF<&t&3}4ZWt7K^#~S3~S+SlWJT5*_%)>H57xvQZr!Vg$+_Z!}7*x@Zb~9xvCDadEab!Hqpf!zdYJ)+O95}z(MI-ghcmoZ6LT1Xti6roIGQ{ zo)Vc0V)ApB%6L4O{QeA0^9mw25RNA>=vzLz&Wum8P`1~A(4Phkj*^Mw&^|t=`L6W< z@^f`vX}kKd>zkt{vPDS#cFM4+yWzS1v1hI^y`R!K7)&Pg~cDe4<-#}Rf+aX5~B!1e;3&e*Q& z^C%!cCx6bZ%utRS@4-y3-HbVA0d_b@+P28|%M+(pa{MJNxPy?T3Kd}Y8@o?n*Z9a{ zpIuy<%j=Ml7j5a~;zETF9ntK;+P+$g;|C!&kS-8BU_!5B227-s)d{bT<$wpGeA=?M zf^m>gChxAy;0ml*uK*QnU8Ewgz2aO=kFOi~C#aW?`8|J)>?9vGrVK6@KcjL@ZM@^$ zdfw6Z8VEz^j2`Ojly_YSW2EKR@e90!*VVwm`g$ zc2?IM_I}Gt<|_jzqiFVIyZRUe^W&UFVq%5G>LA8z%Qrgw#Ox#SJ~i5MP@B*>RAMBf63W01kcsTggCc=-r#4Q?gDBPwS7;)7wz* zcR<@D`J{uD;Lc*9c|UaI1dpw$r(WRYohfR%Ym-MznQJFGs|P(RB76V}7Wf6xQkphz zS#~_`t~bI@>uuT`kvg%3k;ge1yR&-CMBIcnxtI(fEz3*TM;7{A25oD?mf<3VjcA&` zwCPOdZtH!i2<&3MP4dcGyoA7~oU}`sK(UQI2RCL)JPezkMjPJ&b_Z8ftcK1I>I9Ot zFHOY)lGIBLvIe`{uAx9ofI)b=;Mh}xiwB@em7J9+xs{rLTx4cbX(u-jFq|;5bpd)y zZcz(QZuv0mv-$et%5`EWjCg3A+4IA?L>lQ(2M4Q#%`@yg&zCIX(;bDoKLRa7KIQC0Rm&#~mT8T(6w^%Za+Wo; zssAO>&Ue^YC4AHRZ$}(nF z{?s~yNqObJ`V9IRiL%#!C5G{2OTWUrzCJR;5B2#;G}g~``4Nb9HL@j`r@hq7oUx4? z^?eucBEfo;J$~AN)#3n8HJz$Nk)1jjb2fkXuv8L_&!UG^%Tkx+a_$ZbA$4c96Q4z# zIDP+=-j#fi_mT0O4-p0Ehu$_hp!fK0d?s7@>nK99aUaT#en!_e?KVX7KnAtgp?dNa8nbsh8yTBHJN#oj_W=;vPCcs2rzk9hMuFtnR=*QVc3uCtX z1<-oOUW5n&{!N`+ENnw43(^6DOM8mdayf%z+%QjdTqSf<1%5Xs@n=u7IT`rEl+NJv zJCiYdfC-mkRZCmXBlq^Z=?`ABdoME78$eUExuYJ^&hNt4rOn2eI4W}iib+*OB<#Y$JRN!i zQi^NE@F`9en7H4C9n%?M5&YU@Ko#I5$*qk~5hB?D(kdJ22R8eWnMwdG6nr{z=NS$M zA)g#Hg9evC6iGF)IFQic`T4*H@%kiTFyELHr)KV7C!~kDZD>4U;{X{N2VNJ$Ck5I_ zrctQ-;uAi{*VlUxHg{$Y@sPTb2H)huPG(${3RNcvMN-ytufD2{o*GI2zbR z-Kf2 zjqPU+HF**%nFrkz$gG)m03;|C!RHjsRZLwGumXDc06z{*oPfpW9)@g3UK9m(7$vUd z+B;T*K01YBnu#!Lq1nZPBaLq~g8@0^dE5=33lreAkPUxORn-)Xti+NJje{5$QE&GA zHjp5|hom?8{+RL;5Q#CEOV3Xu8jAx=b76p|WBDu|juy<;hK4JQlY^jp^{h243#;RP zn-l>)pz>;^d-h>4m8SiX?&w3~m7>M1oNt~R%m$mkDpcBJHW&#*dYPt1Kt2^P{?_$1 zCN>|_n+e?hUVqB2i|J&4nNvJ>u$QoCQ%>{Se0)fE*G6y4bpj z%NlNYDzNO$CvP)njT$$#?!Y_}fzM2cT7NwH+I%-J=cjyZ z5@!}4W(9PIQ)s`{6{cxTmCm836k=x6!@YX8PDVaOux~Yz7#8EZu8?M373~6yr2{gs zcGa6}(vKrY>Um1GaSR|D4`idp;()g(b()RhN9Q}DpGNKHufEsD8Us1Uc$cgd?CbjS zLBnH$7G%_jN2@`j1{mY{D-@`InL($=5B!WzKoVY#cn%i#VV#((OQ>U#I*F{nniqNo z5?QJDUz6C{sUrhYO9S@6T=1VPtO1tB_ay!r4p^}>>pIR!TbhxY#C$s(C2UaE9mWON zxqTubT(=Piwm!gp*e@SG!2RhVnF^^fuf6BXYC8`9e*?%Kzd)3@J3!-CbbgRSu|W*^ zG6}NEVdbRVu5LX_8D3@I%}U%+*>XO>^)xRew7TtCJO)~xHg*ozA^->z+uV|~O=ZJU zVtqoH?mG{oS37*Te@M7VaNj(p73>6xW#$iB*&hnNR}>vq?SvYEPZz*(BSI?=@k4a1 z;)ax~BbF~Mry8pPB77R38Ju4f=*dTaC155r;}roO?qe(l{GstG!)yzwolfAmGSheu zItX^ALvl*DPUN$=J-$IG6+ce*!Syi^QfgT!^C19RBemKGtUF&4!@oADd2GKzQa?blJ}cZvTZYQj;=#jEzTTgh>uFObRJ zc+Nu+M)U(MKinT*8U(#IL!Q@!j;XH~tNCfeJkwv-tF^I@>+wI{${ouMD%`CX08j!T1h{4iOe*2jt6DQ+~bx*LLY< zkkpZ2X(vv9 zLpgkcAVOl|x@}OYkj_V4HI?uo9Ej2jp^Z>#ey3w$v!NO{7Vk0d9ID}EM%20p*N>9k z05uae&fddAi7Ub+c@r6DP%k89H>gWnv;UKiN6YiMRB3T*mpVt!v4GMD6OaP@t^E*u zAW(gMpi`EH(#`)xi|487lbjRt*apoec_VuE=W68FEL2}bm!&>3(et@R{RDw(EBoU7 zG1#7TjM>oT&!>Mg_ahix%=8S{JjhRDH3neV+M)zF9wMoWhun{hu@up3b9ajR?#fJ3+(%$s#v@ z`l4a`c^0!~7e0MMQljCO(9~BPvLAOlXY_S2|F>W#P24&A+ygqcWhit~@^C3d zigF|eWIZFOX%`ygq^fLl(DWFTa}ErMsfxy8vB<8zuU%n^j2wKCwB9B0Sev@9^a5fz z?qKS47hpUZhMfG}f*mc0HKDvG#J%>6+ept@=(8{2aSL2vNoXYdqCJ%oCz_o6rFafS zUL%Dl_R82BzmS08{WnDgK*i$dVxjdtWqWRI&KfxmTtJg-4A>FXTQu;ocA$G^=vIPeXwCg0 zh@OO5&O#d!Ax%zZIlBAa2A$rv4Q7GyAUS_5nW9e5#Vg0e3Pp8oP zB^S`9424Y5Qw*vPgXdi(K`-3TKrac{(OsorkpQva}Q!AlabM{eOUAw7Av zI(rIij_r$Y7%vGBl=lDtAOJ~3K~%O>z7CU@uA^uQr^I-jE4(OH3_0ChL-J#I=xFk$ z&M_1b4I2{R;8h7GIX0;CW4YsF?^L|5zC!mB0zKy1yo_KU99um)p|$E-EhZE?w(fN$x#n5obqQlX9Ny9b0al$`Wr#Qo@EIc<*LEQh$^D!;9Ap_V z?qPk0J+C!y2OkVb-oFmY0oYo(47GWP5R223<=*N%lgEKQ&O;Eyn4bVLX>8)tfh%c~ z&1G4o(>zy-W;2zbps>N4;t+%Y{G5}c*Z^8~uNmyAcAO(AcZ+_aKiubl4Y)a08Mm>i z@=iz*IAL~U>{@u@+uN#5o)8f~pVzZ7*W@^xBkJzBEN?_KKC>yYlCX8-fL$P(ckMLr zTzeDX?bj;l?5LO6fo5?r%_j+nj4Vi$<@A-BnsV!qV+()k$D;ZSZHw(i(PHas1@{E; zigs>c`#xsaz|LDEW&RRPJ3&hhmDQ24c4>1?uS$%2K7#d|80C=+zbWuTT#272+AOY@ zV-V4nDOxKG;Z*;fn>cLmv9+KC`S%FEKB+E1`;cq9@{O)EN*zJTG^BAz*dizkf)(z6 zkYUj7Xil6#9&>}rZm=KbYI!2+Yk}?#REYU*5v5X$0n=S6#uZ-cy79k4g9Dqy$ z2kh2y9?#*yRFhb8s=B`9q_Wy&+ZkoFNlOYh(NygUw@ZJSX8( z1geeQ)bL?izpHQLg0i_($E;&yT@3u@Y`&e_NyIv_F|DT<5;TzAHor*Vz39)}M^f!Q z*u-;MyG3v=Drs8{Z4t*&89W2d7&oOdls z!&>R=mtr^I@0N|Tmr$H@EAgmZA9M8=v`t9kOURk!{Kr?TvJP|x8mx>bgy(?&ZN31= znY#6nj%vEV^BB$1A^~T-6bY(pZK8FY!=Z>Kd(!V_DHZuFcC+{dfybRQbjM5$)d(hS z+GIZyWkb$)0fq0vo3J8YVLHMJ(fC69DMj1{A=?k!(a^fg*K;GQ;BhNB8IYK@LC!T7 z_?yt5O2^#m2m_BfheD4>T5jg#Ywa98cc1Bv6NCC*9QG*!$y5*Wd&0NwI@_d9!_1jb z@Fr!|Og~12ai=+mkl|h%SJRVM6x*L3Fr0w(3DhTKR#yNszT`3GNwm%K(udG*8C?s%0naWu}c5nw?| z2BmsjO<;x+yeM$6`enH;FaGGlV;{J!!2XOWS^STjV=;WxD=~39I~br^7(6K4g-EN{ zl^=DR`k{yjW}>`c1C3ehhN8i6A*8m*flbp)3>g#`)7V9kFI<{Lb>M18zYZ|SBi zT{^E5xIs7UosEYTx(G4y2@yy?v8z{zA*v9XrtjeYURs^ScYT0(lqI|abDZ&5lLEuT zUx8DB!kEOD)&0p4u~nAD%y)C&4`ghA0(+at66agL2y!J6^7Y7QKm3Sau{uz+ z@Vsxs#O4kZpmnR$ck{5d0|^V;0$op?7cHnwUvQ1xrJnG~?7gk`x;5-3UBEcgO85|8 zvsuA_H#|U4ydj;5%p)>aS4?ej$xrQ#TK&)NwJ^V_#zd^K$p@@8lPHT2jLGpcHC-p> zHM39fR>#12&LDx19E{x-SlU>x2PaT;aKx&nq!z`u=N#Q+Ljmt^)tV01a4hRz z%arflOb)@ikQYdpj19rzK$(}q1&ZxmPzF_G|2$g^RRPxK6b6~#J-?-K0{x7jzKjhT zejUCrSrq^#?+1Jjp`+diYksqNQ;lyH^LH%NG+@PTAodIwHgZPf#>C%V=nfVLkh`jW zN(|M)A=ye&*j&+$S~_u`bQpaaHUI)JMrvS&3j+wo3OLV(I7r`u$1mgHJS8#<2_b2p zTI_M3Lm;U-z(>*<97U4{;S|4(i1X&Vnr{E>IiWoT!!FcM#~V3LaqWByM4wu&yt$|Z(_mZ;l^=4%~* z`6gR6TZ(E+nqzUGzl-mhc|4Dl&GM$w>D9qDWcVQ(_u<#42FHDQWZg0j1{Z8(3Hs+S zi7}N~%2=3aOh1R_pOQdW&frXbRS%mt z&iKFShtWal99}yLz)$q`lL8j>x(luy-?%74Gk-)dI<>(Z9OEMyekppF{_B!<9y$lO zXK~Z+XNSYM5lb5{W7hgNY=L|eQNotZ4ImlqAJIpCvr4cD)ZgL!)ii$%1?s)8cfHND zc;o~~76~=p)e++__gHAQ&MkBg4&sIe>Q%tc{vDQ}@6h)rd>jtyod|7Uv}3@}-P+5O zb;hQf-Nw8UYQhQ@2(+UbMr!zZ##M8|&Y&)rXicC>Ma*4E$) zc?0KKHaT$*0WT{@>0H}h0*{nXusMT{(`;kGzw)U2FbW%76u{XalEW-@>O;8cP#_zd z$F?zA;*B_FYVuQ*^ZiOg$K+9t+!&{kqjdpWjhzkL69TzxA6}!YKtD$xRK~XwbH8jJ6U3lX9^5ta3gRLvYsZZ>qQfMF!5UNn?YP8Krq-$#Gije zVky`NHC$9~L`|90QQpLdGR9&+|Ant#VGi90*>NEuQ-}d!Vpo;L`7;OmlUxCRM}cu0 zKW#Od(_&!z@eQ(G1c@k{gL#=z;;`y0w&W9RQl~9njrLD$WN_B*mI{%zCz*Ct84)@x zZEuSHjjV*dIBnVXeo9+TRKIwmM12{DM3=1(@Vr3jSZ8i*ZNNl@Q$XYmnp))*o9$w` zOkSGmF!$1JOX-|FAjN8{DNgoa0+SIWDlwKBpZM66VV^GWI`kF;YFC_rQ#Yg9S>I;RlH>1k znmziGsr20RxYU(aA**S|LYc}kgDE7h7!1YH>+L6Ulxk|tfK@;ne(h8PIc_2y01cem ziK=SEg0*}9HDTTIRML!$aIHKmAjDvZy33!9RHCynsO0 z5kE?kBN2~CO6sg>oR99FY29J}gKDgPlRxgWv!W%Bz0FwuvJ$0l(= z1hbiAE^ug0r3BtV)~3t(xsdqK*#i{$<__{P`} zp}C6eq}51|=8zX)N4^b@YCrv}eW|RPqzU}lRo-B%bo^*x0O%t!{6*5+(Fu8~Dt*!^ z;g@4~0hT#bg$n&bz3Vv~H2_*MljKb@J$@3?gORsW^_Gkks_lf6;7NUutGL5<#e%G2 zbH>cKoG|j>x?NO@bVkE=4+8H*E8rdJ2ry-twNfZW_<% z=r76aL5=Wa`w?2Z4nQocV9~?|xsF{{Qqc~cXD?UvdY>yC*GeN`@F%aK<5T5}!S_V;@r7*sY$Q;J6E9efenKy##kXSCU#9J9Xclw2^?_UW}LAstsz zQP=Or;d#0WXX0Lib|B{K+iIF_- z>QgwwpPlney3}HZRqRueVHpS#U_>bD1&M~nbTA96t2Ui%nj}}gp}o~=ADwr4=(Tedjk0G#=wKWXS_tb&BI)lkMU zb2H2#o6l;&A6oknm?woIGpA1V{A*G^R{_ICnYj{j?aCq#B_`J?oji19qGuieH3D5;(gO$;jJmCu~gM*nwsGXldQb zQ5Yh-_kaY~Z8>29TNXWZSX&|mh-7n$V#ZK-y{AlxqJ(D{H<`S#V)e|I31J!{1W({RKb=O24EjH)wqe9w`mGv>XKG+ z`JDJsHQUo^hHm=5c`afeNm9T*=4FORO~fM#Ds=LGf+q+@FpC49dqu8Q@%Kv@24M?{ z@uV(JZ#mD?M^_w^xZb>CrOjN0DD0%YfJ=r;f17n4jrf1p{19epy6vZRHaRCOk6_g} zkknre8IM1Sk~ENu0#yce*CE5I0+-yV5nR>{WuN_5;yno5Y-E>!{lbc`%|!f%nE#&*wv=zSdA_AK8=sN=Lub zIyS~+9(UfmEavZ+mEaC>**7uCd9FS=xX--Z6_lBq8M^89O#1Dkgole6lw?LD$jIqU;39OD?CE?}xS9L5WrM~edm z{Kf|B*g{8EZs{_q2j z3~1Dy6_f3pjS7$wL`GuOpvkdpY4}$Lpa4+D-*vK$chch{&1>c;{|+eoQ&?9gtUQl) zAnJ(489x=9suJ@PubQo~22tin0NfJdkZp5KU>^p$#&ZYdH65%h3qhQkclNjNO3SFw zDhb+(tVWlgA3W!uyX~LZ(tc+iD&?&3C|#Ni?_PM)o>#0jb!khv%PFE%WYqoBpB%{S z%)%wl@kl%jv8XYb8Ve=ke9H9BIijme->i<=$?lf1049NBR%NBrYU4uH`U7U}PHTlk zt!W#dc5?M^AQ)c*&m5KuFXB%qA+sMt+iR>P8{7V`18e;}*;8SDE>_?=>chs7HG64i zL){F7*lVHRp+75LTb3fI^(w=9(fD-e61ve$i@lh&H#_j2J?5qYSqrP<-vi5kjYJ|O zyUKdlyjmae8HP(=w`mnA34P*~tk*#B>((5QKLYJi)U0uy@&utHE8^tY<~@WK*%ii- zi#nZs*mVXa)CC&$TKa_Ig>^BP*%cT1baJf+6hqwS(PIqTJqYCJy99n@n$1)Uk&sY; zC@?p{Z3uyhGGuJ37B0>-%4!G_vD2>=W(+iym&VOw^Nf^GIUr}4D#kl=2eM8xj}@T_ zF2duX%?+orCn*MIwYfre2LZoQ9}08U^B2Z4=u{4Dbx> z0U3YhlLQZvWnk?yD|TOkIr8XpW8-2oWpkyzU-)T@rv04HsLM3%3)A=v)Z!R|!6~u~Twrv`g49Z=zH+-@6dia6SRCdZyHI8yOq~hfPZE z!7)FFxS1{qQgbYPmqEC+I9a-xE|1|fudw>Qrd#y;e5AlutNqz5(wO zzp$BB$=4n~|vN_0}xhM#J8SszhS92|Iu;q>$eRpHB9Y}qxMH>IgyuA^@jHABH zi-c(}HIc87%!4ve`NXH(w5#-r;o%-NlXxb48v<6jQ4JEDHZM}^yIi4l8i&Ysp;z6I z$^1qYHa$xhel7saM-**1n6YYx7@V1KFJyw`fakc0whII{U|=6#9R-Lh39bCo=DL-+iI92`9 zX)5=0^D~d+KjII4_>U|R>CyNfzh8}xRSEO6>;HVV$p+q5?w#K7Ld-oM?;MRmk%_+? zsLXKBMTFMpeIxp5)LIkh`ZXdV{zGs>!YB!sK{JFQ?=_AqCRuw75P!l={uTkDJvL|( zdQ-EFhE~CpSS!4Ec@_yC(<)!-5MM1|m43S#6>ugpjgYEFEYN7s)Ojpm@0UKT@n*5G zrmzW{fVeH2;BSD{r#WsSv6VwVjzwCG1y;S(F9^_dphoP z@@%!pTExZOSY(nuh&$t6<#Wqim;DAV_5i5VUzR1Acn5TcsFj-_tg>~$^pRD34-YLg zowE1_hplfpE3{{67}k;8w)N`0?PE9@;Z=|O4FLN(C2nyskIHN{V8^gW--aHD2e^@@ zwn5sGc*~ga6_IV`xH(x@bSxqp@zKELjAzV)(31Q<(Zr9&&(nQwB*d?{!x?irg*Zqp zL%xK}6l0k1{s{s@E-bLlU9N(N7?p^gxC(9>J`x#p$8ImV>7P@*UIDG;PoH2%b`--x z{1R*{Hj775LmTKu)K|T4J56+jrEIrOe<;P$@XTzEh1XJ|BUx|@QVDMpx0J7y^{Q<3 zO-|G>r-3!3D*(I##omg*B-M?cWrRU(ISaIte0~>o*5p4lcjupO!S+p@igl3jZa&Mv zLwT@Zpkb1L9W2uiQTw6sgbMLLL4E1-h_EEOIxP~OfTuc zZB-gFW>XvWN|yJ%Bh>T}|IdXV663!#s~U7IjB---1)k8&?(xn(KntTCfprD3&tqU( ztn^uwpK*H+(!VVku1RkLRAaB%)(7Y#ns3<-(dIsgU5UT8EZ>_XX4g-oFfwS??`}Ko z6RMA?ROagMYnZ3+VIPrzD~Cf<)4H0!cmUfMwI+B>>!3w6=Q0YRN>_O~CAhICKXOvjXZWJUjoaAkO8Xx+iN;gKX?6Xwb*`wjuF)FmLZP?FUGtW z8xbo-H%Vl4bc|S?6?89XFXI`6EM6xFF0d1QE4#JP90HB~pYl1{p5WoL5=Y<@?YvWyB`Kx^V;(&8FjVc?dI z1#$$Rp=;{gx3%(!S7%MqZ7_LpcWxW0w}whfa-Zj2{zjiZjsEgHjMGNqQGA1EIPf`9 zt+EOPs-KqAQSVtCU?Sqrhv@TF}x7{??S>QHy&b5j30D-OM7Oh2v!p2`QVo~hq&}Pgn zii$n~pAs^20Qe)+4r}N_E=UUE7i|(R&1Nhy3G8eW5|V&LEw4VA=Zv<>V5&{?}Aur<3T#yDUni+#)4< zXxQlzEfK9V&?m)5<}JqY^CZEz8#DShr^sAPlffhoE^GQFYjuSPl@nTu0OV5K;QzrN z&p*HDhMvX_1G~{?>bG(r80_M~KyI@N8ziyj^WHtN#?yPCufHMy9Fv$N1+;SanmfNH zuhsSDqRTzD&y^brMAo;B2kmp(mANLsg)DbH3%PnJD7`R} zNnXabJK`p?V|?Z83sP}Cyg+cWtf^m`k8!oOW~%Cx-|UIiS;oq}dG$4PV2iBj429Qs zB~%&^@?GVq#dYv9N(HPFf6{KbPs+Ez1wyIutWbV9gkcDSGKhi+zH>exi|DmN)&f>j zCv6hNOIclxR>I|^YW=QuMtlRCz-=v5EW`ICTm7{IVwHsRFg0@u$(I{M!Ze1;?E3!@ zHFa5Yvc~IAf3o49PN&zCPYOkm&bY{{M{8dPem>tg`CgxKe3~qMUAsbD!ePVpI6sRs zgAADrB}02**mlXxr`r&DrT@=1!L}(wml^4)mD?dP-)(N{8gg_2;53^_&Lh;mtv2kW z>Zp$v3cA2ppT-D6$bzWM4({+~AQ?L_h846gDj{)YVOVJD%V2!loIBF?`YKybeU-RX z^n6E9qWexX5cfI4eM)oK+Dh}c{TUyt{T*m=^3W7cpo2{veCHAYvC_4ylhKoc&` z!&4e3>>v|LL@Is}X`P-&Z+J>(V!B$8No%*vfMcxI|4({0mf23|dVOMp+1G38=lN|k9<6^iBOFf>G~zav6PD2DHeFNS z*YWe5-_BtG03ZNKL_t)nb~K0N{O%V%<1>s*YVDIBI?dj8=T5^$_5DB(zp$zM1(;gti$mMg8ppzMm=4UXYwFG;G> zqe+<;!)lygF1FQhlId0Ok@tm{7;iZ{r}X>aUJrvTe5uD@>0SYe^dW~}qbP@AY=m8b zicN#mOGrg4zu!l-Z5=lDvJuUvyAIT)%n>wWzV)%lQ+T8I-pdjRbX(M%%*j1px<;@J z$W7C6Oj?Zo+M<)#adE>rA0Qv?$6fu7f+c$p1S=2%E7Q>V#3;ye8L0-b9&Q+wC60ZL z*Gi;z6frGCb0zd+d@^Y*ROd-zK7I@jH8gOvf~%Y(6cEO>Brg3Fcy%n1ao2w=32o=V z2<*pgX`28K9Uhqu3!wy(Q4?4*z+$4iEe3WOLs(X=C5+6&Owu|qa8f)$0I`O3B)rXC zS-+qDNx^@J==rqZbKiDce>M=5&xt-?yW%-MYtezmMPYcDh(E-ie~3T-`_KaOb}dR& zK+2~d!!NJsvhgK}8F*#mt(w}i{_&Y1KTleC5Q=3h z|90~+;Ip@X{;$1u<6ia|`Ub}yGEsxup+9#c7ffszJLTIBu{$~IROFq{L>gg9Fsjp> z=Mi~8?2#%#{x&=v0a}Es#1V)GjM1Y=kRGj)=~(V4c1i1Dj@cEwop`K{T>SNwd0jwK z6-^^j?@?J7;ObgB8$$a4)FLd@@mWOTJ>EfzH1f6hQVC_&6uz>iBYr4{ZPKy>>~E{h zwF(F>cQ$rwJNLP(EsN@UyxAx;yRd&WqE8^U{${uv>%wkyVrH+?Rbt+BEOC6Ra36Sv>kI?@LeV%i3ejC?L zFKO~d;_l3#YS39?BL340Kb@s#aiFn&$!O5fZzFx4a{fH!A3PvaE=`>|Go-sc>gfsU zA3v9doE47?@1sL{Jz6~PQMLiHZy(Kd+8JA|>!Rxiz$c&9fV^0F^Na&pTjl*(4b@fo z5QxXV^DuJy1)W#ELDtM1f6d9Srxp;d+a%4Y=o!eNuld4nM(rEzpDRVh7;92M0*E4=LrT$+N9>HGnq;UaO!|?-6vFSPpmis z3y6O-9RXtI<8i5sT2oH1$JfS^BXePb6iYVc-w+7dIU4b>ZU z1dZX~prV}euBn`FTq%r5VGq2_bW3f&R?R93R_g-O_We<qy&x6N2r+ys>*a$bwd=QJw^XC-r4x|N~Z~7xAWqVkpeIFV%L;Y zH)WMh@&GVNFTACJLIj0Bz`*{xi~t-SwBz*YShzN8oIx>}JJ6DG1@S8efI&T+qN_4E zvn}L^&z!<|Q%D|cPPa2AilKeyRoN6;5jqJ(CFim_@;*TJEiY$uI0@~9HNh+zU)zFL zI<*};{=kd!bP@vuj0*^ z+n9!9ETD@7wR6J zl>s3MmaiIYmHrA`k7Z(G4TxLZ2xK+ej{D8eXtqjU25z+2Ci^-SrSsubDx>iaqI#KSca9T538AeOvn~GW92;Jf|ML{USsZm;hfxvN-anW8O=C4s2-&GH;Ih zgT^ZA_rC$srU06m>zo6AMsH^t-=D5kQTVQL0^Q=0G!`d?55Ysw|)b#Ugh)6v^^npJcv=8 z1J2c@BB4zc8-8gkW-89O;G~15ZpyYBqyR!LFRG1LQNGlgr0p{9tHOv^Kr@Vq0q;>f z01!sivo_8k#C-%$5LAMfRh~Knq+m__&MZ;}<*UN;NQQVvd3H=S28hLPz`iTKv7$_c zORhlKY~O1KNoQ0BHO)|n+c-Y^$?J5VsvRbx`cC)zTRxzpIz|11wO)YiZUtWh=$ zK@yYdg-wVN{B)wpCmlJZkz%l?5TRN7CzO;e;HW(0Dq zpBw|SAYXw>0D46%^OL)JReU#iHtk=p@FdnNil3rEkaIcg9=pq1{dfXDs_&sC<5Vi} zWcW+#6?xcHQ%}i~?|1&_x+D7hXOuV)YQeVvw$MCEz4(m8N%X z9E%LqzrhdamXt_L;-vnW8?d|xMdPTBdt)T{en>OV{qR_A+6C*o2B-E!8|DkY! z0Iu@mK-V-RZqcHEu!0H|SU8YO%e<^{thOaUdNs;E0V2q0>i#6TehqL?#Sx0 zq3Y~xB8YIqykSonU$$sqi5Z{y&W#-V8e;BWI2bGS!v%~f^h}YxJy@z!u^non8is7u z?sujlNL|TrBV=dr{lw#6f{F-UXCyvK!d`ce<-Ao=-An^Ee>GZ6^KQbijNa+ochGs; zbB!jIK_Q0P5;$Qb3gcg=3Xmtq-7CLm!R}K;@8%@PNnwmo!Y>%v>Oj=KSkI&!wK1!X zY}F6l!P%>*9SWcU$o!16mGZA7#BCi5?q7alvj)$e4<;0wSiX7Thd z@O6+mW3AnbaXDd^iI_im8ZfdLD{Sj=QzT}c@@odt(BaYahsG!W+(pAzjgh{%q29p) zX0;OJp*kQ+lDQfe>`ongVCw)aVz@eO{YhE0=fCGkg7Gqj&!%uvVG;)Q8_j{F%q><4 z_9tJSH_=#Ma|=JR9h$R5>T`fhbN>NJN7}Pzx^dWDHIKH?z`}zx%+?n$wA}Ug}a-A)9BmPB5>&2Fh_Vg?`&v8X0*Sfe8FIEB=$Dv2ROF9qIx{0Oy*QuE_}9It2rZCx7r%S8Rl z1x2L-2^*dZqih%-woOT7^eL~ZY%~|DD^a4lm@k97^vRpGGr_buN+wwO8ax>jzbRGn zf#ZpTsaLJG4fFgh9?^>3#R3Dv3g~}CL`~ko-{RfT5#`fp3d1}v4;|@$a$&;$5hX%H zmF>0vHi-Dz1l>~>yFQ)ud(6Hu7ppIl{9l5NK$g2=b=@`EZhrh*UKsK63#7_$LHu@c zAZ_Ny!^LeSS6bn%>PyOGT`jlZD(aym>lAEg_79OQe&3Y?s|a-0-EzSvwn8q2LL ze1-T4g3V%DI8UJU>lV8S<#4!n+y_Bl#D_!O#OeCpx@#38LDpfd%&eO$c2gL^>usdo zH1{5aB|O02h`~mkNn#pa$K^+(w7S|4Fuu$^y1{xx0&}! zU5|~;-T=>Cg82_IfmaK%d_REsYJ>|lKmgCaUI~WyY*VWP;LKIN0`Z!Od|HHc9RzDT zfvw(Uy#YI&*>G zlP4aw>DAc$Uu0qjHe`n;oC|E^^DgVON!D%njxb;G6-`=!6&*t~Ecobtqn@z+mi`D1 zN+hrxqnQ|m_Rh|g1{%eQ8`#$M{Yk|T;(Md>9rep0jzoi^)aUP)bgCqCFpnaMnGb3yc!nh55wh{~)r#Nb;mAz_E#s6PQiF-Uqmg1>?~4 zLAn1a>ewdAAxWZ<9f6cIhl|dqLeF{d3j{J)HbH`S2Ii~JY9K)5FAtf`)14l_es5;X z`Y5;y%4UT8bVz>)4TPkP0RCteih6TSBz2^nbmI~y&N7rv6@as9C^UJDDf}{BMr6v< zVRNI-E@8+m+1emFo`4z+l1G{WKS>Jzc^-dmL%;f>!A=r#Vs3)T$zC7#M)W*R9Wn+9 z#^!+wTo+8Op;hYYi-^j#~gAJ>DaLsEQr5Z<%jIHB!m$Regm2* z==8LO?4vYZGAD&1Ww+#sM$WsuU5FR=Bds^TY+$i2!Q(q%RR`m=OY6l079Ufc0uA@(I1_(EJ5$_zdLF2&w z)b*JSd!jrQ}Qtrun+Ij7E+;C9KroP%vpw zgpovFsrN^RipRE&rV;1qW;iy3XAz+ELLDAD9vp@QEKT{xxEm;2^(t!D)X_G;gL2L4 zp}P8AbyZIpri8|n|A&bGbdj1KG@CrJnNUpjGGuqKBXgbMqPY+-7m^Xf1^Zyn zGqV!PWkX9{m%kCuhx(9_j8NJ6Q+jPDCxwY`T;Va;XPkEPD!Ej2QL$EaU6cvW$-KFi z$U(bO)pI`1-=e=9rz*+vpEzp{}jJN~1GTS4` zB`6|j)WpKe-tJdQ^h!k|_jD_}gDcQ+*r}~YX|KV112=4J5$;i_eBxx`X;bCtjz_qv zRHcP9rxw+PXbP#FKG!`dFBcRS2Z*9Q2-r^g9N9EmQQh6+oJ}ePp4509Wd}|DbgvDt z3r5rh6ug1on;CfU-nrg)YomTe@vI)$UF54v?mirYJvv{SO94*;>rD!MXhckXk-*?+ zOzU&@5-qW?!=lHj$7aPA+w$mq5jCf6Ee?Fv;y~;k49rz{cmW(^p8PC240dgk_9Tne zujiU#t{%<9M&s#;czWginrqQRrk!~ntNJ8gWP;Bt32fQMmb>}Fg1#@#?@qD#BY%v( zBH&QkI*1a9q1CIL%O+b`7uRi=u;ZhAZ3jSlvtR8-xDc?YfDq-p z4%oXOgrS5x=9=h***K;e74D%f!V}@;6dq4j#b@h8$+W%1`yeJHFS3W*Zeyk}{omA+ zlz$*gSQq6u4P1d76rdoZo3;Xl>so)3;k5~y;YB15LG_rJ%{L2(>{tFWembW4@vNq= zt`}!t#0v)E&m5MZnO~w*!@GKS@eyYz(Irj6`g)&kMg{~+J zW;2hmzlEK<=sxHC9AHX2VyJGP&cQasx)j^jjOXCm6eH7I43K-*sP>itMEf>kw0-S- z*sn6k0VZ>t*@~zukdQnLwok&cE2j)>OdkmXww9tn%G z%P~}-RKfapNvwtrk`BfAe&+9!cSEC)3&~NUd5Z!G{FML8WTVhF!&-a&G^SDJScx08 z3Kr5DrLU}F`WaV|tQgQ4k>vNh(8~n^qBUzaL7#ed*fi4>UDE}-pl>gj6}AWSePk-( z%1%II##F7yNfiV>-eA9sh5b_m1;iUqH{%lWrh4Jb&H*`4SIyz=?8h|5A^{UoYd>q^ zDFqQaK0nd?g))SJjSCBv}8sK>|;NtGj^ z57yr8R!mOyK;XmPu8AzJ|0$kMY|3}xJCO_#jE$+nakTtYY1csYYFh-!i%K#!FU5dS zN}jyL;rfAU1rCJsg@8NF@vWE8q#LYx6A5A`@Gx&X8H(-7?OM5mUw_aI3 z+iHDbCb-L}jR+=~eY2#0Lo?N~O5dr*uGB{w<4qvash9^}DXYAbnY})g5-k)kZVC%* zu$uGJh)Hcm%ckz@XMw-bKKdit%BspzX1+svaRA8wGQE=T2)ocXDGntP;6DY^Njpqs zdQZc61~JViOhQbRx%tbmCE@w7*5HiBNkB*kx;Gqbu{r?h%ck1TnBdJ=gc<^J(45XL z1#0GR&rcQO2JXXf&=w_H2uY?l)Yrnqbt@c8#=YjbTV&5J+74!NT-Rl^!xqh!&c1#u z_Di3%tN4Wl$dGL4K zu+j=QhiSNFS<(({_zE(k@Hd77@f%*uw-?IoFdMh4d#Iib#!mpy+_5pl-?*^0!|G=x z1|CNE>3r=;hm1lnf1XYM1|t6rNZ-~Eysl>Jv|u=AH7NtRI{UmI9)8Hoq5Qv z#V9rDY{-W`J?u{lPmWQ9^W=71D~nR2c-s#>P;#rDReJ1bjw<-nGB~!i#vEHV^*Q?Fgi3?<#YF7@mGTj@Ko~<58FK$G zS^(M2mm`pKr1~)_p8scB_Z>FNN)}Kg&16#LN`!2X!T@u(pz&=u#TyGI$I|?fvMp5} z*rGd2DAW3hEbOH(Kb<7w6yLR~bKQ?6i~nnV*w=ZeE_+bTvCbjr-%Tt>m8iYff^nK1 zrCSSDV_t@GJVVV39c;lL#TKYOy$r%%g-zdYl+mHo`lez)xHnu*E2bRjPP91DUWxd| zdM6EusQQY5N<8d`(9Gi$GMS3_lTo-7iVA`?zzxAbfSrL>b7fMWUa95`T8?rywbr*a z$7O8t7@)EVjG2fkk=yAH3Yd|MblJd+99f8eoNngjgHK3&YiF|LLGo)j3X_rn###bW zI!^cyfBu)AJHbEwZ+IY?z@XsXGPByLtu0(+-G$ShPxbKU&$C!z@7j*!0gfe0OdcB1 z(Gt56@GjiV(}lz)*i1uIwI(Hn$O|U%p*wFYQD3XWk+IidRO7yv>`eOFv{&fM%o3MU zo6%m$#mes@koZb20(9Q0R7gkmN!oPds3l$!^9|uYR+50PU`~Ax0{vIxX?fTrys!CT zs83!BOP_TukRX1Q&5e_Bl%(9&+*5{Y!xYp*L{8~&;7C$?sHuGyZt!9rGjAcIlKZia zoN!X#G89f*7=_``E7Y~~o^56?ts7yu#>wdE8CaPw`~IL<(5dz8WQ4COH^A?eGA*ei z{#Cu90CA8+p`R;{PTUD(t;p%)2?Q-|Iw9jvR}r+W&^X~|+p6gAs_0G3B*o!F!ydPl zhS%q4D!&hL2nXbB7@IgVXT+{2#Mb*B`~pGy5r6IZHCpD4s*Ev04fH{tt=Hsm3L?{GiqI7`g5@`z)Z}4h(7;I&r=Mi(u>EVxkWI@VM^0> zxq82GAsCA#jyZ6=D6nX{4cy&%7Qi5V01I>lO`Z?OPaXS+1%qfU7KM_9Na56um`f_8 z&jN7>(zW(KY5bb0qGTtI)!1|kJU(T(nB0RJqe2GgAhMaSEZ3Ldmw9mGW5sS~x%Ct1 z$hXh>aktpGsV6^$Szx2?@gJCqaj^h*l`^#v)AgB<=lfKJ%Y0^^x@T#R8QxjU-rNT>W~! zV$g`_=Xr%;()fqy^Uw3y#Tf`4^lcg?znRur**skRzFvTgR-^;GGAbGJOe2-EFl;Bk zHFdS*{EBcT)LZ)x=sDVGzG~xIR_2l5rLtI(jp6L(>HKE%XUoAQhs$r{GgZU!!+RcQ zGDz$9%F{#SuW-FvzyA&f4ZSZjd0w7`ZJodqq`~EEhGd3i=&4-FJ8N2YDK+}ZKHFb{ z-Ms#WZs)WrQt(9}zL|A53C0_d9^Q>nH-gjKekXGFeVnZ;YxA_3Ta=AS)nD)Zp#3W{ zZ~;hv?(u(fMm%6+>36=sVs^6KzV>$sY)@1~BKQby-YEEOyXR%|&ern~3|~_a$1WL^s}W7tQ$iSpO*d2Xw=)!OgjtbYG&gNo zQSL*9M^H7`cPOiLGZ1=7!yc^f$u>KutMhN-{;aY_G#(upPaDW)@!q$FsGHZ>e3AA! zvUN=21xNvd2vkPFjIAF88g5?Fq&xQ>0O_JGKQL%4h<+umL2}4s^E-g7K#lrSQ||Pw z^>8kII+aJq&}H|pcpUqX7oIX_NT3gjT2{%TACDzVS|T^Ag>PA{W5M~hbw*CG?n~tm zs>{fZ1hC^BDds_cq5euc5!SX0Amip)EH~e^3#qMw=_?Z&J7F<6I^^WA{Tmwp5-cV8 z9Z+%EZ}%0`Xy&N)Y4Q+Bp&Jz)1jvVfDdV#}+3Yiw&K=x<+jX?(Y1{6hGdUFUzi0H2`=GO!dl4sRs+&@p(N$lNbj+Z zo0#ByGGd8<){JZ7if7}MY;}W~uw_g^5{ue5kc=c*UPEfp~$y=2PQf2E9Y8dl=Fg zT-J+Y+6hlnuS_7fl=O)}R;hhUq8^nDoOyD_gzv85x{dsOz^}wgLgWJ>4nCuhGz(FE zX}QZpY0&MaEgQ-eB59pR80^j}3lX-Hal0I2H#?A{DR4LQt@_Wwt z#&{Lfds#{cazG7(O)f@2Wp>wDr?uIozi|!IW5d8Gf95}Kk)>@a0;Rb~&}b^b3THHwC%~fuTywpF>rq}U*8!Mm?mIBb)JC)~15V4Ab;2b>RN*16 z1G}BHV0DyPwBiGpECU!!bJ*I+lo|D{!To`7av>^}-+d8@%GQ9M1YfiQJTYwVLvy`_(OT3YvyuKT0NPh6q=>i- zsZR+5S0aoBvcIg0w!BXs*_TJmi_f(T!x*wU6Xy$EDk+K9fGujdd@XgPjZFY;yP~c~ zaugNxQj=tn8uEFq^aAz+Z!WCIwf+eFw6@G$_y4Hn*&#xVP;x$d>e2 z)k;yA_O}b_PEXwzZqA`HeU%G^w0vRRELgu-gSrH(h#4H-jnkG4(|UDs<|XCGP#~&Q zsr0|(8P1^4ogsqZ;l3~{&R6QJ4?BfM2M<^74Bg#0#N?}nRRdliP+4=^3U0guv9!Gx z!uKjb_8H1#CGj}PCOY2Oqv;b)wTKhN-q4*nMZpnPb{+2W#la=JlKB8}K#sqgP7=Yy zj8C+)_xMe_5I4SfwhxG9BzHbU?R=jRi>uOJ8S_IBb4iDUX1vD9m`CF4BCY+h8#DWE=B<@sH1d5@)1E6EGJ3bpu1)Gd`3x=A)%SuHqpQMoU=O^VY&7=zNaldk_rG9l=~10gVq|T3-g^4-qvc9*^a&)dPw2 z36R-@KJK}8o#Z^2Sl}fh{sE&L49>KEBLR_4U`$Zco_)QT7ONyn%F4C2Nl@jP{`vSw z#bMdRfzgh277^w(#8`jwOSBzO!73LAz4ZtHaF zbwxJp$9ue*GYaP;6+hH|*7;lrXtME-K*i78W1>(0tRDASvgVG}!C;(5@lM4M4k5o< zP$0%ij%y>D--9)(tf~+#`wGEXiwr+l_x~NkMnm>(( zYs`HW*&F*~aTINfol;TGfUYBwX%_|#RS7(@DNIt@ADe)3Ouq)Ft)*^zUi3jeAkyz` zxv(g&I$dd$=`3hJ6<}yorV)FZsGA;RfM41k$VSAM3`z_W@02SF6(36_nx_`J?V_X z>YGkLb==9eXe8w%9RA(3+KaeqQ>24KFye2~j_vim{O4HO8eh_tTYzsGT!3F7fIVGh zFo|McUB5L66bQv)z*iy@&D7SWh(x3l!6;Z@!U?0@x(rBl1}9PAje*twL6SDv&F~L7 zg8->TJ@&zIyh)qjipAK?OD9eY*F}zV)jV^OzXnI$T-vykY1F=nCjyB6g7Ly!o8(4~ zK0@Qx0t@#i3V!Hsq93N#6DywYb#ttLmhYnt=p3_@KEhEknkpJWJWbAjXo4F-XsKCA=*U?*Und zv(^aTdoCqriw)!ddXDxLWJb1(uDpRtuY)=x&$(ixFsU8c$ns`5Yl-_TZ@Noi*POYn z3Bl_7LQz~a37OH@A}YTPg|8R@4~d+nh$q*RwY{QxbeY#R8-zkftat90Wlb0D9l1;* zY?l~Rg(nax&3=J#LmL-j-1|;gTx(+!+cpV~ce!iEqN^1sLeS!!YDYAIKHT7(%<052 zlCVNiO<3VI^ipF(Kdi0DI1AB8j3vT(ym z)5@s*N=p;$yI8lGigHJ!VM+v=h|>*n!EpB+ZczhaUOUzLMUj{r_Y}cs#DByhfoz|R z&aTJRgd(Xo??`n%96M3l$dQd-v7p8D^xvXuM04p|EN;z3g5m8I?j4-gx4Z}F>~T`P z5-LRXU>h+#9<#A+LC+?Ij}EhJET-5p(Em>GfguW9(IwV4JH-?yDJnD(bPi+11E|c= zpx*1T$%FDE|9s5KX#=jTV3eQ{#ruz?t+rSvOF8<30IbL)OZjY@>~Svtz2HP-(G1*s zo!z9%V#=Dt>+t)w&xU)yMgb<|jtf(SN6BUu7z1i+%ACHg4d?ZUqmjK3m?0fv*oFsD z%jgRRJAwsBjn*D{{V9SN7v6&)mPH)DGR?dCZUZ`Xhp+!-j5iRo3ljgn?RVgsSmg|06OzwetW=fbViy+=1rv}BTu~qJ1^70o~{3Gh8 z*LL&j?cl_^XDRRF<(IrP^PANvgZJC>&-MEoik?yLU=~+jf91y#ESbTQ_Q&Opj;|f{ zsw`z6yph++Q=4xHoVp7TihTnR8(&1fC~W(Cw)z&jq7YXdQaAK+s-8EGMhN%u?K%L) z=nr3FN)X1}7rtUJ5bP(|lbom)8b>1$B%46M#ya!jMndJ0k@Rfft*M=j3F5Re2u4C- zKfKdhvRq{~o;j!(3vrJZv<+srl=k&QXLDttz!3}zlC~TfpV5nm_j?Z zoHi$dTzZHfY<^w^`Lp^d7gZImXb~!8z{nKUiDVVwlTF4!WGzLx%1l2*pFiZsd-T7p zFBha^JL~W`c>>38 z8H1N}ABVOf+g$3V8UmzyC-A26#y4G|Ml$v&EqY_Y0M6`s#6$BAaajwE59RNpn z5H9e`EA4jO4F-uD4obll!$S#jgk2Tbokq`71OG$RA2VnE)9>)p$`6Dl*WvI&5(hn6ojBbU~h1oViE{2A}*P{``scyOCHN_-HiJ zpV(W0YRtbAG+$f4o!V_!T+TikYHv#hLt>Zegsh3rn zh>kKEUw|E+s$&Px_?=Uhdeq!0Na8ZNUD7{!>R{Jb=itv#==N43J1qFgm1yCjd;=%w zOJp-^I4@x{H7Y|Cp-_5OPT*tKzMVRU(1Jh7SQUbJBS(@iK-3vve|oQEg?i25-!C}4 z2bn#3j5jp0Y&5tQc67J2_7Zyc-h9~&JV8*k3LsD;^0O8?`lh)?9x8~Dunu#aNNA=t z#z0|<>wihP2-Z$u1oPF^alA0+_=xJ;x-z84p+ccYVHIoOqwS&!NSP>luH5QM>~3?n zwIXeKjY0LR(FdpjgxYwI0#-LiCCUyBK|xJ1w=%x{iiQn{FHlC2>OKp;B!jnl{kDAo7ct#(x80{O zc*+3bf5rN@`!4YT*=~o7Ay;ng>cr>DDf1czXW0PnI0J31TFZJ6lne!sU-sw3%-3Vp0OcJorwUNKm2X?nle#xC~@wY-8h&d&76r4sn=J@89%Q#w4d>GgW0RJ zVw)T+XFYOvyU=uk2VP+-w&v7feH{_a;$5Vj^SU&6Pyi`O9GMb-K3zad$yN{e(UG}% zpIs!_@jH&e3X6P9J?-5=CerMIm^kgH4+KB`=}EuEEYF|(_d_9Vm>>j-8cxicNKtWl z(LP{hd+njSzG5=!jByN*`K`a-g_%hy~-DT2u&z#dPBAkHd-Rzn(HQZCHJ&p=j><+?$Lt1OjM6{o> z`0MltC&po`HGQF|?mA49X8?io8@~X>g)8E3vtlyA@#Dh}UoSYdm!t*8yJ$e?d;ptZ zTawvj;HKZPTJ03A(cP_gB%ij4UG_LVoa9O|mK~fg5KO+H71C39UMM?xjY>WH^w>)l}|o2l66*T`}q%1`{{T4WYU8|TRmoreBkI@ ze}Ixn35djkn@DcwXAtRm-QeeMnCk(LPCJ3}z=4|knz#t5kFyP8G+-$>!VqWFD!Z6<(GtFZV}vEpMZBZn-EoU*Wc(5YH9>wtIR-j+>yh5X68fO9h`-2|IE zc_CJaFs3rxQN?%FHGi6axgY%a2^ zD9-}KYV@{@T z;YKeG(PvXJwaY+`qA1ME4TgAW<^)oM5ft9b)~b<$7K!xjSmKDT9+KleM-~Lr|6*eC z3V8k>$GmCN(uH!*WW|&9niL`XqJh5xd4h3(=*y6-Ik2dp=g`620h7;qO?4FJixD)J zPQIQ2;Zp<==EPXLRo4xqRW>>jv^~|VWPu>N>m!-aqs`+wud$Lh22^EclZR(}GgGb1MjU)Z}zFxHAu9T$;;X+mP zO;SdMDZi1d^tNCF)lXj>Xyl(7$buvWdXlh^1&xChbX^MHs>A2`?A*`K-+l8!L(p|L ztPEqW}y2dF7;sQ1ktJfMC3G@DZO$l-c#c z-J|9dr_#Zl53p&)ctt|k+9=qV3&`S0mI6l*wl-N*n7S-|3HGi`% zT3EF9gh5$UFXHbFByy5pqDV<UI;sqSdLTz_mp4*kJNzmjBuU)ZmNnqH(4Hf&n-Xp-&wrQ-A6B3?W?8~;sY zMe%s#a5pP@FS-9+!-jf#x#_>{GD$v%?cHRW^+>IrCb|gj6jiqY=Kk3{rQ2*E0 zSUkh0>j81aZ*=Bv=GbhY!_Y7^&BZdT3lc}QwsL*1bB2^OdZdq?gl*6pn6p_`m_8_< zSZIp{OE;2@zRtENZI$8M0Y64>BP&%VnM)0VsB*&|6RmeSWDo+Lb=Vh>>qEd3BO0%Q z!3{61&94G5eT4F028T58<8fBUN6m!(JVrR4vc`6k8H4ZhA^v$5If#k4FB;5*{gq?& z99OU4>P+fuDIGeHct{vSi7(Hot483%x~`Z`FYZ!dVU=3|Sl&TZOy9Bff# zz9Fg$=z>f2QQY@eOQnK2$I0NxmB{IH1S|giPQ(yX(KVvF$gkbPCSEwAQ|09X|Eq2` z<@@(i9JNScqCpJZN4tnvvC z>vA{b4RfuGyq}zKgD{AoE?^r%DrjW-o)TBstoug2*%xibC55VS^HrD_5AS zzp1sKghq}z;<2<3uF4xYrf>72MtTinn3OH=QH9;Lj2Uqp(tqSVAzN>pNdC~=pFk!+ zV?)D+$37pUh{u6zElM=CaUzr2F9Cg>L$af2Jc|S4wF1@E4aa^|jE!esq!7+>SqVJx zWAA{!`J415Em)xdXEyd7SRrn#(vCG52LyF9v%c*)5+&P$8`v7b_n=7pF0F0sAyxBY z;RiO|;c6!`j_YC9o8hf24&-g^Opd#WaPI}oVZA#&SzPCJ7R3`fY>uME^aGRvY+4se z52@4p6$?+qDV*<^V}*Z(8G$#QXlFQ^AJJoc!1mJB z;1IA? z^z7O`f`5T)i`E+t?)uNMW~3-iE2-x!Mjw9uBKqrZqCZS4iS6}5alKWh2r(0W5~G)! zW^?09k%K%B9Ax7hTx-d-UY?Il)4vm~HjK|}DUGO&r%%M>SDonR{1FC4{?RI*D1^R` zX*Nx4Bh8A{N1d|s;A3nYf@~DZC*3q0uN7znIJC;c?S@L6-vI<*-uuf*;Q#00E>Vbz zSCrB>YK&t^n5bK~tE*o`I)EVu;h&9A6mnZ?0BR7(O8Z=Uc`AUfW)`l_hI> z-wt5XP8!ovUyIaaQp@gY0Xi=xwyA1S9Y9@?R(^#>S{>`U3dyQ%-L#mJI+Rr9!QraTv!fEF4w5Zr3IQOeX`jcBUW-uEFweT26WY*M zgB(l%GNo!4Za#_x|&izSODt4)48 zj^R|MO_V{w5F+3O$zk%?`I!ZBaiBI9#}BNsJpgT`-8a*4)u|ZbEX4kzuK%|~9E*gI zh2j-7(8Jm}61Lop%fB#ow;j(K8KF9#HP4HLj``s@hH+)AnZm0WWw2{2;3KpKd1ZWY zLUR_TeQBUeP0sK-No<=(GSiVtneYK*5lyqB4F?Drdb?b|L1EDyK)OfxHwe6JRzh&P zegf)6?zTey7Fv&OFzSpOn?z^z$l$5OOIiEMb-=rwqCxj6Htg^T0s|mFVR4aLb4E3X z9zg%F3*1`M>6fM|e-X9B1i0d2Pymw+G>A=KIs!{$EQAC`7{%sEiInlYgnRQYt-6b|1Wj#wq-f4Dhh%lverKR|NnYMRb`ld z(2cv0J#+PLsv@T?OMnmpB+F8}*Gt96v2rCKHgM!qBnML_qG5Wu&acGCSOn^^++|g4 z>cAHOegn{NRxs21w3f4~aqu0aSHEp%71Y7vKHSfYSyH+$yB`w*G=B+@s7+XmQMnj= z_gr(3&uMU*fvinNHUhbfY&;tb-%AIv@koqIUS!ejW#-b0lYSO}nvGZ|d#qz-V+S>+ zeXZ3^PXt^UQ3%%+L1LNbFYEIF#kX5={=0zQM! z;-990Cwk^oJ+@ptN3gZB5Qvy@?dF&e%9*fB@0XFi)Al(6E`NG^>H^hBbz+urm@_FNZtTD9|;DRqeY?~4a zey73IIC&eYXDsrisr!!pxn)JPY-(ej(i+49fOz9BDAL^~K(6n$Vc|NjG^|Dhyyi6J z=oQZp^8T#S0TmHuLi+Xi3S|GOK`*E~nJ9Nnl2UJF|CzF#%l)K7<=a%!khy4kM`!e- z5SAc_P+)4sQ6>zek$g{K7|b#GHBL7mX2Zy-m!jMr4as7!Rbrzx$Hu=qc2?($sw>TU zzbBU83jwUPj*&;@c$&r&(R|Qm$FQO6#3zx-Cg)mP#`Z~iD&rW zF_v<3*da(?cviKH#7}b#^491$NpTsGN~#f`n%e;^CQKO&)1$nZ@7KAaifk%}Q0S*s z-kc{+mgJbjq*U;H7-{Z#q!a1^Vq{glKr66IzUp^s*7G@~GFSn%7zFUnF^%;erg7c| zFM@?_J+ur6KpgX23XTc}dQx)-4@nxSzZzkcA7whfkd`FpWt$ebIF|+j;8$0HR+e-* zt}iJK4_02+9((yUdbdCva@%f3K=cD`rNp;LKQ&O+mv}O>JW1xf!K*4ad@2H)835Ug z2mN;CNAbr`8(iwGjf3$*jBY&2ZySO-dl3k^bAEQ!k%Q=nBR4?^?LcPJ5dZ+)K=cFg zo1@S`I4s$GJY}o?05H#c^GfRxYihr|v`j`rN5?46d+rhAX|*8a&q1ZF)PTUjP{kHf z@#a!OA5+!cNQ(+>dIV1UmM8sky2E71Dd1RK7L*q7%ymIujjGCxzKv2r@6dGXKIPTepZbP--? z1DQ3B3q*EU>I_ds@uUJ?KHVPwMR=s|4+_odDOr)Zcm|>Pya#;zkr}s7_+f?o$kmq& zUyLp&TbLk7P06p~pP2e`T7NPh6H2(&Bp- zZzP22sZ92S4fhy6@zag+tp{Q_Du5 zMvOml#w~4`JX~Le{*+Zz9rZE?*u~{uqixeu4V=K%Obj^JTj=e8-I_%F7*1juZUc>i zF@4vE>6s#p(rtU+u597?h~x!CGWL1luGH$*)`BH$1iDuii4IIoVW<0tENGP8^Fu%} z@k$D9ZlAM$SEpaisT`Y*zYd#6h%k%;qV&+AI4S7n{^P?nPW0o%)Z{>lf8W6A@Kx+- zlM##Q$X*3UlDb0ry1PrPJxonbh<|93l6ExNgoTNCnClQ8+0Pu$Oc|ja?B1z}Q z=QUDz5-TtsOGx{C6XZN+=|AYHaJ5tYo&qN5_gIdtU`{D?YQxLlC8hDs3m6 zl90+NKKdSa2)Yg=pKIj1Ywg56d*m_wu6rKpVcfHt zX7R!|WnAco!hQkZZ!m8G4Bg0449l;b#-Lb}k@y75kW$Le$hzVQRRF<%D?bqRKoJ?O zgy)%*2C$@lS9;sr@0uI68X59U95+ESu`9mWDd}8UASC3rbg!w#Prw5+;;!Lxh|K89 z2kA=4-0@zc-A;y?*gv5K^yXh9~Zf)GAZZqqk{Y( zz7OIdlLA&(H?)1BAHwobdpx?Y7&NtVx=6pMxxYgvnG${q#?l(pXS!WuMx5d#tYft@ zRXRyt1aLRk4KvGJQuL&93n%1q<-vJVMkSzDx&-I}nYP2nytsM=%;nw4?G;r9{a9NASPg(Kl&ErfwOQEG? z4y9*qJ{DkF1~VGWs^B=*(B=rgB7C?BmG|BDa=-~r(@#B3u^K+okWra95CP!J5;CaM z-ZKF~_VO6C06hEwPa_WEGxFN3kjABz+i3t^*7acur#2p8(`9;c1JT_l3;N$A!zU`3 zWC5K+g^VzRbx!jB_QjlAgic(fdtbdQp5xfQOh-Z>%tU?<25f)mv-u~5X+GvBn@`efMc zah&KB(p?6D2yes9@y5{$HaJ z$vtN0Xv*lIyKwilCS|Hb4zfE%3yF__IKCsn$xm}+3zN>8;C}54B`QYJf=VS-XqR7< ziz<`|QN&yNfn&=aw7q%3;;>^@5L8bTD*;1-1OOMT87MIYZ1ZP{yscIx$<~o;WQfnr z#IP15;hbL|<}+PmtppHHbYNe#X57BacJvDCw1|L8pSzF`1AW5aK2IAzWNlL8&$fqs zV;Vd7$YfmoS^utj%}SY^G*~#cXO*@Zscfaf>{kyvFJqXPMz$Be{aCu|3+U(f1LT?z z01y^*w1x^m7iG9O+Fd`zE1A{JnDCql#KsicGXj|34YLP6PKKO1UQF~4qDK;w$iyAS z(u9G_4h7rHfc}JQOH@UwL0$C-VB%)B(IM##19S@iaHmVs`ZL^Xem)agWLDpf7%6z=JrakWH>n-&km-fGhK0RY>{i#JtBgTc)E6xs6L~(C zz)jN}yPa5qHBrze3sCW}>g2AHP@t{IJf3z@V|5{g6WNcctjBr`Q5-F*P@s{bZgLyT z3s2vJdT@>*2tYU%1OWH~y!yl>vbSXbs(l9*gsBPR@em%36EgVTJUh+M{&nOsj(S@| zH-I_b!Ao?Q|6O2|Amg#B!%nQFMCP?Z7>I8|xjo25x%*G0oeZ@xOF;dBsT4$O`S?o; zByYKR%AEo)PRmQ!@>P&l+fV;Iv>I*(FoTW#ncqR-tdwVuYH~yH7n)0K&+N^4DSFXA zvH@_0)XMltx85W1Y*IqaJoRY_aqs*X*<_xN`LkvVq76^>SeYevL;0pkpRRpExVcaucHqS>^Exv{5 zOsgRW_Dwh=Aw?_()>)sGD^SYuy~!w}d*KUC;wDjuTD!3}Z*m-8kU4HysrV9$4qj== zlXUKp(Gx~lKoXyBPu;I|z%Xrm#}SaTu2KwhoTunM$=q$d`Jw;J za_$47_d!7bxK|r$eZUJ4zkshUAo>BlpUUhSf>RRLUOlvJGPTsf&xwjyavz2dAfJ^U zHm)bQsEd_WWv9DRgH~Z(Q0k1q#cg02c%=0-LIK5SPRmVkJJ}}`BM}O!%VToZYcHC9 zu4h%U!p+<3z1(;VzE!K^3LdN_9kxaH!4UKEZ&`G#2<*6+C2dO_cDQZUz)^0?d3=b- zM!H;3F-r9fyfX7z9@SAmwLP5zK3?A`<)tZo8j_NgKIv;`x)$5i7>8GDFSoOTz-ziF z^eF~^e6ZqaNQ8O{Ru7iijn_qx--(8;fWZLTgqT6uG59p>P*&j&-%_r09k$6Q1%5;4 ztNCpg-m!RFI_@U`emQjOT%iF*-6B@}CAyXd!qv^F4$A%ZT!FcGu$8ZG_}3tooSd_? zhg-}PMe+pJ9>dWfu(^T>Yoi(m$5zLr*AQsNrz`FlFTgL>xjN*vryFGuR(%P}JF$?b z^pl01Uh3y-OGx8?-k261t`W(N4aJ*gU5=GxLBO(4o~CQNoHasKH$qD8gJw9NGb_*woGZsM762~p=O+-{%Iv>2dk7hA+YLkVP1sL{!w#OTMLE94! z_7BU}@}-~oPsl=Woxbjkff55`2UlrV#@&G6qzy)1WP%tJG{qDANgia~Uw7!a8|dlD zsA2RR@UbT3N|KsZE^7atNoV1T<+7*>yonZO(P8go*}0OUz?kXzf9~+Qa~k3CPCxSh>Ml zl+S%c!FeXF*|TVE*NBLXHeLYq1w_Bwgn_V-ehs&7In=Q#q{!;U2a0W`9S@s)j+bSpf6`wLv!=!3 zhl}q7flH^xXK+qej9-Oo{n-McN?Od2{?bEtZ1xPuYPlyoKXVUn<@=2Mehq;0_y z@=Mzq#&`w#;&#UeIhRp>CmnnGEBl;lAhBHh-fJ=&?H@HTil3H+?-#v_kh&(8-=H;d z?g;cuX{R9T^r=#8%z}Enb%N23=6>dC#)m~h^HxN>AvT-+gHZ^;8|EP1ti&Kc)x2^6R9G6^vvYo6}{ynX>*e*s+IYo!OVoHz#XnVO59>8A4@4B~p6 zJkeGuAYOpiU+oXqi6w>J9zV<`-O_@Uj{O#1;vKdh70<`PG1Hf+HZ%vNPsR)(nMZ|| zJv_4xRf2xr%q5e}5rCN$4(aQUj$&r2P_)%v%2s@yKUEXYCS?@;On>rN+=mFK3>g>d13`s8I^Sr(lwVRWUki?x{=75=^Y{ll^rp$(q zKN9Y2XqKDaMhTfUwWk=kwDh#5Q)tK2a4vigg85M6@n*xQ6kn{Wa=iWCwfYG{g6aJC z@AWe06H9c_NDPZU|6J4T$W{P+qM-_BxFs>G2f3*U=v=*hoes}6O^1rl%-^2Fmcz@( z(hz;!3h=dG{^3hAy_ zvOVTE+IRuL#g@|p%(t-6HfF;S$P*~N)3zcZFKUztxIfd1J7sLkIo58LS>x`gZ#pFv z4Zbb|@#;(0K~*EKy!8TWb0?ezo+%Q)-|uF&ffrU_u806IuUKEkRNnSh@>2x19;WXB zfcOn~eYdyvPEbEDCW`Ud$KBhlpQ57rkuTv~?o%?;M-D!J0nr6`t33)l+3mVQiJ#95 zS_G#`KdO+5n=KcDPAW!mowM7G1`C3RCHUl~2FmJ3zV z=<7*e36(u&B_r_=_Us?oK?NLV6>KO%HCDP&aDfW+qvf@kaz z4L%mm^~tUZrd?q46)WpY-VJQO5e|3{Z%04{%n9$@w=C_`Y=fN^q#?|p4fqb$LI5zr z|FzrhI4}K%6&#PQ`GFZuUWX4x2=8_i4|I{hY5R4*`aA||P5|Srt5d$pmE?@+JjS^~ zgwA-DV2yhT__ya--|aG(HrMHAxY`+sfUeP*H!_%`NjZp#c=7u%n_`)pxAnnQ?<12| zCq-;BO<=?u;O7U3ZXj51O&b}q14mbHHlWLwGPe6EQprG~ld$2<7HlcQZjDCI>L~Tn z`b)bpm6UnMK>%XH!elz}h@8>`dqIj*u{sTFqE+0W`UT*V0$+gFSNrXg1K#NaT-Qan zL4a}mrr5NwpGH7^1wcS6a9LTr5GH# zc~l5-bX2aZvrig^%w-MBOIleDGcKsm+D{0aU^_1VqY&G!dz zdahs={ZL?9SE0(|eXftR3D|IVgF|YPW0TxsPGZ0!AGzeUC>Xr$B*6wJCC|hYHU0mn zn-vXs&lXq9d%$9s$Oi`HNhHOQgS&zNAc~EP(Nm9b5O8fQs$)`-H=@I@{a~=%X6|Nf zdn1xJFUi1n-j5lO4aOdnB7VXA4iqj}$60OUJ$v=~mbAgw`qX)xg-tgA{Pc+fRfm!> z_4C**D9Va%dTV0W+c_(t1cWk1323Dg~D`Qy$6uVS@UmSq;B1_v)+l@YAa zaH;6-z)&Mo@iv6n=?^blA|Ik70JiM;S9|k4c_tW}s^Pt+zjU3Okw>@?0lt2L?wa-^m_f06rClTB6_6l!xPhTl2 zRoh0D)8sGPB3`b<{DLW5U*0COQs=}?vr-qp^##}U3jn{M*?6{EfW`zasd5g^DYh6wSaFJ<%vZdH5jR!_IOkmXeb2*k?#8kJ#3*U_D98I0=;ICO>V!*U zqdlj}b*SHQ?r=2icY*-BZuOjAq6eP_q1=+TjwoH8Uh`)AT0WWm)IV(UhmV8SI++-$ z&<`lDl624oo*U;AE}c^hGPGwsc0Q?O%#-`-vTAWtw*+^7NhcAmnmj8{9Yd}xlSb4#`chonJW zK%gF!;R?PPvDQ8GOoGNAHEztqTnRMLUEgqh{RN1>U>@5wj${)9Pd#Npz$cT|X9m!Y zYMg!V0>u9V@I^pB|G<6!)5<=IfRjqsP(p_%!Sz$1h?c^efR;S z0H^TgAw4a5@8i5zC}W^eVBVAw79_R75ta0e`^)y-IS7_%*&3UJLpVXu`YT_mJW`8> zNmyH$&X0!($c2KcVALr$c5-#X9iKs9t-lZEV?dLn>5xJvYTF*+_nH`(qZnppX`$k$ zz|Q3U8NvE$gd0Fck62w00pJ2c?8{ug<|hb5zx%*RRnpzJ!ILYCEVrsr$z-M5eyL5E zr2MG+Dbh7~BmI!&cgJ3>(VVcpXW$Nvkq@*>mNm<+H}V-fTa(RiN=}EHapBM#mcyyX z(qw{r>U7W{X9us@kD<#5Ux2^<2N3`2F60Jc`nm1jqu#@|%&2x5$o96iJxBZk{Q3`k zef~N57N>VIw8VJ|*3-If&l}10^R$tRb$ddsi#naGWhUToFBeabV zzwjIA{uk)}4Q7d?kULA$%E#+THvl4B_yu$!+&}*oxz)&C1V0+3N21F(tV$WF_n2yp z^d!a`ybn46aC{N`=;dY? zq|5uhVh@2%7$mDd`Q;rjawzIu9bpYdY$w{Lil@gLfjs!_x!l({!|=J|YM5-&-g4)g zJp8_uMD~PWg?cRHF|b)d;0^prrojgu9Czl{j?Z3b2`W9J9hJwVeYBa#=(co!D+fn0 zjIrt8OMm@p0pooW001BWNklR@u1qifAUhxU6!`uDAlZ<)y`&@n`MJ zav0;yIN!9eYU3k)Uuw6viLTW&QK zV;&aye&1Hc5d8E>)HYE7*B9XZyH6Czwh$4Jc2}CWW)xZ-v8|HWuTr4kx&mInvq3}F zGP!0@&bO|o8G2|Z=hC{oetE6C|`M;^^=%>oTeA_ju>OcG2P`q z!Oh{!`s@Z2dI;L=%GP3+1(&r?u~*|RLy+&(Oqa%9)+WStNP@dZW7%SXVz$P@+=Zrw z)b$rdMnvo!l^q=>!Wk2OBvOv+X`39$`wi}WY1up{pBV6%qRbtLNdir|8Y9%6b z@sLNmk@Y(^E<|X->(w`Y`)c21-|Fp9IIwvdbSv&QZ6d_V1KKSM9q~4A5XA69$F;GI z7+v3e?3k0}OFTt|7QGSREC+HW!?z@P2zx}>toq(?JLZ`{PGoq_V!g$0OS7qOisq1T z3Gd|H!%f0I5ywsB;%7}1d;xy_KS0<&J8(Bm$>=3~YyIT`F)gk#s4NTHU|=P?47fN% zb(sz@uwixf+@n2Q;pkpL5MuV+;1o_ED|_G+7#oNe;MZSWX92{-06;$`aM*nPX;OmN z9VT)gJUXM*;byj4pTn__&Gi=megWM7tj`m?21@U-cjbHSmvjAUzGAlBvrx~1gQtX= zZ}C{gU!cVuPl*0u-HqDU1Imx_pqX&ueXX7EAZsJG?I{=eEK+ITY$TK^xx!7KsOzG_ zy^bImv1TyjQ0DdFsO-2%wV9p}Nt!Lp-s#ylv=Jg&1>HB!CroB(`?CI|MpW@2Kq#42 zw($Q*PqNH8lKfKx2XAP;pHDZ$q#xkR%#ZUxaDA+1xh>_-R%q8>bB{2BJ@qC-FPp^r zZl#jQE_i@RDjxvw?|~5<8*!?cI+#^}Ub35i-SH zodtLq$B`4V_=ws{8vObXTwnhO#Qq>pgT@gd26VB@hMZ?29}qjK#6xFB_$gC^UfYb$ zYuTuzpQV8`cAB*UynDfHo z3{#ETV;3pp?a@L%Ygi=XcgG|qcQtJ81i@q+p$*7NJm!Xfbk?Rvo?GOden-%!h347| zs_0z#9Ui59R}FD}x3^39)s0g-yLyE$zE@b^Ad9JaZu#G^IGook&#Jn?8{km=kePg^n%fonM98TQW#aX-_S-&7c#V zOu_t)7uW1Tw!2zUVzKF(n4yIz_YspuYSumMs%?pj;Nqr80yOo!=)Qf8JFpedpZ@oSYdQ3m&94iKj0Nzasgofi7)8nq4 z0wpS_eTbLK$7!?HIkwVH{6LCV@oM6fS|8(Ei&?(jf|Vy}qMQ6Dpe>VM1D}RFK`;^1 zA#2=mvH+NKI`oe~561Y+6)(3j_$X6!hZ4hOHxNH{bTW_~? zOGn9XIR7Jpsmh>*=8KuxG=_(M6k3Cv$&@1!?V~{Ekbu_QM_fE%_^QM?W;Ikt~0?o3h+eF3#uE>?@N^M^mbnzhU3WzxH2bwH`rqcUQ4a;zh zrxh4;f1+PvZVR%BcF9O(ctb!$W)9NBsaf);kTae(LfXpk)UG&rU1jxrlLq_x>k<8j z`Ldb|vk^h`0Bb;$zwC*v+|lVcC(o&P9o=%x-y2ybWs{d(xxXyLKQ8_Zi}JZ;L^kev z3Sm)Ey>|oV3h?9LYrUiMRGDN~Fx2hb6`aXoag=sO0nP-0=fu6rrV(42VS9IgBY6?R zv5>jgbCW;Tow#|_7JTt#m$xn$!wia|CXD6FWA@m`%5O~k5RhQJ6$2PcQ`vX6f+GJ_ zECxn6g&>RH#3xhHBp-*7NVIB0*R@!eq&{vtmDOGcU^1GnIg$&BpD^I#x13%p zKPCs7X$g+obgToPT#Rsw95ln=ey=ClcHWbK;5PtVZIS@K0LY*FtN{YT)mG{B|1Mb9 zV|i^f!L|z>m#IEuE`Fcj5P_)wWJTqwCa*NNsB0idY-T7xZrZ}N{72lnsn>r1um3cx zeMOiij`MSrCv^WX5CQbn4%hVyu4t0LFYMx5lMd{tUbkG}Cn8d$FQ=F7y5xLj`hMy=- zRBh#wD7_hAS*sQple;MKU^WVabO7*uTZFf0`%2XgdQ@3v6xq(DrH==~Hh&SVNag`&svmieaA5=}Gj<)51Nhy7Tlz@`J^?Fz;GxgBPuo#XT5)8G z_WZh!G48KwAZRNI##`O1Els`H5nda9d=5MwcWH7W26a9&?fVWahAu2I;IhNvb4cpG zaTuJl$;V`8?PkoM+YrgeD_%S^^n>2=zgo9E?)*9;e#7~QXQ#+$$pbRKP zJ4SX)NArDO0&J56qk~E<@OU5R$G2~_hka<@ZeM`c*V^z6+p(v3eYfwr@f$AuQqu+; zvz0(RXgbu_$U#N1s?ytd9KW~sd@evZv50jqX!ry1^Y!%~xW4{stn|$+H3K+Ks%g2v z>Q|MqU7nNYj;`N*@;6&aP~~xM%!q1Y+~Jb|N-MAoc&pOJ6OS+US0U$#E8ycvtdJ6Q z__HzB2FD}X-C{aptv6O!LOdl%D%b4&rS7{OZPxPlcxx7$&HJWpWBmwGhtay0(w~GX z{-AZdK7_H3TU1q2b zt$+ZcJ~n?nz!|*mbO-?Yviv=)x6j@)ngPBT?6IKVOyFq1tM54g^X;ReVvDdg;0qW= zn7j0tbHJ2u8XfjKo*SK$*f?KAzX9=T8yB*iNC?EtXj>uhH3$0bXgKI{ZJ!zu-9Y`@ z@V4JU-=pN_G8vNyZ3#D@C?HOs-E;2Oml(7jCiZP=WfZizffzBov~Hc84TbmVi?oEK z3KEUaD0qIBOE@dluA4en0I$CQ#N>c=w{vnp(2E90wE5$HD0&{@Qt9aXw5EFm*Y#bT zB=CGh|Lyw`G(C4VEfmVC6vXkVsx&aowDnM>k6XG_z-xm|iDfET_glIj4@8zmBzgRfz-Z!kvEDd2L5D7g?Hu1_7w2NK+YAcaoQ9q!8qM1I;W=&Xj+N}O;9onSLbp@ zW!jMx$~sn)EN^pgie;2J#Kz%U9qk{k&{kxAgIz=jf$C3B&$5@6UFt_oNzs}T9uYTP}5*uOhS z_aNX|zk7QU0Ni$DoM^cYX-GV- z8BcT_jLOr|@quC-^5{-;1JF;yaeenUXf9lMU=Ue*X*bfZrWG)>WtnQerZI22G8MMB%o%+tmib)F;w5I>M)i{LL3R z4P6m3M&Hzndc_<)mU-L2z8wkr7b`Jj(RX#nuQf26b);2S)?W$O{p4}Ta_vG7fbC5C zu>t`i`$k3@wyWkH0MYB)u~@dx(rm14-P8w!iha8R?NgY)x{Aju!U(V4=KPbgr%de1 zXYHr^ZDwPB00`P70fwg?2fQdA=msQ!3%_B86b1HnCQx_TMKzi>K7dlO1Kfe>IR`&{ zf5cN%nl~LVtKp)~I@AX{+r*U7dWI3eA3kl}&z2wV>DJcU=~UI{#I=FP3dk|2UbaD) z#Sq*4RgRv4oo0E6UXQp;9CA?ajhY1B#<{9OT*7% z%W)Ut_c5)Y4|@Kzg3eA5AgtN%f&9o1sLbjX^~LZ_>7Y8J#qrZDE`|Tb;gZYGm>g&u zRuQz50G{Ayf+*FS?44S=sozv+2kiI3a7&(ZdT?E?fST|n-X=Jf~e}H`xOM{MO_tY4#UjWxP`+hs4 z>&d`t7ygKAx5pqzF>s?64y06H>u->+um1qxSD!B$D+b0!Z8ykQ7d%iC4EPl>aYmag zSf6BF09PA`<>rEXbxP|P$*>UoAjmvhx;X#!Ouw*I;OP`~yQFznw+~~=X5as6Qq+?J zf|<65#Q(~D2U6%OSqS@n1Yd3*D@f(r$0fDN@pifc_@ny`p!(exF9zef8fmeDu5XHS zoz*@z#vIXo$xp|@Cjo3Pe-wOeX~wBEy00Sn**>$n<+g>N*yr#U+Q&m1Jg8==-HP*G;eMqq&9;s_2fKTgIM}n~|0KR;)5!z|EX|GypP% z9Npz8D0;K8X#>&q`~Dg}wHWXM{Q3*VMrmg(_+DA)xfg}aOV%&>{D~fkib8(vnjZWB zU|$V!u^rs|>J3p6HUGoDu|V0d&giQp6T{9RK;PRyetIDJmzHqp0naL3$15M~63PNq zlG$e}Ik{v$S!T1e{MbM=tB8HIOVihQWdMqO2mb#Ay))1a1lW#0biD=#?JxOdpRBq1 z3L4MR_iO#3No2u%F3x>M+}Azag+SM6V8264hEUq89jGSPF}x3sgGo!ug6}7F z3)&}5$nfyz*`QrcdQNvUP&^XggZh`U*YGN9_kx}-b5e+kvXdIkUm*FtRu$##RGuAq z0LkEQr)}@pNo1a?L$$5`gs|*zCkHR{nSG0V5QZbActR$pb<(%blri=SE!9uA5Po97 z9bUVvc2Wh|6cFl*OjtEz#tpT6;*=+Tv1@=eWE?98nxJph8Z0QjSy}<6%#l-5ta|7i zTi41uYQTdSTGy_^7$bi7fD+awS6pseryHe_bL()#_+4`sEHSB#s4Pe5I4XZA-M@nl zzYo%H89+3m-hrQo;%~x3K~E$}GZ#9>krrCvmX3X;42k0ibB=n+zCqsw5bZk<&D@J$ z6lq%3h*aK)J>8*D%E_NaScMxI>NeoJ%Vd60%X4*hLr>2(Kxvg_1RvVrKfh{j!@!LtTUJ*PojAN!;~ zWQ;_y3$ruFDwh>FL&j|*Ks-_MnI1vm-FP{89?sY3ZnafAtrVVc^MOc$xlZYAgErR! z{y3c3?)f(>QXCAq69OCBkDADfO=3h(Ec!7Rz5F9o8en*1;Ulb^A;>s-#7YoM<+#XU z=1dp)3&~WPR1;uV2513_mOB=q2U9O*!#ZrKRNK%8dWU?Y8oL!;9=F*?8Zd4P+D z^}p*^A4qLJ&v4^gPwNx;6M#DzCng~o0n+LG(hC?7Xr4oQ1Ms~EM6k5_!$db%tLRqF zj6VcrMzshK+G+wsWXs(HRO8=N2Ty|Bn71&m*syW@)o7+_s?z6hSp{R`E6k{#c>M(c zcryAV1UR+1*m6+qD{rAom?8~de4I?h-~;}Lz2Rf=We@8Z>eN!h1_0a@ zc)q28=QW(x>Awghrx`h?P2{V1*2a96_8U zwfIu9RcSlU%`;`hFF?Sw3p3s?gFlz!~3hhdmA*Gm&p?TM7CnB~KxpkfuQ0 zbySu21OF`sEtIrE@i2fij3B@~j&B2;*Rhf3*v;SL9a(-uJ11h)g|RDY7l}lSU~t`w z@wfA6E3O~awavw&kMFiZEL-g|_2XK7E%H-FBqu?hYVXMZ>a=l&@XlB3iL`N~Eq*l_ zcz}_8;dlsu+Gd5jb#KwrcoIPnQh6%-(HsXcF*$%)0bdUycoyvCjBvapY=z^!3%Diz zw;T&R=%j_be1M*dHN$;ASbQId^Qic#v`z5!2t@>>f0x#zq@K^E&BGfNS>gEH^GCtUkD-nBs-dvp9K?kYHm*#U_&VhoW*(mmpshOSSPU_UC0JbI`$ zwv8NwGxBji%~*McjIam9Jg)ZnchU$TJEnt7-tlJfVh;}oQciX;Hk~MytW2M{xxnioDNDs!_0;)v&6*spuYPI){4m@k|7J&ZWW(Ea5Ef!& zeakn%Hqo2mnE4UbL^|fCRpan@9#X`yia^y}9h2*G+;tQ%d<%mecl6Q6UJqhhjT66q zLfhb3^@00uZT}h$7Ay)0r_QLjLw8~Nv7p3%5&)74}$iu z4i|-N8yc|_-D;mw+;bkH20a-Bt8`Zw#j@M1G%K1WBBP>@-fXEwrf%a zYx8Yw93IGf&v_0*Cc{YZS38Fs0O(FSAu54sn_sHO zyIC-#5qvFd+;MqMY+auyVIQ3J&C=E`*t%q-1CBB9bjW2X(6yRJ@H0?^SE40$ukZB0mRt_ZRz>ji9gje6hZzmT{JM8mb z+hFHWFbw`iKsryyF~>nFs+c6NR)EQ6PIQd{`P|%X*bYVsgjN%ZHA0WI(@x4C4?NyQ<%VPJ5?zY#M*@28&JAZ=$TA(uK5ee^woJXYKMUf6m$w)a zoPt?+{3qZMpAlng7P)?GcLe+Ch5RWxJbs{2p5i0#Hfmk3?w?SoYQ_^o;!HGAc^enK zmCC>iYWyL;&AE)$TCorvGzKc;7=(?txR-nM;l$tsTBUk!dW<#;6F>2kkgEd7e9iIq zqt9>|_Y)kOC^kq-9IFPtlx@)QJw>-yDG<);PSl;_k7V`J94+K@J?R7Xu*CN)at zSH>q?I)i<4+^@waed<#kx?BC`Nj(~b#>7l_8xXNy3xu@%PF@&P^_1sBN4|{oQLy-= zue^hIlL0YQs%^E5O&FJ%c_J@Azp?k->BW;Z8Qu7qo)`c)a9Uqi$YPYK_TBG zVVo6`Ax$tBdo>-}WXByB+R4 z>q!7LL9t$hGOMKxdC0H&V{}xQ7hvd}e|#f7S#71F3BvY0ze=nE-EIE!8at^6?P`c` zhhGrame{|Yf1Qua#bXdJafyKL{zm5pBDsh zwL!C78{~1UqFSwkCepKf#(-(=c>n+)07*naRQY*QAk+1oilPr}iL~yQ3v=7KeeF+R z>o)r0xCWaRaGZL48oUpDG6Mm1{x3tvcSz$Sr>=$D6$B?vfaiVM7e7_rfxbd)w&zF& z*CGR;x7DH43;%wsBw_dLH`LRlPV{qLf=uQ&w zaxvDbACTaOmqi%;UxgS5pgxoPZ@@wSTJAo;^OW|>(dH59Q$7pw(Z1u258akjdAELR zXZsJJJi?Fy&>m=cyQenxQ}BZR>kj&qb}~tjyp6H;vr5Oql=F`4%El>9=Iww!BUh6Y}>kIJup5Mh|-)VfF7mgPIT(IBLs4Ewa0DEk-2J07-d84tEpw#nqC70qp zQ4V4ko~t*;K_rZ$P%3~h20CqUfo#(@%N0L^I1h#+N1NvxlQJ0Y_mGr#ZkNf|Zw3L@ zrhSjiSCBVw&%DpP3cRHCMOcHB1$`Qg2pOUsroyS1$1GWgHCn@!J~O!~ zHFvrlD~oe80f!ewzc1-u{gv@JMNQ$>2tHIZ)1s+)q*f2!pLb0uq{>CD3(or!w|XQ1 zr`#!co4YQ4=VVBq1SYmuM(VgpUYtlEd)I{B$&$i5|KvP$=iSzec}JJOh=*y(`{zVs zvqH}zE%^j8ASeWAmiqIyJ^|G~xv~?OuZ@JN2e7=re}n%H(!{WMKKX>K1m+D$p~R9D z0>xV{8F<3yLH>s}lnJE8fr}x|~ueoaw zF(GxIAEG00pCY>uQE~;-2z1%h{yatrm%)8_&8j0Sib&fjsiIE~6{!~kri zfER9oOwX&Vz^-#>gE=;N5Fy^s&^~kG9Wrn`v1VR!heYkI@%0TrzX7rx@3(UBW?|z; ziwBOM6FG3cV}Ize=&@vD_RPH&&(!kyp}rsuad* zwZ5EQIdi|ycg>h*{#se;>x?@s`)Rf!zDcI@{I)-{YjV;;zS%H$tc)RowH9btKd&Y{ zW%RTQ0Q3r^KNskg!D}o>#k{tR+V_2E_N=^U`%HiS5DzPCK(k5mr)fnPOq=XXMG0$8 zZ>1$2tc?4CpM6@igJ&~Ra<9yLg~~g9;QIfJR#NzJyH?72(f$DhJAZn*8pmH-B9BW7 zm?#2#DrrS7l{0a&xZugN4+62U!S$h0H1?41QnmK=b|(mIv1J`*%#XnUCWLr!DO3BK z=viz`3XBPX{=Kj36&*D~6gv+$%wvb;fu}G1oX1-&nyuJ=uqy+o0LEg3p)DRFaUbJL z?KNwoHQUs9^&tC zY&r?L0q!5b>*@nB6TbUQ>EK0-i!BaObN+Z+CC&pA$QVQe^lOf@4}k8zF=2E(p~DC_ zaqEnDUW9;{-rE)pWHtn(N7tzUVtw%fewh%|S2#ClG90HHtj-PG`192nsM>R4p5`Fa z@eS8gcO4vfwKM9KZpHSBTZiSIk7EB6IPNMuAin7zZPEjop@<#xuw2V3l1L@6dhG%4 z`kn+%YU%Y9dxzlF&gYHg=t_y*e`fY6L+$M7&rpdrg>zGOAZvF7;t7!rJ%lrxGJqlO|0s5IDr2J>j|_!hExOAj0#30SOntLsUEO zC{RD4@rbXJ>9|GbV{XaNV)u_^T`f?ZTFm888FN39m9XCjJiR06rbff)27v%lxfJVV z;;RZU3xO*sK5Sw$IY8l!i{%qjAH_Wg(A;gp0+z%EDD#-GBH#t>T;}ifi2wk!<^KJz zZ<6P_b~B=C%|-Kbfl;<^=yiR!&8qO4gT5I2a4gL?FLBdydl^+jr6^d?Ey`J|hha(v zH1-<;uw&XJ)E>&#^dI4vbcy}m8KS1=m4-n3`^bn*!m}>#8KfbFz ze?C^x%ri}YfaqV5pZ{c(nl4=|g`G!?)?}f$LUUQG8@u&Ju^rzQEg|6L^!lwCAP{yA zV9At4Fa#jpu$lReBVd7xSH7Qguuf0l8Ko179If29WL&cW2YU_OtV$v z^MZX@n(Zw8&E|sy0ZW$F9;3-;uj_!oC)({E+1h8y31I~4b0=R32MfFAe>VX6a}e2h z$DfR3K9|OeVWx>sb@UljDDgHh=AcC3p{5P{_ICc}5DdqM&GA$>-&M0>XLe5$e{7h0 zrlY3+9yvvwt;&O^wR>sA<*Rfw%_kGs4vF1qN&v=g=28uwa(kw1*}}i{Mq=^O0w&!| zS;?J_c;iKR9Yjm#E#oYGKkmnmI)3+!HXLM7jCnTn%`Qf>??rg`6)KNtbhn}54TRz- zX53A_c$UDzAp%^#+Qb0#Z<*(SUQY-NCOSUkzj0jvs2}Upk9L}aqzkUThXEk4?@|+R z+JFRGDwr~L6_glB&cG-%9POdB&8i>G+f0UfV9@yBJH9u4 z#g=~m3wG5EiEd^qu=34i*hAZ4vY_NV3#y2sEY3F6-EDSph_bOZ$c6yxq=m2!vCc0`gwoNH`h5)=QB@9J8P@2TopJWc$N<^D z;|hf!7SDQ6&Y$?4le#8z%`wTgQT3iaLT`Ua>PpjO*96cuD@?$2Stg6Q`smG&s;&cm zJJ_-_m+?c6*>U;!n8MfbaaAVIZqb~e_qeQad{Qsg+Kyj{F+Gu8EO+is^kTU4B~P)Q zrZOED+rhB+%+f3k5~FTata5b-EZSG3CMCIG$}a?&@0}bLcoR^uGbj91!SgHQ7u6q# zKSBaNl-e8H8k^es>3xVascmgtMM(5uJWBU3&~2_5GpH*cVw_A^$k_U=r^?`&uA+Mx z;YSE1`WT861m0fcpfzwoi%3CuHTN5SS(SNyH-0yTHj@IDFZqiw&lwcDOQsYL)LO?J zPWJAsHXOKB@HovZm@wIO0>4)R4LVKOVHj=hCkL7v^s#-*r(Yw?IdEuBXTLctNCC!oivw;c62d z_usKo*N1G2i^O>`J=7B&KBdU<6y(4vEeA~sgBz<2yW!9m!eqkh`H824o*ankA#Ful zdUpCHbK;=;#rl|F7kZ9b#C>c`s&YDK{62vs5ZM(o<+)Duy)w%9tr3n(${~IYypUZx z5byp(HYedjKM6zoz>wryO(5s*q--4}0VGR3D?`P%a8O5LV+vt{-=CbmczIqB*tTdKQ;h31!5R4PKVsagFK=qvG-I$o>4~18_F{&J z+dgzTidnt*A}Q|;?~mYQ^>>*_oWa-LH$cf$qVB#?-%eJAY!NzpQO~-v<}FvMz3F9d`{@Vh=WigmfgoEo)9EJsPVCOOLNj(e+o>iv+~*9l z?wZI@Xjq$`az>ebu*6{4DWowWH6{mk?QX++VJ-;@I7G?xF#!NzzK?)xJqi1F2h~pY zr{?GjKFN z8@w*pVFE0lyF)Q6G%_RFz;C)Ehk8WsF|dx{O$tXvEN`bU^g9dkpzy3n>+};{rv)=R zUYl(CMEhkp>Q-$zuq&{Z~-02AFj#voBl3<2**L1rtTTApRHd!2@<0I8Wh;9AwD>6>G!YvM{Br{>vnf zt4leLQUAOOdoPsdlo0h;0{7#0r`bHc)|}at&seOCZkip}wmzoP0@=eG~*DBdEH9)dY-fw-$1JwJex6 zCaPaZ6vlZV286ODJVWG}Y7sQLFzPT2RlipoTUi^-CT~T4vCxO*uq3sJBhqog4vx?jL}ke}Tr}cIIiVo-H@ukxxGX)e+_?jqthd zdAdKhXLE9scR$Ry(NC5)N8+-J^6!8gF2z4Dp>t?*im*$F|j@@urUT zz*3gN-sZ!&M>$8IAdxf+dC5E>t={KeC%9x1_c%Wl@oXvajcf6Nh^0Qqd8+Nj^J<|| zf=YU0vB~Dg`1(XXUs0Z&9Flp7=ll}%mmkBnXL^53FDke6i1g#Iy`RuI$xjY5??At> zu{3+GgLe)Z!zaARd+?^fHS9TUlbf-)MHysOm8hv zlIcx7ilwmqlf5K#2rGdRZ_a>MKoIOUUmN4J`7M6fvcAouDk z2fP8fY&t_L1&&YW)lp7=gW9ii%mnLA#W?yDqq80zMh_wk+qC-;^+b~Kn^q0+|Zd6|nngWhh@BmpVuN-7+O=o7m{piN|ZT`R!O$MfoWS^s(oXJGnQT}L5L1{jXQHK?_ob|7q zkHng|ayt*erLW08oY*qDRn3bgt~gyPRPjVYv2T1nc*;seb{gQ=&*Y(Qfp#I^OIhcoDif6n zN!O*K!CT+aC~5H$3RXVNui~deR?anZ*k;eh9$+r@dwH+-eseg9;ZU`V&2sc2hWL5y z)n=cTARJ5Q`x~PE-7Sg90nX<5Q^>~Tz+}wXG2OvrZ@m0F1H}<|&U6>Rk8J2~;4}x| z(Q#l7&TufX5VRe{mByU_S}AE!wtJQ;$a!*ArKe`DUGcr6?K~{UIdp){_|$KSW{^|v znPih-1)Wg|6n6kXcRvx}cPl%N5v+iEbrj1;wU;}{gWeY7o9o8cnjClwaxFk&NtF6<5zePR9oNc-qeLp1d`3C`vLEJpz;vI(`}yk z++p-9%|ci&C+(5)EqS*%LHUR`pqtV>&Zj1i5tbMN$i2cIgUD5!-}JG(l&^p>#lJ`( zEnE9ZN(*AF!*lYL3rkc_>V8&cxH8{^XOlx!8V9jK2mvkhu;+ZyVCg048XOQ}X18AY z0dr@Ls+4c_rxh`zeLtg9RIOJUb<{;j{KQb<=#=Z7{gZ7#o27_Z3**)OUzSV7$6WhA5M zup>?9i+F2CK&A7(E5_?aTI>KgNoB#9otZ=PJmY}fx^o#Q9+UbDux)6L(g^(Rxz{Cq z0v(>nXCdw$RS^9Ky8nfnZWwzrV0av2rtyW|Aga{lKQ`GU zBDn8=;hx7B(!$#Nbkoqwg$TFaHiErNIPHUYo@UY z+CQ4$RpnM*Wr0N&r43$lQrPtsj`ys8YXABe(t7EmU=L&4GVvYOClpn079FKh zi)ug3+RY#Go{U#`xf)+}#5yifgdzr>JT`fGh{pMl*xqVdvU=c1kpnDlcxnB>i~}?e zuj==iSqAP|XB@pP^+9;-%nznziZ1As{%Gk3MT-#lwO!s#uKl?3oEFYF_dI6I@Y*PC zUA$MF2eSpw2pO;w1l$r+Ki8f_q6|?}-fcjJIKFDgc1mCQjiQuoGBp}v_YWCl%J&V9 z%DA?13=jq{*cTC4h6G08;b^4|u+b#gObn|C*os;~@r288ersNc9ETBgjFG-RftW*d zyhKh-f92aUzssOQt#!paX-QlvQxaqGA%e*wZf=14U!eP+xjzBomvchK&G{Wsu$FbWUZRl&ypn0(XS-VMAe5@_oabx%mvYsX}fuN9qhO zGUB_oJ*a9$OH$JjfZc;YePous!ykq_jO#Rm7u(gI`_gHQ^u`e+cxX9f5VCdNI(x<+ zK<}_z2{5i8QO|$1z*h%osth3UZZncb~ya;poe7#OyWV&$jfuz}sU|i`F}MW~Z~>@{qg8*+}cw`Q6{H(MzbfinR( z@fF(RrLQ1<;dJ|UDJRN^Z^FuKr8ydsBexrW1f2L8+`aLDnV&-lEvArx^aC}p_2508 zR8HOcdJHjTTKZr!`BO3_IZ|$rwVGI2FC}B;S6kv#RnkE@2%wTy6h7p?I zv1coyl=2Ol^P=Ked%aclm-=%YdBrIrNu$4NwurgAQ`|e?mUeo;L9{Ykbih%$533lZ zgxnR*xhL4N)lt?lKcIg;g5Kgy(Us^aWq8S>9S7Vu3WV-d=%V5E15$b43H?_KckPJL8q* zB!G1k6e%Puu;-{WtJOF>fVh|pY1^1j*F9m64H$(F+p#)U5Nz6G;$uknA&kl~CUc6i zYbyz`Ft6a$R0NRd{BlB_HysQMM6FK{+|UN&0PRx^gth!EU~~$u{}F)v&N-r)RMq8g zc;=-`>og7}qp2@r$^8XuuaRm4X{Nk-c~<; zj>gCDKj=^xYQAkwo>$sJ86IoDnCP(kVA#QrMeml5_H}=N6f$#q20?*RUl1JR#PuW5 z_N(kC!rm=sVK?&W%XqjfpTKj3-E&ykVmXG2+%_=!axw}nnE@DHr643f&jyt8NiJ@5swDtaeRl)cX|nqB;yCGX&?ak1;8Qr zMpF;|NUjzQd3DMIF8O#y7dPn2boxD$#R|z14ebCM4 zhk@ANR>!5js=o*pV8yUj22G}fJyR7T*_ZGtR2P+&U^ zf*)ZQ>D)~29Vs2$`a0{`U*cqik))*?rd|;}6B#&pHO>t7`8Dr6u$=|9{oJg?C&@vx^t>bC|J|G36Cs`kp z)&(t+e@^iZA$XKubDQpw13+x1t1^siQPdyMAM=6%;5j0|CZ9{PSu-#os@-rs~`bvlS zna15}r#5L_W&3cwA7jZFPCzwMhcpBoc1fnrcN$fqLey2 zJ~PMW^TUtx3CNBlu3`L{_B1w;0RYW$lT0x zWKH9iN0whe$qmdulD{8?pq&e(;@+1o2jp!LxiHo|H-5`$quf zwWa*`rKfEb^JAH}EG2M7bD& z4ohrq{%L`i;iMw#=@vyr!fM80yqmL~g$dmyY-=CATrQ1Ay@>-+Ywa}+0_TZi1PSb> ziVj7MO;S!O#2!_;C8~}j#$-D^DH&awKg(6I5pkugp=UK^TxmTbqOubOqETL<{xtKhQ$XzThyVn1{{|Tp4E(#}P5Dd(8aewjjhjgTAj6-m*f_s_)Q6oI z(bLE01?>p}z?EzQnaGm!b+E>L-&@(8?h^$)XwqYCQ(`T)RkmA^$&>T4!))uFnhI0o zs6)>D`ARLXZamG?LCs{PEF}3)A*qiL>c$QWm`*0h>bGGXEKebpS%-vP)}&>AM!!LB zHd5*3xFe~obpQY$07*naR2*KTF7(m2{uLDGwQ7LoPPCvh*EEgU6J6O`OxJ51RjHPF z)f3gd!c(rMpTSq*4YJb_;v>H>2pu9iHV-ES9GP$R{S5vai$GkWCBm{!@jOgq_dVNm zE~mZ9M^B3Wh?{13YU=BqgpdFDW`%L-bD8M~7$@OZ(>(^_2?E241p;zj*sXy|`?LV1 zFNtOq=Dl?w*=GmG!ri>lmaMU6t&o|8(OF19>|e06#Is%V#ubuKy1?JosX{A_&DZc_ zvsbgM;ZGX^dH7u_7F2xbW?+}%`=D1-nj641Tgg}Q=Qz-2ad`BmZe$=rHbVu3@m+;Q zpb^?pI;Y100RTzp7j?Wye3F2zCLjW!AAtKe*yY%^kIM=$iJ7-4-sHe2!+Wpi^_^cL zXe$Or9e#l3N&>0CqX}k+hX_Ewfn4Dx3e1&6D~f^R2q`2L(_jxt-JcQyu$?{)y8MU6 zEIMRXK4Q;0PN%y{iUdSIomDJ|nd?ENO`<%JUg$njp^;4#SAd60UVj1$80oX|n>^J(yW*k?&q`)Iwbo}7wg=!7`@2hzU>h0C*e z@f4XClkdu{o_pBs=*edUu*TceC=ROOc+i8O`uPOG9F*8fO{FKv z0@9c@X}hwEikcY68=YCfkg2f8XCX1w0sil!!Q506yC8L+bW)}k{b!Y5 z-@H4!!jB79sd=Ow#B~*6G1FlkxXThNNMB8!OLN5O*q1cUPuejnAK=_I9uvb@j=eWP z4npS}<~3N9IyCC%JEe$W9jMz-OZODt5k(3W(NvgcA7Ag)N1my|<1G)dJaohT^EXU$ zOa$uZoq3Pn0-JGe`gB|J$Kc7efXHOk0a#GR z1Gk*h<4H;HkY2fqL_8s#>nPMsx#B&3^FYp;eW5J{FU~3o~%sQa%$Qd$EK{Ht^dTbC~qc^+0 zlY>momU{&|Io{}*4{~D2vjoH@oE8ss|G<6!yUa3M#vev0daya@V302UzBH{}eeYszPZRMEDy%u=1m8*@!R*oBU zTN^ocpZ8JYtS7XMG+i8K?Cy_E>3cjq3luve%S6eLB zx!kM$X(&=uGAL{I2ZjKgLyL~Jk$6}jO!hX{a!hZx1ZEOCk1H?fPKC){Li%qxEgQur z1A=1?S!*~lvxb#BMeG4I)IH?LR`}o{EnjX>Zz$Z~a85zrYOcw<-cZ7Rs<;SPl-VO# zn~ozW^bxHa!0Y>$0()=A_xrILOm0MQbt4~}L6Z_<6VNztT%f9!aoMcI7YV8-P23Mc@X+U#3vP~pnYRX!6q#j>HzeDxrw zRu4QP=Rhz)paLI0_S({+7H}y?Y7n3?B&NX=1bygqtX!SXhb4&1S-CI_&W<%h_vIDqk=;hm*c@F@s-ObGfd zeS3_ZN0R>n!EUYR3RX}j2K+koQkEKyLe`v~d6W+#jr2m)#ePx<*d``onsfh#8~=gp z>n{KrN1p+-eGU5Yi9-I)C~GEJUQM+~-pq+6q*Y$k#nE<+wI_9Lx&IZLd`cc1*?UA? z8q+BBM;Nr*PMdriK6tkB(*1OUQ+VtmQ16RQ8zzN?9(^+MEQoR#%nb*SVu5l*8-G7X(vF*lE+$PeRs8Y7LO=d^1r6y>ONrs@;p1k^<& zld6sp``2RdAMR{wOqa(^Y3`>30B}H$zf{xLs)e4BIiqya?^O&iC$2x)Cl?86zK<&w zV11L%iLcyT#T73v=8U|EW23#tP{SM9f;x>B5j_ybi)zmKI`M+7g1kmFcxQj&(oo&4 z!K-Wg#;=Y?2R9qOQC)j4elq?{;OyT({`F<0A#nbwSPX_5jcf#w-OU;|2+~ldmIf)R zJyu!y#krt?wmDr!hC$r=3E6#D={Ng9oI9Q-aJj+Z&3=DCd;-vygSWr&eKUdlT!8;BoRb&5{eh^%E%@>)iGXmy zeg6i!odFQ>od3&!U>+AGr{VzgssbN)yLn1f?4^8yMr;C^m95#qCo*5BP|O*Wu3sHD z_IpNi+Qr=)a&+dkH2bI|N{%Fl$YP@&@L+8N|H@C=3u6C{yrcg}Ts|PQ6>~k;tydm+ zwku>jh73&!R}_3y3Cel9mB6C~sV&c-%46nSNmh4jvk%}wlSwwBxFus$%U9c^GZ3d3 z8D^dh()To5aeu+quHHFpo0sHd=Xo_sfm}jh-%ihCJ?pD$kWCJJDC!(x&#U>sa@TC|)f_9Z z>}_MpMO;L!vr6ey9ZF5y$KBg2utUNX@dxPVZ@{nrvpDodO-2s-5}^&|aqS&J07)+BuoDoT$Q%kLPV@Rt5Ddk_jqD zZD5dtt<%2rnVmnyF3rRfo&2z_f263f>H#9J7e_=-7%|Sm<$?QzKL%tj5{UJ4taq)m zTd^bdx%|NGmF5&FeiE36<^Jk~{sRVuq`eS1&V5WbnIKTeP+T~adivECB@lbvQTE+K zQ$$dTCL0e%RuZv+{ti^09*7fQKkHnFRy{4UFAi@jpNYs^cI0G4L<7~8{l4*>n5V{V&{j0bYN#G`elsXlHq&j6A??bQ(KWD-p&U z;zV!O*{tA75W8Kw{{}P2_UzG2=62-4Ab-?x7q=FX2{d!{g|#g5>Iw0u43GX}xp*#v z5$xE4Z6&}DAP^w+a78yH>(WGA#qL%HuhorV1wEW)UK@A$?8LeBFM9h-qKKXX5H&MI zh_rmI1z(sdp9yg6V{^h$?oD{cYE?lK=6S{g?-Qz7Ho>fyn(*L7dTFeEDvTgr^XOJl z9)Rq!tYjH@G0CX^1~)hId` zrD+gV|3qE1r(_4SGkV6~IKE*3UHj85M-b-}Eat_q>&J+e7j; z$^E|HIL&1qzLe`z*xCp@eUMA#`Z8}Kv-*bm5 z*iw1-@`-5HdL@FSoefuWyywA^H!NxovPp@6j*}n3)-S~R^AEr{)c}Y2;r$N$H1P(5 zV2!e5ITZKNbyT%TeitAh+&_NVLqo4MvLDXQ}rmEuY6oeHP`qnxR_hbg;Uy?3bX$fiT z$f4&!--Pq79Hlr2U41_I2jZg~12C%U^r`m)L)Ev!vAW*E+e zxt)D7aONxOOV4vX3+M=uC;mMIfyJ{DWK5#p<}JM z|MjRR*Zb%1cJv{(Z~ASWt!Der7XL#ptWDCmBcQpzfyjo<3f%YK>(l~)P4dJ;BL1jN zgztnMYllOs*bjQIr5nWOMnqylgzp6*5Q`XZ*q0-v37B^tYkImEfi)#3EEwSprD;c2 z0cmHF%kVwWPu&wZww2!Rf)TE-nmZmyGmvl>)ycD!05U##EYt>`fpt*n$1PsblH_LRH!!khYL%9Uh9LNKOVhHnbIlHTjTk8pPAWQ@@vxuS9^28( zx|M0U(vGu>0QO;DC~!kM;Rs6wFu5(_(4i|WE9Z6%k8T;zwA2<;#r?$OCW=Lut?ck< zl}x0vCQ5I)-14a~hA$SjDY-B_5e&MQmdb*&6$CrIn-g=y&Xy01%Mc3hw4;nLo^dZT zjm>dYLA53l^-~dKuxA@4Gnz8dOuXv)&B=ikyh}VRcw*eF$pVhnZmme7Ou#4lwe+sJ zK2v8`Owx?2`H4Z$hEDUj4;XD*vjUVX(Yhjilaf(w#r1l_hDGfWdgNt_C3m_U1uUz7 z_yW_VwIB)T{s-=#e_N!4kr~Qrkk?F?3|iPJ$lrcm0MsW3?tj77!&2ojxmo1B?*gSK zQMS;bcLT)jgIqm8Gu^eR3LOxa!V}%he*He5{;ZvL4-Mh#(nmP7Qj$*bsR-D%sl8o_gkY-RdtQYs<( z(Y*_p492B85;iZF^+_tR!Lyhcp`!_1O3Ir`Dg@M)#IPUUL$4RsbSl#w*PRO9J>z-? z_I1vVk%IvFh@Gq>pXqSDdA(ebl94+g_RBv9J{jgkLjS8EWb5U|;KATCn$SMOvw@`R zmd<3L@aBkoxpc_y)0vSS^xjou^86EnSsAYrs<$tY7%(l#8Gw<@!>~jsYO)O^!Fd?p z0`Dw2t5DQC)3DUxh4>j=qQ9D<0%Y_h&K@;K=?Mwu-|e{2Pe=b)E-7NMMx+b}%_$Gz= zf{0BLJz*1am*9a7KFX)tUxXSzbgm$2q4;1EvK=f zm?Q2UIOOo9%%=#bl38=b*mdw(*V3~If(z^ z4EVhB9V;?z`AALLl5SvtW#4$9as*F8D_5WlA00VEy8rD1{luP_0p94Z<2X$KtAxJH ztDJdW%HH?3a%tK>`+3Q*4ENIckV{qV+;Mw~&9>RM&supFP<32nu5kdvhPq>}`~b5FhNBF+?4r`#g? zOn=*fKgs-ra>noQ>9A{*W2%zMpO;qy>5oki|G>;F>8%LynXaMuVI;QVcSD&6YyhzbL?FgN&vAJTR0ZoFOX7j_C~$N17CcM>qS}uAeCzGZoS^ow|P^!1WCP7u@$haNqy8Z_)-E&z8w$ z$du%*D%#krtS3;Fb64Mx{N-oan}(5%9nu#zj$}Ou%nQ18f9mBQ=HC_@@>h-E=z05; z1OUU&ULwG=P5nYFk{_!XB%W31)uv^eV`-~pwAFX)C?JfDbBKPJz0n5Rv!5s6>Fi0F zTF=r0J&)d_mbH%crHUklPi(FMZ&IrcX>4$W%G0$BPmxywc0kQ}{SvhEUsnd7AtN}B z2Gt)YbX4dKq9=CWO5+_5&p=ulbxu+C!tYbWT@nR@tIB%gf|&C{%%jpOxc^`7-gVn@ z6ju`!E2?Vu`#8(j7x11h^E{tgC4sA));`Blt~TjSF3P%ec>vQ z2u3)jfHxaeX zO@a&8Tr4BKUF&i#nKQYCy_9ci*J~&r`-#FHP-)6(JY_>f{QKJ2i{|x@Pq$0i?kl{_ zqHD4Kd;Em~4V}WV{@(+Dcjbon8142xElKz|NUjnOSQjlhC_>vF-Zn&54_Mj8>-#MR zQ<8hB^ZU@}w~HHXM({ z7PYczMk6dnIo?14kVphOGOx9GcyPnwFX957%FK$C1-k(6ql^=*PKl_?g4H9pm@{?h z2OChb-)&z35VE?HP-qYN$N=31$&8o%6?*?O1y+ANZC~u)5e13bV%8-#BGS7@R%w>N z#NU6>=kNc0olQV=Zxs9@>R&rpveQSF!ELi+W8g#l{onTn#eBS)@00)UA7oDB3}~a@ zeRF8aR(~xucy-6|Eb(SK+81!2%MRU>JQdzGB~3B#=SB^5U-|!--p@lMx0_>T`h5RI zm+u08+uEgduFS*~eF5Ofn5WNvGc~&4>%#x}YsPBm2D<*wO;p)jDZ87l^?X-QY9Gd~UIBb+p~^#ahECG| zbcmzhQ@xER46|09U}S@E;b7`*+>CIagH+Ym#JG~nQGP|LgUlA{^Rhwk#CDyM?9ExF zw7#bj58%u#fxN;0a6Np}nG0-lwaNJT27yczY!#bkMARbA>@)DG{zVf#@6M{%a-HTQ z9(-Ef)H56fiMsc14al!E$ak!j|KdO!Sg^_CJU1=ZHa1F-3+?+QjjxBG8k`Lv+s1=; z?N;#V2VcW!@qJfm7g``gt57~DZ~4j5cw?JEKF+-)mNkg$d_IKa@dj?W*oab{6JRZ) zZjjsXxi$)Z|8L^o|9$CG`=a*s52CMMgd}y*6y@@gyT<>b);Ap5*R{a;{@?WZ{r?ev z{^|@XK4^TV(<~O#c|mDiXX>(6mOL7LtcO0!jNrX*4!8+f!R~x5xNW5Fs31Lj2JdL@ zz|k5Jf8Uz~eCrDlx)VG?pp9Sl(84#^^PE2K*@)(b?%%}x5szV$F)<;ZMwji3r)>T- zcq3eEftsKfcmSKgc4hGDPR zHZ*>qNc~1F|BbkWn<1xjX423+uVH<;LGy*Jngi??{39l7JBm>?P$r7xmteaQad(c^ zJE(0p(lcmK%Umpr9phXY_o!z!Gk0B22HS^-eqn_g;}Lzs(-{tqhzVt)Qw$d`Vn%?_O{7po1g8)%P3p^%ji5%NZwy%TJ`hv^i zMD)-iDx;@4Wn$tcoaP`ANz;_y^U2Bh*oJL`;7ZfB#MN{YCBTpVYpt1%9J8=dRtd;#(^_GH$m@cDdf4Z{y!%aictyV!bnS zo&Mk2FTVijhM_h=q@71r#+_+nuf9(vZA6dhiyD3VPtLLNnyKFJYKC>*LXUZhZFs_A zb_KgB1BwO*c>3#7pWpPMFZ%lRFY22~bHbRUx?FQ9`0n*z-*m;0yS`aEAi&At0C!eP z3T$U0i|qRU{R+?Lwg($L+_c^{RoNUExX!$c2OCED}2>PVv9N_yi`Qz+VJT5M6xR93rH{dKcF^$^=yR1uLo$= zH0)xE+OP4jX*Y7r?ZGTMu=!i+zh-30zma^Rgi(;hEcx3E%d%pR^k8E- z7+7x({W=fyv0)o8{^r>DUYm#{-ZcVqe65b%%+C07TPG7S|JFu35>i0R>PI#`R@6{X z)?620HVLM9b$_ug2-%An7IVVE0F@y@5AFUd!~=kn=XT{pSNb;x;}Zh`>8V# zK#*3fXR@(r=IvX!xz8c$t4wFQ6-JMJKS^7v{?&#o&9}-y$2_#r1beU31*OE}{UZYH zTA7&tBK{DyuYVGMeN8<;bo4tTGy+MxHVMY&rrS)K34cTE$pe(Mp#aY12(tOVpNybJ z%-`c+MhF9S_wQMI8Q&os!UvQ2bu6E3-asxS@UAl7+U?}?cQ}_OF0GP^_dlukWIQ91P6-1KvuO8#g;g;O4 z-7OZu+RN_n8svQmmdJh%zG^tT@5UTy-X~KNh|_Kwm10Pl=FAsX2!TC&UZp~k-yMOj~aFzR;j3??-ip52M)PaBK@_GOJd zHtb8Gm?UO4HGPCn$D`5QYkXi&68p{H=UcsA&|7#}p_yVcM#lw&d2^0yQwzxUe%{1z zGN5vOnAz_^H+z{5z{R>-C>+=*n0K3kPZEZPTkN~Sj3n%*s~2P9>wiqYiSEa^=N5xp zC?z}!`+}+_D{ke#zQXeH2iI}L^GY_dPji7WT7O_#w;cJK^ruBLIr11=4T^KE+RER! zpEM!-c)NXC>jZ+;f+hs~^Yt0dFS=fDx#~ZnwP08|gd1RY36*$=_if}4Y%^|8rp7S}GAITVe}(XNzo z6IsZfS&1C)x8EHL{vEl7+8G2kxladV5*|5m%lDCaB<`~lP@sT5{^EILES2(WQE-75 zAG;n*{^a5$kV1dN%F`&tkT_Mde(_OBr{h3qK_1>?2VfaR(V}5KWk3Oryl=OI61Q;@HW?%ea#yLi8(i-=Y9zz`<|R z;l9*9ip?*kdGf!++x@})G(od8ExL%86P*QmH$EwPy*d%%MH4%80qOIK59{Y^AlJU` zX$j*tCzWV^cOdh1+4w*7`TRG1K7Uhdi!U2EoxJ~i`k4gRc^3WA{$;}dz^$d51MO>W z4rEzvYxkzjhj?rboajphy4BrrT=*&OE;nP9bU3YZmV4ZF_my5%W%ZUU0iFu#QcPLp z4SC-Z?5%TgW+R)L*Az{t$ovzI^*@3<`TfRtHdz9@4)z zUtguWttVT}&tk(f;)OOPw%Bo|wKcc#bq;F!hj;yDK|<3z$6CJv^l_*7LK2Xvl$z~f zUut#!6Z-beaWCaxuH1kpA2%$na@bvf2FrvZKx_IgTzMS|bGM{b=wCq-;w{YFsp)OX zu*ZXKcu9>tnjZ<;Gww0U*DG;BTyDyE@dUvlNpy;^=L}WrX8!hTn-rcHV;G0mn#?pm z6J}Cb%npl+aj_2EgOfOPvlm zj*Da;CP)BNf4zYEr#ty`FZSfqMSW!}=x@Z-I=f;MkU#fb-PLCdtpkkawmXK-Ke9i} zRTb>)%(>^#9Gq!d|6rXb+3OQv{f|HN`TlR>&u?nn=sN%6Afx$w#M1;sc2oBmzw)pB zCC(i9{)^_TD1x%s7$Bl+L*)B!ng`Clpvb^HM579CkR~y`M4qZCF4NHq+YJ6~ zBiL(xg%R;&&?7pVIYtTgHhaT#K zu?I-5jsNXZ4w9*QYkvkoS$6Ui{z2gm;=y4UO;KQ-VA=K@FcXy9ofVrZ?&WgR;^*uC z7r#0$k)8o<#ok)K41x_fnTc=Qtb$|L(W$?$+XI{a*24V#HOl;GF06tfVZ8JBjT_Vb z$Sr@&(*qJ=o2<`tIFgrzsNB;L-^wt(5*%)BFmL_?MS|LR4^>OB4xT2P@~HxX6MLB6 zHfXxiM?3o=Qvriwwn;(WUvz@wQ<`1|4HFXbUeH_xO!S;PQS30(DSP*uq3w$recgIs z6bjnD@C_l4ehlM|5;OHf7cbRoC2R^O^xx>3rvnYuyB4;xG>jEbi537Y$?#OMQ*)w`dDb{*cExMncPk8+ue0`vQ zH@EB8y@_G{c>$mfLI*xB#?mL8Pt_?rlJPHrEEEANr$U4~z{Y1YV=nY;g> zoU(#*pBvS~El9fg?va7Xm?~UKD>?t++4uNW9X~7CzHu=^A9_$@Gd)~WzME}G? zrhXujY(7Ccc@n_^7=~Vcvty^Jxw8GI^js6`8OQJYZWDiqxpJ~~$bJl)0m+K0&~nmA zbS+I7VB{z|7tww-)RDtCS&-4I)X&-QV~oa-Ih@@jWnQ4gT#l_79EYSKXdj|WZyEHb z3O>Jyzkgp}OK5HWBlOQ=ZNA7Bbdle*NIW&5XKq9GyFsV)EZA4Pj>NTV`g=5Kxnp>!? z-6`z-XhW1x+8OIN2eh!*R(&5?#7#G6QIv`aA$VoZ({7RESbC`0c}EVj1HNNqWcIyXTeN% zX8K0K-$eX&NMerw`RW}wUBKjugzpsDyL8K!#3g0_D(XQebp^1^hZYx}x zH{`i>uF%x@=6(q)y&V4>Tli2^9)DSt*T&GBebeXn|G5t@B%=09oK1j6)aTQTlCJ{{ zuTR6?pW;+~^fF~-Syx>8nx8TJ{HD+6FB)eEwCj+<*7{+EL`=Vlzx&2OW&GX(B)7vP zNemaL%oW?Nb3K}#Nj>i+e(bhN&DZ6RE(lQBY!)m|lhGZUO-OSU7qX0rCT=BbHsjAQ z(v=Jo1j_jln3n<%{X!i$?6F7<_x^qjdhwOB6h#Xkcjfhg&itu`GtoGnhlu@GV2@z2 z2IzW`<^xrnMc5jURNMyyU8t%vF{Wh1(t?c_SdwGwS4&;CjBRR%b9Lf6q#qd?#%@4& zy4FD}90xwmkuAouL-ZNS6tge{rUGm$yy z%Jz-Mc=OTdPD(%5w>I0?SOD-%VLs#w@USIr+A+<GG)@!9~QxpX(j- z%XNHRHp$(tnvs=G$1i*QT(d zceS6moPX;kY?ITz@8EPe4H5vD`1}9T_viQZ%E8yKYonl#ak&uEy5C<_xWszBvSK%) zJisHanoSBgPTA zB&9Gry8lYU&Au*40!>!5gZQjk^iA;W1(#E(@VANTHcE}WQb;W z8Kbl7%p;;d1p)q4<#u?=;|Hp78wAzVXtuV731;ptmqDlB=eB7NOg3c#=iV8vG0W7G z#YlA*D$PXPm>PdXyY4c2NCn-h0Ze|EyeoXu>GXUTrCoQD?H1P9NtjG`nWN-ezWLfj zIhYmIbDTTCzwdWi)j-0YmFnp+;sq>1s8vOzqD^(Vx#~J9e4l?dC9C(AILv?Gz z*u=@^gkEW+9C{}1Rk;(Md)5zUeiRudB2+$JuTgV{?FruGhWpgEWWkF%jM6#1wwL zz_KEoDifBDDx5xw{=B5|){A@7VBZPycQ^fedjOmuBRd|w9( zemTp|>R@AoSc)`G%|o;u}OvKvi@+d$Dz0}RpGJcF;pDCRVxR}JLmz(<@rz-FP7 zxC9%NExWzZV&i0xaSgH%ziQE2)xx$97CKSr$+TUVl@S77%C03H#)5~YE=*26ZQWzJ z`_u%TvY-^DO=Lc(MVk&pYBkereZVB!AJ=$%jAq4G41kWKvBFc;$<8kepEOAfEMk5a zFBEO^iQ4x|Wax{2>UzT_fsxELL7&WK^m>X#ZXtDls1hF=KCEcQ9sJ+K^!Iq};5rQO z>zh~YHH z+fUq_Rctxv7h#O6j#syo-zK_h^(h~RJXz1XPMkMPZozvy*O5^&tUBDbt~(kslp&0SoVnMe(c zjqXjMDt|#T<0f||-Pd2eXjbdhvfZv^Iq78ON}s}ob0!mk2?j;Y>1>kmFY1}7ez%_8 zwf&5stnYTV;^Y}PXBW1+>=<*SZCrYlin~t@VGVl6Y`3%FX+OShTB<#w>CQinAx^_A#?VcBh+^b&tn(OJC4>A89 zOYX}NUVq!zz-p!s5%n({xMV8w8E$x5eL|R{(tdLFxzSyyVB68Fe))6Ta~(TN1wigy z)Hxm>OOe+bJPEZp4cx&Z%NM@Nn9g&WR9MgmVvkmMR=1M+O^gkcEqN$oPiR);@vfm4 zq3cqWxXrUS+HS!4`{nP@e-n3Ix*PloMcz8vLI%j~XV#61||pDPf3~PJGS|5&wTF_EqEvoh`l~tfCcGqD0DPZv)CE^?zD0o7}(~* z;r_2ibJq8-$H{SA-4;ojG`1#_I*Uh-sJCgj;8G^-ue**kd{J*g&j$_V-?B8Bde4svSF?4{}YS%D7SbEpT*kV_qkm9ss10f zeMg~>NY^2fCtjmGr-z}W?^_5jpVVK!n^RGsbmFo({4qf8JEV9*72T0eHqHVLweDR| za!uZvMVZB5vEc6!HX>>~K0`e_MvCqi_B>8+&Y|^pnxk)*y1Vk`VnbH)@lp5XK}7&f zc*e(v=<_{|1}zNwiqXx24sL8a@|;GyMKC^MV3qzuMsDo>ZzzL;M*YbMq7QvO|Cflr zeS7&xgTrX`;!be*dTOK}G7?TjRLbO%G_9kU51&78Y)0JtXR0=u-IExPCrd)-Wn?~j zW?9XvJEUdDk3eyF1akz5J8lGBL)gdKbm`hF`(KvlbN+K+b2YtdyMOfqg6|Y*ZnTBG zX0$5&%U}SEVX>Ju|M3{j`Bu$hJOjCpWox`u-?-pOx-TQMbt=y@WWB9gh9(M)GXz$A zKkP}?!;qx2bm`gVS46W-Vok@6wVtiKZPfN>L3-tJd2BQlzWw@XR%QKjrlcO&jj_>! zZI2CL*5;cya zXJl{4c=SsLGEmLy`&u*x$t5}nhp#m@~ji%w#Rja~`}w(EHvA z{&9XWPPr%Q0|=A)Y<%{Bv9Y2R%mw|mp&)g#gWJ5TEPgPzYs}+Ld%pw!Fw2tm&tHR&@oEtG*Hk&pUdiSdcl)te@1oq`ra!v{4&;tE3x^ zz9MWke@z$Z_rWx*`7ub35-f{T;4bUZPJoxZ<2Gfod%4#CAlOT0o~2T!&RSBgO84Fr z0D4bEdXMyorNlO!n4N8_O-br0sp@*E;sjpd1k#BmbUJ=4WEKyN-k4!4{^9;fRk zhnN_B(2Z`2??b@QGA5o=>wq49d?A}pDB7RTG*;$>8WWo64O(DI`cpUPDIM2$Jfic4 zMV{FCrh-4<<$~wyzwQ@10RKVun5m*D7Htt#kH1=n!@zUbt#Z76bC->Cl{fgUJ?NWM z#kcrMASvk4Jhih5Q+!C`WgoCW#o)^2Gfy7vFZIhOy7hN@cWDPyR%Hs6M7L~gwQK&W zvHyl%X1L$M5#_CG3Z_+kk21;S_h7kDha{>ru|F+icx~!4L_p|sG4um8tNBE=Jkq|m zpoR+{Qs@vhqr8ju{j_hU`!4@|(m#AaQ>UdphZGMT=sxm!dYjSqPVzB-(Nvp#^}|iJ z)&?dXi~s&@Y9c27d=s?~ar+0g_KS$Vh`zF5DriYDHC=@g3bfN1Z^+yS681)NUheN| zU!Tzu^od6M^a+fTN3vm=)ts(X%-~>AmI=Zz$M=bh4YsX7)E-Nyg*+x;^`+Dc1o0%41tq@ja`NBAU!So_(C)kR#L2sE)-dkd8v2k}>@lx{l;kGL zok)*Q6kMAb*LS&ZyyK9??^}dsL8fRvN$TS7$y+7MFpiX{alyLMWN-x77>v?yvA<%phZ!F8?sm7 z*|bU6wbyu)WhEaLNTSUGs|6by86cRdFN}$J+eSaMI7WbCK!H!B)T?-!WzH|Hg{9RG zj$bWV=Vk8{9t*`Xin^1Hjh`r zUJBUf4+ZpCH^zQdw8jcZAwAOJPb=^&n;LhNA4?wO6fcn7qn&M@ERI6ktT-N!iSG&C zcMxR#y0&_9H_y!;ra+uo(%95>9Ctlp)UNdOgTye|A(g4q56Z_PY8PMD{)wZ@l39)0w_GH` z%Tp|C3wFHd?|&~EjVkjcnJYFainnZIP4g`K_ajHt6aQ>y;0JYN7;E+i+)#|la(p^lGq?`Fl(3A7gMTZSk`h6h@3+k` zI3+s5azy-TN_0c~(x>o@r*yy~t)({7{Ld|!?h0ms{3y>~!B6_WpQ2Tt1n>lbcdIAn z?K(3Nwf@<|Ut~`M>Qp)PEB8*)=quWE4A99p^IKjuLpa*WwB_PJzt3m%h}nAE=JT@ppT^Z6khAi%gqk$=M$?GRVW zFfh$&9UD@9^}fcvAlYYNs^$H3jOw?c@rGBNeCk>2bUS{%kz@yvWI=O9GpO`m@(4fe zm+wCEX_eJ5vlx)dd&)7DXkASpZXpIUhg!1HFAV}2Y*uA9_M=Ysh}HZ>AwHx%SZ=z_ zg4I>Hk?UoxxBc26XW_>c{&u(Sdb#sNTEr#TI6H}-GJf>$fHO%aBpSvuQw6E2OTOOP zfk4J5CknY5X#)eAtM&W6s4{EWcymMXe~r&h(NagZgYcDi%j1I- zE|%Q-NpRXbGCri+T_;*a%_Dlyt!rvJBVDq64A6d8 z0W*qaC8qxL{Qe{?9H;=dK`<8?wp&l2e2cvtnu)l-ZXiuknrO77=t#1FXA;M7 z!7fI?74?$ixeul4hx#d!K9ob;OwFSUf!%gM=0mtA8bzBgem zIp0rh+}A22tSo#uYOBYW=Y350^$(BjA~zRF*YE3BJy({KX*1twPyIf)(WvY;BxCh- zYl}FKMJgLRRKD%Bt46+0%xRRMoCNQpy}G2fLsM6dY?wICg(y1`t8w0~j}KPV%tdQ3 z4j#|KqfbZ-gr>KAbY2NyJrK;$|cb6 zGQCw>JjQXA?q@uuXX;*4m3-T>`@cuzpzEnyQc=JQIIz{N_J1ON{X1cAd~SmvdN8ZO z6CfqQWC5W3&-H`lCIL9OP;V9lbfa?zWce~@vP7p z+*aE@MBIq!IxLW=-RqM35rXlVM6-hZOgzeH;U)@$ajk&IB4e(T`l;Pn#wv@^TumXX z+2<)K*_KDk0=*C-{$5V3N_1D5STr`EoS_LufuHVa?VrKi-4#LjI9+^VeTjLC400K_ zg^DxLy#UwS0@~YTce8C`vzswTVbnBHYiwQMDZZ7#pV-c#j%nfqi#54OYed23NQI}$ ze4NgiL9yIOOUhM9OOSQJz}1b3E#sK|Hh&K=2>j{+;J0iolDBehSJkh&=d~V$-384} zDsSiktqOfPmR0)`z4JIN78d_+Gb9IGWxAP+1L;PH=M<1qzJvRZr}oGes?!ejGBPea zGTSR(*Mb%WfjQ3L&KqpGAW|4-)#OUxkwAGJ(c1AVRRVjAapc>ZNaa?oAMXPc~K#!6v)bcQgtrI2cl-X9eCeY>gU^IKM>)NOz&b&t{_shidy%T8ZWizwu^_F!&aFosTQ7i)}&3bZ1w@_!ZwZ z;q$5Mg?g}S?Ps?2p9Z_%Y{((n)xK)FAh@k?iz=ab?+OJQz{ky zC?QLewJ2y+68)@w)X%qV=5LBlp)!q{ERTaROzT(E65yH$g>k}!k!1^?25$SHs*rRa zv1IhF{d_6ReE)wVI6HPKIvP6BLBA?hJfq7_hrmICnGpcI89le} zM6zn{jp9V>FpmeadxNC>jq39gfkCIF=#^msoe$@zMi7Z&41d=Fh_`6Tpm?e831 zGr!-0q8>47tMys}+NSpSa+0`1gabwe(|DUG=pV(!On$y@a-!?@rgy_Tb38&-$wjT^|{7_uR60W*=uXxe+6o3CvT%H ze(HX!+m8Bx`f33!uJbJ%U|JTm8=_sC6K82n zln%0$Ndqzn^h-MSy-%kiEMT6ju8OAI6M)w5NctsjEw~E&Tl{UbV={|H{VI-3<#5oh z+O>hC8v}}Uz>*%*Wt%lg4`aab=@cr$*l=KPvhha%&Ch0{`;az>s6~oa?-^8a6 z5ykB@t7XwZkL+|H99-Krk@U|NFy~#w;0qo0bmag}&aMcn>%8iY6slk^4`<6Wq^MaQ{hG_3(9cRpdHJ?%sN`pVK{l$ErH6WhpUqvgx z!(`#2#^M+5_+W7nvoQyot|hR*$N3b7G;VDd>Md-E%C$pk#Z#GSDrTl@OS;}2 z_~tr7y?^eeH$a%?c#$zQb0u*_dX>pAfx^L!C$$jlu9EBz4BgJPmJI3siZ8neX#tzX zG1!&A2+qTF2p{1ZLxncN((>V#aYs-Lv?$YU&3|#z_;eP${miC8RQ`jT28!+*8&k4v zrT*>2XxbkPJ>oK}#|8gM31-boWkxV;8@ZWdJ|MwDL*3<^;6}Y`eoKc8P z?e1&jXE62U=>~hXrS~hk9H2gBZ7JXPZE&=EWo`=y{DPwhop9?(@X@KZRVOw#-qiss44|~5#(Bew}x<24>l78M5NN*&37+* zV?0IYe*ltgG=!MgiKEfUpIr**#TV{ZBavDOY%|9G)}n90zkGMA zG}Nmt(pTR-L6|$BBCz>a*-VrDYIK>%Zw}nxH+8C5|1?-xX}PWY(c`_eiX58% zskeusK|J+S2RETg?SCLvoXppMRzk9b#Rfrl$GVJ(hu(SfTY&Qw?5p^5pH1+am_8zZ zXn^CHbX-5piuvpQXLo`X&sT%~nlgWW6ZL=TyYnEOfonD^!3Y|SmvWVSf(;5LiVwws z>4(lq+bR^~hQ_9eA7#*g!${C8x$};;?HT(8>j{AfiA zg}}S$YxkEX*=U}!@rb%cLN|3f9AdO2rLICbg1M;hEb*FTvudgPP))l)8`d>)j_f8u zyk(>Y9g>1Z9kPuPoFA%#`(=BejaeL9!D?RhsrOI#`bn@EHTq}4+WiwhPy@?}{Eetj z|19rynq>}avmUu@(^#I3t18p1e~0~yFqWJ6?z%HKHXYgZK4XN<5U`K8QbM;>UI$^f zpm6xLM%c$`5B?H=<@+ZAy>JdI!0O8Wv-k2>pxeg-^VkmJZX=)ARQQY)uGSy+L2(`I z?}RjeGHMv2X`JFjlHV8V15gB8u zhg=h!n!E5ln8!vzf2!bmocI1G?)Xo^o1mvWtMpv*9~0Auh<_7({-${dp&6+Afubvi zRTcv~le>ABMm;XFrx;?I(>Ek5&h5utB)z%2jjq_*TmUOLL9tQvw+m}QW zOGppL({Zt{tC^9ER@R1scHTZ6R_M&KUV$#&Y;+xI;{O*P8wmuIOQqAIX#rc@qv{DO z?T`+U8iEekK72n9pN7wEWK|BV1>hFwTKB(vv~m7=TFN^6eGK{OuDN z{V*&^?ehPhmkSinAcz*qzFN9+2UfpREfWp6Kc4uZ>r;bY)Zc4veZxTVk$oynJz>Dt zLM%*DxjeLR2o|Ml0+2G@x6b?ijoc&vdpZdn>@1ou3J13?=fQ8lgd|%sssN9TZ%h3V zjYeM%^h1xc5zX(kGy2?GrVP=X<~$;I&(vz?1V;QU`|8xoKFvUsy~~YgHeF|QY^G}R zo)C%BOn;c-!vUj#E|jhZ!ZzvagtVw`j+a)Hebm6$MR(3D-SmdRl5WN_s}JaBK3MxXzG4RZ8&%g%1LMDIf9dWu>%^Y;{rebq!GYkv+}npcVa zYKH_o@(273XUo293k^>(bqHfw8EK3|Ooh(YcEKi`2i=ZozT+#<9pa8X`>bJH@MOV~ z^gX(nKqv)x0WcEfVNONz4l5f(4?KcZ!G|tHYjUc2KP$^0C$SkjPDwlsamTdxi552q zkT;)YwM7%CUoI>?KIRWK5oPC{@p*5uQMADVX(sRWY5Eu% zyFn+CEn2`f+;oRj^9iHdV1Yj>M7gipRS~z1bIj*Tu-4S(D^6@1iw1aLV2r1kzjX-c zjK@pYi&0j^tmW2hp9ojR%l*G#qh?~jHar-C>YYW$MCZCcX|4!zxIO~!9{b}wagIm2 z%S@GhS{L!Vx#*QfK?wtEB!~U|l1tUJxQQ(G&3e*%()UE3~Q`eyk>=? zW0J2~MT-)|m&Ov;t+6mDtM*SOJ|)XSR291W^J&v*PS^t)UF0e7_bQ6S_}1AFXt3!z zaLy4RXI;7`Q?c6uis2kiviG+$e*J=!ouhbxwmmT+eGBOZ7TmahOC~2iK|`v`|KngW zfbP%IW>IBx`X6M6KMVhsIf^{tOUx|Moa`KIv~PZHS5W?nL7a3D=_T4K0Tg&lbtWFY z2Dn3RyAvCy&`teyytjzQYZ2vl`J=@Q0vzo64-RDFQh3E;QE_aOC?3;TZWKF5L5G7$ zyV^HUhDpS_wc6Z}DR1rEaxuAXPVqS76N58)rcYm7g?gmhi%!_Y+8}wt>t6FS|H0Cm)V=> z6ujrlmU;=uM7r~Hv?ktPbm7&?Wb8WZ*5m8!d9R1l-dN`VV%dht#26HBvPd_%)JuB3 zjE6jP&()=V2K;jPbPU2?s}VP;z+u}VWy9Pb%UOztU-MSth+tMs1m zb=Y?+H+z!+ax59OpNTIesN@67x;Oj$EH1D?u$QpyuMy&8I$Au#VBF9m*WwpKl8+~k zZh&6T=}CVly`^_xBbdp$ENkQcnroLvjzVNS&9)B7zXJf7S@&rE5%Gr%CAnXWKhfvp z6MQkH2CqusRve3dw@|Ode-&v98#;=#TyVtwTvDFiDcG#*s}eB>c8ytfb8yIe^CQ!- zO1P4-Kc`UPqsZ$j9})WoV*Pi`^gvzNjCjUJ*v>A}9L-Iea_HpfF)KLEV05LHGf3o# zue5)IoY~f!11Rr!*Hqi?8z%lpZ`HQLO1ZmP1~@$&S?o4!9T!$vFF{v)#=rEFkvcqs z%1Ue>lk{OW_f_)V-W*s4Ynl@C{G=q`)G>A!ZzJC7v)_x=pUi)*GKg)S(cQ=}X9ev> zvyGf+6Rb`LKYk8=K)=#0=8;pKVt>MC)_22C{M(?wi*QP3oc|lgd+=mb@sPrVCf#n0 zb(y`gJ*p5ILx?7**l8z8^j38vV%;PRg=v5vHuzNGYw?dc1d$D4NO{xRl1*Vmf<~heycPv z8Fw@C+GYWCQg$%s1SPN2j*7&WO{%zpc1$F{>cXb=18KN3B(ZxKH(WgRpHc$TlL!25_nOUmk6pJ7MALWg8}`8W+uli`dI49?EYQA{CJW zkprmg&p~8s-C!_r&!k}oR%!c;lBc^Mv7;PHNww#UsIQ{JJ``f&_QLA9>?K!K^6xIA z5Adcbz(OW!&``0~I2KGf3_&QWIUo+7S>UjkP+tyPv^CNNz13qRbxhl} zN3RSeuvDUip1gxo>hrTPb#+v;-ym4nrjw1m3*y=o20r@fw)XC&bfVja4sV^Tzyz^$ z>n_Jp=@SMCmNoeVHApWue8@o9a!-D)HS#(xyG<~eCpcQq!jvkV&2a@<&zK(``ZW5b z!PM9SgNGeTv_T`gdMNbU@@W+Scl+~qpY`^LZ@1E)tlk*5&`IyhW?2*ec$5_MxU56f zL@AJq$vlz!s)wcGWb;kTF_8zN&LqC?RpYP@@k>LJU$!e!FH^?erNLSY5)Ad>PrLqQkhoUwwNU zWzMtZKylxA=5J6xLFa&0EXVlP(fZ*I7x0P@7@pc7f96MySaJnyt_mF4jpjX~BUhR^ zQxVsAQVih-INovB;bx^p9CDH#6dgh6jq~Q^aqiEbbbV^`ESi!5!D75WR<^rGW)VTr zu6%8ld2G`E5vaw(dV@Va&mMkwPk#6bf&~)j-$*N2j)Ui=yaiXFEq^~&i9s_@CVDfy z|AG2MgHJL$)Eha%trr@Fd`o?7B8&o07EEzs*;5k&J#xoUpINH~I@FdBBvxp7nIjtA zIBRd~Q1M0|p1xf2aY6MKrDV11Nj9f}owZr78*Bz(NH&|hHkNL9e`Mu{s`8#T;P-&= zgMRYkwcf=_a+j|Bf5O5v@{ z!G`G0Y4p^C)W?23G{9~6qj9|39g!dESfz4d^bio*`N(Au0 zQLDVCI$xGn`qYC@%+QmIr2!Nk$s2AQ1!|TpFppum;WK^oQQeY{CMTf@9vcYLlx7B+ zZWP5bZY_Q2rH1DyY0%%vSkef~*Y{6|NWEg90)x_xY!E9WiTTcB9^#|)I8odtQW-JM z7(44kX4fZ~->PIggHGnd=Dz6nBL}aMtWjx#(&sUGmRR-k^j={3pd&(rkp85#kCkb_kCU1jJrAl2cM!$CA?k*1}pirOCc$=@(Gm0xCljv#n5MyGji!ghW zQ`fO;eNm>5MZK#>roD1O2h8-1)fZGIzIhXXouFMFSmN01?8J_BuWydR2SgP8AA%oz z?P7U@K1{g+I}Lt@PyBAfyMFEG!q^Y`>>>6)6@&v{TzaC;&pdv`VDzaVogHI`Q(FMd z-4r0VZJR@$8m#AgFdPLWN%c;11IX1ur6@TJzse z=Z))%O$8nWw_ivt|^V{n|{o8sy?G3$5f*`lExw5E3AAiUF2@qhS3+MESNXH^|Il$k_3^$# zCzI^T>LG7;oEe_{8xvkn3&|*)Y&4&UpNP-N0~EFSt6w~r712j&G0oEiLiFv1*5k{u zs>S@oD1Ry-|EItuliCqwkx$v|rqs&lmy0UR?Du#lyMc79CU!r^Tg1Vr+ z+r{)5?E6X7Iu4vonfwl)fjram686DUxqm&H7M)|*r=%}=R+Lk9@a0i^2-$N)Kj9Nn zXXRmM!M`-MOh&s-3gGHR55mkpV5xhKKBA!O6hGxL8Vwyt3Zp~5uPhAHaP*Clz?$pi zxH<#YZwZ?6QC_&K4)sa4Li8h^X8BUmGGT*RM)y+e%bb|6I!!a+QOP#QXKalbOgP7EJOJHBbH1a5ym_6K z4VFt*X{IG#AsgKk$ERA**foy{mR#>g$4(R3hC+lo2bIz0R#s}ds%A7$sYO?_gb@!1$S{in%qrTxNB27M!!Iew3%BI6MzCu=ATo3SX%sKTts z^km#TC34154!MmX)-sRsh?OVEWK4gB{sxjvSKWKK=F2qfF5cx0^_6k@p{z#E2PuJk zcN=csXvDbXs*gHkxH9Yi2^(GD+KOL45EGz7SmC=ny(AL6LEs<`7NP%bFjr16poUq3 z>jT6^E(#ol@|54Ja+zaR18b7=&+Ao^--7ypCnonEdmP5>#!WV^D6wuB)t%yT&{Lx^ zOs6zGU45ssr~@fvi-=4A^cphLKR_4s8Q9^Pv<X~c0(!+jbJi$2Rvuf`v2;>OI>l4HITV}G+#z{c>l*h1LCx_+*pOZQK@j zIUq;rwsSr?x-x^+grySDcWEw{5(OMt{pKt!bEEDMiQr}z0`W9J_i#$nlwRfu^Q8DS zIuFf-PcKNkt+23u0Aos#{@SJ-)Z{Q+-*$3aXj3Zojl#_=sD9Q7J>#w3=w!}wUCgR{ z*$?Sl6<*c(OC=5Jk(Bq z7eSH}q=&HQkRMVgGueo#Bv;4X7hbckd5ytE+XoKYlIJtX)6secKays^XMGEX5+}GA zyqcG6cBJ#;`d&nVmnQz|CDb#=;+JC@^S)ZV<>;~n?M8y4qTeMwi+P>PW)nG+@>(LV zwMC7lq%WC#v%sbwhi;^$(fLq`M*T)+;omn&IkyAK#7KiX~5%LhzHpx4C;-j{i4>su77>AcO1yd zb1usR_WX2z(dkh8Mby3~PtA0H1FY;)yuQ-E;}Ne?o7|US(r(-7jsvJn%NkZ*p(JG-$Nb zt(Rpe+}SL{rrns)Y`GL}suNM|QH`6jyBn|j`YVxaoPI}^+Z0o2(J1fw-4hG@We7!P z)K=~9@+UXu1T;mn<5#s+Ea2L^jjP4{jylmb&sf72^wEN85&Zf>Q4iS!MTn;;3o$Q! z1LT_83#oOfETqzAu}4;l+kkO6w<5IOBrgBQ;ueMx#iYsSkcT5Y$q0qgSFuxYKeg?8no1wDEk0ReP`h3l} zIzo@k9wLObxEC{f5p1{99!KW!XVGVu4|)(^So|N=NFF$s^ELyX0{Rv8jqasN?CMW^sfI8h{A)(CDJ*n=(293 zOU~_9{cPHoJW6@rl=1QF1t&O{lZ6wM`^Vy#chCpXUl3OPt1)P=yn;1;#lZE>JXrIW zoIMb?@P?{-&Qca2#~1TL)KOM|^_x`B)k1cy#(f+xSGDV*Ob^0&Sp_|rHuZqf6#YE0y372|LISMX zuiV3EI^MJ0wad_BS}F!xhBMQ7!4K;a#A=*k+RY2la>Yj4Kx&^EKig{_(~Q1yKmDb( z_C$tKAlaf8F;(4E(1cV!t48x(XODiB|E`m$Dz*egwpvkcCx{E1xo&7SnX5*kw(VAK z>8JJ^DjDUDj9Q~cUwWJ-vFld69$F#si~5b}Znw+%MMR&wyqcf^&QOSEvOh}LCE(~7 zDRYEnmny1{eK+zPby6EiF%k~!Ud++9*1Btx_NFBPiX-bpwgsdK}*`1iWb#NEj6j<*bERj z4B3=t?ugio-`lR$K8Fk~d{pbr70=)+>N{zpblhz@S|y2eM$Hq5CPVNsN2|mmcrIh| zg^VaXT=NG336?=!lS>BaOXIZNZ=qBf(!F7|Vo-wNB0VUJXguN6@u*cm*LmcNMG>^G zgm<}#G3*-*Me@JpyEc-iV`$grK)cRtX(BEneL(8f+q}x)Ez>qO37D>p0+=-}a2dR= zo5$hDuA|#@$-ZTOzz^T4MuxfzxG&;)i2|j5lE+neMv$c-s==-dGNb@kpK9w$s};l^E4#o_-(>TN#b5W54+fq zcQP7jf-o|siG$)GTtLd}NFl)>8?Mn`LC-`8HX zkNk!Q%eQJ@Oh|_cjo+H)o|mz6`ojD++V?MP=sQ0y>d_~=ByC$J=Tgbr%HCf0elI6p zZ$bMalIIIRUwM^ATavof?%Xfh-sl?heOBTZoqrL!$XhA*0L{e4!w8SQ5fS~=z9E`5 zs?k%tKGoGImkQc!<1O~R_1S*z+wbis__ikMMH@d*kmJX{J)-SBx{H7bAVb^c@wOE%hRS#`k>1DdF;x6x&|jx8EVS>$Wk zCQ&aloE_ke&!gWfeqyVBDz&8<5KDzF%(hux4o zaWYW?Kfv+Vt)+)Ls2?#}bIq_;mQ{2uAd+Ct;V>cUa)5kC*ABrM?ISp$)$YZ1QyY%x zCW5xvt1UAt^{(d?lHXOF^4+d49yX%(%iu^zlzfEl$fOqv;Wn~F+jH9Gje{)Cu6Wb3 zvXX1Vf3XTzI#gABQCbnRW_?ZEa0AZcl$E-^F_GZ38@pvb4B_A&l*YIA`77u#%ii{Y zWFtDm@2&WaBhs4dl}}ChTcJE}00D2rSD2}>kz*{^5wi2WLoj?4roqkGx*6Re_r{g1ptx?~cj%_a99m z0^~t)vOyq>2<8JdLmOS1`Xg$0jCJL(+MvOsWQT@n%e2^y0(eUkoFpht#=0GRH+isa z$BMFv6|=f+3(8e{wa8mQLYAE zsy+V6{XD`A9ruYDcLY??&%mC%ZThJfkbeH8y`Aypspj>6BFWr@EcTRzbDo`qB-$6v z(_*zeEj2X+^(B<JO{Y*EsNPy)ITw0HB9TZB#G09@fQkKMw&ES>TKu3msrvHD7PZQ z@G@#=EymJJ@$c}mW`*nwuW>}V+oHGgal4?iFq@Y?@%hjCt}yTVY9eqFXo=F*m$dSx zQZib%bZ0AnRBN`zC-UAGz6`PVVqFV=DAKS6M?GZPhw;?fCp~{ihv(zLpuF^$i*Yb= zLDTc8s?M(0%d9Jm}8a8$CPHODzG@5Bc4F>pL_yG-eV{zmUx?jD=MPJ)fDI(<(uAWh?Fv5v(>TSxLUSo93yZXLyrAHUi+ zipRP&&h#^M>`Kfnh62r?ChSl!Fg544VncTp(8T{s=oggj(v4MZ1<6mu*B`)nin7bM zyb%L@K?CI_!Y@MKh~J&Bz=tf(AYkW|C!&Y}r`n@)&tIO2M2{piX|eFcL<`QTuu8r* zj>H?*{XNO>WHyw%u;H;K{d=?1B=h>$)NkK54!)B`^>>R8K|u^#e$I+lxR=AvlDr$t z+BWWQ`bZ{O7J51?s<^fLPJ;aghS350PsmCL3+RX*vyES)E2Fzo7P*C$z^{R}c{zGD z)Mj9m2_?e2h>YfaD=DsP%(0$pG0Xo)6e~ePX>bnBv}@WfB$GBNlWM4K3XC97W#{0I zKdlwe3foTpoF5XdH`*|C%kw)JwvB{y2fT(^8Ak#}BQO)2;8c8z|jw)&9gRg-yD_YmWjREVy0j2boO7--|4X7 zsM2ODzrBlZflen>dkC}eu?tPkSZ34WQvDNkJH9P|!V?6B;F8Wv7kty*O#o#CLG3zG?8p(jLV&SfgR{g$`?4nm-SkWk)bgz-{-#gg zp6X|8utu=pE301&r_iq{KFi&Lin`vf>ibNp&B&k&tM)4bW={JiLDVemziUI5ze(F- zTS~RW#+k!F`CttTYBl{cp2xbS)>~nHKo)+2*Ha|p67oa12g~q2C$RImQ99=UKFyuF);ep z$<-NLS%T~mQCY-9LVYt_I{xG!eL)2%eDCo&R^eQAl?I+4*{v{%KE69etj zIVq%c1EoM@gRI)Wi5xNOQ{4$%JEW-IWh^1s?E@La^U!SRn0oi{gxRdRFPpk!uFpVL z8ZAjAI_V*kgOMMba@8|?T+~E!^bhulVJsD+M9y+S_tPA5C1$uSAI~1F?m%VPBka;iCdB%jxF)%5pna8wG~n0E8P|ch*~fd~amH zGDUTdU;p`5;63xWV6Lre&otor2X$=Aj*McOZ|Pc^ z{#k+th6_c99`HJ^4sBde(k-qL0If&+(O&X#rO}TD=cF{gpSb?7OxPTFj+ax-L zd2)TjRy%VBLU*cebF+Yn?$>^>5v$}&3T8{65TBxbw89Mf_rs;|tg!wEg==J$vdH_hG|!kAMC2+cP?uJabG&S8Mlta1%xJiSA0s zwjEEQplO-YNwDT|>MeWLcwXBB3v++UZT;!A06%$#q}W*mKzZ9`onMB?HlWI5-Y(9+ zfi}E3Kciq%8PLs%678B7(|oZnRy;&~7c_!lZ)^~pI5E>+2r|YLFh4VtyzO0veBklt zF(XnIziB$V)I(aGp$%WR-_v#PWOAY^JC0f^Auh=#L9F$3HE%W44|TPK zuYec2gV=Wwd4CgYIOc0tEhOE22?eu(27}t9=C#SqVj3PJS#;4uKA45D`OoD&XW#nR z?IQ$K9tz{1nUg<&xI1uSmjBafNqBajIYi%@PLYl~g4yV0*Fhh5_5f}5= zBisYcb_;&y+Qs%NY+le(CbFMAoVl7zG11cm9t@tjl$he$_#}EU3E=!oa|9i?gC^QNL6uee8R(?>U&7&n?G^Lb=h4rpsI3 zkPx3T3x||~q`jCF>=X>47)L)dA!JT2uv!aH26Dnofj+C-V3$1ReQj;GalZ=gmAw+3a55I}?9~~AV5JHc8r_=% zENmL-x-2_gHdL8H&4RWYs}aiyk7T-CUpL@yTBYM_QqOuX_4s(TZTdek#@-uT8#Qh>gi0{_XV;Cq7oMbU4S$l_-PgykQr~^v6@-r+>>+n6meAYtPDGBa-Axn^@wiShe%ztougWA|!4retF_*NqliI?R z=?xFR6)^AH1)VxEG_Ma6PVJ{kyHrPV}RQQIY0?x zPYm{x$xQRyO$90JM*d8tfaI9dU_A)iC$KdIsI?n3Qk($Ias6|#_yPDQZp?iw2Ro6&wk`0wykT*$ zJU-j4ntz`7h+LR8HWUwsv5upGB>DITNw-13;HpMA>TM#6SP$SZnnfZM44C});0-bJ zGrsqCGG>il9ts9cip?0tkKGB?fs`#xrFhwRL0!*o&2B(bcXb1>Q{i?E<&xiHvP*T5l4OI=hoPl6(=}aEgZI~q ziJlGJx0_b;X#&1=Z~BM+X-|OOt->Pu6&@9*#WqEXBEj;izt9h0^Zk1GvD76qHFfY_ zNC{l;hHUmo656nTiMt%?F`La_+1pMO>wnRi9ru`xdXGA|aUe-VfstuBFzpkov5ei% zc8|}?0-kn zT2zK`U2qW7cACoZ-IvjguIXtIrQL}(-e0JS^+a$6I&&e}2Z~o$n3wPt>6K!tkB`3Z z!T8+5H(N`Xe$Lm|Z1CMSmCDw@MD+n8S?YOF6!AAV-k7tF?mQN1fr2)#8P8Q8G z6Qj@7^+t9jDmPZ-&Rq(Tua!>1@&M#l)s@z+f2Aa_;xi5*5o@jU<$reExY9NzYHT0p zMY}wh_BKGLEax}v^qjuye?GQVt0y-KPZ~uxJL{u4k*Vp|H&)dgKZ^$^z4Uc8c2O)u zi)WjoLQ|GMlIl95V_47JDyCHkMDfY<0UJgWUg-S>97XKy&4K+fEL$&8zMcgzeGNnzRBB~Z4~)-~$S4G08|5ZfO>?>!H&B8GA3 z8@f5dmTlk9k}2NBZSWe)K@+GVdAv0M03ZNKL_t&n)4bD&`rZo;v#}8e+H73bL9N=@ za{Je|)2Gr`(gCtV5}n6`?^Fn znU6{3Y0FDdstm0^cD3}e6&|W^KDV*gsXj_>Xxph}qRYRv`zZq1?5X8Z!Pb9cdw1IDD=@t^Va<9H%!G1j#LU7AHoibk$ z98y?e0wg_4zy=}TclIrIjA)kWz5CgIEK7T^ON9l3fK{%t!@jR}6;ig{bgqMxTJ`N} z_na8Kc+AU2=kS|I5kJ9W<#(UDp4O^;Gk?(JI3sy{7Hoq^dT=57+2GqKdO;cz3vp(A zSedG3U2ouHNh?E0Y>CCSg{F5s-FFU@Untt$PNG>l)SPNbMwXvD;%fE)Q@?NC|1XP zZby|^l`o9#Yc}*iY@lQ94&z~)pgjG`QvpXgpnH1x#5l4o z9Qlay!cj00_eUeQCN1MjbC-sy&~j~WQO>{{1Z$@Lko@?BUN2)J{eEd3J? z!w*SL;s`S+Hv_zzOqoWX<4kBUCCUxLQ%9w8L2028eS@!L%$5hOAmf7o?ygkRETw$p zj9l1)aj|Y?FM|R|0U!l+w+6SFu=Xt*8&sALHf-^j;4)7&AUr4tcVCoJZDuZ0MwZyt z?YkISx1uj$8{N?QJPQRAMj=`&T3YiCovQm(UV}g11BrM?N1n#B36F11{5V*Bk`eBJ zbK6D5h&Ln=NCR+6Swauaxo`;tY8GlY0&Y^?@>CkFXTO{w)mTLmcs#9vuUg=R8wAb#T@u|kE0L_46gp073Jb9$@1Gk1+70ks+0xHNB;#8Mc%5d- zgnR{p7w7RXpM3$}X2J9-ZV`}&%0UC=F@^)ZB$5qS2t65J1U(L^UHhW`sj>G+2I#Z- zZD_Bg6w)4eI(-Yb)NXx%Y2_HdL@ea6TK^KolU8Uhsj5ndqkvo@8FnC%c75+{Dv5uE zZq0AF0ip%Hn3OCTn9n}6NjCC3-B!9jF8P~OAhf<2@DFNVU&QT;nvnPP=X0(1Z=ckp zk6YE6^sL?;~c6@CXeEufp)hYI=CT&3XA=VgJOxI%bK$aaDwBhG9Kvp07iahnL z@Kv-?YX+_9ImkM8f1mRAREBH6fF-5J&$glIG;ge;IYQZ2e~O?mK9>Zal>IY`!KFr;Bsu zFh`342?AL-iKlmS{z`r~=(s&M)$(5bW-KIQ8;4^)>LL17@`e@7EyohzNXu7sJG3Xb z4Fc)J+xUOoK-Q3(-V3e*!c5HjyE%5@`Yt7+mO2Xs;hkkvUU#aKzF7>Fz|!s?In?Xy zXl)a6XakUHl&x+}BF8+|09eKm7afl&AEcSa9qmHGHl98K97DOwTTsyy-ui%(+^nPF zy$>^FKeF`LDn}c+(Ie?CA+z|2By=gNV*ZG2tIAK&0WAD88(YR32D8mtY+YX}n?GQ( z+ZTO({gbF~4BRNMrvOaCVDPfl-M%{FegIp$HbvS$seS#Et__C2ugwDGadmtY%lVs1 zqNGj{0-v~S#{@TGU(RPXgW;lggzF@We-q>kcKsCd*U((P6=Y+3U14YUtLyje!`rxw z5e@^g8zk90L>Z>|4W0ceq2I|m-_R3py>Wa6RBy3m+$4K50YB$}B9YgTKL{bYpW#Ez zRIH9tPHghO|ClFdPJl0~~go%5Rm{b^8F2WZG3Ikki+J#7t^vP5^ z!W)`yb$fdMPC`(!Vecg4nzmxo>hIDPro z`x>b;P2hr3a^_(&Y$)p~ulDO-^!4kX7az>TpAQjzz^Dz*(SS*xd%{Cs)E!Z7*J}%{ zQTxaLqQ?Is{{D+TpTDUQlT%5}?McFVjrqhGo%!fxjmq_Q>2fj= zxZiRV%h*;JQ+ z>+xvU#B_GbWR|p}K0CoJ$t(+#T+2&~bEKkx$7r{bWI%Gb(4ip^cO!KOP-{Xn^o;tu z`6#hN3m!AZ;rNkE+O)%THHUbxi=J$D;yN3P)eSrA5M|j%G_*lxL^>^tNX6v5+N1*O z+Dh+bJsMEdS96GxoaUP?@T}~!Xv4rg;OdFbjn?Xl{ETI=!PLI~MeWx=snHkFhl%MM zP+#z@3L-z*y=6kIN=og?!NWy5+L}wIPev$Z+gDkd}RtwObTnL(Bka z|M>%;+_XtT31<$`Z(qCe_qB2Do5`uXtp8vWOK*s##Ff`Fw|$VAl7=7D6hmQWp^1KX z|HB#;&^?{8vTardH5}Zn>-FqA#b}2>aF;56Z-0bj9uEm3?%&0#MkQ#F)lY5~{^+ zI1}wnH+*=Y!7m+MNIH{w6A2FwlvZ!j5wXB?&HSB9HNj#NSY(EE*LD%GJVp;47g?0; z!f@n}(V>Z3wlZY5n+U9I;pRXE&vw&dQtuL#CQOeCHEWIMlLcj6T6h^XoZPd;Pm=6H zWj=y7F3GFmnXm8ohZ^0eSX`(lUVc&g^Ygy+_i1QTz43 zh}$os&tJs+$<{-g{E*@4#D)wg=5E<4brbrh$eG2T9bAieeh3lDQI5ud+8Uo>i%{m> z%O6c-l-AaC?cF7g;AJ@X2j~>u$WN?wc5-#X%v%Y0hDXaOQpsUjaL5c);RSoPG$2P= z8(Y5Gv{=*?o8g%F9PIahp1m%9(m8UFyQEF-88tJ%-jJkompAln7o=tH%^(Hpgs#M| zlz|=Yg0_!8XSjePA%>HbAmjAsx&o9Z^!A2yx7Lrg6)+*+D!b%d1RDggV~*Xn39xHa zA+}4VJXU_q*9M5Qq@fmw$2>%V3jgC9v@;i`xmgp_v?*&N8h7288h#DAG^VIs=e;KfI`~~f(gU-L(NJ$f+SN9uon2JQ zLnqlc?J|=%8PE>BWoy*F{&_t)Kwm`sA*N5lx8z-0RjvIBt~p?4YV<|z>mStG7k$3} zBI4gBS$MJtcs2FH=v*1f(4s`FFScbx_e(;DQ6EUt&K1`d|EfL-5=4cCF&a}9s)AsYA^M{RQewBevU&+zxuO@rUe2++4LYG40FUtj;aHi_u>b=E-H zZ|*u*2LB>F*HaA4-$d<;zW(vQh`#O{W&=raB3IWp1m%q+Kc)qc$~|B7)6}7o4Wg_A)widi^+_YV$Z8) zzrZ$}jbt{o0vn?;4w27g&w?HV#Jnng{iOF)XJ;-_cNHf#5X5Utc>^Cn8?k5Zd9}1L zieUlm60bY{yz1W#e+9Nm?rnR1BmG|E_?gXtA}FVm<4h@)GN8-`@@=Bs*jnD@7?PD0kz;VR1jfThJ~! zueo6N_v9J!J89b@ITGtm^$1(1JMQ8LkT-6_F)Rir`l9>5VPixheaDDR<0?SrXzV78 zzXXij>ksD1QUt*lHqC-(HfA#>1v3%9hU5u{CCk{kDSPP>!)a&pdu`=RMEza=_UoVb zH2%J6nxq0u1;!jCCQDegyFZ0UL|?!DMPI-E1$-;=A>ri!pHDZ|K5;A*oOm5K)E~r;ihbnpX{N; z1*B&XSORk1@aNr<*>J|?`=NK{`$94fGIqhoKh37= z>ChFPRY|H*Z}0Q&7Q3$+Kv_u_-EN$6pFTK9DtweA;YlEBMB>$K@rYsjf9$<$)OJ@< zAXxi(zwb*zkU&JkEf5&>=Q*6I<5+vYJb)2;P3FOiQo9<&s8a zoW<%93?Q~~23(9nJK%lUO3TC$ff3YY8W;#9%z}`7-}^o_KhD{ESJkenUHhEpO+-&6 z&-XshzEst&UAMi@KIf2Us?&!m6~TBktU9h_^yHxL=agFNR1l0g=B`{KC=caPj(pyxBh9!!gZ9LCBeISTs=-caFPlXJ7wa-T zZy=>30NhWX+@9&P!A}`9bcPj-){OwAcLP8?p7`FKn{4squ40F(TBsXU<5>fWznn*f zzKdlU4`a~h=Frpt^eD?)$=izTp37z_5j{%K-Zl`x+Nz!Wq5QV^83pN4Xn8W~d}``& zB+4#73)@g%ncW6Sy0xB`q*0cvR&k4Odpv>cX-3g&71ND2eM6(`(m3e%Q|D)8$Q=W8 zep>##qmOQ(o-f6%Edb%Jqcn{6j;+~{l!-pj#AEZtX}ZvkOsBUJ6y-0Z_#J6b|M5J<~2+&rxV?R???(C^Ob|a=pb#9uNU0?1-e%o{(bj+3uv%mkGaxL!lWKC~v2;I&HM2 zGxO#EAi#dlNl9|lsj6A>X=IK1Z8rJAiM!od*zL|0zPkGptibx{Ei<&KsQwsNbV`tw zNu$w?NIgxn3izo9{RWCia+a+#&sF19Tdhw3^pfQAQE858F3a)H)K?F!OsJM{H`+&) zln$wV0*CwnaQ}(5Yk*8d*R(oiqnfIN5w26yz@x3?C*c5jzyRfuIKT?J!BFyw)u=rx zY8l^S_2%c4WU8V^r7YUF1vMXR%0>u^E~d&^7jTyNk3#_-M2EXHI<@O=Knvu8!}x-I z;)#gfM7^lNzYlT0NQ^yS6G!TA5MU<83C7TSS1L79HQ`_vz`AikMlDn>1=6-oU24fq z_qvZdR1paCZU-r{Fg3=-ykA7}()lJfCJOG=34>JsDid#=$U?kLA%rBF$#1Do(otq) zzGpr`UMnVbD0`^4d;lErg{79Wb~3UgnzC*P)BES6Ns~4vQ}^OHd26FzN&LaEpPrND zv&vh~GN2C@RMByrgY}<@eSx{x3>QQ6M0(QHLA*CZg z+)vLNn7eCxql|Q>lgFd2M(EH~B-U@6EJr%uZh#0W-)c+seF3WgOzoGIDI2k*19sWIcLwC@n}LiOAie z6Nj$3t-K&6WmEkAk_B7$89Rr}Fs)Vj)?9QbVe^xJUW&`@2e9-usF zP^<~22M_{Su382(;0C5TiM}8Zkgcn8(#rj2zTpGs(*(+xn#(nm2Yt@`Umo|49;vz< zyIS6;Uk|jP!sdg0ap?BOq68mS;Mo+B|DauPQMPm%7v(;iVUTW1q0gt1Bu>Zk=i3U3 z?e2@d)`zQlRLR^*_n;}Q;=}acQJ5cI2ncC+5u{!0PPc}nB;By44bVbqZ^+2Dv|KKR zE%x|d{g=&~Kb!o@v*w|73f62|ZKte5^L_bueX;|m+fy=!s5DAu6H9bYBeJy5SYH|5 zR&(U|vPIB3Koc*rHWm6Va&+Ve8e0#{cprl)M96ru<~nNLNO2wVAl!rUHR(eWuOeM! zlm00BzE^+%D`Q*4dze@KMnLHtnujRSp8Tx3-W3q5iRYCL96#tr&d`Nj zqv`PaPh9XEI-%7PQZ@*J3hN*(b>@|Pn}C|&BmhikUm`9a>D18F4PNN73HAd#U?qHVtFO4KA@~V#88l3 z>N{rTq5fe>c;W~xuoE=(LL2)*28p6k4oJ-kDb>10NRbd-fBE@sB$1SDh{Ai0l?_>H zMayT!hmQIOg15w}6zdO^n*BvO18H{_CfQBT9pHzPW~)k>0w0&jo;jZ*FxVe)x-}v} zeU^fUV`#gzr(Xr7Y*S z#NE_Oi4YP_pCVY4Efh^zA_`S$6bIC}JYKKqZA--BA=gJNWeI3jA?l@HA5N$hAG?}e zH3*m#s9$POxVCtA+RzGz>r|-u&LM_X@`t4A0c@+3T4G;xVCC+fI`n z3TN)$eZu@mxmJtKm2N92a|~Vp2C<>UwT8>G6GWqcyJ^7<+Cwtz#G--jPN-hN0*2U3 zqgK;~b!C$Ppfh$Oke4H&yf~SCJbbjg5ops7S!wGObP9^2ESI~aqr$-4*p)ok^$w(Y zP7OtyiggKin`b7EjgzqEM|Lt1xEz*y_Hkc)VVNw%Qclq^YSu-mhUFOn^&gR;xd*2; zK*0S8B-lYZItP62G#pZP%1>{nlRofAJsOaG-SkQ7m1-IvTA}m~@zOfWiai!(AGVpT z(2i(yKAOvp=+cF1{*;FY&LznC6#GXXwzO4SNNb1y4@Yy zB9l~K&i?HMV?s91cD8Ix=0~sMCsAi(8l8E&kCi-i@yVxm1Jr~k#80Hn2v>f4m7>{2 zR3xZQPQKAW^bfg^l9X6!Xw5KEO033Wu?r}EsBe>Kks_Au2`EpkpIOZ1Et%|ucYrCi z0Rjb*kwN53yf~p5emMkXG|vMjLw9?22t^-MJGOE*I?dWRaHeY>Sx9PW{*~sff@w%g zZAj3fcCNP9IjtIz3L3gu#{zZcHDp91zDBf533j`)^9I%)5cZ76<~9TX$ZvCDr>+EQ z_A|Bh(`1vnuKxFesnQt{Fzq3wGXO_t0Z-0*1VB|xM%G}k8+ffML5~J%>Sv28&{(4( zw*@Hulb+h6(He@zx@nR0t0^_1g1HKs!={Qo$!Q>l(!K~M5htRUI|S@rJr= zu~60WCbp4BJP82VM7L&55nIfY3RucgJ6o0S*tf%-v&i;^uwlSoFLFUB#~h7R;Mw+Nr=qo7lSZPA~MA*K4v0fvXJW0WW9)?WsGckE7(kgrf< zWVNI&-)>+`mN|!xQW&?VAp1`e5%`*k@`9MHBQ;T-$Ig67J1D z$dt87_N|MZCgtDaC7uMh<}RNRpoq}q&8?UAb2Q(lzn>oP*KsD3nI$%LjX4TGT8%_u zwXjun!1M*(@zVygGU0TMHI+?}70@V;pl9(Z9j-AIti9%ERpc(irEP<*95ru^qQqjt zC!5QLje!4@2!wnzbCRktH&qx=6tQKp16F_fcgcnW(QAXER+{%jq{^675xFh)d0w|I;TtH$Q;gcTogv10d}7S)}cbcTk|^ z0$$s{#k4R^b?WWhIrB9(CMO$fDIoxqGfYYj;U^(zB%r(x4Y;}x313V6Pe!}Ie<59E zhWnBB&Buy*^y$=slp8D@9%w((RNJXEF_}7@PYxJIje@dLM=Y*Xw=DQq|g{m3SMvNx)XG{SmZfsAiEh!9>wM{}Hmt190gn{nREn?k^I+^qMY34s5~X$iZSK z*sj&m(E$)V?tG#$H!T+?4~y(ng9N+XnTZqJcX6;yAnmn~MZKyV6Zu})?p^L;JC|xO z2txLcCje{oe547p@>CO8pS`H4TvO@vC z7BOdH;ppIY|VvP}<%Oc1?%!Lkm-$ z<7RS}J0Q9*)t+Pv;>=%@vO3HG>;skxsX3a70o`%DK?Fb zJRY7l)PFTB)IFE_9Ed1bqETav=L>@}qYpj|@Da;Hys|<5B$^_~1N(uMXe2i_Iu@vv zY86%N)8#bI94`3)if5` zikqi6OWvDG52r*s$S-w9&zrzEUpydw;z8+1jKCkbF`uAtF*&s;fr4sU! zUXBfs$$b-#xD}z-NiDM1B2FeJFC&Xxn>N2|kA{^iC+1uam{Hl$Z+qt}6q!D zWTh$=vMNqSdvhUFvmMelK}y!B{oqbHZgtz*T(Bn}wO@Ivlw)`FQDZ#O-B*sZY`}GF zUP8cmqAKZIfr7K}usnH6P?MB0wUHohIU-ip=SiYL0G3B1(KTPcJYJnFEZaU}$DibH zc5rI1oRaU0rzt_&9TgsnqRJr`15Ci~f9uJID*oK9oVbDM4J4R&I-1GokjE?d%~g#I zxUS*+R)&=1qjAvqjDpl{BTjT_3cfY;As%>fH`fuyl0;qO|D-)4ke$iMICUQ6{mt4m zTd~Ck&B+QIigW?OOkhm*thV*^cN2y&ldjg1u-z|f2uMD|o2(6zwu!7ps7YkL#({ON zwEeA?_YvD$Vxy!l+qrbwFkntupHzv%tgHwOak_n)UfJ%Nhkih7s~TRcZ~4l))mmZ{ z*7#A4UJoC&8d}z)Qstka+TN_W#P(PNlMnhcYI7PMREE4p=O^&@im`3D1Q5vBJ?F}Q z#YT51|De?;uy4Dv@u?S_GzZ6GV*pA(i1ZP;s5BXED!LZ}mG~Ii=x7?MO`8pZ?$HrS zSdHP1lgP=d8n->Vw)0;62113+h`Xz9&T}k%gX2YQou>a>chMQR6Ux-DIwJGFImU>& zouKp349rR;I};>0^`O?)0Fsc_Y1HSLPI`TDuzE%5!*cqrqFT=1N*Ee)4nKX8+4q_a zq$Bf&56}(1>U9tin8E74cB`xA>&E>Cm<*fqQoW;dg`L{WbiQi6a<`~w-7np7Ewr-!3Avu%|yP-kE;Zh z&v(Wlj&Lw7!{&=<4}_=22|G~x=U3LsiwK(c)!k!so;o($Gj}$|7(_gb_FHq)W#17z zR!4NUZ69{`S^~_}lj20J*&wgE-N5R_`BaGPHOYGip&dM<&aRzn zQpH|3E3~l%WZSu?o5{3bfGB?%y*%a zW5)tN-+*-;*6Zv0{z&{ujP?L&)fqZXL$E~8BJw!^nIH_UFH9I^aH-E-N@t|K0p=?* zt@h;~ms;!Fdp&whdVG5I#78u&+x<*xYQ^9j$_t1ir&n?jE}&~+4yO!CrNaBlM@BDA z!La>#%6Xg%1CvA-F+98u6JtMnrs{_-i_Qo;o`GqKJ-Kds3G9_mbv%~|ET+TdUVTmm zD=U@*3_XZt*e~-A<-NrX4s`G?HI}MHYQWW=$Zya^z1Sw}0zWjUkwyS{0oCAB>6ro< zvkF2cUOSt5^%L~+j|y}HAkB+p(t#d(k#p7Tt{`bQH&~)i;%Xifec~g>z9ZA?sq*J> z?T$EW2aOM*2&%&Hsm|dmC8ynf4M?9vH|?3SU5~ehtj!KB+Rw&Xx@xG^_$K30n`Bn4!{kIYjBnRU1FwM1GgnY??LcJoO zX~QUMzCN29Ki^)l;h5FB#iBM=m#gapQ| zY5{zW$JP4q5Sv@nE3Q_!4a%-0+Pb{uF?e)-#pvsOtdXmILhq|rFeYA0Hx%XKklWzl z^{_Z(M}*Txt93O3YPoy636~HIABtk~+YCao5Sdt-+x;l6^1X*! zA*0bPv$~%*M|N5V=@?eJ28qloi_PvACd7OV^%Wu(8o*F>C8`nw6 z^Hj(;XZeXFZS?F!7oC44-Y4>9+Lvo1*C9^Tw$LK=XVR`M+*;0m`P$lMgC9{cdFvu7 z&pfgE$MI^eMSasy(>g#}T||3)=$rZWRCxSEWuW^$7n;lb>y=$vp4G<3u!L%-e)Z6F z@1=z^q_yhRbKCEY(62S86ydthdeDLct+R@H-cG4kn+NTtX;wXijoiR{AVH7E6+@m% zv9dv6570b<)~VA0^|Qnghbuc_8?;gR7C{y519F#iwJPj>iba7he^wqvXpK|PT zkE;a|hu)4$d61qJB)N5&S4|4P2CKB;lA~X=d-oOxsww)|Xgltokbgyhl#Y~q*?E>g zK_`+3iKINkfZ8qn9q?@Wna|}QaqZ`agr=QnBzYwKc(}uxW!kuDndiq3DUc;VdDO-L zO8pYvvrV?vph_vD*N(oUTkp;&2XT_#kA9aSfZt+p zC_SO^sr5AaHLL(#iOf@@5D5R$(7>kIYUtwdWzvSFeB()RN3zG6y=ERWV9C3^qa>8@Ne(lpHJl-*+_+<{J ztAvG?%7m9oUZItfz${2LV{mxKRCww4%VmOFHi(|Q>^2i-F+b)h9YI^p;n=j89CF@B zji$Isn;)s!1ap~`WWGDPHWs-N_o1D*+IB)$5;B(#ZdDr6S-w< ztSxAjVfx*^AzdBgA?Gn6?$^lLm0zUeQo51;hqq-Dqp3duOhI>T=XJ7Z#khr86zI-x zaI7<|eg;8?1Q>NycB_E!%Y(8SR3r&!XtTs3Nt9j3G3Z+*%ci?8gEn9w`A#b=u7aG2 z0BJW;w6J@X&Ix;DJxyN?MH4aA5F~oE8hbKOj(Vf)g-%i=(M1+N#{3b&Sq4<+hLXT6 zyd|LJvGltqPHXK#|FW`J0BLT0U^F>XzQ#K;1F%;!W>SUamz>LXV=D+LLAiR~y(L2f zhktr%fbEr76U(DwDsxu(8do!^1iEidv(0f8YW+q?zS+5W>?{cri(zzO7-8g87_F%1 zxzCUDa~ch!mKeOLh`s|}`)$#(HXi6pLpajb*EkUl`1yr^oD^C*m0o8@=jHdXN-oed zlwUNWQM`xHb5DnO-LQXC*_*XY%&XQkiBHve?IK-ApIlW7>Y|6|U@nJyckcOFA?Ssp zO03g2tD^Z%kP=1btx)=y#@cFcD$%);VCkJh?ocNOG)G-3^yaMYRKlX3zqF*Zi&qh* z7BW35b7pWYugV62cZEn-im5cQH(^l+=Yuto*5r$8_#{cBa<6gJL7S&J~oV zazGgz8E{83-vi1biATo@AbIxe*5PoOU(6fQQLN~&mSBQ%HD9{?fO>Q`k{!n81(LrH z5QzrF2wXu(H9uNOxYvDC*SzIj>I4+MV8z|_Cy2h`Sf3Lle)Z4-1o zgjD^mqq1*1Qnt_yP<(drrQu+@419-07>dS@)D}y;*%q74y{MrbLLM~EVa;1D!T7Z@L+eS7OVklmkd)O8?W4MZ_Nu@?xwJj9Uz{ zHI8IPQ~SJVl|elfya6IS7H;Obw;yxSO^Id z=f)k2%{P;06TE5am&6Pt-lS|2SoF0dg5VPLYg#73ny9Q0Rv%pk`SdwmKa`5By-frY ztTznmY9^9%>mN57mG~x^aea$P$KhR z_c*LqN1q5_#SJ0PvEHIb5dWNg;hX9!7D@C{ifD?tAj!q$wZQ(l2 zCna<%VZ+Jxn<~vo>Y4WZDfieb<7{LwXy46iqw1Vj-b8^mAbHO_O@^C+d%nUR1GS5` zg|=eq$s`~%8GJgnTYV+vYwEGfvPG)thF2>zQIP5+g_@oecIy%;QBG50{f; zpc@LhE^~%+4D~F%w&6_XsF24kT}UF_M6AC(^}}tla@|5ITmtZoKH168+yS%kb)UqA z(y-_MdjzGwk;RcX6QniNF4(VeSxfPIG{=^I(kIb4Hq4*vFT~H@6bAN2xR zECSs(jrH*`1jo=Q!~hRe$8!h|Q$B>gtpNpLheq-JLz%#6y{@q#^XRgsdjJieUbA2l zI<~JrgCM$~vH+)#+$afQTcK!tBfuljsYlW*bTqrtr#$GCdb|;2tUMqX%7A)2QKm#b z3$n1XqHqNXnngG$B)KFEMH;5%B65+z_N4fH3 zb_({aqHK+<`kD?8Ar@s!9)&|>86!Hl(r-Im^$mj3!Q1js%AG~eF#9$7c6#$r!~!)+ z+Pwt~8l++q&<32|J?##bq1Zr@p>UuII7gIq^?M`h9xk=cs3*CNz9t_Ks=27w@sx4V zWEyE}EfoaTt|gPoCqH9hB+z*eC!1x|r}Nw;M3nv?$+Edxf8hwk94j z8Ikif@DyCl>ol{a;!C_u*XJ#9-mwG&Ge60`f7{eR<}Wpxoou7ol+<}N%TaunY~)33 z^W-;;*I5#ZOSY^Z)jF!f^pJU3zAX)}2LvL6g{u^9b9(%hzAEAY>?+MG>5Vn-A%~e4 z>iQfymWMU=c~WTORn`aTJE~r&^c0>d=h{fAuJv^oYp0`y6x?cfUx3c7uTO*+J5KFStzh`{Pn3X8^3t@z}`%gAewzZ=lVHhZU66 z4>$=qZCJi6F<>zd<>%yeG&8YDc=WAQ&<>LRsRD0)yg;YmV?qP~MDmLH+E3p?BPC^X z07xX34&BT!SR1-zL>Adm)KZzuCkoh=d5OA?WeKqTjwb$+0vYAAUW^JqP=MWwy-uI-^#%|H`ET5H!9!7f# zAmR{!by%VA|4Oqsai7Ngb1V%)=O^mZ62{xJ-lec??u))t)|?A{eKrv6Si|v}KAy_g zO-d{pOXWD)ughQoZV%Shzi@6CQ8Wh=^4|3%-Vz;DY+|vx2I#B7tLb zqoV{`Bz5Wq%K^`%2KF95UXkD5f6ZYk|2U*k7VzK5jYChnBGA`JO?~L%WlX1IEoC6& z$wyBagUaeOGdDj%?0Y7MOQVQ9OeVc+ah?}>gaM3 zozwt8=|`R1j2_xGb!%T(d9h9tl%5su2+-rhdV)<#71pNj*w!xs9x{?gli3(iaux_y zGsN-c$lVEeBVmmD8zM)wA(cnfLu;Po0PF4^VvSoJg(NCk+w(Wjm~}fZU~`_A2nZC z`)VW8IBx50-^SWD9aE7Lc})Jmf=9~R>9hDMp2b5?TCy#azkHi7*Yxm+4(2JE{w!*m z+n@*-(h3ptg>x=D9R?aJaif*pkF<{vlr#+9PAru z<*j@iFW$5LPN`gb^|*A)4P=2UjZx9^F zf9017#HZ270#-4>{(yZQ!SAmzqhTm>S-vbBKrrtbKOqsNkBo;t9f!VI)tLiPO~Zw} zLX<>{Qc-YJBVA={nBBQ05xN)`+JBHKn($3AxFWQD2n|5Oj25f)a6Dy6ou6+Z2JYT3t6W_(o+cu`XWZNm zOImdyD<=j?slFMgKJe8AdWs-^C5?hS$wLny?@-^@B+b=Go>OtHo6luybNVk)SdW%f zI+#PwzHQ&-A>uG*TRliT83F3iXKt-M#t5n~{}KNjXgw>;qWIsvqRFrFrJQbg)%~!KXAmzzZ zZv>X(BTkK#A^$cfJp4GgK|`(LiAR0So_C)lztT}SGU=OPqA@o80c4Ct-x;~~6O>Qm z_*xS(kB=ji#b7IrslZ%d&qUIkMkl0nkYivOZO1XG&d1=t13U>Z0%+N}vM^-6teN&1 zLUPq0uW{)e1l?#OAF91CR_({ZlL)(?%~=8sO5fG zFcDpsmsk@D8q^=6_-Nys%Zx;3x>`Pe$}8RwV4w9v!M7)WQP`2T-&Q^o&MHdDd&6~8 zYi}6TZ&@;0+eH3S{L_M6^DT67Uw=3Asq)i~je0)mrHf7p%v2q;etkedx&T%3t4#jQhbrAcm9?g?t$(Kdbbbde8c2qU&!iN*R~u@IJOIP~sfTQY zhpL70`bP}Q+{)UlWb9DC)Q23Kd!*aK4w!79@m(W`t{eLkGZyj~zk;v1t;7}eRO4y%oGefILbF79PNd;L_HVA|zcOYU*nhTAYnAVu5#l11ev{Fc`d4-HpJ>5k12LRZ!RP0TH1k-Z~H1sNCodM`=*y6nDVvLWi z0?7DeG8qo$b7MpAES8XvZ+i?8H;8FCr|*dS5aynaSFC889d%NTxhhiPCoRuK|x?_W(tia#W`rlhRItu%*~U#3zlf?+O`9n zXb!e<$S#Iblc%IUDADcGiv&KQa3AR_wQA3Hg1E!e!k%Je`l_LsW)BGQ!8)l;%xOLm z>2WZigR4|lN<;Gz;}{PEe*DTYuRnaJMKP%5TykVf+l)laC2bTNMWb$!PKr8rgVAKf zI5ayLy>t9^>2fd*d}c2@!i?vQQPsshi=J>}f|XTc`mS72a22Ri?!MB%aGk|_5|&)r zI!61{7}XkI(UUkb90=KMkx?D;=AwTR^AHO9PJoyDgBm`$^-+Cj-3M>6dqI!OROc<= z-vid}Na|SDCCt8k7JZ2cxejc5e+%>XQe$QPB}7SlT7r{yiPfo zC9yuKEdl_3<&oo0WRBTd+o6+rZ`D6RIoescA(wX(YijUM8zOKjVi`dVMsrRD9nMpO zL>8Gx>)knyj%u-<=t9}nL(w5w`mD`>_G^I0AhcrB*B^Zh5wPMCu6CXFFdAnFV{9v`YusP z395R>A3;#hO`hX#$Yp~aXITfe($QJ+G^j(fb=C?I{ckxJ|OK ztdJt@Wwn$;n<@%wF#vU&DmPBPDL-q( zgz~V^YFF7eYtIXa0N3ZN(Z%Dpta=5=F#u#yw|-UQqx2p+`Qqrv!NKyYvUrJ)>CyDE zkscLIKHPq|9tpp#JF);TmM;rH)70xr$D*}sZP_DcjeBe^nIjX$#2(b$17i#k`Hd=E zKptkl9BRrqXzUpUMel@Zm)l-Ka~TkXHZ@D2(bF|B>iaCH+k0&*r61G95gYG;Wm^uO zY$J>^(sG;At|YDstA!*O3IL4K7XwH0jJ zw$gE&OMb8>Eeq&oaVd&U^6(|cumh>9oHp&29hk+u?NDbA002mWrAEwy{#*^|7#Wk+ zkB)>*iAkgpSdO#%-nIc-{t0Z5`SY*aTL9f$CFY2A!ChCqc<-`@gdBC z`Q=;??Z^Ezz;FhyFcJsp*(3hc6&_zCEr2Nh®JU-S2_K7@Gi>Lq^F!}xf@Q@BKV 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Μ7a~e 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 z zZ{7B={uKDqEK`X;4))m!p&1y&Aj}PpR45YB@Iga7i(L!E7Qn&6kGz|+^;w~zY!9(pwi79x5|hx~pubYV_2;4J&2tcsoIrdqj(`ZIJav-Aml}6A%6uf$*YkMRg&~rOYvgA^Wf$ zVjz8P@KFDgd)W^iD-}-|<)Qy?>`?=;%l`t>Hd=VZoqvMNs z&+$%w*f`3w#)?2c)hd_JFvLa`?-J=G@eul=y@0C&nOc)K5QpOJ$RZCs2`z5W9+NlLBd zWIoJ*>U8ZDef0|Z^zp~i_jQQeXg<#Ec@BF8(1&jJ-0u#S$}6{9TwAr|7FWapfA#~i zXq+E{jhpBNRI*5SFsaspQSSTJ9xr)=65-s3>u(H9q~KA?kKVazYaSX^Hl8c4v6Dj6 zr2?;MAiSk!MW%<>E~3H4_Wg~x`iIbMP!r*ko)w}6DYhe?^@OY8&$5ldBJTU#L0qt)R;j1;k0|>B9(Tn7K)9xw-mKv(&ATcHt z>=q;Qz`^dEg%)@UM=uJes-hcj5-E`UHNSqK(jPx+Z*5LBBQJ$B%+kindgMz9bXB z{uQq&kK<@G0n6%~!;}(!%E>G2#@$qECueW^b;kreB^)r`>|3_%Y5Vbj2AWPU*NOB8 zbF*?jBzOJXX_>06;bg)$Kw=zsbb|l4cH;7Yo~Gv^I>$={JfV%R5ufnA2%x9PtLuXg zWwVIsL{9p`01v425D4n#&^s+j|M~L}dbbHVesc;Qre%`a9!oePa@<8_DPC1)EXhkL z$NFVdyXlVbShEUf@LW=qVxj>OOjNy>?}wTI?zZ86WnJuhun_A-rf~~_qvKj2l<>vY z^3N@Q!{;gb$7rlhU5!dBk2BJdy2T3tmo_sJ5e)GYn>9#<9A~nj~Tyy^RQc*3QoS zKJA}hCitg%>$fJmjaJDYa(361WP8S2*;%nw4f3IPmf%WloE(!~jl9W{1{(=Uu+#gK zVv-m*Cs>pejJN8@mPmnNG*!dD>sa40@AqAK2u1*j%28U?9im==KAp-`f{iV>9=QAcyl-+L1LRYjH+ zup{h_^_zH4az8jZEA4b3{tffbY{kK^0tS|SoHF|Ft zop^K)TNJP2?UwiA%@x&knX38safMoB_DDd#!H-7s#FA!Ivlc#adL+=KHt(Vyqe*1; z1jKAVR`o7PFRLy*-YSh{GPcgE>Y%Hf+_Z2( z!fK1Ictw>XLT%BDnAG7k#*FDVHazVd{?>wHJE6}q%uEdhdNTmKx;d1%PpL0qLwuES zm2XeI>X+h6I)l?=uC5e662*$!{MHd(en6DbwrV87TxKC^=<8GBnacBLKk*wnW8>sIcF(_OP2&3!@L;c|sXQwNus!KT}ImPDc7Y)$lE zD3lsTfbQn$H}Ku#t=tf4#oz+lm$Bk{s~o-=VaY#IlNd?y760@gPmqKz-c|b`PmGhw zLKjpDJ<@C%*WYLL>=(+px8_$NlU^Z#KD<3p+||q984+-rkDZc8TIT~Y-#kVcMxpaD zo4kA;PXbQ52KlWYDV2rOR?A`##0s0pNkUS(78#Mo1B${tDRe9z9<871^ReZb442k` zJxoiwg1K9q6Yv|bZBY33Civpi-l*8pI{7It$E&0O5zqe)f|(`%C*f8%W+LhVt=|jS zdz#wN0_9aBCn&+gDcZuXf{~x^*gEv_a3S9sJ8H<_pah@Dsd6e`-LHsfyZI;vg`6ht zdpZIC@|uiQ88;v+);dR><={kiAz$j`F(PA{i;VQ`kahHxRzR4u@mk8`3eM4+V+Dp&SkDgq5363(8Dc0bbU6TozH*=MQV?lCT$U-|kfI z2|*guv)Y*3tBhMPz<7qZzE6zDgB2PSm`@6X21WFXHzTK_pG_>;rju=n(QI#Q&jHOQ!-#SJOAZUXh(DCUkU{Cv-2A}xblyHxWUi(0cHvfzPVex< z|ALY>g}uBms#jo&%oJ^YJ+#KJR|PAhzvybq%w5;gVIiK?#a31#!@LItOCoS^Qc4~< zI#y38>~PsQI^Zgh7c+7o+aOoo--0%(OE7L?92!;2)RhX)XO`|OUCr4mh5Zfh39h;g zT!wG(c7lp-xeZ&jn<-l)%-{#B?bG+;{P;pTizu~<{d9Z&18vDe+Y zRgm~^8+$CsDuG7vuW3*e62~)Jq&nK;R@+#EHeHq)C1h_i27yDg5y#sniLwYMJDx4w zf!rvutNClH2BhIT&uPi3$OP}IDIaX-vf|TdlT|aa6tb$u*?$9y|1&^6P#2yIBiM(F zg%N9%1nIH(!~UmdwlIljF{SjxrP5VZ0xs&_UDuZi$o+!hQ^D+WwC3+(z@9OPH@OB2 zT3r10{iN#4Di~3GR~8;6g~>XKSNH2vuLf-RwO!}NS{_6!!iSZ4d;yjG@$-{mDb5oK z{JTIK)P@v@^9U z&4S-*Z5n`Y77W}_#>rnDZY#I0PnRp{PR^fAN@jAnFX6VD?-|8UQJko*Qym4^N$9{M z?sN=MBA1NR8JrW&T0VsKp}-BSlqE_0ksnob%c3w2mppLY-YvN#XWiJiB3NW=`S$q5 z1pts9JjMG6B0YQITaF*xe33W2ajASJ@P0RM@r%W9_wl}1nvh?Ql&jjFnDtq!hp;!e zMU0BbIIxe|nxzcKXTo7*%*TtoB2V>I+l|6R@z-FH^v_BYr>sr@0VK1+8Sn{kT-}bt zE6Mog89EwL4SaR|*D!Q{SEunWGp>uva6*eXbIbmk9d&Va%+9L+x@A1|a(=zX02}&h zLOmf_m7O$8rhK9Wz0z5H0s#=Ur(z)2Li*d}eHV~mZ0P?h@(jii z*gjYbz<0jq0Ipx-Ei;f;MOZFfH`0F?!tsqhemMpbE;X~1GMtW>andqbGbwh$F}L-* zhoyOK%i15gvRw^~&BZPRVp`U1pACP87eFn%;;2Y-oob2leTEJAZuYMLYFgFZdp6rUx)N?-$2JOypa>#`jD?kkYqe=cJMYW3TLesDaX3<_~+8!NH%{ zTz%qwB3_#B@#Ix+5_|0h_!HPCfv4y3)j}=R96?u zW$ppB@RNx(fO}NePkI+P#I6)Nt$4CiH6%azoeoQku$p$#;(8u(pu=()AJ1E>nrEq* zW`#WdA^zOtj&BSqEuF)Q}(e#yLj`qB06s^_<*O<0MW z+}5#1)?E~3JfU}mcL4vv6ZWiA&nWng@IYO3epSbV=&>8mFy)^&Q`0xy#9og^l<2Eh zgI>Np_tt>VRNJ%oAI7ymOL_|~*g=jSwxsbBDNEcP9}0|u|6xn9NI-F9LjD0|430{f zeWmQ|!^7Y9?!hUW#>=d50emX&$qul4IP|H7N&KZIDoHo(SsaBc6Bbxko<(}hZa!$K zgF=_uBqy9g^#EE~-9E9!Mq5vRnd_LTzmvi@a5pqLmSp_gt$EymZ@V~wXWB@Cz_~fR z+Q|f45gg(#!{_s7$LE035P#ODOobR=_Od-bi5s2y;I@#bik*hexXv^YVIxVf`q8yz zq5%d+sexS%h4FcS~C)j^=x@txscP|1(mqU>yjSpTpS7*ppsQgx00Kf1T<`<`*&W;~&B8;CpX8((Rb|Ykf z#mU=!MQflMEIh4iYg$z7^%{Ah1g%J;p&wp zW!~_@8^V+U$d`!67lo0}Rg_fwNAZt-yZ#8h0OllfOE?K%yjn+-hVWj!kV4F|6glxg z<1m%2vnL9l%PLA)B4{o}k$I6DJ`M`iysrI#*LXa3BFwCg!La*zk+(tcSSzO+%k%l8 zU%7fus)bkJfK_$;eiQZ2)M97h%Of&tN*@b|kED!&9!G#BS|_VDRg3Jb_7v1#9Ct=| z;e)u3_GeL4q!?f?A(b0mSJF)mGZYp?*E;EoFzAgN#EN&7&O1v#ObYWmZl_j`Va#T{ z?n9E@`0KK$2AJEYg%<<-o9XUg7`iOIv`@ZmE9;5gQa%VA>1GW+!6SYOmVvL^GPZYm z-Ig$cjy|iyafm-T&m#~M2?RUmwGevEY|%VN(BPe4Z1J>+Zi~K!fK_=UFgPko^#(C(H0V>F%X~fa$Uexvefeueh<}&Ulr)PtM0$iW zDk@7yn@NEdXqJ%NgAXeZZWf1!9PP(G{rAYL>KvSgnj`$ygxf)LHTlaP8QM%3v(^sUTxcj@C&`4ih6(Tc@>MH+ zP~_xaCZYcp=0166x;Tl-A8SV_Z@(DrdnJnMsB4n=i;_>iD_kJ2*T>Y(spqJ?I#xIcd%BVBdcW51)%U>#Qy}2!@TMih4 zt1AyD3rV=$RYrz;$<)p$(?@F7Pbb}i=dYSPdp8`_ZrV_7XE0yx(C_TReIRN(p=*kl zAT)9vurnJ>O1>RsJHnXdMd#Sv9yR1~2_;7{M8-zK%#voPJT~lXPJ#jpAhhYfPUF1E zIU0B#p#-DZqaRpFjf+QinX-{EWq2A~UrlUnQ|WKU+n`s=v@u0RWyEIhJ&}EvT>xeS zU_5lRMAZx_=X2_i`Yf}zzLT`pB5L|qgqb3L+MqWYX~bd^jKb97+lqqLgu1Sx#Z6-% zICf>GLAr{`rOxm>bDAp5h&2IpQF^IdLv3Zr^sChJcX_7ti4UB(pWnB$4EN=2yC&aB z1Zq;j?VL_hctX9FMIlQso=QEE%PMmhGPNA_?zEGyHw<&Stw{Yn6xQ;HiDcQK2q27o zO5I3+Pbz6n=G?-AU)b8C+0mUn3zGI2baz-*Tfr)YrfXZ)6cfx2=ceRKVL(MF01Y~T zNtT&ODHv|(vP`gcFfOwsW)cp55*#vuAHn%YaeDoG=A1sVoNEUxzEYQ(m#Ee4pFVX8 z>;ceIXoM`s?Z#j5{)h@_npdNqk_PQdEv|=@qUDA)_~NU_!AUgD8OeCtjTaYO9~4Wn z!v9xfWe;P%uafMxF9f_kRud0-d&Rywa32be95ogGqbW)j94=+QT~W{F&AKX^nuDO< za&H~^qC#!_o_#TP)3=9{$^%i?I1%}HDCSaJODVa(8{}69PDJ@jh%tJ1n7!LH+6)zInr`(%Y{w*opz&-0ZWUd(|Tr!NN?qn^C>os(F#=EC6nZ)l!% zUg&+~4U<1p=QuQ~{6y6G!WZz-6knoCBYU7hEE>vPG`JY=x2)w_@WG3)$DA}?_%K;g z9O#<^d*=aR_9@aM;@{rg7k=y%rz_WW_XJK&8Z9*s8;0q3J1>x-Z^9At)!P$oikAOr5I0o+dU>2P|3y=n zS`Lld+$;ZIgSq5abD@f0+#aUPGwJlyCdxw)T{m!dU*}YsCFU1wHVe*LZg4jdu+}*N z*uO&p;xvU(poL|R=;b(CKqLJ)w>!8fDmV@w0Z>5SG!NhGDR^T{`i0F1xp=O^*~SUb zp9Okp38+nTY)OCPK=WdBRn+pnA0Iw!-^`0xaBWOddyPyJkC7d)8~2~3;T=Q)uL^DG zl4LRMePU3jJMerxgzLPq*1|q{aB*`usQvTRbOf{O2Ar#&j)%E6Lu{^i*KqPmgf6+! z6Yk)iu07VQzWHSrBHU(&aZ6?qy~A(Iw5>ncreJJ$4dPoqjqz+199f#!V`{wJ2(sT!T&}j}--m^By&nw73Ll6%^>Un0dg{<}_@^3Vqt0 zC&=K0sr3ft!^qb6Ss$ZZ|02JB)@HaTtRmFzI9;>tUM4x<(mz1gdqY2?v8!J0 zAmUbZ8fIyA{NxE_XA2EZxbS@;ZjT3@jMv+UgK; zkP4qqMmhgy0>uTqK&;8>=`>mm#=&yT^<4dES#(ghBh3Hvf$!U1fhc11~n88 zQhjgMrZI2IGIIK~hHht0;r_L*g5Q|5rNfo$nvmr~K_iQ6hjf1Y!bMX!55vZN@ukGW zChM$b$z{T(dXmM4-ulHQsFI+Lwm zk?3Td=q=g@iRJF-#FAy`koAZDmv-kC^l;^JF}1-mlNFpHh_sXgb*VMS=PS!<_6IozbeX7WxkhT;P$=a2- zJ?*>sE-M@V%~)GLv;vpvcVvw%Hq+(pBs&0DM_Dd98EcK*=(9@ki8oHznoT^t`KGE| z=w;%kXbu*UFH%&p-nMn)QO3d>DdEG^t2#_j+;nWLL#454pi`b-uv50M?#{;*`NC6~ zOWflH^SHUy$9%PVwZcQZv>%>mshM)gytainw>2d){*KT#4TRlfybNI5j&4*7m7;-t zFkwljV#1PCGYylT&-h$OuYCS@aPsu_9f{4^(h96>pmJ`>)Lgi%>}6z3?gPe~ujcNi z)!mmjynl1AJIg0IDw-{wq#UMYLoo`S&aX-&^JZ_@<;c)8o2syS^VK+*cBoks!R-~f5wtDE=nT&akr^Y4sq!C_Owi?)rvABJYd_FRm7 zZU?9;z8Cl(JQXR3#r4+aV=dXO0A-Ck<1{zQep-KF3Get_ZLglD72;u*wdV8hzK_;K zeIUulE|z;F@UM1d7}>0Tqx7sDt;xHHNZSH+#qkk1pRwQ8XYQmOk+A@(Ee*KcveaHwoDxv9sAhyz$|zC zJ*7VH_K18QuiQSf3JtO=hV_$6_l>LOnTb7u*r)cW>X|p-m4dgmlV2|cEO8Y&&(d&m zky}Ok(j~r8_q4M>=^y?4(2G3!@8h_IIVyCX%zG4;vlbYvG6r?q!m0Y(DXzLVDl|8v z4tuc2`3z3pxmO(W^z(d+Q>1%z8+d*Oy!xZp9SzAGxwKy`&*my}qENO9@>Qw~V)TWH z)Ltv!>Ek`0;TKig=ND;9Bh5U-eA!X~Ww5*1ZcxMT0ArD?M4b&V_teK`mUwhT|(~yb>Oy;hkq3LEpOV;wD%x zlYI%T&`=~COj1*%lKJbA$A{5u@1`&qAIdZ0v6g>)F^f)kR~u6NmiF+h1ux_Vk9xWK z1M@RVNXI;Tgdy8)HFTP{u5*F>py-i--JGLt>$dTcW6yi3G|$c(GfRZbZKwLTPf<7P zwR2_aPqjq-nrlP>?5=3g4#eytwmI+QE?BlJUZOHIk&<+-O&sai?zMK7IU#LPen|7m z`1_SygTp%fvv^Sx*xCH^1E=gm9Z_em0sb4zqSJiD`-(&B~omI2B=dIB%Sub(830B|%#Q~t)#4nk04@P{y6=FShu@l=}-!R>; ziKMP&|80M9SYa?9kyPNn##tQ9N!D^dxxtwXG^28!7xBMk2{wcHcT1kst!ZL(_q?QW zKMCEOds>2R!Lzi766U1mS_VnVk7?ahrSwBCDtz_&$+23F}GD@N%Q^Z91;9Er47eyD%rtNmJ+q%F6wk((G|MMhJ4otGE^SIS`t6T zQ4xzRD%$^E6RrI?)N^UPy!`2vc^D_s(Q1+F*tzy_b0eOoCog-LI0Ni+7-f*h;rdI? zU`=vqO;J;{FCs3x_F0Adf^z4zq(`jt`30gW%l@&&`IzY%Jb7mkPu-{9gEJ%(OAk3T zG3XZFG_Zj;^>e{m}u5~-hN=@MXh=M;v)F+ zfh%Ow$^=+SEnn;pOR`N>sPrwA@xvDfBLZ?Vn|MRCt8eE9ekc=JpEJMZd>dq#c7-nG z`)I*vr^_6=X9>2rq46y9`Jo??xek+on}95!Fv4L3@CQ^GKAkw2&2o?}Zj&34t~MQc zttPggi>b{Ov9*X?Z^mq4PyXGZ$hllG)1YQgyPDS^as%yOhw(Il{;A*BN>cz9^pzoA z>6bjy-6rODj_hDUuA@*^oU>z@L+AiT{Gv%_B z*B+H@m+i$Ujp|Hn|4j_?;mCOT{?LaxYNdKS3YQ1!uS@9g{%sf-d^x&k>yVjpU=cTw zQN!9xph^jI*?^U+&EP4;okaK9PCX4zo=0Q(qu~g7@IV96VH4|%#jq&>3!nP_-z4v6 zXt~6W!y5QUa4~P!;vsM|iSV#3q4~WECVSCjL3NQckHgJf`ob;ey~te&3_59C*Hg78 zr!>)g_1HnM}l^^w2sM!oKqB+N*p7-9U+Wn&#pzamdC zdDo!vl`-a*msiO#2+sY-5(I3%^7HLGL*kt^_D>r^{lSJK+$FX%_cvWYZ*oGPbvhzn zoze=3`)!hAPNQP?wW8=T0j31qC81aXk4iE_)mSEv-9sDuXc?5$W|(jFX>8ykR%1?~ zBNiBLrJL>2)T3%?b0FhB^B)}3e_t8d``0lNt9FU#&*RY(=apRJ&_cX3ALEZ{LC6f* zGL=?n)knvrwy1Is8ti}yDeqUc1a|C=IQ}i!%;I(o!Q=aEYS**S{Eb8kx-hbf)kijF=N z^^Gq$}M1{y_oYQZ(nr7F&YU|KlDp9543|1xFuS*?vT(>a6CjKs!?hK{GSM7u33EdrRlxpOv79aTSV^=`4E>MMymXndbo{WZ|^h`p-$w^^LfB#6_9c(q zg8PS*t9b>LtVeMG!aWPGes*o&ZL8dV!D{oJa+r6fph|B(l7#-DH>`(G6q&NM#=iPj zdD{g)DH8U0g0w(g)oj$6*oengnH3BB%uDJcmWt$t#uVxUBiW2j?h_%)MV8+xU>=Oe z{*XI11GI>rkKI>5e}0JNxBbb15dNG*dG$Yew;$$v<4nl;SFQ$;zwt~~Y~v+^+c zzK!}FO#b~x(~U_da2Tz5FDlr)NJwr@7#Pot!|}^cZY6E^@K1Xg@pV5l(Wm>3;M*9# z|9v2{xMZdROg~TQFI#7Hp8{7AT(fQ7@Ll`+&v{qsL8f(c;U3M-OL?$vxl7J>sK*I# z{31*75TNql0J?{c5jxW|3(-z>lVYSY(7ucMOUC2jFy{X&ojD>8yi7`L@IA)Z! ztFH(IT?U@Vv(~*Hu?H+WOj2b&+KHQGIT3(+i@>REoiZU;$+dl{kdx%iVun(abwggnOt{ax2u(K8%D zCC&~kssLPJP(`qs3g!ddi=3}GbuxE5l)8g+a4qk}cgXVyeOKkTQYpF@RCM;QFSaY! zQHvFUqiLSSf>YTTVG--%!tMoec5gV->f&fMtouPDc(u;ef2RR8!2CnBOao#gVZKNv z&d6l6_GXjT(=E%+)`|kma1x;opkCM>T7bmc9@kHqXL%V#`*%w;Uv!=(*0)`kgp5U= z4y>O5z)sYHx9(!g8mAxkqJ;G>uI(#hSGAM6Ri#(eY3<7bAB`-LG;mbA@EqxVoK0YF zY#t!{X_Ak$VOm=hMoV>sofc#$Ax@3R@R9#9Va^IltV`Bv9|9SnryhG{GsA{4yrwg@ zfDp-*+JQ%|%qA-T7D?yi&#{je`?3=Wj*~5+YUxX0$TfMSYh|c0U z+zC_njsRxINf!u51VJhj$MHYGM(@~|6=+yUZYr$GL}F zQBsF_SpmQLtNC(X+3S{G2WioIOc|bU>QT98Wa|-31!9pFI%};WiJ(RMZ^y>>UQ8NU ztHL|Y^xiGxXYUQn_QARPq+a&crLt1O!@^h0bv|vzDc=}}$h#;Z%f^x4E3wPyB##v(^t;~_YhM-I?*0^*EgrTWVh?!b;MfpEN@IEyCJ|lFq!lBO5Y{ktZt8 z9~IJUcxx9QsR2e*nujN@q%g!-Wd-9PZZKEv8pr8>Y74dy5UOvwc>b z;IgNz!@*SUYmb9l@CuITx*G!y-)%7YD2t{WZt}&d#WvT_7-&fM*NnY%+eiiG-!?+% zszzVAGk^Nima9=N-({O1x2g>AGWo9Bry(p04Ro!u)~PMbFbAd`MF3@!*Tez#izC?n{`?SnH7pW^L)J zsZAf#XdZKFwj0FY(B;0dx&~mkdaX2qpDYF1GT~$P(t5AmFv$yDZk$Bh?GF{U-`k8mb)TRtxjIo zWNRP@GeJQEnI8&uoym3)Pv(@|&7abzeyC%m_p=)0+scp)pC17yyxDE71J3?!s5!#= zzL`o|>Yp1+O3F|C&D0Lx-zCp7?f7F=K%pp%0)5%y$()M`QbmMH7KAaXl*n5UCbS6n z-_1RMv0yf=1IBX?b7EQgJ)C?T12eaL$EF*1$mQ>aP!ICMHgFez{e{@)gMWiSu4#B%G@y%WyWH3_L*<}BpNito{I^G; z7?ZJ8>R2$scWTh#c^bbssj$Q!CL=vt*T7$|WxjbVaD7~LQBB_pMX2NVk1r_B?=LMT z$3#479#aY}7Lg9KzsxZS%y>Ca5++y1@F1rkDlHlhIrqj~AA*I(Pmpe41Nk`zC_Q8FzE1 z%9?vrOL~2@z86%7%fH^O|K;cR1+5k#2klaSQ=Nydj|?>0?oJPiuoXZO@&-8fW-{z> zNPoN(rS4I*vWvC03)&dIa+OOfrLe5MJHa?9forzG)t$Z)oqPvtBGr~Gylo4KK}t*VcA_m zZ$GI-_nL3(i=8wLlbtVWi+4eWGn!#53_{BN0>F7i_2o6Dclmb5f~|iRfEge+R{!ek zw#GfzrN0>7baI#u@SB<&i*jw(ZFxs(gjd4+xBPc(<&rc-aSa`+>CKZJVI1FF>{ZJT z->e$|c;g^D5EXG4gk0@%Lpp8&@Db!<6%yZy#viUh!gXCpxhRp@?L!Vw96r(|HY+Ij z??UcQ}ORmF3jr;IScTXmFUbArZPjLx_vzZ(h}I$N7}Pa32+yMLHEj;ec&%q_=g&%Jp;a zgts?=32pVZXmEppu*cCqQSd7G^fW$F!zTo95>-wP*@~@qr2fZ6#80&zEMy%M{;fWw zQy5{L)W%ME$zny55WgmjZxy4lJ<4XDRIgU_j@`V=0}w!cuW zIWsE`2Fxrx`*s(ANz$mBM>J1~xfoLnAGc6dpyLH3HNd2nQ zNxfaRvK&{}JH%R9{>!sq$QRrKzZ0C4K0T>`aMkrI)N>iPv1kM8;3}08&c6x5;xg`; z3NtzVApnm=(5X?4U^?p*c~>vZJ)Uqd}pzc8O9b!x?d(W+g| z`7>Z^tb~f%6_!E?vL)d={twfKG&eZodNOnh1sR$vW2jB%9pf6sF#FQ#EZuycjs%4N zAVL*+Qs=Og@A!A*S-uP(<`P-Q0A;b?d%hKF76S|beKW>0>d`eMVAfw;#CPhFWWz(p zng}j?#$bLLFx`BPV?ut*nJS(G^vtXM>qNeaV_3f$9|``w*Z{DNQqagGs3;cUJZ%$8 z%yedOxb4{P`Gd@&=V4=+tqv;;iqY)88+2OH<-VW`N{9EmhgZ))5nET=&|_p-2xf-$ z$vS7c9=svyr!=kdw86i}GKA2Qo#Jl^6vH7fG>P6<(340(5}Xw@j)e z9CXUMKG7LlJ6jRPY_TU~am3lt<`OJB8$7jIL*yEsuOeSJI4paCpNXZ5uhQpf*S7#2 zYlYgUFp)&(6t&>eblvYat58#E#xk4sl$sh7`RpPHXX-c2wO)HV08sOKXzhlKKBX<0 zU4CyUieHYP%%MAy;B-=NmO2b|awo4R5spyjFZ5%>!9l4?V3CWkt!qxBj=bQu=cEwhB+vO3;U6hHo8GC5bEWpEZ#UKhD&ghB zrd0E5Wr4{sg{6q06SEpzQk>CO(9_F_*}aYzQ63iZXQdQ`pi%r=BButx!$^F)a3s`N z{-?#XOjk%@2Pgz2(m8*fKwiw`n|~B(O-*U zU;Za%OVovDA6mRHrp%|iI_xUwe8*ZyA|uqc{AjB+(ktQXX=R`}r%Yv^?MOGV79;*m zvhJ7-?%aj}3iszPdFy84@M(GZa1a$PO&j{9!*a_y3k?gp!=k@$!i~=JUF~5Ntynu_ z88>&S#BsduCrsO9k9QsP=7-A~np_u3&3<2_Ew9iiPKRW<+`XbQWq7Rn_32hRT@F>F zd0d6&oZl+6Q=6HWkZ?gZsNeZUrArDe(W8X-**SpGDCPNL2CZi>n)p-rOM8EgQo1&S zI`qzM8%Y~N9RPH_Lu|aMWqELo?>chDDy9SDhACqc9kAkjoeGaDvfD~m`3oK}X{_)- zMT0|u%=X)N(~L4j7F+AAlefX<G%sc0!)|1e zAuhhGM--`cLr=*r%WEt9j{r!{oz&B-;#{p#u7s!MiM|Yb^UyZdgA5nXL+=kTeU!x_ zmylt|GSlXB%O>4aO^P$5*gi+YgaeyWhk{ zh7g0SOnT=!qaO0DFblucGlHxYv&*QT=hT?;r%1!`KS;1{HZTk}BNp-C>9{|we@UR~ zIGS?*vOYc2o#*jMk%1u@n^=Bi^pYsR&B5v#0;9*WGFXQX>}i^SebG%RGF@yGp$S+R zcc@S5$K$!|b14no&*uQ6f)7R3;Z)?(1GGfy%&P-;90UNeEH}XP4p+Aam@4e8hz^d; z*;xaMVPooTo=#T6AuYm)({=Mj5JIhnH8fbj^(W1st{E~8*qS)jOud3^=brsg2v`Zf zI$Y0xYzSvi4BZ8{k;WBov>vhTIx=fZ*z@0yeupOa%x~9OhBb%DFV6c|NMqVX>OTI| z{F}{EO>LHRq|J2(%ds0go3|30okcm?uw1c?|Y5y?Q3t~jnvrvQkTFOB>xX?3=PbyzCNn0spc?%2MkEs)m z%`jaDHUDI~?1@X3bv=q?ZFH#$PT(mqW|nZFgbjvh`{|k)A38&W>5dV}qm{(i={{M+ z%`Nxc3*&E@=d>ogQZLAMTK+5Sk|XhwqetY=^ocrE9ADhiS~CX^<*xJzGp*{F64%M311wZto*#LOgf6_(d=%vj9_s ziR%v&wX5`rzs8e3;$1x5J_aMcjylhuh55C z4+VRebWAN6Jep(%Kxlur!Grm;oLf3m>6-f;KkgaZKHe%29%_PMP4-(E3#Gk5ob0z` zwmFIRMUS_Y^_`s7uhQ+A(6&q%qGXDEs^S|Tw8AFSHD~lV3yhaGW4CYr>LG1>&U%ER$%~-Q>cdd@b<-w>nTN43@Wj@&ZB-dhR`RDEc=KJ4L z^k0m{9nr4!_(={nz5o3`rryE5)8}U!u5H`4ZQEPh-P(4$wQX%}+qU_s-L38R-S;@2 z-+u4>16L+j=FFK)W`a0Y;LHCh&8D-o-j)FccH!IPT7j+xr#K+vWKcL1m-+UiQ$x&z ztf>tf9v#}qEiu;s(BlYL%5nPkQc{_ApQ6U5JO^|NxCqix+a*CZ;hAR&1|SxFXs2rH z#y_+DKkfPmC2fgmR%_)5H}mEC^DId%K-T;9B!)Qaxu5;oWM1@=XrYPQcYHZ?x79bD z$QZ=-!F-p7{gwxGWj3m|jEPjbDRXj3k1V{&sCN>_#ucQUY*Oh!v}x#yS*jL&Vs4xl zQG;_beS1sh?zb{kq#L&DOd_)`g(UIiu5BHC%q{W`zMG^?{*2gi(HtCibX`Oc?0n4( z!2~@6kaEqt{?`-b2X*_Fv}|#LFso4|EH6)~7Hv;|x>VidxIf#XE`%^smP^%or<4;` zpIJzblr`wo6uJ|}0rt9VmJY}??}k=T?F%IlBJ*ZNiA5pVuX5CMAe1a>lXBV;M|4V z$76&R8Ob*y%jvilgrYL=1K*sP`=^`>+OL_l4()AU*i{kSA>5&)wID*28 z=@+l6e{f>YI_I0zwU}>NJ*Ko}(S2!KyYq2ibUrz*zVR&_d%$K79@H;zB&HGxK0n2f zPek7H2YBb5)Av9Slf}62QDG{N3p+yw;)}YKo-La~Q_OD+zzvhX!ht9oku`O>;8OhC z>j$2u|EUi$M&bWs4mX6qgbVy~eJs>kb`bCm1zImV_#z3zS`@U~;lo-?7|K9%sSjx1 z1!?Qb?mfUJ_e1EPFXq9UalQ$?{~l;<-EBhtn0>soB}EfjQ?CmRXGETWz6vLC#2#l1 zLAnO!gLD`s4<`c?4nYeBiS!xn$BHM4_}+fkaYn=Rt#HgS#_A*7R*Mrds17oFD;3yHT(L!WjRbg?&T<^i08*bDBxxA7%ur3hf(jg~?vurn6 zNMKXBE|;-0R&Vc8)LBF#+G+bO?c_9$_fLGFop2tQSU1FxdF-}hJ>Gxj8sT2vP;sgs zB;jppWb#ky*WLkqf;0}&*qLYTb*_65EbAq+Ampf(rdZ-k4OSDbiQ-EM$MnF*VmK~3 zVN!DmpzZ}U#@kaAT9*zi;g*z*pb3f=bw{-Q^X!M3F)ntDD=}=(0RpVEN#@-c#-$Kz zV=O{#M?$VTWb%|cOa&T<CAu0#92p^SV}yFPk}!q@#(9t?N47>R07`v3!7S4>$Gl{2 zRg!BZ9D|6FNC#S<8T=&TrCoLt*Zfr*OHE>H!-&nil>@B*LSnGRDq3MX`s>*=pA9A< zxHqF=HxcPDZ7ti?;eT?2GBN{+kREyk& zw$CszGw|~;JUn+a<95CFWxWm`Z0aY&WDu>%dRMDk`ZlS3=dTt#c!I_7))$&S^NBsU-D5%WRf` z@V!_>Akx$bBRb67)-Nn?GUK|iTuxH^P(jzYq0^cYk}Fa2eqI;8(TWr{4V%J$MP5(ctd(+IfU9NL*}JWk8k?WAcM0Ax|CK5?+#^2O=Xg~ z)n0t^IPQu2pBFkf+>{2wyMD7H$gJyMG%)~eE0~{yL*TRC$^IqMIe}Sf!tj(9q2u*(Jx4s#`h<^1;PZpyaX=49wER` zYI~RA^N!m7Q}CuVuG_2`lZ$S7CuiBC+Uq{OmhrRwnX%&beLNW5;bh#KKE_zTM~a&0 zR5ou-cn(_gS5tJTjeExi!@xE_a^)2pu!-wQ4mNBu;p3|V$vdtnoiuBTAyzN!+dV=? z{%eY-3&~rk$9WD>sk^fxV48uQ4;DJYjtdVF;eT>>N}JV#UZ9>Hg?ts#V5V zA7%W{7V2oXtMx@5JiJCGB=w;CE%3X4GkcIq53kjijlCNlrlwfCjaWMmV}SQn|GpN= zmVi%Sa(%GjyigRXBI5|JH)G~%fQTG|9bMAV5j|#i zs`XfI@-R$;k;^Z*eP){~fx_tKZVfH1!gd^HN^#3^P*St`J`MHuQYuN($ zRnQCC_lX0DqD$rJXrh!S%SPpe5@f|%1fVicaPzEYJc#&ROs>AOr~3>O#o0Y0iZ<+VP9Cg1BhU&w`V-2Xjy*ig>kafVy<6-8>vVd6#vHqd2zFeqMI zMMl3ma1pJUloF)8P)Gbz6Ja|_zSFGR_p{p@ZUM>%VJYO~kC!??B15TI!t-b&iXf7O z(x3%=QxA;bs1IUmOBhK)t1034j%8?{aQxa6aZJ-5T!0bU;<6n>6I1m2Irvt0OXJTR zmLO$F=7RIb2B4(sn&Wkrm0-K0l(;OAm`?GoP~h5Mlqb7VzP+)Oql6qp7qL+_MKGwH zZ2w28fMQ|EuT;Ql#$&QrhF}fdb})km8gF~}pZL_|DZLH0psX;kXM@erLhznvi9qyQ z{ET)BWNrkI+E8Rt@1;6ozEq=g#^DC_L;1Mu64UNW;7?yf2_YUlpUj5{TZh^y#iI2n zcodG-&N>+GJu(NLd~~@_&dx%s-rbM17+m+eT`1Gb8m@oh0iST2JI}|dzCB$>^eG!@ zpE2=)xb!#GAp(*X6ISy}XwTMo)=IvuRV)H?2UG=xVNd+>#aT#du)wtNeY&-NcweJd z92Ks!^r<(4gc`$`osR*)KO47Vtsqu$>MKETf!%@2nPgojv|Z9%jV+*B9GT-kOyoP< zZ(-Oxwp;g6GFhMedJeRce5t6v`$u&G`Zr#Gu8WeO2~&7waaj+#Co$^TcXxJdX+-{5 zdK5XQQ4i5Vv8OsO78JgY%7s3+f#0cju5thWZ8!Skl~$tI<7YAWUAFOU4PBAzQ2PJ zEZeFo^8Pfu1%XDl80(u`$Nh{4cEI)C{IFISSADjXZZZytX}8IvEm)Xx$7_OOUOg3) z=_}(Jby3vF>=qRI%CNl*73jCNC_3;0lZNX|BKIKc#`wuOh7rs9&qTYJeI?z~`t1tE zo#m@NRS1mPNW2`8!V@~yvvs{uDi@dwfJG|uUROb3BlKva>`)(dC$8G1+914!jGvp| zQ^cWT4#2b(}U0&{o@3NI6W&*;Ps70uxE8c4^gi8SEdWGoqj1s7HR^q|L8z4=YTGIqpN}yt}p*4`IyU3_ptl5_N=;; zw<}Qb@m7;aZ&cz&O>A!H1{Z#q+^u#6E8|II6EQKA9v8V6Qw#(U7rXL#>n>WB3pu>j zV!)~Si1c3U^2dPs6Y>Kmmn>}Us??zswB&>A$?!y8^6+Guj^j^Uu;|!2o8B=$3T!n; zHcS^cVEXjTk+xp(ihR19m*R+=zXxw~P$rm;d#}E_um9~sJCJ_O>4rxOyl)rNhXq`S zues1__BRcT zzQ&OK6-Lm`%2>9WeuEq3U8>j_z~LlQX_2+=w6rVI=tf?dC4!(aGYLhUa9*=J#!+=s zVRXUF=GZ+DX+^Xlo8tLhhziDsX%CE}YlJ>Zx;F`AsYn0)VRFrxz*pMkZL6y=T5VVY zpm*<&op7B$4>4jkM#^ib&*03b76eM~gAIWrgK1)a$F<8($#zl?EcgTgYO&H+dRsEe z6S5q`jbxh^!%W{8hVFQa^d#8cbje+6akU3@Xb+$daDMA5d(G;k1gxu6+0wdCnoes< z_ip+cdg{a`{ijG&T>;c{Xg|OF(nijCO-n@nssX)6Ty7k!vW9Mh%EkH14D3u4(=3#K zwYhul8_`S@yrV<^eKRW*)@3NMMi3P#0sG413Q7ljj{ajX%xeWJNHXl7bMQc)H6O6U zTc?u_Bu`ke5jE1SV*knhHuHgwah4s4kfBN$fpHt+Y>odyPY+ayjP@Ejt69rei0~cz zfZgvgEcE_A-swx4>;@Yc*TUIFoR?4z0^DtU@J7u#hP-+MK&*AI=%A!@6XBXj7WNn( z@J5=SWFnmp9GedMpC5sf;Z~f+9k&5*tW)kkQ~($B4v4ddnbP(rn^me>$25<=xf5a3 zu<}vq$GqLChf@&F7K5o{{{e#m*q2HJo9p2+|J$8nd^7rqj_#DjUO@=K>}T~g^YpMa z5UrPSRER%H5NOhmK0(b_I7NsT@Kqk`#(L~4ZYaKB_-Uvl?*u(l<@~CQp8}y+37=BI z?PmdQNaybJL}R!J=9K#e{HKkPXjm`k?}rw+wSfoED|03{oUqy-y*ohnD=)4e;jN@i z*3V*lLCa1B%Ub*f$>^NOHP~H5*MEM^Q#p}6b z-DUGeHPT*T3=I1}Hdbzo)COi=g1USzhO{6^x;#x?c-vcPg$ww%m=erD!SE<6UQ)}Y!o5H&ttYYzcLMECp}*Z4Z4|sB zH9Zo!*_S`5e`1vE={+5=+8^XM04klszv36gL|h50g7JUcERyr|;oNp|q`UrJvp=zj z30IQ7TxFT}9fvV9ZM?hx7jjE|9o7OnrZ8qXv(KM~{TRd1_WDKjxtca`McP$$8xzPt z@=f_T2}OX0s})5EeXW!h_~g(Wu5q$3wE_3oSj80urhn-G0$n5Lo~{FLNvg~>*ZTF6 zKG7ri*@FBHvjK43B0B3VHi1t4f4PMINX^l$m&i-vGE+~91@>BO--x+W$3vG{Ru`*5 zgdViLQ5uCx36{|oC5F@i(69uzKb$3`uy7qBi6)kH2y)}IZ0q79%%=l^ri)pi87{@J zn|#v521UbxCNQxdv(tT{RV7~Jwe!ztrmAW3PD;Zx$(df%Zgs~^(0`UOfIq*vLFmC; zm+?M-4C(>OQgP=WtOwdMCFnZprXa*=$6o8i(+{H7aN5)91+6nA^OSgs@K7h}l_ez& z4)DPgs#%n^gbZ94;Tsv=4ept=t=-`7C>S#@TK8+q72YWa=@D>isXVi5Fql)z=T~sc!8txdiOk)g zRZi#1=}APo)jjYPw)em$sWp_m_JIBu?RrqKb@~<3;!cL}Yn>!Hglq`WuqA6AYAnGI-;2`N?uwI$oS2}5 z-QYi`i-2uNc(e@=YnPu>qDEcM^sjEh`kX!}K-1kJVYQm$TILwF#R1&GW(#z|rkzl% z(l4;e6JvKE!;_-P6dkq?BM9y?f+u5jo^D0oz{R}a>7YVV3lm5MLjC$E8t;b(>~M}` z^0$BQ2e=r_bOzY*%E}fk5`Opnt9xMX=g?lemxBiA8|lpLrbgBK@^tp$yfBEUSeuhY z_V%S0M)b2j{dRDlrPm=zAVis-Rj*sjYLHq%Zy?$!FUA84j zmk*Z^2WJ`#rp)Jg_Xw9VXuYld&}#a~^PS%C7`LB4wBGk(O~?j!_V`qdS8@zTWhk~2 zRJfsx8$55rh^DBOn)EPnA{B_~jH(kQJBdys`B=27f81pZFe#-SEvny6f=(QO=5s@0 z8Hx2d3HPt3@Ek25koNBdQ%&KraYW5+K_2PAi-U#0-gtrRE~dINX(yvXcZY`5N~fAn zq&Q~(w{v=tbeA*uG^`)S@_M^EazzG!6fbzz4k&ZLj9tH669v+d(=^-A3zRF^V}7&t zJ8avK%1t;gU|yQNCpqAQEuRjxOy~TfsAf1*q~a^HWl1M?a@_$~69-4RW+g1vE6tOg z{*L!KoZyT*f=#^{?#^pBXg@!5oE=F%|>$fz~Z;!4&&MYak&B z0@uh|py+I;Rz7w2L`%YCNOicp=LR)wiW6P+F3|qlWH*?2UgPoCWzEJ(u*qkSH2w%P zxj+`)eJhE14F-*{g;qh_Qk@Ak@eImiBy}VccF&XD0G(giwB_N{RPN|QHuP^iRkDvyJ@_BcqA{68} z0TSXT-L0s6W9t3NZ$F<{sfD@mXtJ^1Y_ZPg29=>!BG}ozeQ~9VBQ#T@l63D5ER1hNw;gNU6;}CF$muoZwmc<3Whze z0SSOimfTtncmeC`ab}y84L<*m4bmbp?q2>*@e<4|P`K!e zT@p4u_)fjW7mtk?s>Q)_aTO*rZ3_gcb4MbPhD@Lm-a7WhRR43c?!wp9eKO>~l&3v< zoWTpVB+?dSCWO&L6-PqQr%8AXpt1ot7Zqky4*$H8sKfT(%Ft@5YKycByn6wjI2d(% zAp+U`5##WCsbn=GHNxy20V7t`uE(tpMDoWJKFlRd9?;8rI;W?gAO6Gv;33@RrNQO2 z8#v*YG$L+hm>>b;e-M)=PNvR=-${x%n7>UTLjl$e3bLAo3zxT0xdB@=?zclL7@g5)MR4+k|yXEHRx znd>cXA?@klfLtL2b^OrN_IA$!?wEi7RTrB_HW^e7P1q9GCYgQ;c=4WC$N@dl0kGfT zNwg@6!s$ zlijKhnoy*IUoS3#qZ6vEC55~TbS3k3t=a;Y>1@e*AMG#OlEP97D?3J^LroG`WA#EQ zX{-a@-=IGA{0@;pU%PzK660itqB*yCNq-aRnn`4T+L?SyvTYm75?sIvd>ISo0z0&dyX-etUDMFcyG|1iIQaB)RA~H2$ zD7WvrcYy=<8!gHI8IEnwhb_+m3e^l)@@?vl%I8`gxVjR$yN21r21Da^k}zt;H6KDC zMGvEwIoNi}B7Vot5lRy2XVSuks8`vb1PMF8Tu61Cn>fk<9wRt$vqHOnL_{u4pxy7r z0RR*8PJ;)`${gIn6A&&)=TAQibD~)fHn-n+4||M5{);LON(CGQjA{22D6|0VU>AeN zM^&_gKiy*se@82ancKd;53{T4kfk&h6xXR32;KY`&1ORNqjSjxP*>TnO`nqa7c+8S8FH`SU~vwbwwVDq}a>H z*ZKe>#);r-T&U_^EI*ybJVuDy%B}r@Jnb@dT@?e&4SnGSas*iiIf9@iqqH^F7TNy5 zFSV$~7VwMGM&~OYC^Da0y5OmG7NHn{6gfKn(#tN1MWW6sr4h-JrWnB83lLSDdGmQU zj5dY+NvQs4ErbU@MvyJi7#C9=&V;(EDWfJ^Q3uesgm|oE5;D-l0r1UB0gv>}nZ!3u zR*noWheuc3$3N`O6V+V>_t0~A6s+nM{C+_7Um3u@(u^_@jw(3`slxB`mMc1Pd&Q|U zj9h?tBXKD*G%*q3oHb7r+ouPOhX6?Urc-E{Kg*yG*NVsRaAX-5B2XqUF#RuE-OBtp&c`|3~j)0MsOKZgJqcO&G63&PR zD!n?^6x}0=RBI(C^-A;AOy_ib9rQrPm(pHY>KLXQjq@y!bwGxCkQ>NDSlHB)skq1! zQIqo-bZ>TA|3DmAJfAo4^qohQLAiy*h8(;1I;6%+SzvcQ2TVLw)Hxrm%s=;oWo_e! zakx5+&{-a@kE_jpg%S_W2NteQUr$InkScx9=UnZmr1z<6CS%vSxpSXFP61JRY9%(~a(qtsGnMs-H443+slSBUhbIhN52cEA# z`YvZ@uWuy%AN+gb#*Gt@oaZt4K2lznHy9$D`+TuKa#mZ|O3--PBQxbX;o?lJb@s)} z8n&p?P?mGAPvc$q^Z32}yxdSN%p{A1z4HQQ_t&Zz6KeACKBBd}pOD2F3M%2>XWo2=4uM-@Cu(1p@N<{=)wX^MBbHB>cYjM-cql zsO^q`WoX4-RCM1N+~`IU{Gk(&M*UPTj93k@ByB#TZW)~9RC|(p3GL5{+_`k* z6~7>IfNzp-n_Sz-)bIUah+cL5wos365tK2z;D-310gC~_hN75#=b>r+3?aB$zuzAo z&@x_YG9zq#r5W#yZW{O${+SbaOcn5D1}Z7C=%;wddTSmrWpaXvD)28FL`C*Kaem{! zqg_{$ckIS!)M^}MynK0uWT`nL{km9X*?W`mRHo~8hFI0KK(gm)5ccI`N zrG3swsb4(M2gzXi%ipgr0RynVq`*hcl{0KP^z9Oc8HUSN4{-mtBrYZ;nZUm~e?feV z*neSs^b5XH-lX}zyYJS1$N1xZoqa!lUnwE?f-Om<#z|FVJN_Bg#x)Do4UGzr%a2ye zCGb}zq?GRD_m~VnmWV+&`gqUO>QsGtdz-;=fB=j~7i#+**8zcWtV%MTUqO$vTbkb` zq0|p^!fW1l->Fv@Yd8VZg{rkv!ounBZ_h1Sra?wS%;95PCI$IQ#-5F=(U4_~XTZ8I z4g_iU<{K+Pg5Kv5Z)vL~K5mq?sM3v&q7e!L=?mgITJ!!f7)(F9hG!7htJycAtyAf9 zU&a3y?}S`YK6LyaYy0iLj_n7oKhD2LN}TftwZ>#L0}C>}!{~uI{W#$xugIX_k?o0B z$KL0rFecss}c zL2sU9vdV7j*Ih5f_eb96B5pY-D5KeXx8cVG>t6ilMXe@4&aTuHiww_;#FB^e12Hq~ zLEr01f(?KTt!N|++$tz7f(qNd)5PBIg0$urXI>Mh9;RbGogq)MHTZ+-=n4x9ArLaU z_&ve0=AzPE4W|CGbE??)q+ziD1KT@T8BZZkA0UA|iOpjVqu&j`K05eJgZ3X#WUHNT zI=vuVBuk&)N*||+Mz+NbPinq(G9U*Y*aG$6A`Cn}ZZ2^@TEC}v05bQ2Uqw4%ggx{- zW4S^Bp7E`SR-$QR7WzeEf?Z`F$$ZugR5(AJ+?TT z4dfe%E{uMQ+(HCBeZ8F-YZNi@S3II;qA*st!U2v9f!e>!A-s_Hxa?oSPt;)g4#NIm z1;k-A<3219&xKk(z6OU9_X+-6$t3`mw_I$|4~4$&DJd>ySk}f zdOuo)h}<4y;g>1vl3hq(f=q!DuN!%*08VymD@v~GuA5H^y(EIwPhC`Y=lid|h3XW86YsAj00UZizIz z*7FO2sZOW=8C>xKsDN5P#2lhPAzgJK5yT+0@Ma5o*q*6PDo$(o@4dUOb>gd29T!XH zUEz6PCNk7Jg5`8i26`d9`63!h817k#AC&8O{80#0sdgr!pH7a#lntB*q|@vkxHpM^U$b`d-G?I0=(CTGmAMT*B{T?-6F z*b|mUWN$8B${~cx6A|%R2|Pl5m}Ei7+FPtvVeRgwz4(_c&f@!>wE9DSt?ax3Zl3=} z0{HJ&|EIix`&X9(KOdhy`@d+~*Zbc3&G9i~|2;!MTH@{0yzyKW$qQS0}e=Q z5b1E#P5p7Q%A1Z+7F{x)Kz)#XCQr$Zg+O<00`xewH#(qh1i!mJyFecB!D&lVP7avN z8hwO^1~~gPKBBy^+BrMIee;Hb>zu7JhZq9xfcju>=JEN}EgC-3R(y$?u$QG3*S-b+ z{@Rp-f5q3E69oSwcYn;UiR-T`T&hcLqxQd9_YdSr>6eb2a0FZTa>0a%--9N&OMWab zN3fiI1jy<#xgPfYWHx%$3z|&&%ysbelhDRJ#=$@b;`eIQO_!&kILXeSzOB_6$i4X% zCMt!VOw^jS63HM(dtC_yZ)ckw10V1hqN-4TE|_$sCSy-L8paHOdyGoAF)=6^?2wC) zI6+t5-!g$QHSG?xdd-RM^t#&IG@oA5{?UzK_N(*M&@bXC_a*s?kwP39nglLdP-YPG zXyEB9g?Xq0!*01tA33pVl(^A1IcrL=bcM^ zkbIGCNp5j7Av8rzErtM}4Z{XKkdD%>;48(2`DRih1E$3LhdY4?qBRgUAx!wN^drv{ z3$t*3(~T$%4cI>LJp@*4r6EdKZOx!od!gBE0G$4-lu(%N7(7NRuQl~L|N82|=o%Yk z$s+Gs?ju^DeU&PqdCb#+ea(XY<8@5y7SYO7Ga0A7c~;UMa32Y5R(iCrxPh-vRyP{Y zR+jK?$qPvroUM_wj~=uT)lxrua(VFyZWhu$-P_a%eXM~5SMP}gA|y=p2md<+67kPZ zK{x-Iy8ZV?!%9TGLr5hH`M8@`hUmJ<6uycdFdCgMf^odSD$|*Ktmt5S8Gl^!Q&?>%zOA{A>?mL+84Zxv zc+sUBQ1B`9JcX!BSRUBNsiKy@C*#kI8yV%XWs1_*3;BD%4X4Wry!Og$X48N!QmiBy z$ynEIZG=4NNHyn7{?l%d^*Yg?0Bp{i2Y7qK;a58JE$frfVZ+2AB zwC`D#?ptv*!xp6Ow>Cr7F5nM-Z@4TCNZ9b6BS^dL*43wL0yl>M)*^rkS3KcnORyFd z4}Ys(7Oc{AK9rKJCfQUe#v4$L9BnlS`&){=@d~<1LI@pgl_~Y#GLKKbk0Za2bs}XK zI>_}=%7}23wRatPx&s=b+sW7Mn(v-_7vzd!~S@6{5QL6v+>IdtYnyKH~t z6q7}RH%*0w=RVpP`B&+Gx#B=Q<_mxUUHm=M z&1Fhx1D8MP19zl0?Y0U1z(8JMkSv%l=+4sNlN-4=1@&lyz&%)Tr3Wfml2^y7TP*6d z3hzLP*HJBHykL$L4BH5@P}DQzV6J57&P`JCPzsyPr0ojdk|1+FyCG?Ptyyh`1{+fB zJLlE)5O&8xTY3C_d`>)%tC{-qHSG?|;GnJM+Cgsz*x{+0Wucik(TD#xZJ3=_hS%w) zu(9Hoa{Dz^eUgC!(|frK=vtRJVC1WOUOg7`RqbbxaWse;@>6NOoHd$L8uy=9X%-9j zHP_Q}uaTP~>QK>IJsj_NE7{E!^vH0E<4oo?u1Cf+w6*3_TJebxl1NK*aG=O|#w{QX z-_V7v^E_G;x)>${Dp^LoBJU{;wQC2o?hMytIfjN$whB(Y@7yTsVY?E~yhNHul>RZ) z-5C?~v?ni8+_lfVKm-Cs*gu$a>Yq(^0EgGZ3LVIsK)z_+Tz%BTstX zPwm3`ZT)+1znZ{h&ayBVu-DOa*^?jAqf;PxKZ9vA#_o*Su$^r#AVkuNYh|nhvBWtB z_u$UwHpot;LTFUi601Rc!({V5N({pNgoEx>#H3$*z>U*SP0d#RJg{!7y!-$w|2a*& zA+0)0P6Ez@!H%qk=H?8ny8~RCS2+k&QB;L{O_MNRKVexbWr8ymZUU5Pvil0{@d*}S z+}w22t55iFBXvpUF!8ITpVkNuYS?e}emqhN26-{lRyi7847uu#STPicOMUgEAbV^A zJ&WGzr+IAGHfxB=pFijT;HKs8&(ymPuh4=9j`P-|(55{G?jHlr>f@Cx?gF|`u39*^ z%Rs17)LQk{MF!;$WsCDxB2Rg0%ozj%U%WI4zjW~P;)JP!uY@>Ia5yy43FwOz>A_62 zBJtADj_G!hY(E3h_B45g<_-|{`mDsT#{}XIAMxH>7;d#)y|dv4w&&h{74#?P3po#D zENr2KtOm@x)1%k{+)&W2PSZPbV&nQqxIwt8LTi`ufA+EHe7=D9>8AMdc2RRj$CGmC zjq2?!m?3Qq`?yW=NM$M0V%f$WPe;<|Pps1FzaEor(7@iNuZxCxAu}5+UrMO6R+HiJ z;GJr~oe~h-lmLrhK7L%nybzUcEVjGANjSlBvS9y&s|lf_w8!R8M9g>9+<=PA6CT3& zRz^1{&yQd;h4cfdDo?PM?~A0~t7K4n-9^Z_m4Tl0R|BjPg@wBc%VPDad()5ug2WVJ zOdBqvp}_NrxNS*NC}ml5*vg654SM#n-%m>w?Yn|IM;TwwY{KgrQy@lsilRMx^Kh&Y@-lLH z_Zr7HlR`8iHzAh^qa^R~p{Kg)->wukd07wPHEv+py%$2@A#55g3a7R<*vLPiJ}>?I z?ifHmu#)EzNcX74Oghz8!5OXR=r=Z=F|Sj`1I;)=4q=XUniGXx4nz z5x4|8lAn7sxfg7iLf5TNEne>er4d8AwjB(ER8NVs-X`0Mx5bB?5BF(8F@dgPb>xw* zuNxSStb)AYEu`N916IfMcy?nqGidfmd=!CEnt@fu#&gU$E-H+=hL$6ye+*|OQ32&2 z>?Q<*G-2D$Dqs*87DygjOtlMfXvN)~&=TX!p_|9-Diu(b1E@pt#P6U#eaONJ1cKf8 z?r>62yLD&Lt~7RwB}gtZm{w?R9X;5HSIV>P1FM6BF_2#ZfHzDbw;nPEt+ zPMT5b6a3u+-u)XlQT7%!W=|ZunV%^C5BoS%bPH`ATUi6oLJk&hL-k~kw(tT~uZAry z`u0i97G2=9@g9*BO_)5Du>=%fI7!U!R*PLMWIJ8KR12zA3(M1;geo!$k!ix}Y?A%z zhk+`Bt3Jh%9~(dK$Gaf+k%JSajcAqM5rV{h;k-glwL_7aJ6I{T+*~OG`Re8lM9GV* z%6}H{0kLB1m9yO|y@L&aFJnQL)&4|F2h8FLOZGPlg6&>&d-DZXW!^nzc(xS3vLh%< z6m>t)KO?4F#F)}b!I&Kl!niloL1hn{Q@1-ON?O+H&T~NlJ-7y=%Xx|fy867nL6aLT zOVb7P4`k{&nc0=^6cA9CJxMR~17XC}g^;P6ifyjVHy7gbBg!K|ZfBF8&A)RhP=&DZ zb5O=kj*8R}OnNd~4ju;!AoR$4ulgM7K7&_{a1R2Sa=^-a`pCjFCMBz`EH>QBx@E^V z?M&~ZiZ$~rie(`lQ*?a1&2~rpR67^RP+WU>)zV=$6$bc})PxKdp#-u*Uw;fnO`5d+ z?Hqi1;55xWvPe!~pY1SSZv!H<3kaeGfhzpH4sP4EL;BFR9X62J^NY`qSZ` znj76z$e7o)WcSHo$_o|g&5G9MH~OGV`N>6`o%>*1gM>0X2Ly7%3B+N9n7QzBP`doq zfOBT#chHaLG8v?ssn|(r#{8qf@P?i#Ht7`b)}Ai$x>)6M1VQH>!x8&QPoMehz*z32 za~LttcIdFk$r{41Fq2$F%ura6-!!G8QJ5E?DdjUS0WR~+X#{saUz&bv!(P|4{8OcJ z4IrFh+nRT=qi;`jl)$AOfHsvnVBIXLJZAoQ&-KH_YWMk9oQd476>o%!V!x!!qLnjg zo9pknwq`&T`xlAXB!u)QK7=<*ye)A>ot~`|zJXJ1uLv}mfveJzX+E#%*sTC>ixSFO z#Y|Rs*OTQq$x#Evf)F)sTXx%fWLxNFX@g6)-&ceeM&e}hYA4Zzp89yFhvwyCzwILh zv%1|p%V;=n_!}0333|XVw?^&Y3^odIYC@~W@Or<^(DQD}$L62W+PnG8aYm9sj&AWy zJE0P328bAP+4nu;1J780nP2RfXuaH~$D>v|59e|;O(50nBq?^J2GS=1ksVOop!Y?V zlT)=qBq<_ph;o?7k~ZHrR5o=p>$rBrV%=JZh=y#2-RJp$k$mxa8TzGkYpF<26 zv;36eh^X%OdQ#-z*z2N-5hj-T<~miy0r3FV-vKYBB2*yvDyoJpLOfqAZsQx|o^#`g zUS+Gvek6lHBBdP43z@$s5+BC@*x-A|{QJ1ccp9N~zfmvC$P%#`{9*Bx z-==g@-J$RTgq4qSYT-DJXd>KH^7KT1VkoD`B$EjqyzD5>uzP5e>CfI{YY>UONEqxe z&!@72Z71tP3n_Wf1H{*BoxLUJpp+e6JMyL@P4R-&Kh`)0!XgZ{_=~TD=CD8?A1#1s z(!BT>UwsX|-&1o}wH1#S<%|}Okeqk#UW_s-OrxqWvXBy#KEyFa_T=E3QP1Y3a$McL z;GqWF@!#eRFpYVp8^OnlF)yYI{9T7ux<9E%%B+r-w#VXkPW_4`%X`w3S>bB`-V&tC zQ532m=i%^e?u)ETN_cgVCQ%(2IG9^KcCjE1wPRUnf5u2teJVTRGHg$6EFm%EV}=k0 z^DQgvS5`U54u(sR%&^lElQia7ugLigp@%2vTtR~2NKw6Csw2~zcnO94{DG|gH8lOu z%FenqF@vDW$YA}&wLeoEa5_s0LbUq`txJ9SppnN}oEup?@?ESDe`$f#H3?;!u+pVf1f4R~OkC@P7mr3Q zu^NGUf0Vbms%xI95?+Rw)rvU0H;PEGN(u9TX)xV1yUY#$xWLS*Jem*eqmV{^a`9ch zU{FHSiZ`jJo(fUW?+mOKF&6relq?%=yA})&T*Nx*fH*{n0Swj zpSgFXly^pw{#O1xDfhF_jLC0&t7w-ODwzrg%93tPUbMJJTj@o2-xGbLJ6EW1 z6`=*zy}oc-oOC(Uh!q9XgcLZ@U)VcBvU?i{x+X2i=L{{(t?x;c(YdsjTvCu z4dNts08@_?MXRE$Qs1{Z|GSSxSTA ztp3+mpPqKh_3D(^eg2s`o!f(1B*P7WCbwHn4ZmgoxZed4mPEwNlI6pgIMrG=#iN>A zs&X-?)=CBgmKt`;p&ZyR+kh|c^8>k{YZJBskp{s?pmFfX_xz+p^Lan>x7V3YM;f1A zq70n8CQ!jzIeH3PO`0pdVYLN|l+oh#$A^U$wpfh~+2rkl_6j}~IA7i;FCZ2uoNFdU zlZ4iSTtRP@JFwKq4)tbJ#d=DTT47H<@T2+BlMb&q)=US3BViOs#|lu_nvr-BNNdrW*9>*y^9I%gkpf5QZsrw=i;f5Fh#xpk0ETLr3^#Mw{^IbvU(p^^ z7QcUZ|Gq(J)JqT z8ZDygmiIej*dJG;dWNqn@GI)k7}8uT+(zolYi?27Qli;7u(%$+WdzJMo2FhjvOy*a zdSYIfnPx_0X1cw^dM13S*lEEo;*NVpbEBHxiV$C6V<~h|H=ki7{Zh?5{v|ib<4O`; z&r7g`|Q<$LQbQ!cEqk`-U6FV3W!M%$7SruLE)@16R%gvD(U{9OaD?JYF0)OB(ojV6U@2&^w{Q3U?b3ly0!E#`%7J^av*w3SO;mHMrKR+))*Rn6=x)oTw#Z>DR zTUu|7$69YXt{X9Z61*P|J37Gb_o_k6xy+&%^U#Nc;p{vlDqSK{u}m5n)iMdNpdUQ|b7 zPBw{WYs_zR@vQdMNOBZqkZ##;FafB9njEPk_AtR1^=;hBOL)6vFoKHlII~E%TbN`K z(q84%VkDn*IG;HrwPBQ*9$4w5SegP2`5CfDOT6$9rcYO>WeT?y&QZ{p@HJW23r?kd zRh%g-V%S5NhmIEzJVkA)xJG;OqTXo3G_PIUd04-sM=uL8<_lcc&rr%or3066 zptrihLfR8`M+n-5^VH?4lTBPaSbnlFHGUMRyMwNII3dpT*yP?D>Mf;erPve|;!jdC%B3Z3jB|5U2^Qk@i&16x1SlWF>9Zg>6CwgUijM1fF%->A zdkY9eQ`rbd&TpWMPu;c|YCJC_PE8-}crePWzgs8I=2$irD?^F)rN)tU5}tUs5_-7+ z8xM=gey6c}%I1z}JLR?gFi5g)%l7PpG0Rk&$4b1sY~7I*xyQ67?Vuh2|o`A z&a(&}Z-Dv2$0~h|)XiLE&uZe)md58F1yS`WG@2jjRnW4HPY13<70!Xu($bH}O+A3f zAJHADrt--cT0$`XHGKIQJBua#1%%aJ ~w8R*Ds@C!w%G!ju`e(F(dM5@wrNhkYB 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

}YKha%cmPYLu;R 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≫~!>s(tro3>}Q3=`~JPtl~i9X^$&W#lba zc;O@-$WkAHo~xr>DMKhO?8u^866AEJ3TAj1aPdpqg~tRdqKt1Ie-%xbTrL^M^+}%I zbPRjx_c`RDs09-D=%fC^-x&8^++uoscuBAfP8tcL+zYf{yCm?(tNarg^MWAU95O|Z zjN>wolmkkQT}YJB(%P6%=>b(*-kT7vxIqB_e8S6*9oh4a*K8iL>R!C6H7%i5=2|Wt zVp&o*Yjkza=%q1lB#;L(?YQZOJV%H{0qmaEqGevv2k)Fw*|r>&7X znp0YDXPtjkpudF?KZCBeZLoNvN2l$(Se|Kgw&1H9nV!x;FUx*f1UCWMm~RxVP8zWu zSz}YqP8+dl?!mzx$w9yuR(qX9N>3(PiIe(ft9|Y~hUTG*_Eb_o(19W6l*9;q#0Ok= zPC%2(;oFTkyAa<;dr5_e*GsDE#I`n_!mziIHh(=F9wHs%&NBp^athjfG|<}_DROSg zbBMMk5UzSeG6~E2#>&WqpT6jfv>&L9y4+nuSdchjZzAw#23|#P|USHwDsf6(2BAsaIpP zB=wk{RKTr3xNkW92?2cGh1bJbnvZ+@)E3esy5Lncf7A;6hf9C;s_tnBax`Z>YBu}c(f8j?yFe{)ze z;xsF!5`1k)EzORF{nKC(hK3%9(UED^<L<%)txObq zV=|PApA6AaO%aaac!s%zl?Dp}0@9{69EiI|DSQrzdMn&|=qr2u$;*?29|3IBS$la= z@lHhDskX0&CK$LgN1ogEQwBD^BLgKC{%W5Sw+g_Zc>>p4#z?8S3rm@ZN5t=MuGrSb z6^`Z`OivmI(0L5N`I@u+9_bavmE0!4v^3g`=f1N7YENTDbc63z4Hh-HO59lsE}nJu z>5yiZMug5{1j68yb&R{!zAr!Hx7YSN17i-ui8)}xZ-<&%4|R1L;EhU*yov~L)IqFS z!FtIuDwbygCQl}78n(kA&ZgtoJL6R%c@Ef$iom6p=s|m;&Z+J#Uo z2}{!H3Z^19nL*e@yi4`Vkl@VYic|gsXc4#0n^#QOM+JyO1v13PgbssL(uK1l2D8Zu zd=&Mt|Dwq(Y#qJ4Pt=KCL`u(k7_B!Hj>_12s8P)pf;N7zbk}K#9{eoXz;kPzNtt%w zmWC$v5zASRy#Jj*%jt%!V6PMGJ==nOtYXn+vDlKSK%u256U^vA{Hi#!E5*WdOz6zo zL~e_1auDmNs;7asg2C*`o<%<)mFPjswSF7Q+%9R|8c&CwCUEr`Kj`n@BC>3&D0M|N ztxnHfefN|%@P;2cQH^Ov4b8oozBr2TaSA8IE17%Xj?yc-DxbtJKO^5vo?aR^3_35l z3+3!~z^~26A(_~dRk>`={gSS()Kts3-B2AFoa*KAVDDP5jUT5-KfFtYNyjUO1j8I> z?oGy-$ya|8of)b(V4sW*>hQVq5AWcULpIK{oSPKVn38!29Lf#HJyK1#Ny|nqP+ZHN zs!@$Gv?p&8j{3%d1Ymf>Po`4UdC!|Pveep0MyYkd$;`4$wg^!jfrlfSO%tIM$0vK7 zzMQ1vnmt6%vHa^HAu5tf0BJbwz>2W^=i0gz-XHys_+rA?S9pL*0x>Da%;7^gi^|P;`_(4wvKn9%|B@$QY@+E^S)=^xn?V#6rNI5+7_VXT3zm zdhq3cyNRIbe7JwUE#Ca{q~u)&)dVO5>e@E2GWb2`4sa}$a zvgHul&8lOsG+uLU=uBc@*&87%XCqnbg`#wM6|we66C8l7Qw0xDTa9j$7Yb(C0B+J_q{T0ohp&l|>lH^yAT3hbIX$qUT#NSEE6^yW0~^~pl+4Ab-B zhnAfRM59UJ6JmmehZMH97u?ikI5_baz?FGJ{ft#seNe8Ds0q|FAJ%?tfTQmLfWZEL zOLzRu3XHAmRUn{O_Ig7psv%vfhB?hhC#@?-V16k2nK9{y`gNZh*ATwvg?|D5 zlO3+dihin8Nbc%?cTU(ZYts$KYO~^(WBs+LV;UnuQlZlc4ng2S@w8>S?U|8DA%#X* zN`_+9u@C>T?CT9TjOU6A?{&kE89RI$@k5^=XmC*YW9NOle=+ABcgWEY*RIDuRf=rI zZCKWBl0|nANt6e+`{_nB(bwI<59mrzFRC}Wa{Xqp`iuNYtsVXSqMi;N9sSC>?tlNa zo}Vsb%l(uz+jrf0M*AXXV`b#`4(m-4ov(RGQ?2*=AFuoFZvWO-_X_RyV;O5p?9|_U zKV05fv-JWQ&awl>|JVrx=!I-o;QmmeNzKUU7ewGe-M_`{`WU1$xbA!&DkT1RUha+w z<^5Q$F*t;dIv&D|Mm7zEr|nAD%ZcP{K-ya_827W&A0y2DEFwikle)v<&$+{ax5)v^$#Ql+lLvZ#$bDcj5%$ zd!M_Wxi31Ow^|;TG_TRHuf$v4uiEY3PD}Q6zaMoOczI(rZ9CB{mnwRlm$gIt;W??* zzYh{*hj_p5c|ibI9rrR9P3tzC83*rp6O)trjAeXEy{Qaj2~bfC^z@I%Um>or;Ftga zeVWvjHS+(Jw?b)Rf9LmUWr=k3ds_AD%>vt7z*04;)@|#xVYet3lB?BL%=Fj3CqugC z(iaxfkeLtIk!oeHSqE&vE1L7x$BzhA4SZG|0G@lP`?5Xl-@AtMV32E@z+29=S|7bW z*AAJEAu;QWmG3E3QMdQ>yZ+Y&Q$$J0R z`F>kA{Qi8tUhM9&TQyGEEzYp%3xUI_F`2}kn4aEepLT~|s!&h(Y4iQBs?W9iT^rZq zB#$#~4u}m={yuB@h9#4V=P9=3?EBN`lQa%E6(`pI&Ne4#oBJ_QJ-$tG2aL`ODf=eK z@U{pKN``@f3$`6N9Pj>oj_A*} zbIQF_NBM6ppVvdT*HzPIW7P^Zf8O_c-u`+SZkCOswZzpLeGq)#i(0m`5t3Wh6rBi= z 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 diff --git a/installer/nsg/install_pngs/010.png b/installer/nsg/install_pngs/010.png deleted file mode 100644 index b4756722651e763693385f7fce972ccb18e4df8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211396 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�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

6ngn}&{TfLf#?+?r8Lk+W;D|lxM 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 +ViH`I;eD$9R=X;7{6{_Ot5r>P3RR7E4Q* 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�FCMVo4N~K=|?bPj{xJoc+ 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�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

b1mlj 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 Pz(ekX()%(=!=V2MG5THX diff --git a/installer/nsg/install_pngs/012.png b/installer/nsg/install_pngs/012.png deleted file mode 100644 index c6ab792c3ba0ca36d1fd0ab230518f9266c7eb77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 216171 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

`PV3Ipnd{4I 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�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
^MOQ1!vWeMOF4b`Q6;mY~C9BNGJcivc9hRjJO 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

* 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 0: plt.rcParams['font.size'] = dconf['fontsize'] else: plt.rcParams['font.size'] = dconf['fontsize'] = 10 -debug = dconf['debug'] - ddat = {} # current simulation data dfile = {} # data file information for current simulation @@ -56,12 +54,10 @@ def rmse (a1, a2): # return root mean squared error between a1, a2; assumes same lengths, sampling rates len1,len2 = len(a1),len(a2) sz = min(len1,len2) - if debug: print('len1:',len1,'len2:',len2,'ty1:',type(a1),'ty2:',type(a2)) return np.sqrt(((a1[0:sz] - a2[0:sz]) ** 2).mean()) def readdpltrials(basedir): # read dipole data files for individual trials - if debug: print('in readdpltrials',basedir) ldpl = [] i = 0 @@ -147,7 +143,7 @@ def calcerr (ddat, tstop, tstart=0.0): # calculates RMSE error from ddat dictionary NSig = errtot = 0.0; lerr = [] ddat['errtot']=None; ddat['lerr']=None - for fn,dat in ddat['dextdata'].items(): + for _, dat in ddat['dextdata'].items(): shp = dat.shape exp_times = dat[:,0] @@ -199,7 +195,7 @@ def weighted_rmse(ddat, tstop, weights, tstart=0.0): # calculates RMSE error from ddat dictionary NSig = errtot = 0.0; lerr = [] ddat['werrtot']=None; ddat['lerr']=None - for fn,dat in ddat['dextdata'].items(): + for _, dat in ddat['dextdata'].items(): shp = dat.shape exp_times = dat[:,0] sim_times = ddat['dpl'][:,0] @@ -280,10 +276,11 @@ def initaxes (self): # initialize the axes self.axdist = self.axprox = self.axdipole = self.axspec = self.axpois = None - def plotinputhist (self, xl, dinty): + def plotinputhist(self, xl, dinty): """ plot input histograms xl = x axis limits - dinty = dict of input types used, determines how many/which axes created/displayed + dinty = dict of input types used, + determines how many/which axes created/displayed """ extinputs = None @@ -298,7 +295,8 @@ def plotinputhist (self, xl, dinty): times = np.linspace(0, sim_tstop, num_step) try: - extinputs = spikefn.ExtInputs(dfile['spk'], dfile['outparam'], self.params) + extinputs = spikefn.ExtInputs(dfile['spk'], dfile['outparam'], + self.params) extinputs.add_delay_times() dinput = extinputs.inputs except ValueError: @@ -306,89 +304,97 @@ def plotinputhist (self, xl, dinty): plot_distribs = True if len(dinput['dist']) <= 0 and len(dinput['prox']) <= 0 and \ - len(dinput['evdist']) <= 0 and len(dinput['evprox']) <= 0 and \ - len(dinput['pois']) <= 0: - if debug: print('all hists 0!') + len(dinput['evdist']) <= 0 and len(dinput['evprox']) <= 0 and \ + len(dinput['pois']) <= 0: return False - self.hist=hist={x:None for x in ['feed_dist','feed_prox','feed_evdist','feed_evprox','feed_pois']} - - hasPois = len(dinput['pois']) > 0 and dinty['Poisson'] # this ensures synaptic weight > 0 + self.hist = {'feed_dist': None, + 'feed_prox': None, + 'feed_evdist': None, + 'feed_evprox': None, + 'feed_pois': None} + # dinty ensures synaptic weight > 0 + hasPois = len(dinput['pois']) > 0 and dinty['Poisson'] gRow = 0 - self.axdist = self.axprox = self.axpois = None # axis objects + self.axdist = self.axprox = self.axpois = None # axis objects # check poisson inputs, create subplot if hasPois: - self.axpois = self.figure.add_subplot(self.G[gRow,0]) + self.axpois = self.figure.add_subplot(self.G[gRow, 0]) gRow += 1 # check distal inputs, create subplot if (len(dinput['dist']) > 0 and dinty['OngoingDist']) or \ (len(dinput['evdist']) > 0 and dinty['EvokedDist']): - self.axdist = self.figure.add_subplot(self.G[gRow,0]) - gRow+=1 + self.axdist = self.figure.add_subplot(self.G[gRow, 0]) + gRow += 1 # check proximal inputs, create subplot if (len(dinput['prox']) > 0 and dinty['OngoingProx']) or \ (len(dinput['evprox']) > 0 and dinty['EvokedProx']): - self.axprox = self.figure.add_subplot(self.G[gRow,0]) - gRow+=1 - + self.axprox = self.figure.add_subplot(self.G[gRow, 0]) + gRow += 1 # check input types provided in simulation - if extinputs is not None and self.hassimdata(): # only valid param.txt file after sim was run - if debug: - print(len(dinput['dist']),len(dinput['prox']),len(dinput['evdist']),len(dinput['evprox']),len(dinput['pois'])) - - if hasPois: # any Poisson inputs? - extinputs.plot_hist(self.axpois,'pois',times,'auto',xl,color='k',hty='step',lw=self.gui.linewidth+1) - - if len(dinput['dist']) > 0 and dinty['OngoingDist']: # dinty condition ensures synaptic weight > 0 - extinputs.plot_hist(self.axdist,'dist',times,'auto',xl,color='g',lw=self.gui.linewidth+1) - - if len(dinput['prox']) > 0 and dinty['OngoingProx']: # dinty condition ensures synaptic weight > 0 - extinputs.plot_hist(self.axprox,'prox',times,'auto',xl,color='r',lw=self.gui.linewidth+1) - - if len(dinput['evdist']) > 0 and dinty['EvokedDist']: # dinty condition ensures synaptic weight > 0 - extinputs.plot_hist(self.axdist,'evdist',times,'auto',xl,color='g',hty='step',lw=self.gui.linewidth+1) - - if len(dinput['evprox']) > 0 and dinty['EvokedProx']: # dinty condition ensures synaptic weight > 0 - extinputs.plot_hist(self.axprox,'evprox',times,'auto',xl,color='r',hty='step',lw=self.gui.linewidth+1) + if extinputs is not None and self.hassimdata(): + if hasPois: + extinputs.plot_hist(self.axpois, 'pois', times, 'auto', xl, color='k', + hty='step', lw=self.gui.linewidth+1) + + # dinty condition ensures synaptic weight > 0 + if len(dinput['dist']) > 0 and dinty['OngoingDist']: + extinputs.plot_hist(self.axdist, 'dist', times, 'auto', xl, color='g', + lw=self.gui.linewidth+1) + + if len(dinput['prox']) > 0 and dinty['OngoingProx']: + extinputs.plot_hist(self.axprox, 'prox', times, 'auto', xl, color='r', + lw=self.gui.linewidth+1) + + if len(dinput['evdist']) > 0 and dinty['EvokedDist']: + extinputs.plot_hist(self.axdist, 'evdist', times, 'auto', xl, + color='g', hty='step', lw=self.gui.linewidth+1) + + if len(dinput['evprox']) > 0 and dinty['EvokedProx']: + extinputs.plot_hist(self.axprox, 'evprox', times, 'auto', xl, + color='r', hty='step', lw=self.gui.linewidth+1) elif plot_distribs: - if len(dinput['evprox']) > 0 and dinty['EvokedProx']: # dinty condition ensures synaptic weight > 0 + if len(dinput['evprox']) > 0 and dinty['EvokedProx']: prox_tot = np.zeros(len(dinput['evprox'][0][0])) for prox in dinput['evprox']: prox_tot += prox[1] - plot = self.axprox.plot(dinput['evprox'][0][0],prox_tot,color='r',lw=self.gui.linewidth,label='evprox distribution') - self.axprox.set_xlim(dinput['evprox'][0][0][0],dinput['evprox'][0][0][-1]) - if len(dinput['evdist']) > 0 and dinty['EvokedDist']: # dinty condition ensures synaptic weight > 0 + self.axprox.plot(dinput['evprox'][0][0], prox_tot, color='r', + lw=self.gui.linewidth, label='evprox distribution') + self.axprox.set_xlim(dinput['evprox'][0][0][0], + dinput['evprox'][0][0][-1]) + if len(dinput['evdist']) > 0 and dinty['EvokedDist']: dist_tot = np.zeros(len(dinput['evdist'][0][0])) for dist in dinput['evdist']: dist_tot += dist[1] - plot = self.axdist.plot(dinput['evdist'][0][0],dist_tot,color='g',lw=self.gui.linewidth,label='evdist distribution') - self.axprox.set_xlim(dinput['evdist'][0][0][0],dinput['evdist'][0][0][-1]) + self.axdist.plot(dinput['evdist'][0][0], dist_tot, color='g', + lw=self.gui.linewidth, label='evdist distribution') + self.axprox.set_xlim(dinput['evdist'][0][0][0], + dinput['evdist'][0][0][-1]) ymax = 0 for ax in [self.axpois, self.axdist, self.axprox]: - if not ax is None: + if ax is not None: if ax.get_ylim()[1] > ymax: ymax = ax.get_ylim()[1] if ymax == 0: - if debug: print('all hists None!') return False else: for ax in [self.axpois, self.axdist, self.axprox]: - if not ax is None: - ax.set_ylim(0,ymax) + if ax is not None: + ax.set_ylim(0, ymax) if self.axdist: self.axdist.invert_yaxis() - for ax in [self.axpois,self.axdist,self.axprox]: + for ax in [self.axpois, self.axdist, self.axprox]: if ax: ax.set_xlim(xl) ax.legend(loc=1) # legend in upper right - return True,gRow + return True, gRow def clearaxes (self): # clear the figures axes @@ -499,7 +505,7 @@ def plotextdat (self, recalcErr=True): yl = self.axdipole.get_ylim() cmap=plt.get_cmap('nipy_spectral') - csm = plt.cm.ScalarMappable(cmap=cmap); + csm = plt.cm.ScalarMappable(cmap=cmap) csm.set_clim((0,100)) self.clearlextdatobj() # clear annotation objects @@ -614,7 +620,7 @@ def plotsimdat (self): self.axdipole.set_xlim(xl) left = 0.08 - w,h=getscreengeom() + w, _ = getscreengeom() if w < 2800: left = 0.1 self.figure.subplots_adjust(left=left,right=0.99,bottom=bottom,top=0.99,hspace=0.1,wspace=0.1) # reduce padding @@ -644,7 +650,6 @@ def plotsimdat (self): # skip for optimization for lsim in lsimdat: # plot average dipoles from prior simulations olddpl = lsim['dpl'] - if debug: print('olddpl has shape ',olddpl.shape,len(olddpl[:,0]),len(olddpl[:,1])) self.axdipole.plot(olddpl[:,0],olddpl[:,1],'--',color='black',linewidth=self.gui.linewidth) if self.params['N_trials']>1 and dconf['drawindivdpl'] and len(ddat['dpltrials']) > 0: # plot dipoles from individual trials @@ -691,18 +696,16 @@ def plotsimdat (self): self.axdipole.set_ylabel(r'Dipole (nAm $\times$ '+str(scalefctr)+')\n',fontsize=dconf['fontsize']) self.axdipole.set_xlim(xl); self.axdipole.set_ylim(yl) - if DrawSpec: # - if debug: print('ylim is : ', np.amin(ddat['dpl'][sidx:eidx,1]),np.amax(ddat['dpl'][sidx:eidx,1])) - + if DrawSpec: gRow = 6 - self.axspec = self.figure.add_subplot(self.G[gRow:10,0]); # specgram + self.axspec = self.figure.add_subplot(self.G[gRow:10,0]) # specgram cax = self.axspec.imshow(ds['TFR'],extent=(ds['time'][0],ds['time'][-1],ds['freq'][-1],ds['freq'][0]),aspect='auto',origin='upper',cmap=plt.get_cmap(self.params['spec_cmap'])) self.axspec.set_ylabel('Frequency (Hz)',fontsize=dconf['fontsize']) self.axspec.set_xlabel('Time (ms)',fontsize=dconf['fontsize']) self.axspec.set_xlim(xl) self.axspec.set_ylim(ds['freq'][-1],ds['freq'][0]) cbaxes = self.figure.add_axes([0.6, 0.49, 0.3, 0.005]) - cb = plt.colorbar(cax, cax = cbaxes, orientation='horizontal') # horizontal to save space + plt.colorbar(cax, cax = cbaxes, orientation='horizontal') # horizontal to save space else: self.axdipole.set_xlabel('Time (ms)',fontsize=dconf['fontsize']) From 23ce29dd19eb7c5ef7097026a6c25b869005f263 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 15:20:17 -0400 Subject: [PATCH 023/107] MAINT: remove mod files and hnn.cfg, conf.py --- conf.py | 191 ------------------------------------------ hnn.cfg | 61 -------------- hnn.py | 38 ++------- mod/ar.mod | 58 ------------- mod/beforestep_py.mod | 48 ----------- mod/ca.mod | 135 ----------------------------- mod/cad.mod | 97 --------------------- mod/cat.mod | 67 --------------- mod/dipole.mod | 52 ------------ mod/dipole_pp.mod | 61 -------------- mod/hh2.mod | 122 --------------------------- mod/kca.mod | 108 ------------------------ mod/km.mod | 126 ---------------------------- mod/lfp.mod | 49 ----------- mod/mea.mod | 89 -------------------- mod/vecevent.mod | 69 --------------- 16 files changed, 5 insertions(+), 1366 deletions(-) delete mode 100644 conf.py delete mode 100644 hnn.cfg delete mode 100644 mod/ar.mod delete mode 100644 mod/beforestep_py.mod delete mode 100644 mod/ca.mod delete mode 100644 mod/cad.mod delete mode 100644 mod/cat.mod delete mode 100644 mod/dipole.mod delete mode 100644 mod/dipole_pp.mod delete mode 100644 mod/hh2.mod delete mode 100644 mod/kca.mod delete mode 100644 mod/km.mod delete mode 100644 mod/lfp.mod delete mode 100644 mod/mea.mod delete mode 100644 mod/vecevent.mod diff --git a/conf.py b/conf.py deleted file mode 100644 index c7b554351..000000000 --- a/conf.py +++ /dev/null @@ -1,191 +0,0 @@ -from configparser import ConfigParser -import io -import pickle -import os -import sys -from collections import OrderedDict - -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - -# default config as string -def_config = """ -[run] -dorun = 1 -doquit = 1 -[paths] -paramindir = param -homeout = 1 -[sim] -simf = run.py -paramf = param/default.param -[draw] -drawindivdpl = 1 -drawindivrast = 1 -fontsize = 0 -drawavgdpl = 0 -[tips] -tstop = Simulation duration; Evoked response simulations typically take 170 ms while ongoing rhythms are run for longer. -dt = Simulation timestep - shorter timesteps mean more accuracy but longer runtimes. -[opt] -decay_multiplier = 1.6 -""" - -# make dir, catch exceptions -def safemkdir (dn): - try: - os.mkdir(dn) - except FileExistsError: - pass - except OSError: - print('ERR: incorrect permissions for creating', dn) - raise - - return True - -# parameter used for optimization -class param: - def __init__ (self, origval, minval, maxval, bounded, var, bestval=None): - self.origval = origval - self.minval = minval - self.maxval = maxval - self.bounded = bounded - self.bestval = bestval - if var.count(',') > 0: self.var = var.split(',') - else: self.var = var - def __str__ (self): - sout = '' - for s in [self.var, self.minval, self.maxval, self.origval, self.bounded, self.bestval]: - sout += str(s) - sout += ' ' - return sout - def assignstr (self, val): # generates string for execution - if type(self.var) == list: - astr = '' - for var in self.var: astr += var + ' = ' + str(val) + ';' - return astr - else: - return self.var + ' = ' + str(val) - def inbounds (self,val): # check if value is within bounds - if not bounded: return True - return val >= self.minval and val <= self.maxval - # only return assignstr if val is within bounds - def checkassign (self,val): - if self.inbounds(val): - return self.assignstr(val) - else: - return None - -# write config file starting with defaults and new entries -# specified in section (sec) , option (opt), and value (val) -# saves to output filepath fn -def writeconf (fn,sec,opt,val): - conf = ConfigParser() - conf.readfp(io.BytesIO(def_config)) # start with defaults - # then change entries by user-specs - for i in range(len(sec)): conf.set(sec[i],opt[i],val[i]) - # write config file - with open(fn, 'wb') as cfile: conf.write(cfile) - -def str2bool (v): return v.lower() in ("true", "t", "1") - -# read config file -def readconf (fn="hnn.cfg",nohomeout=False): - config = ConfigParser() - config.optionxform = str - - with open(fn, 'r') as cfg_file: - cfg_txt = os.path.expandvars(cfg_file.read()) - - config.readfp(StringIO(cfg_txt)) - - def conffloat (base,var,defa): # defa is default value - val = defa - try: val=config.getfloat(base,var) - except: pass - return val - - def confint (base,var,defa): - val = defa - try: val=config.getint(base,var) - except: pass - return val - - def confstr (base,var,defa): - val = defa - try: val = config.get(base,var) - except: pass - return val - - def confbool (base,var,defa): - return str2bool(confstr(base,var,defa)) - - def readtips (d): - if not config.has_section('tips'): return None - ltips = config.options('tips') - for i,prm in enumerate(ltips): - d[prm] = config.get('tips',prm).strip() - - d = {} - - d['homeout'] = confint("paths","homeout",1) # whether user home directory for output - if nohomeout: d['homeout'] = 0 # override config file with commandline - - d['simf'] = confstr('sim','simf','run.py') - d['paramf'] = confstr('sim','paramf',os.path.join('param','default.param')) - - - # dbase - optional config setting to change base output directory - if config.has_option('paths','dbase'): - dbase = config.get('paths','dbase').strip() - if not safemkdir(dbase): sys.exit(1) # check existence of base hnn output dir - else: - if d['homeout']: # user home directory for output - if 'SYSTEM_USER_DIR' in os.environ: - dbase = os.path.join(os.environ["SYSTEM_USER_DIR"],'hnn_out') # user home directory - else: - dbase = os.path.join(os.path.expanduser('~'),'hnn_out') # user home directory - if not safemkdir(dbase): sys.exit(1) # check existence of base hnn output dir - else: # cwd for output - dbase = os.getcwd() - - d['dbase'] = dbase - d['datdir'] = os.path.join(dbase,'data') # data output directory - d['paramoutdir'] = os.path.join(dbase, 'param') - d['paramindir'] = confstr('paths','paramindir','param') # this depends on hnn install location - d['dataf'] = confstr('paths','dataf','') - - for k in ['datdir', 'paramindir', 'paramoutdir']: # need these directories - if not safemkdir(d[k]): sys.exit(1) - - d['dorun'] = confint("run","dorun",1) - d['doquit'] = confint("run","doquit",1) - - d['drawindivdpl'] = confint("draw","drawindivdpl",1) - d['drawavgdpl'] = confint("draw","drawavgdpl",0) - d['drawindivrast'] = confint("draw","drawindivrast",1) - d['fontsize'] = confint("draw","fontsize",0) - - d['decay_multiplier'] = conffloat('opt','decay_multiplier',1.6) - - readtips(d) # read tooltips for parameters - - return d - -# determine config file name -def setfcfg (): - fcfg = "hnn.cfg" # default config file name - for i in range(len(sys.argv)): - if sys.argv[i].endswith(".cfg") and os.path.exists(sys.argv[i]): - fcfg = sys.argv[i] - # print("hnn config file is " , fcfg) - return fcfg - -fcfg = setfcfg() # config file name -nohomeout = False -for i in range(len(sys.argv)): # override homeout option through commandline flag - if sys.argv[i] == '-nohomeout' or sys.argv[i] == 'nohomeout': nohomeout = True -dconf = readconf(fcfg,nohomeout) - diff --git a/hnn.cfg b/hnn.cfg deleted file mode 100644 index d0616bc6b..000000000 --- a/hnn.cfg +++ /dev/null @@ -1,61 +0,0 @@ -[run] -dorun = 1 -doquit = 1 -debug = 0 - -[paths] -paramindir = param - -[sim] -simf = run.py - -[opt] -decay_multiplier = 1.6 - -[draw] -drawindivdpl = 1 -drawavgdpl = 1 -drawindivrast = 1 - -[tips] -tstop = Simulation duration; Evoked response simulations typically take 170 ms while ongoing rhythms are run for longer. -dt = Simulation integration timeste; shorter timesteps mean more accuracy but longer runtimes. Default value: 0.025 ms. -N_trials = How many times a simulation is run using the specified parameters. Note that there is randomization across trials, e.g. for the input times, leading to differences in outputs. -NumCores = Specifies how many cores to use for running the simulation. Best to use default value, which is automatically determined to match your CPU capacity. -save_figs = Whether to save figures of model activity when the simulation is run; if set to 1, figures are saved in simulation output directory. -save_spec_data = Whether to save spectral simulation spectral data - time/frequency/power; if set to 1, saved to simulation output directory. -f_max_spec = Maximum frequency used in dipole spectral analysis. -dipole_scalefctr = Scaling used to match simulation dipole signal to data; implicitly estimates number of cells contributing to dipole signal. -dipole_smooth_win = Window size (ms) used for Hamming filtering of dipole signal (0 means no smoothing); for analysis of ongoing rhythms (alpha/beta/gamma), best to avoid smoothing, while for evoked responses, best to smooth with 15-30 ms window. -prng_seedcore_opt = Random number generator seed used for parameter estimation (optimization). -prng_seedcore_input_prox = Random number generator seed used for rhythmic proximal inputs. -prng_seedcore_input_dist = Random number generator seed used for rhythmic distal inputs. -prng_seedcore_extpois = Random number generator seed used for Poisson inputs. -prng_seedcore_extgauss = Random number generator seed used for Gaussian inputs. -prng_seedcore_evprox_1 = Random number generator seed used for evoked proximal input 1. -prng_seedcore_evdist_1 = Random number generator seed used for evoked distal input 1. -prng_seedcore_evprox_2 = Random number generator seed used for evoked proximal input 2. -prng_seedcore_evdist_2 = Random number generator seed used for evoked distal input 2. -# evoked inputs -t_evprox_1 = Average start time of first evoked proximal input. -sigma_t_evprox_1 = Standard deviation of start time of first evoked proximal input. -gbar_evprox_1_L2Pyr = Weight of first evoked proximal input to L2/3 Pyramidal cells. -gbar_evprox_1_L2Basket = Weight of first evoked proximal input to L2/3 Basket cells. -gbar_evprox_1_L5Pyr = Weight of first evoked proximal input to L5 Pyramidal cells. -gbar_evprox_1_L5Basket = Weight of first evoked proximal input to L5 Basket cells. -t_evprox_2 = Average start time of second evoked proximal input. -sigma_t_evprox_2 = Standard deviation of start time of second evoked proximal input. -gbar_evprox_2_L2Pyr = Weight of second evoked proximal input to L2/3 Pyramidal cells. -gbar_evprox_2_L2Basket = Weight of second evoked proximal input to L2/3 Basket cells. -gbar_evprox_2_L5Pyr = Weight of second evoked proximal input to L5 Pyramidal cells. -gbar_evprox_2_L5Basket = Weight of second evoked proximal input to L5 Basket cells. -t_evdist_1 = Average start time of evoked distal input. -sigma_t_evdist_1 = Standard deviation of start time of evoked distal input. -gbar_evdist_1_L2Pyr = Weight of evoked distal input to L2/3 Pyramidal cells. -gbar_evdist_1_L2Basket = Weight of evoked distal input to L2/3 Basket cells. -gbar_evdist_1_L5Pyr = Weight of evoked distal input to L5 Pyramidal cells. -sync_evinput = Whether to provide synchronous inputs to all cells receiving evoked inputs or to set input timing of each cell independently (the same distribution is used either way). -numspikes_evprox_1 = Number of synaptic input events. -numspikes_evdist_1 = Number of synaptic input events. - -[params] diff --git a/hnn.py b/hnn.py index 93df0360b..ad4e3994c 100755 --- a/hnn.py +++ b/hnn.py @@ -1,40 +1,12 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- - import sys -import os -import shlex -from subprocess import Popen, PIPE, call -from time import sleep - -from hnn_qt5 import * +from PyQt5.QtWidgets import QApplication -# -def runnrnui (): - pnrnui = Popen(shlex.split(os.path.join('NEURON-UI','NEURON-UI')),cwd=os.getcwd()) - sleep(5) # make sure NEURON-UI had a chance to start - pjup = Popen(shlex.split('jupyter run loadmodel_nrnui.py --existing'),cwd=os.getcwd()) - lproc = [pnrnui, pjup]; done = [False, False] - while pnrnui.poll() is None or pjup.poll() is None: sleep(1) - for p in lproc: - try: p.communicate() - except: pass - return lproc +from hnn_qt5 import HNNGUI def runqt5 (): - app = QApplication(sys.argv) - ex = HNNGUI() - sys.exit(app.exec_()) - # app.exec_() - # print('\n'.join(repr(w) for w in app.allWidgets())) + app = QApplication(sys.argv) + ex = HNNGUI() + sys.exit(app.exec_()) if __name__ == '__main__': - useqt5 = True - for s in sys.argv: - if s == 'nrnui': - useqt5 = False - if useqt5: runqt5() - else: - lproc = runnrnui() - diff --git a/mod/ar.mod b/mod/ar.mod deleted file mode 100644 index 0c300dbc2..000000000 --- a/mod/ar.mod +++ /dev/null @@ -1,58 +0,0 @@ -TITLE Anomalous rectifier current for RD Traub, J Neurophysiol 89:909-921, 2003 - -COMMENT - Implemented by Maciej Lazarewicz 2003 (mlazarew@seas.upenn.edu) -ENDCOMMENT - -INDEPENDENT { t FROM 0 TO 1 WITH 1 (ms) } - -UNITS { - (mV) = (millivolt) - (mA) = (milliamp) -} - -NEURON { - SUFFIX ar - NONSPECIFIC_CURRENT i - RANGE gbar, i -} - -PARAMETER { - gbar = 0.0 (mho/cm2) - v (mV) - erev = -35 (mV) -} - -ASSIGNED { - i (mA/cm2) - minf (1) - mtau (ms) -} - -STATE { - m -} - -BREAKPOINT { - SOLVE states METHOD cnexp - i = gbar * m * ( v - erev ) -} - -INITIAL { - settables(v) - m = minf - m = 0.25 -} - -DERIVATIVE states { - settables(v) - m' = ( minf - m ) / mtau -} - -UNITSOFF -PROCEDURE settables(v) { - TABLE minf, mtau FROM -120 TO 40 WITH 641 - minf = 1 / ( 1 + exp( ( v + 75 ) / 5.5 ) ) - mtau = 1 / ( exp( -14.6 - 0.086 * v ) + exp( -1.87 + 0.07 * v ) ) -} -UNITSON diff --git a/mod/beforestep_py.mod b/mod/beforestep_py.mod deleted file mode 100644 index 3e2552a7f..000000000 --- a/mod/beforestep_py.mod +++ /dev/null @@ -1,48 +0,0 @@ -: Python callback from BEFORE STEP - -NEURON { - POINT_PROCESS beforestep_callback - POINTER ptr -} - -ASSIGNED { - ptr -} - -INITIAL { -} - -VERBATIM -extern int (*nrnpy_hoccommand_exec)(Object*); -extern Object** hoc_objgetarg(int); -extern int ifarg(int); -extern void hoc_obj_ref(Object*); -extern void hoc_obj_unref(Object*); -ENDVERBATIM - -BEFORE STEP { - :printf("beforestep_callback t=%g\n", t) -VERBATIM -{ - Object* cb = (Object*)(_p_ptr); - if (cb) { - (*nrnpy_hoccommand_exec)(cb); - } -} -ENDVERBATIM -} - -PROCEDURE set_callback() { -VERBATIM - Object** pcb = (Object**)(&(_p_ptr)); - if (*pcb) { - hoc_obj_unref(*pcb); - *pcb = (Object*)0; - } - if (ifarg(1)) { - *pcb = *(hoc_objgetarg(1)); - hoc_obj_ref(*pcb); - } -ENDVERBATIM -} - diff --git a/mod/ca.mod b/mod/ca.mod deleted file mode 100644 index e6571ba47..000000000 --- a/mod/ca.mod +++ /dev/null @@ -1,135 +0,0 @@ -COMMENT - 26 Ago 2002 Modification of original channel to allow variable time step - and to correct an initialization error. - - Done by Michael Hines(michael.hines@yale.edu) and Ruggero Scorcioni (rscorcio@gmu.edu) - at EU Advance Course in Computational Neuroscience. Obidos, Portugal - - ca.mod - Uses fixed eca instead of GHK eqn - - HVA Ca current - Based on Reuveni, Friedman, Amitai and Gutnick (1993) J. Neurosci. 13: 4609-4621. - - Author: Zach Mainen, Salk Institute, 1994, zach@salk.edu -ENDCOMMENT - -INDEPENDENT {t FROM 0 TO 1 WITH 1 (ms)} - -NEURON { - SUFFIX ca - USEION ca READ eca WRITE ica - RANGE m, h, gca, gbar - RANGE minf, hinf, mtau, htau - GLOBAL q10, temp, tadj, vmin, vmax, vshift, tshift -} - -PARAMETER { - gbar = 0.1 (pS/um2) : 0.12 mho/cm2 - vshift = 0 (mV) : voltage shift (affects all) - - cao = 2.5 (mM) : external ca concentration - cai (mM) - - temp = 23 (degC) : original temp - q10 = 2.3 : temperature sensitivity - tshift = 30.7 - - v (mV) - dt (ms) - celsius (degC) - vmin = -120 (mV) - vmax = 100 (mV) -} - -UNITS { - (mA) = (milliamp) - (mV) = (millivolt) - (pS) = (picosiemens) - (um) = (micron) - FARADAY = (faraday) (coulomb) - R = (k-mole) (joule/degC) - PI = (pi) (1) -} - -ASSIGNED { - ica (mA/cm2) - gca (pS/um2) - eca (mV) - minf hinf - mtau (ms) htau (ms) - tadj -} - -STATE { m h } - -INITIAL { - trates(v+vshift) - m = minf - h = hinf -} - -BREAKPOINT { - SOLVE states METHOD cnexp - gca = tadj * gbar * m * m * h - ica = (1e-4) * gca * (v - eca) -} - -LOCAL mexp, hexp - -: PROCEDURE states() { -: trates(v+vshift) -: m = m + mexp*(minf-m) -: h = h + hexp*(hinf-h) -: VERBATIM -: return 0; -: ENDVERBATIM -: } - -DERIVATIVE states { - trates(v + vshift) - m' = (minf - m) / mtau - h' = (hinf - h) / htau -} - -PROCEDURE trates(v) { - TABLE minf, hinf, mtau, htau - DEPEND celsius, temp - - FROM vmin TO vmax WITH 199 - - : not consistently executed from here if usetable == 1 - rates(v) - - : tinc = -dt * tadj - - : mexp = 1 - exp(tinc/mtau) - : hexp = 1 - exp(tinc/htau) -} - -PROCEDURE rates(vm) { - LOCAL a, b - - tadj = q10^((celsius - temp - tshift)/10) - - a = 0.055 * (-27 - vm) / (exp((-27 - vm) / 3.8) - 1) - b = 0.94 * exp((-75 - vm) / 17) - - mtau = 1 / tadj / (a+b) - minf = a / (a + b) - - : "h" inactivation - a = 0.000457 * exp((-13 - vm) / 50) - b = 0.0065 / (exp((-vm - 15) / 28) + 1) - - htau = 1 / tadj / (a + b) - hinf = a / (a + b) -} - -FUNCTION efun(z) { - if (fabs(z) < 1e-4) { - efun = 1 - z/2 - } else { - efun = z / (exp(z) - 1) - } -} diff --git a/mod/cad.mod b/mod/cad.mod deleted file mode 100644 index 4ec879cba..000000000 --- a/mod/cad.mod +++ /dev/null @@ -1,97 +0,0 @@ -COMMENT - 26 Ago 2002 Modification of original channel to allow variable time step - and to correct an initialization error. - - Done by Michael Hines (michael.hines@yale.edu) and Ruggero Scorcioni (rscorcio@gmu.edu) - at EU Advance Course in Computational Neuroscience. Obidos, Portugal - - Internal calcium concentration due to calcium currents and pump. - Differential equations. - - Simple model of ATPase pump with 3 kinetic constants (Destexhe 92) - Cai + P <-> CaP -> Cao + P (k1,k2,k3) - - A Michaelis-Menten approximation is assumed, which reduces the complexity - of the system to 2 parameters: - - kt = * k3 -> TIME CONSTANT OF THE PUMP - kd = k2/k1 (dissociation constant) -> EQUILIBRIUM CALCIUM VALUE - - The values of these parameters are chosen assuming a high affinity of - the pump to calcium and a low transport capacity (cfr. Blaustein, - TINS, 11: 438, 1988, and references therein). - - Units checked using "modlunit" -> factor 10000 needed in ca entry - - VERSION OF PUMP + DECAY (decay can be viewed as simplified buffering) - - All variables are range variables - - This mechanism was published in: Destexhe, A. Babloyantz, A. and - Sejnowski, TJ. Ionic mechanisms for intrinsic slow oscillations in - thalamic relay neurons. Biophys. J. 65: 1538-1552, 1993) - - Written by Alain Destexhe, Salk Institute, Nov 12, 1992 - -ENDCOMMENT - -TITLE Decay of internal calcium concentration - -INDEPENDENT {t FROM 0 TO 1 WITH 1 (ms)} - -NEURON { - SUFFIX cad - USEION ca - READ ica, cai - WRITE cai - - : SRJones put taur up here - RANGE ca, taur - GLOBAL depth, cainf - : GLOBAL depth, cainf, taur -} - -UNITS { - : moles do not appear in units - (molar) = (1/liter) - (mM) = (millimolar) - (um) = (micron) - (mA) = (milliamp) - (msM) = (ms mM) - FARADAY = (faraday) (coulomb) -} - -PARAMETER { - depth = .1 (um) : depth of shell - taur = 200 (ms) : rate of calcium removal 200 default - cainf = 100e-6 (mM) - cai (mM) -} - -STATE { - ca (mM) <1e-5> -} - -INITIAL { - ca = cainf - cai = ca -} - -ASSIGNED { - ica (mA/cm2) - drive_channel (mM/ms) -} - -BREAKPOINT { - SOLVE state METHOD cnexp -} - -DERIVATIVE state { - drive_channel = -(10000) * ica / (2 * FARADAY * depth) - - : cannot pump inward - if (drive_channel <= 0.) { drive_channel = 0. } - - ca' = drive_channel + (cainf-ca) / taur - cai = ca -} diff --git a/mod/cat.mod b/mod/cat.mod deleted file mode 100644 index 51ccc94e9..000000000 --- a/mod/cat.mod +++ /dev/null @@ -1,67 +0,0 @@ -TITLE Calcium low threshold T type current for RD Traub, J Neurophysiol 89:909-921, 2003 - -COMMENT - Implemented by Maciej Lazarewicz 2003 (mlazarew@seas.upenn.edu) -ENDCOMMENT - -INDEPENDENT { t FROM 0 TO 1 WITH 1 (ms) } - -UNITS { - (mV) = (millivolt) - (mA) = (milliamp) -} - -NEURON { - SUFFIX cat - NONSPECIFIC_CURRENT i : not causing [Ca2+] influx - RANGE gbar, i -} - -PARAMETER { - gbar = 0.0 (mho/cm2) - v eca (mV) -} - -ASSIGNED { - i (mA/cm2) - minf hinf (1) - mtau htau (ms) -} - -STATE { - m h -} - -BREAKPOINT { - SOLVE states METHOD cnexp - i = gbar * m * m * h * ( v - 125 ) -} - -INITIAL { - settables(v) - m = minf - h = hinf - m = 0 -} - -DERIVATIVE states { - settables(v) - m' = ( minf - m ) / mtau - h' = ( hinf - h ) / htau -} - -UNITSOFF -PROCEDURE settables(v) { - TABLE minf, mtau, hinf, htau FROM -120 TO 40 WITH 641 - - minf = 1 / (1 + exp(( -v - 56 ) / 6.2)) - mtau = 0.204 + 0.333 / (exp(( v + 15.8) / 18.2) + exp((-v - 131) / 16.7)) - hinf = 1 / (1 + exp((v + 80) / 4)) - - if (v < -81) { - htau = 0.333 * exp((v + 466 ) / 66.6) - } else { - htau = 9.32 + 0.333 * exp((-v - 21) / 10.5) - } -} -UNITSON diff --git a/mod/dipole.mod b/mod/dipole.mod deleted file mode 100644 index 06c0c5e4a..000000000 --- a/mod/dipole.mod +++ /dev/null @@ -1,52 +0,0 @@ -: dipole.mod - mod file for range variable dipole -: -: v 1.9.1m0 -: rev 2015-12-15 (SL: minor) -: last rev: (SL: Added back Qtotal, which WAS used in par version) - -NEURON { - SUFFIX dipole - RANGE ri, ia, Q, ztan - POINTER pv - - : for density. sums into Dipole at section position 1 - POINTER Qsum - POINTER Qtotal -} - -UNITS { - (nA) = (nanoamp) - (mV) = (millivolt) - (Mohm) = (megaohm) - (um) = (micrometer) - (Am) = (amp meter) - (fAm) = (femto amp meter) -} - -ASSIGNED { - ia (nA) - ri (Mohm) - pv (mV) - v (mV) - ztan (um) - Q (fAm) - - : human dipole order of 10 nAm - Qsum (fAm) - Qtotal (fAm) -} - -: solve for v's first then use them -AFTER SOLVE { - ia = (pv - v) / ri - Q = ia * ztan - Qsum = Qsum + Q - Qtotal = Qtotal + Q -} - -AFTER INITIAL { - ia = (pv - v) / ri - Q = ia * ztan - Qsum = Qsum + Q - Qtotal = Qtotal + Q -} diff --git a/mod/dipole_pp.mod b/mod/dipole_pp.mod deleted file mode 100644 index 5a9179e18..000000000 --- a/mod/dipole_pp.mod +++ /dev/null @@ -1,61 +0,0 @@ -: dipole_pp.mod - creates point process mechanism Dipole -: -: v 1.9.1m0 -: rev 2015-12-15 (SL: minor) -: last rev: (SL: added Qtotal back, used for par calc) - -NEURON { - POINT_PROCESS Dipole - RANGE ri, ia, Q, ztan - POINTER pv - - : for POINT_PROCESS. Gets additions from dipole - RANGE Qsum - POINTER Qtotal -} - -UNITS { - (nA) = (nanoamp) - (mV) = (millivolt) - (Mohm) = (megaohm) - (um) = (micrometer) - (Am) = (amp meter) - (fAm) = (femto amp meter) -} - -ASSIGNED { - ia (nA) - ri (Mohm) - pv (mV) - v (mV) - ztan (um) - Q (fAm) - Qsum (fAm) - Qtotal (fAm) -} - -: solve for v's first then use them -AFTER SOLVE { - ia = (pv - v) / ri - Q = ia * ztan - Qsum = Qsum + Q - Qtotal = Qtotal + Q -} - -AFTER INITIAL { - ia = (pv - v) / ri - Q = ia * ztan - Qsum = Qsum + Q - Qtotal = Qtotal + Q -} - -: following needed for POINT_PROCESS only but will work if also in SUFFIX -BEFORE INITIAL { - Qsum = 0 - Qtotal = 0 -} - -BEFORE BREAKPOINT { - Qsum = 0 - Qtotal = 0 -} diff --git a/mod/hh2.mod b/mod/hh2.mod deleted file mode 100644 index 7d0884c3f..000000000 --- a/mod/hh2.mod +++ /dev/null @@ -1,122 +0,0 @@ -TITLE hh2.mod sodium, potassium, and leak channels - -COMMENT - This is an adjusted Hodgkin-Huxley treatment for sodium, - potassium, and leakage channels. - Membrane voltage is in absolute mV and has been reversed in polarity - from the original HH convention and shifted to reflect a resting potential - of -65 mV. - Remember to set celsius in your HOC file. -ENDCOMMENT - -UNITS { - (mA) = (milliamp) - (mV) = (millivolt) - (S) = (siemens) -} - -? interface -NEURON { - SUFFIX hh2 - USEION na READ ena WRITE ina - USEION k READ ek WRITE ik - NONSPECIFIC_CURRENT il - RANGE gnabar, gkbar, gl, el, gna, gk - GLOBAL minf, hinf, ninf, mtau, htau, ntau, tshift, temp - THREADSAFE : assigned GLOBALs will be per thread -} - -PARAMETER { - gnabar = .12 (S/cm2) <0,1e9> - gkbar = .036 (S/cm2) <0,1e9> - gl = .0003 (S/cm2) <0,1e9> - el = -54.3 (mV) - temp = 6.3 - tshift = 30.7 -} - -STATE { - m h n -} - -ASSIGNED { - v (mV) - celsius (degC) - ena (mV) - ek (mV) - - gna (S/cm2) - gk (S/cm2) - ina (mA/cm2) - ik (mA/cm2) - il (mA/cm2) - minf hinf ninf - mtau (ms) htau (ms) ntau (ms) -} - -? currents -BREAKPOINT { - SOLVE states METHOD cnexp - gna = gnabar*m*m*m*h - ina = gna*(v - ena) - gk = gkbar*n*n*n*n - ik = gk*(v - ek) - il = gl*(v - el) -} - - -INITIAL { - rates(v) - m = minf - h = hinf - n = ninf -} - -? states -DERIVATIVE states { - rates(v) - m' = (minf-m)/mtau - h' = (hinf-h)/htau - n' = (ninf-n)/ntau -} - -:LOCAL q10 - - -? rates -PROCEDURE rates(v(mV)) { :Computes rate and other constants at current v. - :Call once from HOC to initialize inf at resting v. - LOCAL alpha, beta, sum, q10 - TABLE minf, mtau, hinf, htau, ninf, ntau DEPEND celsius FROM -100 TO 100 WITH 200 - -UNITSOFF - q10 = 3^((celsius - temp - tshift)/10) - :"m" sodium activation system - alpha = .1 * vtrap(-(v+40),10) - beta = 4 * exp(-(v+65)/18) - sum = alpha + beta - mtau = 1/(q10*sum) - minf = alpha/sum - :"h" sodium inactivation system - alpha = .07 * exp(-(v+65)/20) - beta = 1 / (exp(-(v+35)/10) + 1) - sum = alpha + beta - htau = 1/(q10*sum) - hinf = alpha/sum - :"n" potassium activation system - alpha = .01*vtrap(-(v+55),10) - beta = .125*exp(-(v+65)/80) - sum = alpha + beta - ntau = 1/(q10*sum) - ninf = alpha/sum -} - -FUNCTION vtrap(x,y) { :Traps for 0 in denominator of rate eqns. - if (fabs(x/y) < 1e-6) { - vtrap = y*(1 - x/y/2) - }else{ - vtrap = x/(exp(x/y) - 1) - } -} - -UNITSON diff --git a/mod/kca.mod b/mod/kca.mod deleted file mode 100644 index 951e42f04..000000000 --- a/mod/kca.mod +++ /dev/null @@ -1,108 +0,0 @@ -COMMENT - 26 Ago 2002 Modification of original channel to allow variable time step - and to correct an initialization error. - - Done by Michael Hines (michael.hines@yale.edu) and Ruggero Scorcioni (rscorcio@gmu.edu) - at EU Advance Course in Computational Neuroscience. Obidos, Portugal - - kca.mod - - Calcium-dependent potassium channel - Based on Pennefather (1990) -- sympathetic ganglion cells - taken from Reuveni et al (1993) -- neocortical cells - - Author: Zach Mainen, Salk Institute, 1995, zach@salk.edu - -ENDCOMMENT - -INDEPENDENT {t FROM 0 TO 1 WITH 1 (ms)} - -NEURON { - SUFFIX kca - USEION k READ ek WRITE ik - USEION ca READ cai - RANGE n, gk, gbar - RANGE ninf, ntau - GLOBAL Ra, Rb, caix - GLOBAL q10, temp, tadj, vmin, vmax, tshift -} - -UNITS { - (mA) = (milliamp) - (mV) = (millivolt) - (pS) = (picosiemens) - (um) = (micron) -} - -PARAMETER { - : 0.03 mho/cm2 - gbar = 10 (pS/um2) - v (mV) - cai (mM) - caix = 1 - - : max act rate - Ra = 0.01 (/ms) - - : max deact rate - Rb = 0.02 (/ms) - - dt (ms) - celsius (degC) - - : original temp - temp = 23 (degC) - q10 = 2.3 - tshift = 30.7 - - vmin = -120 (mV) - vmax = 100 (mV) -} - -ASSIGNED { - a (/ms) - b (/ms) - ik (mA/cm2) - gk (pS/um2) - ek (mV) - ninf - ntau (ms) - tadj -} - - -STATE { - n -} - -INITIAL { - rates(cai) - n = ninf -} - -BREAKPOINT { - SOLVE states METHOD cnexp - gk = tadj * gbar * n - ik = (1e-4) * gk * (v - ek) -} - -LOCAL nexp - -: Computes state variable n at the current v and dt. -DERIVATIVE states { - rates(cai) - n' = (ninf - n) / ntau -} - -PROCEDURE rates(cai(mM)) { - a = Ra * cai^caix - b = Rb - - tadj = q10^((celsius - temp - tshift) / 10) - - ntau = 1 / tadj / (a + b) - ninf = a / (a + b) - - : tinc = -dt * tadj - : nexp = 1 - exp(tinc/ntau) -} diff --git a/mod/km.mod b/mod/km.mod deleted file mode 100644 index df88cfa60..000000000 --- a/mod/km.mod +++ /dev/null @@ -1,126 +0,0 @@ -COMMENT - 26 Ago 2002 Modification of original channel to allow variable time step - and to correct an initialization error. - - Done by Michael Hines (michael.hines@yale.edu) and Ruggero Scorcioni (rscorcio@gmu.edu) - at EU Advance Course in Computational Neuroscience. Obidos, Portugal - - km.mod - - Potassium channel, Hodgkin-Huxley style kinetics - Based on I-M (muscarinic K channel) - Slow, noninactivating - - Original Author: Zach Mainen, Salk Institute, 1995, zach@salk.edu -ENDCOMMENT - -INDEPENDENT {t FROM 0 TO 1 WITH 1 (ms)} - -NEURON { - SUFFIX km - USEION k READ ek WRITE ik - RANGE n, gk, gbar - RANGE ninf, ntau - GLOBAL Ra, Rb - GLOBAL q10, temp, tadj, vmin, vmax, tshift -} - -UNITS { - (mA) = (milliamp) - (mV) = (millivolt) - (pS) = (picosiemens) - (um) = (micron) -} - -PARAMETER { - : 0.03 mho/cm2 - gbar = 10 (pS/um2) - v (mV) - - : v 1/2 for inf - tha = -30 (mV) - - : inf slope - qa = 9 (mV) - - : max act rate (slow) - Ra = 0.001 (/ms) - - : max deact rate (slow) - Rb = 0.001 (/ms) - - dt (ms) - celsius (degC) - - : original temp - temp = 23 (degC) - - : temp sensitivity - q10 = 2.3 - - tshift = 30.7 - - vmin = -120 (mV) - vmax = 100 (mV) -} - - -ASSIGNED { - a (/ms) - b (/ms) - ik (mA/cm2) - gk (pS/um2) - ek (mV) - ninf - ntau (ms) - tadj -} - - -STATE { - n -} - -INITIAL { - trates(v) - n = ninf -} - -BREAKPOINT { - SOLVE states METHOD cnexp - gk = tadj * gbar * n - ik = (1e-4) * gk * (v - ek) -} - -LOCAL nexp - -: Computes state variable n at the current v and dt. -DERIVATIVE states { - trates(v) - n' = (ninf - n) / ntau -} - -: Computes rate and other constants at current v. -: Call once from HOC to initialize inf at resting v. -PROCEDURE trates(v) { - TABLE ninf, ntau - DEPEND celsius, temp, Ra, Rb, tha, qa - - FROM vmin TO vmax WITH 199 - - : not consistently executed from here if usetable_hh == 1 - rates(v) - : tinc = -dt * tadj - : nexp = 1 - exp(tinc/ntau) -} - -: Computes rate and other constants at current v. -: Call once from HOC to initialize inf at resting v. -PROCEDURE rates(v) { - a = Ra * (v - tha) / (1 - exp(-(v - tha) / qa)) - b = -Rb * (v - tha) / (1 - exp((v - tha) / qa)) - - tadj = q10^((celsius - temp - tshift) / 10) - ntau = 1/tadj/(a+b) - ninf = a/(a+b) -} diff --git a/mod/lfp.mod b/mod/lfp.mod deleted file mode 100644 index 975a4dad1..000000000 --- a/mod/lfp.mod +++ /dev/null @@ -1,49 +0,0 @@ -: lfp.mod - -COMMENT -LFPsim - Simulation scripts to compute Local Field Potentials (LFP) from cable compartmental models of neurons and networks implemented in NEURON simulation environment. - -LFPsim works reliably on biophysically detailed multi-compartmental neurons with ion channels in some or all compartments. - -Last updated 12-March-2016 -Developed by : Harilal Parasuram & Shyam Diwakar -Computational Neuroscience & Neurophysiology Lab, School of Biotechnology, Amrita University, India. -Email: harilalp@am.amrita.edu; shyam@amrita.edu -www.amrita.edu/compneuro -ENDCOMMENT - -NEURON { - SUFFIX lfp - POINTER transmembrane_current - RANGE lfp_line,lfp_point,lfp_rc,initial_part_point, initial_part_line, initial_part_rc - -} - - -ASSIGNED { - - initial_part_line - initial_part_rc - transmembrane_current - lfp_line - lfp_point - lfp_rc - initial_part_point - - -} - -BREAKPOINT { - - :Point Source Approximation - lfp_point = transmembrane_current * initial_part_point * 1e-1 : So the calculated signal will be in nV - - :Line Source Approximation - lfp_line = transmembrane_current * initial_part_line * 1e-1 : So the calculated signal will be in nV - - :RC - lfp_rc = transmembrane_current * initial_part_rc * 1e-3 : So the calculated signal will be in nV - -} - - diff --git a/mod/mea.mod b/mod/mea.mod deleted file mode 100644 index c237b4b1f..000000000 --- a/mod/mea.mod +++ /dev/null @@ -1,89 +0,0 @@ -: mea.mod - -COMMENT -LFPsim - Simulation scripts to compute Local Field Potentials (LFP) from cable compartmental models of neurons and networks implemented in NEURON simulation environment. - -LFPsim works reliably on biophysically detailed multi-compartmental neurons with ion channels in some or all compartments. - -Last updated 12-March-2016 -Developed by : Harilal Parasuram & Shyam Diwakar -Computational Neuroscience & Neurophysiology Lab, School of Biotechnology, Amrita University, India. -Email: harilalp@am.amrita.edu; shyam@amrita.edu -www.amrita.edu/compneuro -ENDCOMMENT - -NEURON { - SUFFIX mea - POINTER transmembrane_current_m - RANGE mea_line0,mea_line1,mea_line2,mea_line3,mea_line4,mea_line5,mea_line6,mea_line7,mea_line8,mea_line9,mea_line10,mea_line11,mea_line12,mea_line13,mea_line14,mea_line15 - RANGE initial_part_line0,initial_part_line1,initial_part_line2,initial_part_line3,initial_part_line4,initial_part_line5,initial_part_line6,initial_part_line7,initial_part_line8,initial_part_line9,initial_part_line10,initial_part_line11,initial_part_line12,initial_part_line13,initial_part_line14,initial_part_line15 - -} - -PARAMETER { - : default values put here - - } - -ASSIGNED { - - transmembrane_current_m - initial_part_line0 - initial_part_line1 - initial_part_line2 - initial_part_line3 - initial_part_line4 - initial_part_line5 - initial_part_line6 - initial_part_line7 - initial_part_line8 - initial_part_line9 - initial_part_line10 - initial_part_line11 - initial_part_line12 - initial_part_line13 - initial_part_line14 - initial_part_line15 - - mea_line0 - mea_line1 - mea_line2 - mea_line3 - mea_line4 - mea_line5 - mea_line6 - mea_line7 - mea_line8 - mea_line9 - mea_line10 - mea_line11 - mea_line12 - mea_line13 - mea_line14 - mea_line15 - - -} - -BREAKPOINT { - - :Line Source Approximation - mea_line0 = transmembrane_current_m * initial_part_line0 * 1e-1 : 1e-1 (mA to uA) : calculated potential will be in uV - mea_line1 = transmembrane_current_m * initial_part_line1 * 1e-1 - mea_line2 = transmembrane_current_m * initial_part_line2 * 1e-1 - mea_line3 = transmembrane_current_m * initial_part_line3 * 1e-1 - mea_line4 = transmembrane_current_m * initial_part_line4 * 1e-1 - mea_line5 = transmembrane_current_m * initial_part_line5 * 1e-1 - mea_line6 = transmembrane_current_m * initial_part_line6 * 1e-1 - mea_line7 = transmembrane_current_m * initial_part_line7 * 1e-1 - mea_line8 = transmembrane_current_m * initial_part_line8 * 1e-1 - mea_line9 = transmembrane_current_m * initial_part_line9 * 1e-1 - mea_line10 = transmembrane_current_m * initial_part_line10 * 1e-1 - mea_line11 = transmembrane_current_m * initial_part_line11 * 1e-1 - mea_line12 = transmembrane_current_m * initial_part_line12 * 1e-1 - mea_line13 = transmembrane_current_m * initial_part_line13 * 1e-1 - mea_line14 = transmembrane_current_m * initial_part_line14 * 1e-1 - mea_line15 = transmembrane_current_m * initial_part_line15 * 1e-1 - -} - diff --git a/mod/vecevent.mod b/mod/vecevent.mod deleted file mode 100644 index c5f624d9c..000000000 --- a/mod/vecevent.mod +++ /dev/null @@ -1,69 +0,0 @@ -: Vector stream of events - -NEURON { - ARTIFICIAL_CELL VecStim -} - -ASSIGNED { - index - etime (ms) - space -} - -INITIAL { - index = 0 - element() - if (index > 0) { - net_send(etime - t, 1) - } -} - -NET_RECEIVE (w) { - if (flag == 1) { - net_event(t) - element() - if (index > 0) { - net_send(etime - t, 1) - } - } -} - -VERBATIM -extern double* vector_vec(); -extern int vector_capacity(); -extern void* vector_arg(); -ENDVERBATIM - -PROCEDURE element() { -VERBATIM - { void* vv; int i, size; double* px; - i = (int)index; - if (i >= 0) { - vv = *((void**)(&space)); - if (vv) { - size = vector_capacity(vv); - px = vector_vec(vv); - if (i < size) { - etime = px[i]; - index += 1.; - } else { - index = -1.; - } - } else { - index = -1.; - } - } - } -ENDVERBATIM -} - -PROCEDURE play() { -VERBATIM - void** vv; - vv = (void**)(&space); - *vv = (void*)0; - if (ifarg(1)) { - *vv = vector_arg(1); - } -ENDVERBATIM -} From 17a799a9d6e5a72931671e805c76acd180c4b776 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 15:55:18 -0400 Subject: [PATCH 024/107] MAINT: remove use of dconf, paramf, basedir --- hnn_qt5.py | 380 +++++++++++++++++++++++++++++------------------------ paramrw.py | 35 ++--- run.py | 29 ++-- simdat.py | 93 ++++++------- specfn.py | 1 - 5 files changed, 275 insertions(+), 263 deletions(-) diff --git a/hnn_qt5.py b/hnn_qt5.py index fd611256a..ccb9cc4d6 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -33,12 +33,17 @@ # HNN modules import spikefn from paramrw import usingOngoingInputs, countEvokedInputs, usingEvokedInputs -from paramrw import chunk_evinputs, get_inputs, trans_input, validate_param_file, write_legacy_paramf +from paramrw import chunk_evinputs, get_inputs, trans_input +from paramrw import write_legacy_paramf, get_output_dir from simdat import SIMCanvas, getinputfiles, updatedat from gutils import setscalegeom, lowresdisplay, setscalegeomcenter, getmplDPI, getscreengeom -from conf import dconf -import conf -from run import RunSimThread +from run import RunSimThread, ParamSignal + +# TODO: These globals should be made configurable via the GUI +decay_multiplier = 1.6 +drawindivrast = 0 +drawavgdpl = 0 +fontsize = plt.rcParams['font.size'] = 10 def isWindows (): # are we on windows? or linux/mac ? @@ -54,40 +59,6 @@ def getPyComm (): return 'python' return 'python3' -def parseargs (): - for i in range(len(sys.argv)): - if sys.argv[i] == '-dataf' and i + 1 < len(sys.argv): - print('-dataf is ', sys.argv[i+1]) - conf.dconf['dataf'] = dconf['dataf'] = sys.argv[i+1] - i += 1 - elif sys.argv[i] == '-paramf' and i + 1 < len(sys.argv): - print('-paramf is ', sys.argv[i+1]) - conf.dconf['paramf'] = dconf['paramf'] = sys.argv[i+1] - i += 1 - -parseargs() - -simf = dconf['simf'] -paramf = dconf['paramf'] -param_fname = os.path.splitext(os.path.basename(paramf)) -basedir = os.path.join(dconf['datdir'], param_fname[0]) - -def get_defncore(): - """ get default number of cores """ - - try: - defncore = len(os.sched_getaffinity(0)) - except AttributeError: - defncore = cpu_count(logical=False) - - if defncore is None or defncore == 0: - # in case psutil is not supported (e.g. BSD) - defncore = multiprocessing.cpu_count() - - return defncore - -defncore = get_defncore() - def _add_missing_frames(tb): fake_tb = namedtuple( 'fake_tb', ('tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next') @@ -99,10 +70,19 @@ def _add_missing_frames(tb): frame = frame.f_back return result -if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] -else: plt.rcParams['font.size'] = dconf['fontsize'] = 10 +def _get_defncore(): + """get default number of cores """ + + try: + defncore = len(os.sched_getaffinity(0)) + except AttributeError: + defncore = cpu_count(logical=False) + + if defncore is None or defncore == 0: + # in case psutil is not supported (e.g. BSD) + defncore = multiprocessing.cpu_count() -hnn_root_dir = os.path.dirname(os.path.realpath(__file__)) + return defncore DEFAULT_CSS = """ QRangeSlider * { @@ -449,9 +429,6 @@ def __handleEditingFinished(self): self.textModified.emit(self._label) # for signaling -class Communicate (QObject): - commsig = pyqtSignal() - class DoneSignal (QObject): finishSim = pyqtSignal(bool, str) @@ -506,14 +483,15 @@ def __init__ (self, parent, din): self.initUI() self.initExtra() self.setfromdin(din) # set values from input dictionary - self.addtips() + # self.addtips() - def addtips (self): - for ktip in dconf.keys(): - if ktip in self.dqline: - self.dqline[ktip].setToolTip(dconf[ktip]) - elif ktip in self.dqextra: - self.dqextra[ktip].setToolTip(dconf[ktip]) + # TODO: add back tooltips + # def addtips (self): + # for ktip in dconf.keys(): + # if ktip in self.dqline: + # self.dqline[ktip].setToolTip(dconf[ktip]) + # elif ktip in self.dqextra: + # self.dqextra[ktip].setToolTip(dconf[ktip]) def __str__ (self): s = '' @@ -906,10 +884,11 @@ def __init__ (self, parent, din): self.initUI() self.setfromdin(din) - def addtips (self): - for ktip in dconf.keys(): - if ktip in self.dqline: - self.dqline[ktip].setToolTip(dconf[ktip]) + # TODO: add back tooltips + # def addtips (self): + # for ktip in dconf.keys(): + # if ktip in self.dqline: + # self.dqline[ktip].setToolTip(dconf[ktip]) def transvar (self,k): if k in self.dtransvar: return self.dtransvar[k] @@ -1057,7 +1036,7 @@ def initUI (self): self.addRemoveInputButton() self.addHideButton() - self.addtips() + # self.addtips() def lines2val (self,ksearch,val): for k in self.dqline.keys(): @@ -1240,7 +1219,7 @@ def addProx (self): #print('index to', len(self.ltabs)-1) self.tabs.setCurrentIndex(len(self.ltabs)-1) #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) - self.addtips() + # self.addtips() def addDist (self): self.ndist += 1 @@ -1261,7 +1240,7 @@ def addDist (self): #print('index to', len(self.ltabs)-1) self.tabs.setCurrentIndex(len(self.ltabs)-1) #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) - self.addtips() + # self.addtips() class OptEvokedInputParamDialog (EvokedInputParamDialog): @@ -1939,6 +1918,8 @@ def get_input_timing_sigma(self, tab_name): return timing_sigma def createOptParams(self): + global decay_multiplier + self.opt_params = {} # iterate through tabs. data is contained in grid layout @@ -1962,7 +1943,7 @@ def createOptParams(self): self.opt_params[tab_name] = {'ranges': {}, 'mean' : value, 'sigma': timing_sigma, - 'decay_multiplier': dconf['decay_multiplier']} + 'decay_multiplier': decay_multiplier} timing_bound = timing_sigma * range_multiplier self.opt_params[tab_name]['user_start'] = max(0, value - timing_bound) @@ -2200,11 +2181,10 @@ def selectionchange(self,i): self.parent.updatesaveparams({}) def initExtra (self): - global defncore, paramf - DictDialog.initExtra(self) self.dqextra['NumCores'] = QLineEdit(self) - self.dqextra['NumCores'].setText(str(defncore)) + self.defncore = _get_defncore() + self.dqextra['NumCores'].setText(str(self.defncore)) self.addtransvar('NumCores','Number Cores') self.ltabs[0].layout.addRow('NumCores',self.dqextra['NumCores']) @@ -2238,12 +2218,10 @@ def getncore (self): return ncore def setfromdin (self,din): - global defncore - if not din: return # number of cores may have changed if the configured number failed - self.dqextra['NumCores'].setText(str(defncore)) + self.dqextra['NumCores'].setText(str(self.defncore)) # update ordered dict of QLineEdit objects with new parameters for k,v in din.items(): @@ -2630,11 +2608,9 @@ def initUI (self): class BaseParamDialog (QDialog): # base widget for specifying params (contains buttons to create other widgets - def __init__ (self, parent, optrun_func): + def __init__ (self, parent, paramfn, optrun_func): super(BaseParamDialog, self).__init__(parent) self.proxparamwin = self.distparamwin = self.netparamwin = self.syngainparamwin = None - self.params = {'sim_prefix': ''} - self.initUI() self.runparamwin = RunParamDialog(self) self.cellparamwin = CellParamDialog(self) self.netparamwin = NetworkParamDialog(self) @@ -2648,31 +2624,37 @@ def __init__ (self, parent, optrun_func): self.lsubwin = [self.runparamwin, self.cellparamwin, self.netparamwin, self.proxparamwin, self.distparamwin, self.evparamwin, self.poisparamwin, self.tonicparamwin, self.optparamwin] - self.params = self.updateDispParam() + self.paramfn = paramfn self.parent = parent - def updateDispParam (self): - global paramf + self.params = read_params(self.paramfn) + self.initUI() # requires self.params + self.updateDispParam(self.params) - # now update the GUI components to reflect the param file selected - try: - validate_param_file(paramf) - except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not retrieve parameters from %s" % paramf) - return + def updateDispParam(self, params=None): + global drawavgdpl - params = read_params(paramf) + if params is None: + try: + params = read_params(self.paramfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + self.paramfn) + return - if usingEvokedInputs(params): # default for evoked is to show average dipole - conf.dconf['drawavgdpl'] = True - elif usingOngoingInputs(params): # default for ongoing is NOT to show average dipole - conf.dconf['drawavgdpl'] = False + self.params = params - for dlg in self.lsubwin: dlg.setfromdin(params) # update to values from file - self.qle.setText(params['sim_prefix']) # update simulation name + if usingEvokedInputs(self.params): + # default for evoked is to show average dipole + drawavgdpl = True + elif usingOngoingInputs(self.params): + # default for ongoing is NOT to show average dipole + drawavgdpl = False - # return the param dict for use by caller - return params + for dlg in self.lsubwin: + dlg.setfromdin(self.params) # update to values from file + self.qle.setText(self.params['sim_prefix']) # update simulation name def setrunparam (self): bringwintotop(self.runparamwin) def setcellparam (self): bringwintotop(self.cellparamwin) @@ -2779,8 +2761,8 @@ def initUI (self): self.setWindowTitle('Set Parameters') def saveparams (self, checkok = True): - global paramf,basedir - tmpf = os.path.join(dconf['paramoutdir'],self.qle.text() + '.param') + tmpf = os.path.join(get_output_dir(), 'param', + self.qle.text() + '.param') oktosave = True if os.path.isfile(tmpf) and checkok: self.show() @@ -2797,9 +2779,10 @@ def saveparams (self, checkok = True): with open(tmpf,'w') as fp: fp.write(str(self)) - paramf = dconf['paramf'] = tmpf # update paramf - basedir = os.path.join(dconf['datdir'], self.qle.text()) - os.makedirs(basedir, exist_ok=True) + self.paramfn = tmpf + data_dir = os.path.join(get_output_dir(), 'data') + sim_dir = os.path.join(data_dir, self.params['sim_prefix']) + os.makedirs(sim_dir, exist_ok=True) return oktosave @@ -2881,16 +2864,20 @@ def __init__ (self): super().__init__() sys.excepthook = self.excepthook + global fontsize + + hnn_root_dir = os.path.dirname(os.path.realpath(__file__)) + self.runningsim = False self.runthread = None - self.fontsize = dconf['fontsize'] + self.fontsize = fontsize self.linewidth = plt.rcParams['lines.linewidth'] = 1 self.markersize = plt.rcParams['lines.markersize'] = 5 self.dextdata = {} # external data self.schemwin = SchematicDialog(self) self.m = self.toolbar = None - self.baseparamwin = BaseParamDialog(self, self.startoptmodel) - self.params = None + paramfn = os.path.join(hnn_root_dir, 'param', 'default.param') + self.baseparamwin = BaseParamDialog(self, paramfn, self.startoptmodel) self.optMode = False self.initUI() self.visnetwin = VisnetDialog(self) @@ -2898,7 +2885,7 @@ def __init__ (self): self.erselectdistal = EvokedOrRhythmicDialog(self, True, self.baseparamwin.evparamwin, self.baseparamwin.distparamwin) self.erselectprox = EvokedOrRhythmicDialog(self, False, self.baseparamwin.evparamwin, self.baseparamwin.proxparamwin) self.waitsimwin = WaitSimDialog(self) - default_param = os.path.join(dconf['dbase'],'data','default') + default_param = os.path.join(get_output_dir(), 'data', 'default') first_load = not (os.path.exists(default_param)) if "TRAVIS_TESTING" in os.environ and os.environ["TRAVIS_TESTING"] == "1": @@ -2933,9 +2920,11 @@ def redraw (self): def changeFontSize (self): # bring up window to change font sizes + global fontsize + i, ok = QInputDialog.getInt(self, "Set Font Size","Font Size:", plt.rcParams['font.size'], 1, 100, 1) if ok: - self.fontsize = plt.rcParams['font.size'] = dconf['fontsize'] = i + self.fontsize = plt.rcParams['font.size'] = fontsize = i self.redraw() def changeLineWidth (self): @@ -2954,36 +2943,43 @@ def changeMarkerSize (self): def selParamFileDialog (self): # bring up window to select simulation parameter file - global paramf,basedir + + import simdat + + hnn_root_dir = os.path.dirname(os.path.realpath(__file__)) + qfd = QFileDialog() - qfd.setHistory([os.path.join(dconf['dbase'],'param'), os.path.join(hnn_root_dir,'param')]) + qfd.setHistory([os.path.join(get_output_dir(), 'param'), + os.path.join(hnn_root_dir, 'param')]) fn = qfd.getOpenFileName(self, 'Open param file', - os.path.join(hnn_root_dir,'param'), - "Param files (*.param)") + os.path.join(hnn_root_dir,'param'), + "Param files (*.param)") if len(fn) > 0 and fn[0] == '': # no file selected in dialog return - paramf = os.path.abspath(fn[0]) # to make sure have right path separators on Windows OS - param_fname = os.path.splitext(os.path.basename(paramf)) - basedir = os.path.join(dconf['datdir'], param_fname[0]) + tmpfn = os.path.abspath(fn[0]) try: - validate_param_file(paramf) + params = read_params(tmpfn) except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not retrieve parameters from %s" % fn[0]) + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + tmpfn) return + self.baseparamwin.paramfn = tmpfn + # now update the GUI components to reflect the param file selected - self.params = self.baseparamwin.updateDispParam() + self.baseparamwin.updateDispParam(params) self.initSimCanvas() # recreate canvas # self.m.plot() # replot data - self.setWindowTitle(paramf) + self.setWindowTitle(self.baseparamwin.paramfn) + # store the sim just loaded in simdat's list - is this the desired behavior? or should we first erase prev sims? - import simdat if 'dpl' in simdat.ddat: # update lsimdat and its current sim index - simdat.updatelsimdat(paramf, self.params, simdat.ddat['dpl']) + simdat.updatelsimdat(self.baseparamwin.paramfn, self.baseparamwin.params, simdat.ddat['dpl']) self.populateSimCB() # populate the combobox @@ -2992,9 +2988,8 @@ def selParamFileDialog (self): def loadDataFile (self, fn): # load a dipole data file - global paramf - import simdat + try: self.dextdata[fn] = np.loadtxt(fn) except ValueError: @@ -3014,14 +3009,17 @@ def loadDataFile (self, fn): self.m.plot() self.m.draw() # make sure new lines show up in plot - if paramf: + if self.baseparamwin.paramfn: self.toggleEnableOptimization(True) return True def loadDataFileDialog (self): # bring up window to select/load external dipole data file + hnn_root_dir = os.path.dirname(os.path.realpath(__file__)) + qfd = QFileDialog() - qfd.setHistory([os.path.join(dconf['dbase'],'data'), os.path.join(hnn_root_dir,'data')]) + qfd.setHistory([os.path.join(get_output_dir, 'data'), + os.path.join(hnn_root_dir, 'data')]) fn = qfd.getOpenFileName(self, 'Open data file', os.path.join(hnn_root_dir,'data'), "Data files (*.txt)") @@ -3034,6 +3032,7 @@ def loadDataFileDialog (self): def clearDataFile (self): # clear external dipole data import simdat + self.m.clearlextdatobj() self.dextdata = simdat.ddat['dextdata'] = {} self.toggleEnableOptimization(False) @@ -3084,44 +3083,59 @@ def showSomaVPlot (self): msg.setStandardButtons(QMessageBox.Ok) msg.exec_() else: - outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') + outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) + outparamf = os.path.join(outdir, + self.baseparamwin.params['sim_prefix'] + + '.param') lcmd = [getPyComm(), 'visvolt.py',outparamf] Popen(lcmd) # nonblocking def showPSDPlot (self): # start the PSD visualization process (separate window) - outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') + outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) + outparamf = os.path.join(outdir, + self.baseparamwin.params['sim_prefix'] + + '.param') lcmd = [getPyComm(), 'vispsd.py',outparamf] Popen(lcmd) # nonblocking def showSpecPlot (self): # start the spectrogram visualization process (separate window) - outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') + outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) + outparamf = os.path.join(outdir, + self.baseparamwin.params['sim_prefix'] + + '.param') lcmd = [getPyComm(), 'visspec.py',outparamf] Popen(lcmd) # nonblocking def showRasterPlot (self): # start the raster plot visualization process (separate window) - global basedir + global drawindivrast - spikefile = os.path.join(basedir,'spk.txt') + outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) + spikefile = os.path.join(outdir,'spk.txt') if os.path.isfile(spikefile): - outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') + outparamf = os.path.join(outdir, + self.baseparamwin.params['sim_prefix'] + + '.param') lcmd = [getPyComm(), 'visrast.py',outparamf,spikefile] else: QMessageBox.information(self, "HNN", "WARNING: no spiking data at %s" % spikefile) return - if dconf['drawindivrast']: lcmd.append('indiv') + if drawindivrast: + lcmd.append('indiv') Popen(lcmd) # nonblocking def showDipolePlot (self): # start the dipole visualization process (separate window) - global basedir - dipole_file = os.path.join(basedir,'dpl.txt') + outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) + dipole_file = os.path.join(outdir,'dpl.txt') if os.path.isfile(dipole_file): - outparamf = os.path.join(dconf['paramoutdir'], self.params['sim_prefix'] + '.param') + outparamf = os.path.join(outdir, + self.baseparamwin.params['sim_prefix'] + + '.param') lcmd = [getPyComm(), 'visdipole.py',outparamf,dipole_file] else: QMessageBox.information(self, "HNN", "WARNING: no dipole data at %s" % dipole_file) @@ -3135,7 +3149,9 @@ def showwaitsimwin (self): def togAvgDpl (self): # toggle drawing of the average (across trials) dipole - conf.dconf['drawavgdpl'] = not conf.dconf['drawavgdpl'] + global drawavgdpl + + drawavgdpl = not drawavgdpl self.m.plot() self.m.draw() @@ -3165,31 +3181,38 @@ def distribsubwin (self): maxh = win.height() if cury >= sh: cury = cury = 0 - def updateDatCanv (self,fn): + def updateDatCanv(self, params): # update the simulation data and canvas try: - getinputfiles(fn) # reset input data - if already exists + getinputfiles(self.baseparamwin.paramfn) # reset input data - if already exists except: pass + # now update the GUI components to reflect the param file selected - self.params = self.baseparamwin.updateDispParam() + self.baseparamwin.updateDispParam(params) self.initSimCanvas() # recreate canvas - self.setWindowTitle(fn) + self.setWindowTitle(self.baseparamwin.paramfn) def updateSelectedSim(self, sim_idx): """Update the sim shown in the ComboBox and update globals""" import simdat - global paramf, basedir - # update globals simdat.lsimidx = sim_idx - paramf = simdat.lsimdat[sim_idx]['paramfn'] - split_fname = os.path.splitext(os.path.basename(paramf)) - basedir = os.path.join(dconf['datdir'], split_fname[0]) + paramfn = simdat.lsimdat[sim_idx]['paramfn'] + + try: + params = read_params(paramfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + paramfn) + return + + self.baseparamwin.paramfn = paramfn # update GUI - self.updateDatCanv(paramf) + self.updateDatCanv(params) self.cbsim.setCurrentIndex(simdat.lsimidx) def removeSim(self): @@ -3232,11 +3255,8 @@ def clearSimulationData (self): # clear the simulation data import simdat - global paramf, basedir - paramf = '' # set paramf to empty so no data gets loaded - basedir = None - - self.params = None + self.baseparamwin.params = None + self.baseparamwin.paramfn = None simdat.ddat = {} # clear data in simdat.ddat simdat.lsimdat = [] simdat.lsimidx = 0 @@ -3521,9 +3541,6 @@ def addParamImageButtons (self,gRow): def initUI (self): # initialize the user interface (UI) - # default paramf - global paramf - self.initMenu() self.statusBar() @@ -3535,7 +3552,7 @@ def initUI (self): self.baseparamwin.move(new_x, new_y) self.baseparamwin.evparamwin.move(new_x+50, new_y+50) self.baseparamwin.optparamwin.move(new_x+100, new_y+100) - self.setWindowTitle(paramf) + self.setWindowTitle(self.baseparamwin.paramfn) QToolTip.setFont(QFont('SansSerif', 10)) self.grid = grid = QGridLayout() @@ -3554,7 +3571,9 @@ def initUI (self): import simdat if 'dpl' in simdat.ddat: # update lsimdat and its current sim index - simdat.updatelsimdat(paramf, self.params, simdat.ddat['dpl']) + simdat.updatelsimdat(self.baseparamwin.paramfn, + self.baseparamwin.params, + simdat.ddat['dpl']) self.cbsim = QComboBox(self) self.populateSimCB() # populate the combobox @@ -3574,8 +3593,8 @@ def initUI (self): widget.setLayout(grid) self.setCentralWidget(widget) - self.c = Communicate() - self.c.commsig.connect(self.baseparamwin.updateDispParam) + self.p = ParamSignal() + self.p.psig.connect(self.baseparamwin.updateDispParam) self.d = DoneSignal() self.d.finishSim.connect(self.done) @@ -3585,26 +3604,27 @@ def initUI (self): self.schemwin.show() # so it's underneath main window - if 'dataf' in dconf: - if os.path.isfile(dconf['dataf']): - self.loadDataFile(dconf['dataf']) - self.show() - def onActivateSimCB (self, s): + def onActivateSimCB (self, paramfn): # load simulation when activating simulation combobox - global paramf,basedir import simdat + if self.cbsim.currentIndex() != simdat.lsimidx: - paramf = s - param_fname = os.path.splitext(os.path.basename(paramf)) - basedir = os.path.join(dconf['datdir'], param_fname[0]) + try: + params = read_params(paramfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + paramfn) + return + self.baseparamwin.paramfn = paramfn + simdat.lsimidx = self.cbsim.currentIndex() - self.updateDatCanv(paramf) + self.updateDatCanv(params) def populateSimCB (self): # populate the simulation combobox - global paramf self.cbsim.clear() import simdat for sim in simdat.lsimdat: @@ -3614,18 +3634,23 @@ def populateSimCB (self): def initSimCanvas (self,recalcErr=True,optMode=False,gRow=1,reInit=True): # initialize the simulation canvas, loading any required data - global paramf gCol = 0 if reInit == True: self.grid.itemAtPosition(gRow, gCol).widget().deleteLater() self.grid.itemAtPosition(gRow + 1, gCol).widget().deleteLater() - # if just initialized or after clearSimulationData, self.params will be empty - if paramf and self.params is None: - self.params = read_params(paramf) + # if just initialized or after clearSimulationData + if self.baseparamwin.paramfn and self.baseparamwin.params is None: + try: + self.baseparamwin.params = read_params(self.baseparamwin.paramfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + self.baseparamwin.paramfn) + return - self.m = SIMCanvas(paramf, self.params, parent = self, width=10, height=1, dpi=getmplDPI(), optMode=optMode) # also loads data + self.m = SIMCanvas(self.baseparamwin.params, parent = self, width=10, height=1, dpi=getmplDPI(), optMode=optMode) # this is the Navigation widget # it takes the Canvas widget and a parent self.toolbar = NavigationToolbar(self.m, self) @@ -3684,8 +3709,6 @@ def stopsim (self): self.setcursors(Qt.ArrowCursor) def optmodel (self, ntrial, ncore): - global paramf - # make sure params saved and ok to run if not self.baseparamwin.saveparams(): return @@ -3703,7 +3726,7 @@ def optmodel (self, ntrial, ncore): self.statusBar().showMessage("Optimizing model. . .") - self.runthread = RunSimThread(self.c, self.d, ntrial, ncore, self.waitsimwin, self.params, opt=True, baseparamwin=self.baseparamwin, mainwin=self) + self.runthread = RunSimThread(self.p, self.d, ntrial, ncore, self.waitsimwin, self.baseparamwin.params, opt=True, baseparamwin=self.baseparamwin, mainwin=self) # We have all the events we need connected we can start the thread self.runthread.start() @@ -3714,12 +3737,18 @@ def optmodel (self, ntrial, ncore): bringwintotop(self.waitsimwin) def startsim (self, ntrial, ncore): - global paramf - # start the simulation - if not self.baseparamwin.saveparams(): return # make sure params saved and ok to run + if not self.baseparamwin.saveparams(self.baseparamwin.paramfn): + return # make sure params saved and ok to run - self.params = read_params(paramf) + # reread the params to get anything new + try: + params = read_params(self.baseparamwin.paramfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + self.baseparamwin.paramfn) + return self.setcursors(Qt.WaitCursor) @@ -3728,7 +3757,9 @@ def startsim (self, ntrial, ncore): self.statusBar().showMessage("Running simulation. . .") - self.runthread=RunSimThread(self.c,self.d,ntrial,ncore,self.waitsimwin,self.params,opt=False,baseparamwin=None,mainwin=None) + self.runthread = RunSimThread(self.p, self.d, ntrial, ncore, + self.waitsimwin, params, opt=False, + baseparamwin=None, mainwin=None) # We have all the events we need connected we can start the thread self.runthread.start() @@ -3751,7 +3782,6 @@ def done (self, optMode, except_msg): self.qbtn.setEnabled(True) self.initSimCanvas(optMode=optMode) # recreate canvas (plots too) to avoid incorrect axes # self.m.plot() - global basedir self.setcursors(Qt.ArrowCursor) failed=False @@ -3770,10 +3800,12 @@ def done (self, optMode, except_msg): msg += "running sim " if failed: - QMessageBox.information(self, "Failed!", msg + "using " + paramf + '. Check simulation log or console for error messages') + QMessageBox.information(self, "Failed!", msg + "using " + self.baseparamwin.paramfn + '. Check simulation log or console for error messages') else: - QMessageBox.information(self, "Done!", msg + "using " + paramf + '. Saved data/figures in: ' + basedir) - self.setWindowTitle(paramf) + data_dir = os.path.join(get_output_dir(), 'data') + sim_dir = os.path.join(data_dir, self.baseparamwin.params['sim_prefix']) + QMessageBox.information(self, "Done!", msg + "using " + self.baseparamwin.paramfn + '. Saved data/figures in: ' + sim_dir) + self.setWindowTitle(self.baseparamwin.paramfn) self.populateSimCB() # populate the combobox if __name__ == '__main__': diff --git a/paramrw.py b/paramrw.py index 9545ed52f..383b64156 100644 --- a/paramrw.py +++ b/paramrw.py @@ -5,11 +5,22 @@ # last major: (SL: cleanup of self.p_all) import re +import os import numpy as np import itertools as it from hnn_core import read_params +def get_output_dir(): + """Return the base directory for storing output files""" + + try: + base_dir = os.environ["SYSTEM_USER_DIR"] + except KeyError: + base_dir = os.path.expanduser('~') + + return os.path.join(base_dir, 'hnn_out') + # Cleans input files def clean_lines (file): with open(file) as f_in: @@ -17,30 +28,6 @@ def clean_lines (file): lines = [line for line in lines if line] return lines -def validate_param_file (fn): - try: - fp = open(fn, 'r') - except OSError: - print("ERROR: could not open/read file") - raise ValueError - - d = {} - with fp: - try: - ln = fp.readlines() - except UnicodeDecodeError: - print("ERROR: bad file format") - raise ValueError - for l in ln: - s = l.strip() - if s.startswith('#'): continue - sp = s.split(':') - if len(sp) > 1: - d[sp[0].strip()]=str(sp[1]).strip() - if not 'tstop' in d: - print("ERROR: parameter file not valid. Could not find 'tstop'") - raise ValueError - # check if using ongoing inputs def usingOngoingInputs (params, lty = ['_prox', '_dist']): if params is None: diff --git a/run.py b/run.py index 557a4dc38..6c51caae9 100755 --- a/run.py +++ b/run.py @@ -23,18 +23,7 @@ from paramrw import usingOngoingInputs, write_gids_param import specfn import simdat -from paramrw import write_legacy_paramf - - -def _get_output_dir(): - """Return the base directory for storing output files""" - - try: - base_dir = os.environ["SYSTEM_USER_DIR"] - except KeyError: - base_dir = os.path.expanduser('~') - - return op.join(base_dir, 'hnn_out') +from paramrw import write_legacy_paramf, get_output_dir def get_fname(sim_dir, key, trial=0, ntrial=1): @@ -171,7 +160,7 @@ def simulate(params, n_procs=None): raise RuntimeError("No dipole(s) rerturned from simulation") # make sure the directory for saving data has been created - data_dir = op.join(_get_output_dir(), 'data') + data_dir = op.join(get_output_dir(), 'data') sim_dir = op.join(data_dir, params['sim_prefix']) try: os.mkdir(sim_dir) @@ -218,7 +207,7 @@ def simulate(params, n_procs=None): get_fname(sim_dir, 'rawspec', trial_idx, params['N_trials'])) - # NOTE: the savefigs functionality is quite complicated and rewriting + # TODO: the savefigs functionality is quite complicated and rewriting # from scratch in hnn-core is probably a better option that will allow # deprecating the large amount of legacy code @@ -251,9 +240,9 @@ def simulate(params, n_procs=None): # based on https://nikolak.com/pyqt-threading-tutorial/ class RunSimThread (QThread): - def __init__ (self,c,d,ntrial,ncore,waitsimwin,params,opt=False,baseparamwin=None,mainwin=None): + def __init__ (self,p,d,ntrial,ncore,waitsimwin,params,opt=False,baseparamwin=None,mainwin=None): QThread.__init__(self) - self.c = c + self.p = p self.d = d self.killed = False self.ntrial = ntrial @@ -263,7 +252,7 @@ def __init__ (self,c,d,ntrial,ncore,waitsimwin,params,opt=False,baseparamwin=Non self.opt = opt self.baseparamwin = baseparamwin self.mainwin = mainwin - self.paramfn = os.path.join(_get_output_dir(), 'param', self.params['sim_prefix'] + '.param') + self.paramfn = os.path.join(get_output_dir(), 'param', self.params['sim_prefix'] + '.param') # it would be ideal to display a dialog box, but we have to get that event back to the main window @@ -291,7 +280,7 @@ def updatebaseparamwin (self, d): self.prmComm.psig.emit(d) def updatedispparam (self): - self.c.commsig.emit() + self.p.psig.emit(self.params) def updatedrawerr (self): self.canvComm.csig.emit(False, self.opt) # False means do not recalculate error @@ -416,7 +405,7 @@ def optmodel (self): self.baseparamwin.optparamwin.populate_initial_opt_ranges() # save initial parameters file - data_dir = op.join(_get_output_dir(), 'data') + data_dir = op.join(get_output_dir(), 'data') sim_dir = op.join(data_dir, self.params['sim_prefix']) param_out = os.path.join(sim_dir,'before_opt.param') write_legacy_paramf(param_out, self.params) @@ -551,7 +540,7 @@ def optrun (new_params, grad=0): print(txt) self.updatewaitsimwin(os.linesep+'Simulation finished: ' + txt + os.linesep) # print error - data_dir = op.join(_get_output_dir(), 'data') + data_dir = op.join(get_output_dir(), 'data') sim_dir = op.join(data_dir, self.params['sim_prefix']) fnoptinf = os.path.join(sim_dir,'optinf.txt') diff --git a/simdat.py b/simdat.py index 875881fe5..675df8a47 100644 --- a/simdat.py +++ b/simdat.py @@ -7,19 +7,18 @@ import matplotlib.gridspec as gridspec import numpy as np from math import ceil -from conf import dconf -import conf import spikefn -from paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs, usingTonicInputs, countEvokedInputs +from paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs +from paramrw import usingTonicInputs, countEvokedInputs, get_output_dir from scipy import signal from gutils import getscreengeom import traceback from hnn_core import read_spikes -# dconf has settings from hnn.cfg -if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] -else: plt.rcParams['font.size'] = dconf['fontsize'] = 10 +drawindivdpl = 1 +drawavgdpl = 1 +fontsize = plt.rcParams['font.size'] = 10 ddat = {} # current simulation data dfile = {} # data file information for current simulation @@ -56,13 +55,13 @@ def rmse (a1, a2): sz = min(len1,len2) return np.sqrt(((a1[0:sz] - a2[0:sz]) ** 2).mean()) -def readdpltrials(basedir): +def readdpltrials(sim_dir): # read dipole data files for individual trials ldpl = [] i = 0 while True: - fn = os.path.join(basedir,'dpl_'+str(i)+'.txt') + fn = os.path.join(sim_dir, 'dpl_' + str(i) + '.txt') if not os.path.exists(fn): break @@ -75,14 +74,14 @@ def readdpltrials(basedir): def getinputfiles (sim_prefix): # get a dictionary of input files based on simulation parameter file paramf - global dfile,basedir + global dfile dfile = {} - basedir = os.path.join(dconf['datdir'], sim_prefix) - # print('basedir:',basedir) - dfile['dpl'] = os.path.join(basedir,'dpl.txt') - dfile['spec'] = os.path.join(basedir,'rawspec.npz') - dfile['spk'] = os.path.join(basedir,'spk.txt') - dfile['outparam'] = os.path.join(basedir,'param.txt') + data_dir = os.path.join(get_output_dir(), 'data') + sim_dir = os.path.join(data_dir, sim_prefix) + dfile['dpl'] = os.path.join(sim_dir,'dpl.txt') + dfile['spec'] = os.path.join(sim_dir,'rawspec.npz') + dfile['spk'] = os.path.join(sim_dir,'spk.txt') + dfile['outparam'] = os.path.join(sim_dir,'param.txt') return dfile def readtxt (fn): @@ -100,14 +99,15 @@ def readtxt (fn): def updatedat (params): # update data dictionary (ddat) from the param file - global basedir + data_dir = os.path.join(get_output_dir(), 'data') + sim_dir = os.path.join(data_dir, params['sim_prefix']) getinputfiles(params['sim_prefix']) for k in ['dpl','spk']: if k in ddat: del ddat[k] - if os.path.exists(basedir): + if os.path.exists(sim_dir): ddat['dpl'] = readtxt(dfile['dpl']) if len(ddat['dpl']) == 0: del ddat['dpl'] @@ -122,23 +122,13 @@ def updatedat (params): # incorrect dimensions (bad spike file) ddat['spk'] = None - ddat['dpltrials'] = readdpltrials(basedir) + ddat['dpltrials'] = readdpltrials(sim_dir) if os.path.isfile(dfile['spec']): ddat['spec'] = np.load(dfile['spec']) else: ddat['spec'] = None -def drawraster (): - # draw raster to standalone matplotlib figure - for debugging (not used in main HNN GUI) - if 'spk' in ddat: - # print('spk shape:',ddat['spk'].shape) - plt.ion() - plt.figure() - for pair in ddat['spk']: - plt.plot([pair[0]],[pair[1]],'ko',markersize=10) - plt.xlabel('Time (ms)',fontsize=dconf['fontsize']); plt.ylabel('ID',fontsize=dconf['fontsize']) - def calcerr (ddat, tstop, tstart=0.0): # calculates RMSE error from ddat dictionary NSig = errtot = 0.0; lerr = [] @@ -249,7 +239,7 @@ class SIMCanvas (FigureCanvas): # matplotlib/pyqt-compatible canvas for drawing simulation & external data # based on https://pythonspot.com/en/pyqt5-matplotlib/ - def __init__ (self, paramf, params, parent=None, width=5, height=4, dpi=40, optMode=False, title='Simulation Viewer'): + def __init__ (self, params, parent=None, width=5, height=4, dpi=40, optMode=False, title='Simulation Viewer'): FigureCanvas.__init__(self, Figure(figsize=(width, height), dpi=dpi)) self.title = title @@ -260,7 +250,6 @@ def __init__ (self, paramf, params, parent=None, width=5, height=4, dpi=40, optM self.gui = parent FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) - self.paramf = paramf self.params = params self.initaxes() self.G = gridspec.GridSpec(10,1) @@ -475,6 +464,8 @@ def getnextcolor (self): return self.clridx def plotextdat (self, recalcErr=True): + global fontsize + if not 'dextdata' in ddat or len(ddat['dextdata']) == 0: return @@ -548,10 +539,11 @@ def plotextdat (self, recalcErr=True): self.annot_avg = self.axdipole.annotate(txt,xy=(0,0),xytext=(0.005,0.005),textcoords='axes fraction',color=clr,fontweight='bold') if not hassimdata: # need axis labels - self.axdipole.set_xlabel('Time (ms)',fontsize=dconf['fontsize']) - self.axdipole.set_ylabel('Dipole (nAm)',fontsize=dconf['fontsize']) + self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) + self.axdipole.set_ylabel('Dipole (nAm)', fontsize=fontsize) myxl = self.axdipole.get_xlim() - if myxl[0] < 0.0: self.axdipole.set_xlim((0.0,myxl[1]+myxl[0])) + if myxl[0] < 0.0: + self.axdipole.set_xlim((0.0, myxl[1] + myxl[0])) def hassimdata (self): # check if any simulation data available in ddat dictionary @@ -579,7 +571,9 @@ def clearlextdatobj (self): del self.annot_avg def plotsimdat (self): - # plot the simulation data + """plot the simulation data""" + + global drawindivdpl, drawavgdpl, fontsize self.gRow = 0 bottom = 0.0 @@ -652,13 +646,13 @@ def plotsimdat (self): olddpl = lsim['dpl'] self.axdipole.plot(olddpl[:,0],olddpl[:,1],'--',color='black',linewidth=self.gui.linewidth) - if self.params['N_trials']>1 and dconf['drawindivdpl'] and len(ddat['dpltrials']) > 0: # plot dipoles from individual trials + if self.params['N_trials']>1 and drawindivdpl and len(ddat['dpltrials']) > 0: # plot dipoles from individual trials for dpltrial in ddat['dpltrials']: self.axdipole.plot(dpltrial[:,0],dpltrial[:,1],color='gray',linewidth=self.gui.linewidth) yl[0] = min(yl[0],dpltrial[sidx:eidx,1].min()) yl[1] = max(yl[1],dpltrial[sidx:eidx,1].max()) - if conf.dconf['drawavgdpl'] or self.params['N_trials'] <= 1: + if drawavgdpl or self.params['N_trials'] <= 1: # this is the average dipole (across trials) # it's also the ONLY dipole when running a single trial self.axdipole.plot(ddat['dpl'][:,0],ddat['dpl'][:,1],'k',linewidth=self.gui.linewidth+1) @@ -691,23 +685,34 @@ def plotsimdat (self): NEstPyr = int(num_pyr * scalefctr) if NEstPyr > 0: - self.axdipole.set_ylabel(r'Dipole (nAm $\times$ '+str(scalefctr)+')\nFrom Estimated '+str(NEstPyr)+' Cells',fontsize=dconf['fontsize']) + self.axdipole.set_ylabel(r'Dipole (nAm $\times$ ' + \ + str(scalefctr) + \ + ')\nFrom Estimated ' + \ + str(NEstPyr) + ' Cells', + fontsize=fontsize) else: - self.axdipole.set_ylabel(r'Dipole (nAm $\times$ '+str(scalefctr)+')\n',fontsize=dconf['fontsize']) + self.axdipole.set_ylabel(r'Dipole (nAm $\times$ ' + \ + str(scalefctr) + \ + ')\n', fontsize=fontsize) self.axdipole.set_xlim(xl); self.axdipole.set_ylim(yl) if DrawSpec: gRow = 6 - self.axspec = self.figure.add_subplot(self.G[gRow:10,0]) # specgram - cax = self.axspec.imshow(ds['TFR'],extent=(ds['time'][0],ds['time'][-1],ds['freq'][-1],ds['freq'][0]),aspect='auto',origin='upper',cmap=plt.get_cmap(self.params['spec_cmap'])) - self.axspec.set_ylabel('Frequency (Hz)',fontsize=dconf['fontsize']) - self.axspec.set_xlabel('Time (ms)',fontsize=dconf['fontsize']) + self.axspec = self.figure.add_subplot(self.G[gRow:10,0]) + cax = self.axspec.imshow(ds['TFR'], extent=(ds['time'][0], + ds['time'][-1], + ds['freq'][-1], + ds['freq'][0]), + aspect='auto', origin='upper', + cmap=plt.get_cmap(self.params['spec_cmap'])) + self.axspec.set_ylabel('Frequency (Hz)', fontsize=fontsize) + self.axspec.set_xlabel('Time (ms)', fontsize=fontsize) self.axspec.set_xlim(xl) - self.axspec.set_ylim(ds['freq'][-1],ds['freq'][0]) + self.axspec.set_ylim(ds['freq'][-1], ds['freq'][0]) cbaxes = self.figure.add_axes([0.6, 0.49, 0.3, 0.005]) plt.colorbar(cax, cax = cbaxes, orientation='horizontal') # horizontal to save space else: - self.axdipole.set_xlabel('Time (ms)',fontsize=dconf['fontsize']) + self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) def plotarrows (self): # run after scales have been updated diff --git a/specfn.py b/specfn.py index 9fedfd8c7..644af01c5 100644 --- a/specfn.py +++ b/specfn.py @@ -10,7 +10,6 @@ import scipy.signal as sps import matplotlib.pyplot as plt import paramrw -from conf import dconf # MorletSpec class based on a time vec tvec and a time series vec tsvec class MorletSpec(): From e0a4867126188d7978f58b4847c8fcd67d211138 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 16:09:54 -0400 Subject: [PATCH 025/107] MAINT: move QRangeSlider to hnn_qtlib.py --- hnn_qt5.py | 355 +-------------------------------------------------- hnn_qtlib.py | 349 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 355 insertions(+), 349 deletions(-) create mode 100644 hnn_qtlib.py diff --git a/hnn_qt5.py b/hnn_qt5.py index ccb9cc4d6..67e991506 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -21,11 +21,11 @@ # External libraries from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QDialog, QGridLayout, QLineEdit, QLabel -from PyQt5.QtWidgets import QCheckBox, QTextEdit, QInputDialog, QSpacerItem, QFrame, QSplitter -from PyQt5.QtGui import QIcon, QFont, QPixmap, QColor, QPainter, QFont, QPen -from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, Qt, QSize -from PyQt5.QtCore import QMetaObject, QUrl +from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QDialog, QGridLayout, QLineEdit, QLabel +from PyQt5.QtWidgets import QCheckBox, QTextEdit, QInputDialog, QSpacerItem, QFrame +from PyQt5.QtGui import QIcon, QPixmap, QFont +from PyQt5.QtCore import QCoreApplication, pyqtSignal, QObject, Qt, QSize +from PyQt5.QtCore import QMetaObject from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar import matplotlib.pyplot as plt from hnn_core import read_params @@ -38,6 +38,7 @@ from simdat import SIMCanvas, getinputfiles, updatedat from gutils import setscalegeom, lowresdisplay, setscalegeomcenter, getmplDPI, getscreengeom from run import RunSimThread, ParamSignal +from hnn_qtlib import QRangeSlider, MyLineEdit # TODO: These globals should be made configurable via the GUI decay_multiplier = 1.6 @@ -84,350 +85,6 @@ def _get_defncore(): return defncore -DEFAULT_CSS = """ -QRangeSlider * { - border: 0px; - padding: 0px; -} -QRangeSlider #Head { - background-color: rgba(157, 163, 176, 50); -} -QRangeSlider #Span { - background-color: rgba(22, 31, 50, 150); -} -QRangeSlider #Span:active { - background-color: rgba(22, 31, 50, 150); -} -QRangeSlider #Tail { - background-color: rgba(157, 163, 176, 50); -} -QRangeSlider #LineBox { - background-color: rgba(255, 255, 255, 0); -} -QRangeSlider > QSplitter::handle { - background-color: rgba(79, 91, 102, 100); -} -QRangeSlider > QSplitter::handle:vertical { - height: 4px; -} -QRangeSlider > QSplitter::handle:pressed { - background: #ca5; -} -""" - -def scale(val, src, dst): - try: - return ((val - src[0]) / float(src[1]-src[0]) * (dst[1]-dst[0]) + dst[0]) - except ZeroDivisionError: - return 0 - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("QRangeSlider") - Form.resize(300, 30) - Form.setStyleSheet(DEFAULT_CSS) - self._linebox = QWidget(Form) - self._linebox.setObjectName("LineBox") - self.gridLayout = QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self._splitter = QSplitter(Form) - self._splitter.setMinimumSize(QSize(0, 0)) - self._splitter.setMaximumSize(QSize(16777215, 16777215)) - self._splitter.setOrientation(Qt.Horizontal) - self._splitter.setObjectName("splitter") - self._head = QGroupBox(self._splitter) - self._head.setTitle("") - self._head.setObjectName("Head") - self._handle = QGroupBox(self._splitter) - self._handle.setTitle("") - self._handle.setObjectName("Span") - self._tail = QGroupBox(self._splitter) - self._tail.setTitle("") - self._tail.setObjectName("Tail") - self.gridLayout.addWidget(self._splitter, 0, 0, 1, 1) - self.retranslateUi(Form) - QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QCoreApplication.translate - Form.setWindowTitle(_translate("QRangeSlider", "QRangeSlider")) - - -class Element(QGroupBox): - def __init__(self, parent, main): - super(Element, self).__init__(parent) - self.main = main - - def setStyleSheet(self, style): - self.parent().setStyleSheet(style) - - def textColor(self): - return getattr(self, '__textColor', QColor(125, 125, 125)) - - def setTextColor(self, color): - if type(color) == tuple and len(color) == 3: - color = QColor(color[0], color[1], color[2]) - elif type(color) == int: - color = QColor(color, color, color) - setattr(self, '__textColor', color) - - def paintEvent(self, event): - qp = QPainter() - qp.begin(self) - if self.main.drawValues(): - self.drawText(event, qp) - qp.end() - - -class Head(Element): - def __init__(self, parent, main): - super(Head, self).__init__(parent, main) - - def drawText(self, event, qp): - qp.setPen(self.textColor()) - qp.setFont(QFont('Arial', 10)) - qp.drawText(event.rect(), Qt.AlignLeft, ("%.3f"%self.main.min())) - - -class Tail(Element): - def __init__(self, parent, main): - super(Tail, self).__init__(parent, main) - - def drawText(self, event, qp): - qp.setPen(self.textColor()) - qp.setFont(QFont('Arial', 10)) - qp.drawText(event.rect(), Qt.AlignRight, ("%.3f"%self.main.max())) - - -class LineBox(Element): - def __init__(self, parent, main): - super(LineBox, self).__init__(parent, main) - - def drawText(self, event, qp): - qp.setPen(QPen(Qt.red, 2, Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin)) - pos = self.main.valueToPos(self.main.line_value) - if (pos == 0): - pos += 1 - qp.drawLine(pos, 0, pos, 50) - - -class Handle(Element): - def __init__(self, parent, main): - super(Handle, self).__init__(parent, main) - - def drawText(self, event, qp): - pass - # qp.setPen(self.textColor()) - # qp.setFont(QFont('Arial', 10)) - # qp.drawText(event.rect(), Qt.AlignLeft, str(self.main.start())) - # qp.drawText(event.rect(), Qt.AlignRight, str(self.main.end())) - - def mouseMoveEvent(self, event): - event.accept() - mx = event.globalX() - _mx = getattr(self, '__mx', None) - if not _mx: - setattr(self, '__mx', mx) - dx = 0 - else: - dx = mx - _mx - setattr(self, '__mx', mx) - if dx == 0: - event.ignore() - return - elif dx > 0: - dx = 1 - elif dx < 0: - dx = -1 - s = self.main.start() + dx - e = self.main.end() + dx - if s >= self.main.min() and e <= self.main.max(): - self.main.setRange(s, e) - - -class QRangeSlider(QWidget, Ui_Form): - endValueChanged = pyqtSignal(int) - maxValueChanged = pyqtSignal(int) - minValueChanged = pyqtSignal(int) - startValueChanged = pyqtSignal(int) - rangeValuesChanged = pyqtSignal(str, float, float) - - _SPLIT_START = 1 - _SPLIT_END = 2 - - def __init__(self, label, parent): - super(QRangeSlider, self).__init__(parent) - self.label = label - self.rangeValuesChanged.connect(parent.updateRangeFromSlider) - self.setupUi(self) - self.setMouseTracking(False) - self._splitter.splitterMoved.connect(self._handleMoveSplitter) - - self._linebox_layout = QHBoxLayout() - self._linebox_layout.setSpacing(0) - self._linebox_layout.setContentsMargins(0, 0, 0, 0) - self._linebox.setLayout(self._linebox_layout) - self.linebox = LineBox(self._linebox, main=self) - self._linebox_layout.addWidget(self.linebox) - self._head_layout = QHBoxLayout() - self._head_layout.setSpacing(0) - self._head_layout.setContentsMargins(0, 0, 0, 0) - self._head.setLayout(self._head_layout) - self.head = Head(self._head, main=self) - self._head_layout.addWidget(self.head) - self._handle_layout = QHBoxLayout() - self._handle_layout.setSpacing(0) - self._handle_layout.setContentsMargins(0, 0, 0, 0) - self._handle.setLayout(self._handle_layout) - self.handle = Handle(self._handle, main=self) - self.handle.setTextColor((150, 255, 150)) - self._handle_layout.addWidget(self.handle) - self._tail_layout = QHBoxLayout() - self._tail_layout.setSpacing(0) - self._tail_layout.setContentsMargins(0, 0, 0, 0) - self._tail.setLayout(self._tail_layout) - self.tail = Tail(self._tail, main=self) - self._tail_layout.addWidget(self.tail) - self.setDrawValues(True) - - def min(self): - return getattr(self, '__min', None) - - def max(self): - return getattr(self, '__max', None) - - def setMin(self, value): - setattr(self, '__min', value) - self.minValueChanged.emit(value) - - def setMax(self, value): - setattr(self, '__max', value) - self.maxValueChanged.emit(value) - - def start(self): - return getattr(self, '__start', None) - - def end(self): - return getattr(self, '__end', None) - - def _setStart(self, value): - setattr(self, '__start', value) - self.startValueChanged.emit(value) - - def setStart(self, value): - v = self.valueToPos(value) - self._splitter.splitterMoved.disconnect() - self._splitter.moveSplitter(v, self._SPLIT_START) - self._splitter.splitterMoved.connect(self._handleMoveSplitter) - self._setStart(value) - - def _setEnd(self, value): - setattr(self, '__end', value) - self.endValueChanged.emit(value) - - def setEnd(self, value): - v = self.valueToPos(value) - self._splitter.splitterMoved.disconnect() - self._splitter.moveSplitter(v, self._SPLIT_END) - self._splitter.splitterMoved.connect(self._handleMoveSplitter) - self._setEnd(value) - - def drawValues(self): - return getattr(self, '__drawValues', None) - - def setLine(self, value): - self.line_value = value - - def setDrawValues(self, draw): - setattr(self, '__drawValues', draw) - - def getRange(self): - return (self.start(), self.end()) - - def setRange(self, start, end): - self.setStart(start) - self.setEnd(end) - - def keyPressEvent(self, event): - key = event.key() - if key == Qt.Key_Left: - s = self.start()-1 - e = self.end()-1 - elif key == Qt.Key_Right: - s = self.start()+1 - e = self.end()+1 - else: - event.ignore() - return - event.accept() - if s >= self.min() and e <= self.max(): - self.setRange(s, e) - - def setBackgroundStyle(self, style): - self._tail.setStyleSheet(style) - self._head.setStyleSheet(style) - - def setSpanStyle(self, style): - self._handle.setStyleSheet(style) - - def valueToPos(self, value): - return int(scale(value, (self.min(), self.max()), (0, self.width()))) - - def _posToValue(self, xpos): - return scale(xpos, (0, self.width()), (self.min(), self.max())) - - def _handleMoveSplitter(self, xpos, index): - self._splitter.handleWidth() - def _lockWidth(widget): - width = widget.size().width() - widget.setMinimumWidth(width) - widget.setMaximumWidth(width) - def _unlockWidth(widget): - widget.setMinimumWidth(0) - widget.setMaximumWidth(16777215) - if index == self._SPLIT_START: - v = self._posToValue(xpos) - _lockWidth(self._tail) - if v >= self.end(): - return - self._setStart(v) - self.rangeValuesChanged.emit(self.label, v, self.end()) - elif index == self._SPLIT_END: - # account for width of head - xpos += 4 - v = self._posToValue(xpos) - _lockWidth(self._head) - if v <= self.start(): - return - self._setEnd(v) - self.rangeValuesChanged.emit(self.label, self.start(), v) - _unlockWidth(self._tail) - _unlockWidth(self._head) - _unlockWidth(self._handle) - -# see https://stackoverflow.com/questions/12182133/pyqt4-combine-textchanged-and-editingfinished-for-qlineedit -class MyLineEdit(QLineEdit): - textModified = pyqtSignal(str) # (label) - - def __init__(self, contents, label, parent=None): - super(MyLineEdit, self).__init__(contents, parent) - self.editingFinished.connect(self.__handleEditingFinished) - self.textChanged.connect(self.__handleTextChanged) - self._before = contents - self._label = label - - def __handleTextChanged(self, text): - if not self.hasFocus(): - self._before = text - - def __handleEditingFinished(self): - before, after = self._before, self.text() - if before != after: - self._before = after - self.textModified.emit(self._label) - # for signaling class DoneSignal (QObject): finishSim = pyqtSignal(bool, str) diff --git a/hnn_qtlib.py b/hnn_qtlib.py new file mode 100644 index 000000000..23fdf0e47 --- /dev/null +++ b/hnn_qtlib.py @@ -0,0 +1,349 @@ +from PyQt5.QtWidgets import QWidget, QGridLayout, QLineEdit, QSplitter +from PyQt5.QtWidgets import QHBoxLayout, QGroupBox +from PyQt5.QtGui import QColor, QPainter, QFont, QPen +from PyQt5.QtCore import QCoreApplication, pyqtSignal, QObject, Qt, QSize +from PyQt5.QtCore import QMetaObject + +DEFAULT_CSS = """ +QRangeSlider * { + border: 0px; + padding: 0px; +} +QRangeSlider #Head { + background-color: rgba(157, 163, 176, 50); +} +QRangeSlider #Span { + background-color: rgba(22, 31, 50, 150); +} +QRangeSlider #Span:active { + background-color: rgba(22, 31, 50, 150); +} +QRangeSlider #Tail { + background-color: rgba(157, 163, 176, 50); +} +QRangeSlider #LineBox { + background-color: rgba(255, 255, 255, 0); +} +QRangeSlider > QSplitter::handle { + background-color: rgba(79, 91, 102, 100); +} +QRangeSlider > QSplitter::handle:vertical { + height: 4px; +} +QRangeSlider > QSplitter::handle:pressed { + background: #ca5; +} +""" + +def scale(val, src, dst): + try: + return ((val - src[0]) / float(src[1]-src[0]) * (dst[1]-dst[0]) + dst[0]) + except ZeroDivisionError: + return 0 + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("QRangeSlider") + Form.resize(300, 30) + Form.setStyleSheet(DEFAULT_CSS) + self._linebox = QWidget(Form) + self._linebox.setObjectName("LineBox") + self.gridLayout = QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self._splitter = QSplitter(Form) + self._splitter.setMinimumSize(QSize(0, 0)) + self._splitter.setMaximumSize(QSize(16777215, 16777215)) + self._splitter.setOrientation(Qt.Horizontal) + self._splitter.setObjectName("splitter") + self._head = QGroupBox(self._splitter) + self._head.setTitle("") + self._head.setObjectName("Head") + self._handle = QGroupBox(self._splitter) + self._handle.setTitle("") + self._handle.setObjectName("Span") + self._tail = QGroupBox(self._splitter) + self._tail.setTitle("") + self._tail.setObjectName("Tail") + self.gridLayout.addWidget(self._splitter, 0, 0, 1, 1) + self.retranslateUi(Form) + QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QCoreApplication.translate + Form.setWindowTitle(_translate("QRangeSlider", "QRangeSlider")) + + +class Element(QGroupBox): + def __init__(self, parent, main): + super(Element, self).__init__(parent) + self.main = main + + def setStyleSheet(self, style): + self.parent().setStyleSheet(style) + + def textColor(self): + return getattr(self, '__textColor', QColor(125, 125, 125)) + + def setTextColor(self, color): + if type(color) == tuple and len(color) == 3: + color = QColor(color[0], color[1], color[2]) + elif type(color) == int: + color = QColor(color, color, color) + setattr(self, '__textColor', color) + + def paintEvent(self, event): + qp = QPainter() + qp.begin(self) + if self.main.drawValues(): + self.drawText(event, qp) + qp.end() + + +class Head(Element): + def __init__(self, parent, main): + super(Head, self).__init__(parent, main) + + def drawText(self, event, qp): + qp.setPen(self.textColor()) + qp.setFont(QFont('Arial', 10)) + qp.drawText(event.rect(), Qt.AlignLeft, ("%.3f"%self.main.min())) + + +class Tail(Element): + def __init__(self, parent, main): + super(Tail, self).__init__(parent, main) + + def drawText(self, event, qp): + qp.setPen(self.textColor()) + qp.setFont(QFont('Arial', 10)) + qp.drawText(event.rect(), Qt.AlignRight, ("%.3f"%self.main.max())) + + +class LineBox(Element): + def __init__(self, parent, main): + super(LineBox, self).__init__(parent, main) + + def drawText(self, event, qp): + qp.setPen(QPen(Qt.red, 2, Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin)) + pos = self.main.valueToPos(self.main.line_value) + if (pos == 0): + pos += 1 + qp.drawLine(pos, 0, pos, 50) + + +class Handle(Element): + def __init__(self, parent, main): + super(Handle, self).__init__(parent, main) + + def drawText(self, event, qp): + pass + # qp.setPen(self.textColor()) + # qp.setFont(QFont('Arial', 10)) + # qp.drawText(event.rect(), Qt.AlignLeft, str(self.main.start())) + # qp.drawText(event.rect(), Qt.AlignRight, str(self.main.end())) + + def mouseMoveEvent(self, event): + event.accept() + mx = event.globalX() + _mx = getattr(self, '__mx', None) + if not _mx: + setattr(self, '__mx', mx) + dx = 0 + else: + dx = mx - _mx + setattr(self, '__mx', mx) + if dx == 0: + event.ignore() + return + elif dx > 0: + dx = 1 + elif dx < 0: + dx = -1 + s = self.main.start() + dx + e = self.main.end() + dx + if s >= self.main.min() and e <= self.main.max(): + self.main.setRange(s, e) + + +class QRangeSlider(QWidget, Ui_Form): + endValueChanged = pyqtSignal(int) + maxValueChanged = pyqtSignal(int) + minValueChanged = pyqtSignal(int) + startValueChanged = pyqtSignal(int) + rangeValuesChanged = pyqtSignal(str, float, float) + + _SPLIT_START = 1 + _SPLIT_END = 2 + + def __init__(self, label, parent): + super(QRangeSlider, self).__init__(parent) + self.label = label + self.rangeValuesChanged.connect(parent.updateRangeFromSlider) + self.setupUi(self) + self.setMouseTracking(False) + self._splitter.splitterMoved.connect(self._handleMoveSplitter) + + self._linebox_layout = QHBoxLayout() + self._linebox_layout.setSpacing(0) + self._linebox_layout.setContentsMargins(0, 0, 0, 0) + self._linebox.setLayout(self._linebox_layout) + self.linebox = LineBox(self._linebox, main=self) + self._linebox_layout.addWidget(self.linebox) + self._head_layout = QHBoxLayout() + self._head_layout.setSpacing(0) + self._head_layout.setContentsMargins(0, 0, 0, 0) + self._head.setLayout(self._head_layout) + self.head = Head(self._head, main=self) + self._head_layout.addWidget(self.head) + self._handle_layout = QHBoxLayout() + self._handle_layout.setSpacing(0) + self._handle_layout.setContentsMargins(0, 0, 0, 0) + self._handle.setLayout(self._handle_layout) + self.handle = Handle(self._handle, main=self) + self.handle.setTextColor((150, 255, 150)) + self._handle_layout.addWidget(self.handle) + self._tail_layout = QHBoxLayout() + self._tail_layout.setSpacing(0) + self._tail_layout.setContentsMargins(0, 0, 0, 0) + self._tail.setLayout(self._tail_layout) + self.tail = Tail(self._tail, main=self) + self._tail_layout.addWidget(self.tail) + self.setDrawValues(True) + + def min(self): + return getattr(self, '__min', None) + + def max(self): + return getattr(self, '__max', None) + + def setMin(self, value): + setattr(self, '__min', value) + self.minValueChanged.emit(value) + + def setMax(self, value): + setattr(self, '__max', value) + self.maxValueChanged.emit(value) + + def start(self): + return getattr(self, '__start', None) + + def end(self): + return getattr(self, '__end', None) + + def _setStart(self, value): + setattr(self, '__start', value) + self.startValueChanged.emit(value) + + def setStart(self, value): + v = self.valueToPos(value) + self._splitter.splitterMoved.disconnect() + self._splitter.moveSplitter(v, self._SPLIT_START) + self._splitter.splitterMoved.connect(self._handleMoveSplitter) + self._setStart(value) + + def _setEnd(self, value): + setattr(self, '__end', value) + self.endValueChanged.emit(value) + + def setEnd(self, value): + v = self.valueToPos(value) + self._splitter.splitterMoved.disconnect() + self._splitter.moveSplitter(v, self._SPLIT_END) + self._splitter.splitterMoved.connect(self._handleMoveSplitter) + self._setEnd(value) + + def drawValues(self): + return getattr(self, '__drawValues', None) + + def setLine(self, value): + self.line_value = value + + def setDrawValues(self, draw): + setattr(self, '__drawValues', draw) + + def getRange(self): + return (self.start(), self.end()) + + def setRange(self, start, end): + self.setStart(start) + self.setEnd(end) + + def keyPressEvent(self, event): + key = event.key() + if key == Qt.Key_Left: + s = self.start()-1 + e = self.end()-1 + elif key == Qt.Key_Right: + s = self.start()+1 + e = self.end()+1 + else: + event.ignore() + return + event.accept() + if s >= self.min() and e <= self.max(): + self.setRange(s, e) + + def setBackgroundStyle(self, style): + self._tail.setStyleSheet(style) + self._head.setStyleSheet(style) + + def setSpanStyle(self, style): + self._handle.setStyleSheet(style) + + def valueToPos(self, value): + return int(scale(value, (self.min(), self.max()), (0, self.width()))) + + def _posToValue(self, xpos): + return scale(xpos, (0, self.width()), (self.min(), self.max())) + + def _handleMoveSplitter(self, xpos, index): + self._splitter.handleWidth() + def _lockWidth(widget): + width = widget.size().width() + widget.setMinimumWidth(width) + widget.setMaximumWidth(width) + def _unlockWidth(widget): + widget.setMinimumWidth(0) + widget.setMaximumWidth(16777215) + if index == self._SPLIT_START: + v = self._posToValue(xpos) + _lockWidth(self._tail) + if v >= self.end(): + return + self._setStart(v) + self.rangeValuesChanged.emit(self.label, v, self.end()) + elif index == self._SPLIT_END: + # account for width of head + xpos += 4 + v = self._posToValue(xpos) + _lockWidth(self._head) + if v <= self.start(): + return + self._setEnd(v) + self.rangeValuesChanged.emit(self.label, self.start(), v) + _unlockWidth(self._tail) + _unlockWidth(self._head) + _unlockWidth(self._handle) + +# see https://stackoverflow.com/questions/12182133/pyqt4-combine-textchanged-and-editingfinished-for-qlineedit +class MyLineEdit(QLineEdit): + textModified = pyqtSignal(str) # (label) + + def __init__(self, contents, label, parent=None): + super(MyLineEdit, self).__init__(contents, parent) + self.editingFinished.connect(self.__handleEditingFinished) + self.textChanged.connect(self.__handleTextChanged) + self._before = contents + self._label = label + + def __handleTextChanged(self, text): + if not self.hasFocus(): + self._before = text + + def __handleEditingFinished(self): + before, after = self._before, self.text() + if before != after: + self._before = after + self.textModified.emit(self._label) \ No newline at end of file From 1e54116bf5488f9c2ca2f69256ba6b64ddf58f90 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 21:47:13 -0400 Subject: [PATCH 026/107] MAINT: remove model visualization code --- currentfn.py | 47 -- gutils.py | 59 -- hnn_qt5.py | 1438 +--------------------------------------------- morphology.py | 624 -------------------- requirements.txt | 11 - 5 files changed, 17 insertions(+), 2162 deletions(-) delete mode 100644 currentfn.py delete mode 100644 gutils.py delete mode 100644 morphology.py delete mode 100644 requirements.txt diff --git a/currentfn.py b/currentfn.py deleted file mode 100644 index a484d72a5..000000000 --- a/currentfn.py +++ /dev/null @@ -1,47 +0,0 @@ -# currentfn.py - current-based analysis functions -# -# v 1.8.22 -# rev 2013-11-19 (SL: added simple convert function) -# last major: (SL: added layers for plot to axis command) - -import numpy as np - -class SynapticCurrent(): - def __init__(self, fcurrent): - self.__parse_f(fcurrent) - - # parses the input file - def __parse_f(self, fcurrent): - x = np.loadtxt(open(fcurrent, 'r')) - self.t = x[:, 0] - - # this really should be a dictionary - self.I_soma_L2Pyr = x[:, 1] - self.I_soma_L5Pyr = x[:, 2] - self.units = 'nA' - - # ext fn to convert to uA - def convert_nA_to_uA(self): - self.I_soma_L2Pyr *= 1e-3 - self.I_soma_L5Pyr *= 1e-3 - self.units = 'uA' - - # external plot function - def plot_to_axis(self, a, layer=None): - # layer=None is redundant with L5Pyr, but it might be temporary - if layer is None: - a.plot(self.t, -self.I_soma_L5Pyr) - - elif layer is 'L2': - a.plot(self.t, -self.I_soma_L2Pyr) - - elif layer is 'L5': - a.plot(self.t, -self.I_soma_L5Pyr) - - # set the xlim - a.set_xlim((50., self.t[-1])) - -# external function to use SynapticCurrent() and plot to axis a -def pcurrent(a, fcurrent): - I_syn = SynapticCurrent(fcurrent) - I_syn.plot_to_axis(a) diff --git a/gutils.py b/gutils.py deleted file mode 100644 index 4ed0ffd71..000000000 --- a/gutils.py +++ /dev/null @@ -1,59 +0,0 @@ -from PyQt5.QtCore import QCoreApplication -from PyQt5 import QtGui - -# some graphics utilities - -# use pyqt5 to get screen resolution -def getscreengeom (): - width,height = 2880, 1620 # default width,height - used for development - app = QCoreApplication.instance() # can only have 1 instance of qtapp; get that instance - app.setDesktopSettingsAware(True) - if len(app.screens()) > 0: - screen = app.screens()[0] - geom = screen.geometry() - return geom.width(), geom.height() - else: - return width, height - -# check if display has low resolution -def lowresdisplay (): - w, h = getscreengeom() - return w < 1400 or h < 700 - -# get DPI for use in matplotlib figures (part of simulation output canvas - in simdat.py) -def getmplDPI (): - if lowresdisplay(): return 60 - return 120 - -# get new window width, height scaled by current screen resolution relative to original development resolution -def scalegeom (width, height): - devwidth, devheight = 2880.0, 1620.0 # resolution used for development - used to scale window height/width - screenwidth, screenheight = getscreengeom() - widthnew = int((screenwidth / devwidth) * width) - heightnew = int((screenheight / devheight) * height) - if widthnew > 1000 or heightnew > 850: - widthnew = 1000 - heightnew = 850 - return widthnew, heightnew - -# set dialog's position (x,y) and rescale geometry based on original width and height and development resolution -def setscalegeom (dlg, x, y, origw, origh): - nw, nh = scalegeom(origw, origh) - # print('origw,origh:',origw, origh,'nw,nh:',nw, nh) - dlg.setGeometry(x, y, int(nw), int(nh)) - return int(nw), int(nh) - -# set dialog in center of screen width and rescale size based on original width and height and development resolution -def setscalegeomcenter (dlg, origw, origh): - nw, nh = scalegeom(origw, origh) - # print('origw,origh:',origw, origh,'nw,nh:',nw, nh) - sw, sh = getscreengeom() - x = (sw-nw)/2 - y = 0 - dlg.setGeometry(x, y, int(nw), int(nh)) - return int(nw), int(nh) - -# scale font size -def scalefont (fsize): - pass # devfont - diff --git a/hnn_qt5.py b/hnn_qt5.py index 67e991506..72f297caf 100644 --- a/hnn_qt5.py +++ b/hnn_qt5.py @@ -14,7 +14,7 @@ from copy import deepcopy from time import time, sleep import numpy as np -from math import ceil, isclose +from math import ceil import traceback from psutil import cpu_count @@ -32,16 +32,15 @@ # HNN modules import spikefn -from paramrw import usingOngoingInputs, countEvokedInputs, usingEvokedInputs -from paramrw import chunk_evinputs, get_inputs, trans_input +from paramrw import usingOngoingInputs, usingEvokedInputs from paramrw import write_legacy_paramf, get_output_dir from simdat import SIMCanvas, getinputfiles, updatedat -from gutils import setscalegeom, lowresdisplay, setscalegeomcenter, getmplDPI, getscreengeom from run import RunSimThread, ParamSignal -from hnn_qtlib import QRangeSlider, MyLineEdit +from qt_lib import setscalegeom, setscalegeomcenter, getmplDPI, getscreengeom +from qt_lib import lookupresource, ClickLabel +from qt_evoked import EvokedInputParamDialog, OptEvokedInputParamDialog # TODO: These globals should be made configurable via the GUI -decay_multiplier = 1.6 drawindivrast = 0 drawavgdpl = 0 fontsize = plt.rcParams['font.size'] = 10 @@ -107,24 +106,6 @@ def bringwintotop (win): #win.raise_() #win.show() -# look up resource adjusted for screen resolution -def lookupresource (fn): - lowres = lowresdisplay() # low resolution display - if lowres: - return os.path.join('res',fn+'2.png') - else: - return os.path.join('res',fn+'.png') - -def format_range_str(value): - if value == 0: - value_str = "0.000" - elif value < 0.1 : - value_str = ("%6f" % value) - else: - value_str = ("%.3f" % value) - - return value_str - # DictDialog - dictionary-based dialog with tabs - should make all dialogs # specifiable via cfg file format - then can customize gui without changing py code # and can reduce code explosion / overlap between dialogs @@ -530,1249 +511,6 @@ def initd (self): self.ltitle = ['Layer 2/3', 'Layer 5', 'Timing'] self.stitle = 'Set Poisson Inputs' -# evoked input param dialog (allows adding/removing arbitrary number of evoked inputs) -class EvokedInputParamDialog (QDialog): - def __init__ (self, parent, din): - super(EvokedInputParamDialog, self).__init__(parent) - self.nprox = self.ndist = 0 # number of proximal,distal inputs - self.ld = [] # list of dictionaries for proximal/distal inputs - self.dqline = {} - self.dtransvar = {} # for translating model variable name to more human-readable form - self.initUI() - self.setfromdin(din) - - # TODO: add back tooltips - # def addtips (self): - # for ktip in dconf.keys(): - # if ktip in self.dqline: - # self.dqline[ktip].setToolTip(dconf[ktip]) - - def transvar (self,k): - if k in self.dtransvar: return self.dtransvar[k] - return k - - def addtransvar (self,k,strans): - self.dtransvar[k] = strans - self.dtransvar[strans] = k - - def set_qline_float (self, key_str, value): - try: - new_value = float(value) - except ValueError: - print("WARN: bad value for param %s: %s. Unable to convert" - " to a floating point number" % (key_str, value)) - return - - # Enforce no sci. not. + limit field len + remove trailing 0's - self.dqline[key_str].setText(("%7f" % new_value).rstrip('0').rstrip('.')) - - def setfromdin (self,din): - if not din: return - - if 'dt' in din: - - # Optimization feature introduces the case where din just contains optimization - # relevant parameters. In that case, we don't want to remove all inputs, just - # modify existing inputs. - self.removeAllInputs() # turn off any previously set inputs - - nprox, ndist = countEvokedInputs(din) - for i in range(nprox+ndist): - if i % 2 == 0: - if self.nprox < nprox: - self.addProx() - elif self.ndist < ndist: - self.addDist() - else: - if self.ndist < ndist: - self.addDist() - elif self.nprox < nprox: - self.addProx() - - for k,v in din.items(): - if k == 'sync_evinput': - try: - new_value = bool(int(v)) - except ValueError: - print("WARN: bad value for param %s: %s. Unable to convert" - " to a boolean value" % (k,v)) - continue - if new_value: - self.chksync.setChecked(True) - else: - self.chksync.setChecked(False) - elif k == 'inc_evinput': - try: - new_value = float(v) - except ValueError: - print("WARN: bad value for param %s: %s. Unable to convert" - " to a floating point number" % (k,v)) - continue - self.incedit.setText(str(new_value).strip()) - elif k in self.dqline: - if k.startswith('numspikes'): - try: - new_value = int(v) - except ValueError: - print("WARN: bad value for param %s: %s. Unable to convert" - " to a integer" % (k, v)) - continue - self.dqline[k].setText(str(new_value)) - else: - self.set_qline_float(k, v) - elif k.count('gbar') > 0 and \ - (k.count('evprox') > 0 or \ - k.count('evdist') > 0): - # NOTE: will be deprecated in future release - # for back-compat with old-style specification which didn't have ampa,nmda in evoked gbar - lks = k.split('_') - eloc = lks[1] - enum = lks[2] - base_key_str = 'gbar_' + eloc + '_' + enum + '_' - if eloc == 'evprox': - for ct in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: - # ORIGINAL MODEL/PARAM: only ampa for prox evoked inputs - key_str = base_key_str + ct + '_ampa' - self.set_qline_float(key_str, v) - elif eloc == 'evdist': - for ct in ['L2Pyr', 'L2Basket', 'L5Pyr']: - # ORIGINAL MODEL/PARAM: both ampa and nmda for distal evoked inputs - key_str = base_key_str + ct + '_ampa' - self.set_qline_float(key_str, v) - key_str = base_key_str + ct + '_nmda' - self.set_qline_float(key_str, v) - - def initUI (self): - self.layout = QVBoxLayout(self) - - # Add stretch to separate the form layout from the button - self.layout.addStretch(1) - - self.ltabs = [] - self.tabs = QTabWidget() - self.layout.addWidget(self.tabs) - - self.button_box = QVBoxLayout() - self.btnprox = QPushButton('Add Proximal Input',self) - self.btnprox.resize(self.btnprox.sizeHint()) - self.btnprox.clicked.connect(self.addProx) - self.btnprox.setToolTip('Add Proximal Input') - self.button_box.addWidget(self.btnprox) - - self.btndist = QPushButton('Add Distal Input',self) - self.btndist.resize(self.btndist.sizeHint()) - self.btndist.clicked.connect(self.addDist) - self.btndist.setToolTip('Add Distal Input') - self.button_box.addWidget(self.btndist) - - self.chksync = QCheckBox('Synchronous Inputs',self) - self.chksync.resize(self.chksync.sizeHint()) - self.chksync.setChecked(True) - self.button_box.addWidget(self.chksync) - - self.incbox = QHBoxLayout() - self.inclabel = QLabel(self) - self.inclabel.setText('Increment start time (ms)') - self.inclabel.adjustSize() - self.inclabel.setToolTip('Increment mean evoked input start time(s) by this amount on each trial.') - self.incedit = QLineEdit(self) - self.incedit.setText('0.0') - self.incbox.addWidget(self.inclabel) - self.incbox.addWidget(self.incedit) - - self.layout.addLayout(self.button_box) - self.layout.addLayout(self.incbox) - - self.tabs.resize(425,200) - - # Add tabs to widget - self.layout.addWidget(self.tabs) - self.setLayout(self.layout) - - self.setWindowTitle('Evoked Inputs') - - self.addRemoveInputButton() - self.addHideButton() - # self.addtips() - - def lines2val (self,ksearch,val): - for k in self.dqline.keys(): - if k.count(ksearch) > 0: - self.dqline[k].setText(str(val)) - - def allOff (self): self.lines2val('gbar',0.0) - - def removeAllInputs (self): - for _ in range(len(self.ltabs)): - self.removeCurrentInput() - self.nprox = self.ndist = 0 - - def IsProx (self,idx): - # is this evoked input proximal (True) or distal (False) ? - try: - d = self.ld[idx] - for k in d.keys(): - if k.count('evprox'): - return True - except: - pass - return False - - def getInputID (self,idx): - # get evoked input number of the evoked input associated with idx - try: - d = self.ld[idx] - for k in d.keys(): - lk = k.split('_') - if len(lk) >= 3: - return int(lk[2]) - except: - pass - return -1 - - def downShift (self,idx): - # downshift the evoked input ID, keys, values - d = self.ld[idx] - dnew = {} # new dictionary - newidx = 0 # new evoked input ID - for k,v in d.items(): - lk = k.split('_') - if len(lk) >= 3: - if lk[0]=='sigma': - newidx = int(lk[3])-1 - lk[3] = str(newidx) - else: - newidx = int(lk[2])-1 - lk[2] = str(newidx) - newkey = '_'.join(lk) - dnew[newkey] = v - if k in self.dqline: - self.dqline[newkey] = self.dqline[k] - del self.dqline[k] - self.ld[idx] = dnew - currtxt = self.tabs.tabText(idx) - newtxt = currtxt.split(' ')[0] + ' ' + str(newidx) - self.tabs.setTabText(idx,newtxt) - # print('d original:',d, 'd new:',dnew) - - def removeInput (self,idx): - # remove the evoked input specified by idx - if idx < 0 or idx > len(self.ltabs): return - # print('removing input at index', idx) - self.tabs.removeTab(idx) - tab = self.ltabs[idx] - self.ltabs.remove(tab) - d = self.ld[idx] - - isprox = self.IsProx(idx) # is it a proximal input? - isdist = not isprox # is it a distal input? - inputID = self.getInputID(idx) # wht's the proximal/distal input number? - - # print('isprox,isdist,inputid',isprox,isdist,inputID) - - for k in d.keys(): - if k in self.dqline: - del self.dqline[k] - self.ld.remove(d) - tab.setParent(None) - - # now downshift the evoked inputs (only proximal or only distal) that came after this one - # first get the IDs of the evoked inputs to downshift - lds = [] # list of inputs to downshift - for jdx in range(len(self.ltabs)): - if isprox and self.IsProx(jdx) and self.getInputID(jdx) > inputID: - #print('downshift prox',self.getInputID(jdx)) - lds.append(jdx) - elif isdist and not self.IsProx(jdx) and self.getInputID(jdx) > inputID: - #print('downshift dist',self.getInputID(jdx)) - lds.append(jdx) - for jdx in lds: self.downShift(jdx) # then do the downshifting - - # print(self) # for testing - - def removeCurrentInput (self): # removes currently selected input - idx = self.tabs.currentIndex() - if idx < 0: return - self.removeInput(idx) - - def __str__ (self): - s = '' - for k,v in self.dqline.items(): s += k + ': ' + v.text().strip() + os.linesep - if self.chksync.isChecked(): s += 'sync_evinput: 1'+os.linesep - else: s += 'sync_evinput: 0'+os.linesep - s += 'inc_evinput: ' + self.incedit.text().strip() + os.linesep - return s - - def addRemoveInputButton (self): - self.bbremovebox = QHBoxLayout() - self.btnremove = QPushButton('Remove Input',self) - self.btnremove.resize(self.btnremove.sizeHint()) - self.btnremove.clicked.connect(self.removeCurrentInput) - self.btnremove.setToolTip('Remove This Input') - self.bbremovebox.addWidget(self.btnremove) - self.layout.addLayout(self.bbremovebox) - - def addHideButton (self): - self.bbhidebox = QHBoxLayout() - self.btnhide = QPushButton('Hide Window',self) - self.btnhide.resize(self.btnhide.sizeHint()) - self.btnhide.clicked.connect(self.hide) - self.btnhide.setToolTip('Hide Window') - self.bbhidebox.addWidget(self.btnhide) - self.layout.addLayout(self.bbhidebox) - - def addTab (self,s): - tab = QWidget() - self.ltabs.append(tab) - self.tabs.addTab(tab,s) - tab.layout = QFormLayout() - tab.setLayout(tab.layout) - return tab - - def addFormToTab (self,d,tab): - for k,v in d.items(): - self.dqline[k] = QLineEdit(self) - self.dqline[k].setText(str(v)) - tab.layout.addRow(self.transvar(k),self.dqline[k]) # adds label,QLineEdit to the tab - - def makePixLabel (self,fn): - pix = QPixmap(fn) - pixlbl = ClickLabel(self) - pixlbl.setPixmap(pix) - return pixlbl - - def addtransvarfromdict (self,d): - dtmp = {'L2':'L2/3 ','L5':'L5 '} - for k in d.keys(): - if k.startswith('gbar'): - ks = k.split('_') - stmp = ks[-2] - self.addtransvar(k,dtmp[stmp[0:2]] + stmp[2:] + ' ' + ks[-1].upper() + u' weight (µS)') - elif k.startswith('t'): - self.addtransvar(k,'Start time mean (ms)') - elif k.startswith('sigma'): - self.addtransvar(k,'Start time stdev (ms)') - elif k.startswith('numspikes'): - self.addtransvar(k,'Number spikes') - - def addProx (self): - self.nprox += 1 # starts at 1 - # evprox feed strength - dprox = OrderedDict([('t_evprox_' + str(self.nprox), 0.), # times and stdevs for evoked responses - ('sigma_t_evprox_' + str(self.nprox), 2.5), - ('numspikes_evprox_' + str(self.nprox), 1), - ('gbar_evprox_' + str(self.nprox) + '_L2Pyr_ampa', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L2Pyr_nmda', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L2Basket_ampa', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L2Basket_nmda', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L5Pyr_ampa', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L5Pyr_nmda', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L5Basket_ampa', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L5Basket_nmda', 0.)]) - self.ld.append(dprox) - self.addtransvarfromdict(dprox) - self.addFormToTab(dprox, self.addTab('Proximal ' + str(self.nprox))) - self.ltabs[-1].layout.addRow(self.makePixLabel(lookupresource('proxfig'))) - #print('index to', len(self.ltabs)-1) - self.tabs.setCurrentIndex(len(self.ltabs)-1) - #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) - # self.addtips() - - def addDist (self): - self.ndist += 1 - # evdist feed strengths - ddist = OrderedDict([('t_evdist_' + str(self.ndist), 0.), - ('sigma_t_evdist_' + str(self.ndist), 6.), - ('numspikes_evdist_' + str(self.ndist), 1), - ('gbar_evdist_' + str(self.ndist) + '_L2Pyr_ampa', 0.), - ('gbar_evdist_' + str(self.ndist) + '_L2Pyr_nmda', 0.), - ('gbar_evdist_' + str(self.ndist) + '_L2Basket_ampa', 0.), - ('gbar_evdist_' + str(self.ndist) + '_L2Basket_nmda', 0.), - ('gbar_evdist_' + str(self.ndist) + '_L5Pyr_ampa', 0.), - ('gbar_evdist_' + str(self.ndist) + '_L5Pyr_nmda', 0.)]) - self.ld.append(ddist) - self.addtransvarfromdict(ddist) - self.addFormToTab(ddist,self.addTab('Distal ' + str(self.ndist))) - self.ltabs[-1].layout.addRow(self.makePixLabel(lookupresource('distfig'))) - #print('index to', len(self.ltabs)-1) - self.tabs.setCurrentIndex(len(self.ltabs)-1) - #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) - # self.addtips() - -class OptEvokedInputParamDialog (EvokedInputParamDialog): - - def __init__ (self, parent, optrun_func): - super(EvokedInputParamDialog, self).__init__(None) - self.nprox = self.ndist = 0 # number of proximal,distal inputs - self.ld = [] # list of dictionaries for proximal/distal inputs - self.dtab_idx = {} # for translating input names to tab indices - self.dtab_names = {} # for translating tab indices to input names - self.dparams = {} # actual values - self.dqline = {} # not used, prevents failure in removeInput - - # these store values used in grid - self.dqchkbox = {} # optimize - self.dqparam_name = {} # parameter name - self.dqinitial_label = {} # initial - self.dqopt_label = {} # optimtized - self.dqdiff_label = {} # delta - self.dqrange_multiplier = {} # user-defined multiplier - self.dqrange_mode = {} # range mode (stdev, %, absolute) - self.dqrange_slider = {} # slider - self.dqrange_label = {} # defined range - self.dqrange_max = {} - self.dqrange_min = {} - - self.chunk_list = [] - self.lqnumsim = [] - self.lqnumparams = [] - self.lqinputs = [] - self.opt_params = {} - self.initial_opt_ranges = [] - self.dtabdata = [] - self.dtransvar = {} # for translating model variable name to more human-readable form - self.simlength = 0.0 - self.sim_dt = 0.0 - self.default_num_step_sims = 30 - self.default_num_total_sims = 50 - self.optrun_func = optrun_func - self.optimization_running = False - self.initUI() - self.parent = parent - self.old_num_steps = 0 - - def initUI (self): - # start with a reasonable size - setscalegeom(self, 150, 150, 475, 300) - - self.ltabs = [] - self.ltabkeys = [] - self.tabs = QTabWidget() - self.din = {} - - self.grid = QGridLayout() - - row = 0 - self.sublayout = QGridLayout() - self.old_numsims = [] - self.grid.addLayout(self.sublayout, row, 0) - - row += 1 - self.grid.addWidget(self.tabs, row, 0) - - row += 1 - self.btnrunop = QPushButton('Run Optimization', self) - self.btnrunop.resize(self.btnrunop.sizeHint()) - self.btnrunop.setToolTip('Run Optimization') - self.btnrunop.clicked.connect(self.runOptimization) - self.grid.addWidget(self.btnrunop, row, 0) - - row += 1 - self.btnreset = QPushButton('Reset Ranges',self) - self.btnreset.resize(self.btnreset.sizeHint()) - self.btnreset.clicked.connect(self.updateOptRanges) - self.btnreset.setToolTip('Reset Ranges') - self.grid.addWidget(self.btnreset, row, 0) - - row += 1 - btnhide = QPushButton('Hide Window',self) - btnhide.resize(btnhide.sizeHint()) - btnhide.clicked.connect(self.hide) - btnhide.setToolTip('Hide Window') - self.grid.addWidget(btnhide, row, 0) - - self.setLayout(self.grid) - - self.setWindowTitle("Configure Optimization") - - # the largest horizontal component will be column 0 (headings) - self.resize(self.minimumSizeHint()) - - def toggle_enable_param(self, label): - import re - - widget_dict_list = [self.dqinitial_label, self.dqopt_label, - self.dqdiff_label, self.dqparam_name, - self.dqrange_mode, self.dqrange_multiplier, - self.dqrange_label, self.dqrange_slider] - - if self.dqchkbox[label].isChecked(): - # set all other fields in the row to enabled - for widget_dict in widget_dict_list: - widget_dict[label].setEnabled(True) - toEnable = True - else: - # disable all other fields in the row - for widget_dict in widget_dict_list: - widget_dict[label].setEnabled(False) - toEnable = False - - self.changeParamEnabledStatus(label, toEnable) - - def addTab (self,id_str): - tab = QWidget() - self.ltabs.append(tab) - - name_str = trans_input(id_str) - self.tabs.addTab(tab, name_str) - - tab_index = len(self.ltabs)-1 - self.dtab_idx[id_str] = tab_index - self.dtab_names[tab_index] = id_str - - return tab - - def cleanLabels(self): - """ - To avoid memory leaks we need to delete all widgets when we recreate grid. - Go through all tabs and check for each var name (k) - """ - for idx in range(len(self.ltabs)): - for k in self.ld[idx].keys(): - if k in self.dqinitial_label: - del self.dqinitial_label[k] - if k in self.dqopt_label: - del self.dqopt_label[k] - if k in self.dqdiff_label: - del self.dqdiff_label[k] - if k in self.dqparam_name: - del self.dqparam_name[k] - if not self.optimization_running: - if k in self.dqrange_mode: - del self.dqrange_mode[k] - if k in self.dqrange_multiplier: - del self.dqrange_multiplier[k] - if k in self.dqrange_label: - del self.dqrange_label[k] - if k in self.dqrange_slider: - del self.dqrange_slider[k] - if k in self.dqrange_min: - del self.dqrange_min[k] - if k in self.dqrange_max: - del self.dqrange_max[k] - - def addGridToTab (self, d, tab): - from functools import partial - import re - - current_tab = len(self.ltabs)-1 - tab.layout = QGridLayout() - #tab.layout.setSpacing(10) - - self.ltabkeys.append([]) - - # The first row has column headings - row = 0 - self.ltabkeys[current_tab].append("") - for column_index, column_name in enumerate(["Optimize", "Parameter name", - "Initial", "Optimized", "Delta"]): - widget = QLabel(column_name) - widget.resize(widget.sizeHint()) - tab.layout.addWidget(widget, row, column_index) - - column_index += 1 - widget = QLabel("Range specifier") - widget.setMinimumWidth(100) - tab.layout.addWidget(widget, row, column_index, 1, 2) - - column_index += 2 - widget = QLabel("Range slider") - # widget.setMinimumWidth(160) - tab.layout.addWidget(widget, row, column_index) - - column_index += 1 - widget = QLabel("Defined range") - tab.layout.addWidget(widget, row, column_index) - - # The second row is a horizontal line - row = 1 - self.ltabkeys[current_tab].append("") - qthline = QFrame() - qthline.setFrameShape(QFrame.HLine) - qthline.setFrameShadow(QFrame.Sunken) - tab.layout.addWidget(qthline, row, 0, 1, 9) - - # The rest are the parameters - row = 2 - for k,v in d.items(): - self.ltabkeys[current_tab].append(k) - - # create and format widgets - self.dparams[k] = float(v) - self.dqchkbox[k] = QCheckBox() - self.dqchkbox[k].setStyleSheet(""" - .QCheckBox { - spacing: 20px; - } - .QCheckBox::unchecked { - color: grey; - } - .QCheckBox::checked { - color: black; - } - """) - self.dqchkbox[k].setChecked(True) - # use partial instead of lamda (so args won't be evaluated ahead of time?) - self.dqchkbox[k].clicked.connect(partial(self.toggle_enable_param, k)) - self.dqparam_name[k] = QLabel(self) - self.dqparam_name[k].setText(self.transvar(k)) - self.dqinitial_label[k] = QLabel() - self.dqopt_label[k] = QLabel() - self.dqdiff_label[k] = QLabel() - - # add widgets to grid - tab.layout.addWidget(self.dqchkbox[k], row, 0, alignment = Qt.AlignBaseline | Qt.AlignCenter) - tab.layout.addWidget(self.dqparam_name[k], row, 1) - tab.layout.addWidget(self.dqinitial_label[k], row, 2) # initial value - tab.layout.addWidget(self.dqopt_label[k], row, 3) # optimized value - tab.layout.addWidget(self.dqdiff_label[k], row, 4) # delta - - if k.startswith('t'): - range_mode = "(stdev)" - range_multiplier = "3.0" - elif k.startswith('sigma'): - range_mode = "(%)" - range_multiplier = "50.0" - else: - range_mode = "(%)" - range_multiplier = "500.0" - - if not self.optimization_running: - self.dqrange_slider[k] = QRangeSlider(k,self) - self.dqrange_slider[k].setMinimumWidth(140) - self.dqrange_label[k] = QLabel() - self.dqrange_multiplier[k] = MyLineEdit(range_multiplier, k) - self.dqrange_multiplier[k].textModified.connect(self.updateRange) - self.dqrange_multiplier[k].setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred) - self.dqrange_multiplier[k].setMinimumWidth(50) - self.dqrange_multiplier[k].setMaximumWidth(50) - self.dqrange_mode[k] = QLabel(range_mode) - tab.layout.addWidget(self.dqrange_multiplier[k], row, 5) # range specifier - tab.layout.addWidget(self.dqrange_mode[k], row, 6) # range mode - tab.layout.addWidget(self.dqrange_slider[k], row, 7) # range slider - tab.layout.addWidget(self.dqrange_label[k], row, 8) # calculated range - - row += 1 - - # A spacer in the last row stretches to fill remaining space. - # For inputs with fewer parameters than the rest, this pushes parameters - # to the top with the same spacing as the other inputs. - tab.layout.addItem(QSpacerItem(0, 0), row, 0, 1, 9) - tab.layout.setRowStretch(row,1) - tab.setLayout(tab.layout) - - def addProx (self): - self.nprox += 1 # starts at 1 - # evprox feed strength - dprox = OrderedDict([('t_evprox_' + str(self.nprox), 0.), # times and stdevs for evoked responses - ('sigma_t_evprox_' + str(self.nprox), 2.5), - #('numspikes_evprox_' + str(self.nprox), 1), - ('gbar_evprox_' + str(self.nprox) + '_L2Pyr_ampa', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L2Pyr_nmda', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L2Basket_ampa', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L2Basket_nmda', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L5Pyr_ampa', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L5Pyr_nmda', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L5Basket_ampa', 0.), - ('gbar_evprox_' + str(self.nprox) + '_L5Basket_nmda', 0.)]) - self.ld.append(dprox) - self.addtransvarfromdict(dprox) - tab = self.addTab('evprox_' + str(self.nprox)) - self.addGridToTab(dprox, tab) - - def addDist (self): - self.ndist += 1 - # evdist feed strengths - ddist = OrderedDict([('t_evdist_' + str(self.ndist), 0.), - ('sigma_t_evdist_' + str(self.ndist), 6.), - #('numspikes_evdist_' + str(self.ndist), 1), - ('gbar_evdist_' + str(self.ndist) + '_L2Pyr_ampa', 0.), - ('gbar_evdist_' + str(self.ndist) + '_L2Pyr_nmda', 0.), - ('gbar_evdist_' + str(self.ndist) + '_L2Basket_ampa', 0.), - ('gbar_evdist_' + str(self.ndist) + '_L2Basket_nmda', 0.), - ('gbar_evdist_' + str(self.ndist) + '_L5Pyr_ampa', 0.), - ('gbar_evdist_' + str(self.ndist) + '_L5Pyr_nmda', 0.)]) - self.ld.append(ddist) - self.addtransvarfromdict(ddist) - tab = self.addTab('evdist_' + str(self.ndist)) - self.addGridToTab(ddist, tab) - - def changeParamEnabledStatus(self, label, toEnable): - import re - - label_match = re.search('(evprox|evdist)_([0-9]+)', label) - if label_match: - my_input_name = label_match.group(1) + '_' + label_match.group(2) - else: - print("ERR: can't determine input name from parameter: %s" % label) - return - - # decrease the count of num params - for chunk_index in range(self.old_num_steps): - for input_name in self.chunk_list[chunk_index]['inputs']: - if input_name == my_input_name: - try: - num_params = int(self.lqnumparams[chunk_index].text()) - except ValueError: - print("ERR: could not get number of params for step %d"%chunk_index) - - if toEnable: - num_params += 1 - else: - num_params -= 1 - self.lqnumparams[chunk_index].setText(str(num_params)) - self.opt_params[input_name]['ranges'][label]['enabled'] = toEnable - - def updateRange(self, label, save_slider=True): - import re - - max_width = 0 - - label_match = re.search('(evprox|evdist)_([0-9]+)', label) - if label_match: - tab_name = label_match.group(1) + '_' + label_match.group(2) - else: - print("ERR: can't determine input name from parameter: %s" % label) - return - - if self.dqchkbox[label].isChecked(): - self.opt_params[tab_name]['ranges'][label]['enabled'] = True - else: - self.opt_params[tab_name]['ranges'][label]['enabled'] = False - return - - if tab_name not in self.initial_opt_ranges or \ - label not in self.initial_opt_ranges[tab_name]: - value = self.dparams[label] - else: - value = float(self.initial_opt_ranges[tab_name][label]['initial']) - - range_type = self.dqrange_mode[label].text() - if range_type == "(%)" and value == 0.0: - # change to range from 0 to 1 - range_type = "(max)" - self.dqrange_mode[label].setText(range_type) - self.dqrange_multiplier[label].setText("1.0") - elif range_type == "(max)" and value > 0.0: - # change back to % - range_type = "(%)" - self.dqrange_mode[label].setText(range_type) - self.dqrange_multiplier[label].setText("500.0") - - try: - range_multiplier = float(self.dqrange_multiplier[label].text()) - except ValueError: - range_multiplier = 0.0 - self.dqrange_multiplier[label].setText(str(range_multiplier)) - - if range_type == "(max)": - range_min = 0 - try: - range_max = float(self.dqrange_multiplier[label].text()) - except ValueError: - range_max = 1.0 - elif range_type == "(stdev)": # timing - timing_sigma = self.get_input_timing_sigma(tab_name) - timing_bound = timing_sigma * range_multiplier - range_min = max(0, value - timing_bound) - range_max = min(self.simlength, value + timing_bound) - else: # range_type == "(%)" - range_min = max(0, value - (value * range_multiplier / 100.0)) - range_max = value + (value * range_multiplier / 100.0) - - # set up the slider - self.dqrange_slider[label].setLine(value) - self.dqrange_slider[label].setMin(range_min) - self.dqrange_slider[label].setMax(range_max) - - if not save_slider: - self.dqrange_min.pop(label, None) - self.dqrange_max.pop(label, None) - - self.opt_params[tab_name]['ranges'][label]['initial'] = value - if label in self.dqrange_min and label in self.dqrange_max: - range_min = self.dqrange_min[label] - range_max = self.dqrange_max[label] - - self.opt_params[tab_name]['ranges'][label]['minval'] = range_min - self.opt_params[tab_name]['ranges'][label]['maxval'] = range_max - self.dqrange_slider[label].setRange(range_min, range_max) - - if range_min == range_max: - self.dqrange_label[label].setText(format_range_str(range_min)) # use the exact value - self.dqrange_label[label].setEnabled(False) - # uncheck because invalid range - self.dqchkbox[label].setChecked(False) - # disable slider - self.dqrange_slider[label].setEnabled(False) - self.changeParamEnabledStatus(label, False) - else: - self.dqrange_label[label].setText(format_range_str(range_min) + - " - " + - format_range_str(range_max)) - - if self.dqrange_label[label].sizeHint().width() > max_width: - max_width = self.dqrange_label[label].sizeHint().width() + 15 - # fix the size for the defined range so that changing the slider doesn't change - # the dialog's width - self.dqrange_label[label].setMinimumWidth(max_width) - self.dqrange_label[label].setMaximumWidth(max_width) - - def prepareOptimization(self): - self.createOptParams() - self.rebuildOptStepInfo() - self.updateOptDeltas() - self.updateOptRanges(save_sliders=True) - self.btnreset.setEnabled(True) - self.btnrunop.setText('Run Optimization') - self.btnrunop.clicked.disconnect() - self.btnrunop.clicked.connect(self.runOptimization) - - def runOptimization(self): - self.current_opt_step = 0 - - # update the ranges to find which parameters have been disabled (unchecked) - self.updateOptRanges(save_sliders=True) - - # update the opt info dict to capture num_sims from GUI - self.rebuildOptStepInfo() - self.optimization_running = True - - # run the actual optimization. optrun_func comes from HNNGUI.startoptmodel(): - # passed to BaseParamDialog then finally OptEvokedInputParamDialog - self.optrun_func() - - def get_chunk_start(self, step): - return self.chunk_list[step]['opt_start'] - - def get_chunk_end(self, step): - return self.chunk_list[step]['opt_end'] - - def get_chunk_weights(self, step): - return self.chunk_list[step]['weights'] - - def get_num_chunks(self): - return len(self.chunk_list) - - def get_sims_for_chunk(self, step): - try: - num_sims = int(self.lqnumsim[step].text()) - except KeyError: - print("ERR: number of sims not found for step %d"%step) - num_sims = 0 - except ValueError: - if step == self.old_num_steps - 1: - num_sims = self.default_num_total_sims - else: - num_sims = self.default_num_step_sims - - return num_sims - - def get_chunk_ranges(self, step): - ranges = {} - for input_name in self.chunk_list[step]['inputs']: - # make sure initial value is between minval or maxval before returning - # ranges to the optimization - for label in self.opt_params[input_name]['ranges'].keys(): - if not self.opt_params[input_name]['ranges'][label]['enabled']: - continue - range_min = self.opt_params[input_name]['ranges'][label]['minval'] - range_max = self.opt_params[input_name]['ranges'][label]['maxval'] - if range_min > self.opt_params[input_name]['ranges'][label]['initial']: - self.opt_params[input_name]['ranges'][label]['initial'] = range_min - if range_max < self.opt_params[input_name]['ranges'][label]['initial']: - self.opt_params[input_name]['ranges'][label]['initial'] = range_max - - # copy the values to the ranges dict to be returned - # to optimization - ranges[label] = self.opt_params[input_name]['ranges'][label].copy() - - return ranges - - def get_num_params(self, step): - num_params = 0 - - for input_name in self.chunk_list[step]['inputs']: - for label in self.opt_params[input_name]['ranges'].keys(): - if not self.opt_params[input_name]['ranges'][label]['enabled']: - continue - else: - num_params += 1 - - return num_params - - def push_chunk_ranges(self, step, ranges): - import re - - for label, value in ranges.items(): - for tab_name in self.opt_params.keys(): - if label in self.opt_params[tab_name]['ranges']: - self.opt_params[tab_name]['ranges'][label]['initial'] = float(value) - - def clean_opt_grid(self): - # This is the top part of the Configure Optimization dialog. - - column_count = self.sublayout.columnCount() - row = 0 - while True: - try: - self.sublayout.itemAtPosition(row,0).widget() - except AttributeError: - # no more rows - break - - for column in range(column_count): - try: - # Use deleteLater() to avoid memory leaks. - self.sublayout.itemAtPosition(row, column).widget().deleteLater() - except AttributeError: - # if item wasn't found - pass - row += 1 - - # reset data for number of sims per chunk (step) - self.lqnumsim = [] - self.lqnumparams = [] - self.lqinputs = [] - self.old_num_steps = 0 - - def rebuildOptStepInfo(self): - # split chunks from paramter file - self.chunk_list = chunk_evinputs(self.opt_params, self.simlength, self.sim_dt) - - if len(self.chunk_list) == 0: - self.clean_opt_grid() - - qlabel = QLabel("No valid evoked inputs to optimize!") - qlabel.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - qlabel.resize(qlabel.minimumSizeHint()) - self.sublayout.addWidget(qlabel, 0, 0) - self.btnrunop.setEnabled(False) - self.btnreset.setEnabled(False) - else: - self.btnrunop.setEnabled(True) - self.btnreset.setEnabled(True) - - if len(self.chunk_list) < self.old_num_steps or \ - self.old_num_steps == 0: - # clean up the old grid sublayout - self.clean_opt_grid() - - # keep track of inputs to optimize over (check against self.opt_params later) - all_inputs = [] - - # create a new grid sublayout with a row for each optimization step - for chunk_index, chunk in enumerate(self.chunk_list): - chunk['num_params'] = self.get_num_params(chunk_index) - - inputs = [] - for input_name in chunk['inputs']: - all_inputs.append(input_name) - inputs.append(trans_input(input_name)) - - if chunk_index >= self.old_num_steps: - qlabel = QLabel("Optimization step %d:"%(chunk_index+1)) - qlabel.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - qlabel.resize(qlabel.minimumSizeHint()) - self.sublayout.addWidget(qlabel,chunk_index, 0) - - self.lqinputs.append(QLabel("Inputs: %s"%', '.join(inputs))) - self.lqinputs[chunk_index].setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - self.lqinputs[chunk_index].resize(self.lqinputs[chunk_index].minimumSizeHint()) - self.sublayout.addWidget(self.lqinputs[chunk_index], chunk_index, 1) - - # spacer here for readability of input names and reduce size - # of "Num simulations:" - self.sublayout.addItem(QSpacerItem(0, 0, hPolicy = QSizePolicy.MinimumExpanding), chunk_index, 2) - - qlabel_params = QLabel("Num params:") - qlabel_params.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - qlabel_params.resize(qlabel_params.minimumSizeHint()) - self.sublayout.addWidget(qlabel_params,chunk_index, 3) - - self.lqnumparams.append(QLabel(str(chunk['num_params']))) - self.lqnumparams[chunk_index].setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - self.lqnumparams[chunk_index].resize(self.lqnumparams[chunk_index].minimumSizeHint()) - self.sublayout.addWidget(self.lqnumparams[chunk_index],chunk_index, 4) - - qlabel_sims = QLabel("Num simulations:") - qlabel_sims.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - qlabel_sims.resize(qlabel_sims.minimumSizeHint()) - self.sublayout.addWidget(qlabel_sims,chunk_index, 5) - - if chunk_index == len(self.chunk_list) - 1: - chunk['num_sims'] = self.default_num_total_sims - else: - chunk['num_sims'] = self.default_num_step_sims - self.lqnumsim.append(QLineEdit(str(chunk['num_sims']))) - self.lqnumsim[chunk_index].resize( - self.lqnumsim[chunk_index].minimumSizeHint()) - self.sublayout.addWidget(self.lqnumsim[chunk_index], - chunk_index, 6) - else: - self.lqinputs[chunk_index].setText("Inputs: %s"%', '.join(inputs)) - self.lqnumparams[chunk_index].setText(str(chunk['num_params'])) - - self.old_num_steps = len(self.chunk_list) - - remove_list = [] - # remove a tab if necessary - for input_name in self.opt_params.keys(): - if input_name not in all_inputs and input_name in self.dtab_idx: - remove_list.append(input_name) - - while len(remove_list) > 0: - tab_name = remove_list.pop() - tab_index = self.dtab_idx[tab_name] - - self.removeInput(tab_index) - del self.dtab_idx[tab_name] - del self.dtab_names[tab_index] - self.ltabkeys.pop(tab_index) - - # rebuild dtab_idx and dtab_names - temp_dtab_names = {} - temp_dtab_idx = {} - for new_tab_index, old_tab_index in enumerate(self.dtab_idx.values()): - # self.dtab_idx[id_str] = tab_index - id_str = self.dtab_names[old_tab_index] - temp_dtab_names[new_tab_index] = id_str - temp_dtab_idx[id_str] = new_tab_index - self.dtab_names = temp_dtab_names - self.dtab_idx = temp_dtab_idx - - def toggleEnableUserFields(self, step, enable=True): - if not enable: - # the optimization called this to disable parameters on - # for the step passed in to this function - self.current_opt_step = step - - for input_name in self.chunk_list[step]['inputs']: - tab_index = self.dtab_idx[input_name] - tab = self.ltabs[tab_index] - - for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer - label = self.ltabkeys[tab_index][row_index] - self.dqchkbox[label].setEnabled(enable) - self.dqrange_slider[label].setEnabled(enable) - self.dqrange_multiplier[label].setEnabled(enable) - - def get_input_timing_sigma(self, tab_name): - """ get timing_sigma from already loaded values """ - - label = 'sigma_t_' + tab_name - try: - timing_sigma = self.dparams[label] - except KeyError: - timing_sigma = 3.0 - print("ERR: Couldn't fing %s. Using default %f" % - (label,timing_sigma)) - - if timing_sigma == 0.0: - # sigma of 0 will not produce a CDF - timing_sigma = 0.01 - - return timing_sigma - - def createOptParams(self): - global decay_multiplier - - self.opt_params = {} - - # iterate through tabs. data is contained in grid layout - for tab_index, tab in enumerate(self.ltabs): - tab_name = self.dtab_names[tab_index] - - # before optimization has started update 'mean', 'sigma', - # 'start', and 'user_end' - start_time_label = 't_' + tab_name - try: - try: - range_multiplier = float(self.dqrange_multiplier[start_time_label].text()) - except ValueError: - range_multiplier = 0.0 - value = self.dparams[start_time_label] - except KeyError: - print("ERR: could not find start time parameter: %s" % start_time_label) - continue - - timing_sigma = self.get_input_timing_sigma(tab_name) - self.opt_params[tab_name] = {'ranges': {}, - 'mean' : value, - 'sigma': timing_sigma, - 'decay_multiplier': decay_multiplier} - - timing_bound = timing_sigma * range_multiplier - self.opt_params[tab_name]['user_start'] = max(0, value - timing_bound) - self.opt_params[tab_name]['user_end'] = min(self.simlength, value + timing_bound) - - # add an empty dictionary so that rebuildOptStepInfo() can determine - # how many parameters - for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer - label = self.ltabkeys[tab_index][row_index] - self.opt_params[tab_name]['ranges'][label] = {'enabled': True} - - def clear_initial_opt_ranges(self): - self.initial_opt_ranges = {} - - def populate_initial_opt_ranges(self): - self.initial_opt_ranges = {} - - for input_name in self.opt_params.keys(): - self.initial_opt_ranges[input_name] = deepcopy(self.opt_params[input_name]['ranges']) - - def updateOptDeltas(self): - # iterate through tabs. data is contained in grid layout - for tab_index, tab in enumerate(self.ltabs): - tab_name = self.dtab_names[tab_index] - - # update the initial value - for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer - label = self.ltabkeys[tab_index][row_index] - value = self.dparams[label] - - # Calculate value to put in "Delta" column. When possible, use - # percentages, but when initial value is 0, use absolute changes - if tab_name not in self.initial_opt_ranges or \ - not self.dqchkbox[label].isChecked(): - self.dqdiff_label[label].setEnabled(False) - self.dqinitial_label[label].setText(("%6f"%self.dparams[label]).rstrip('0').rstrip('.')) - text = '--' - color_fmt = "QLabel { color : black; }" - self.dqopt_label[label].setText(text) - self.dqopt_label[label].setStyleSheet(color_fmt) - self.dqopt_label[label].setAlignment(Qt.AlignHCenter) - self.dqdiff_label[label].setAlignment(Qt.AlignHCenter) - else: - initial_value = float(self.initial_opt_ranges[tab_name][label]['initial']) - self.dqinitial_label[label].setText(("%6f"%initial_value).rstrip('0').rstrip('.')) - self.dqopt_label[label].setText(("%6f"%self.dparams[label]).rstrip('0').rstrip('.')) - self.dqopt_label[label].setAlignment(Qt.AlignVCenter|Qt.AlignLeft) - self.dqdiff_label[label].setAlignment(Qt.AlignVCenter|Qt.AlignLeft) - - if isclose(value, initial_value, abs_tol=1e-7): - diff = 0 - text = "0.0" - color_fmt = "QLabel { color : black; }" - else: - diff = value - initial_value - - if initial_value == 0: - # can't calculate % - if diff < 0: - text = ("%6f"%diff).rstrip('0').rstrip('.') - color_fmt = "QLabel { color : red; }" - elif diff > 0: - text = ("+%6f"%diff).rstrip('0').rstrip('.') - color_fmt = "QLabel { color : green; }" - else: - # calculate percent difference - percent_diff = 100 * diff/abs(initial_value) - if percent_diff < 0: - text = ("%2.2f %%"%percent_diff) - color_fmt = "QLabel { color : red; }" - elif percent_diff > 0: - text = ("+%2.2f %%"%percent_diff) - color_fmt = "QLabel { color : green; }" - - self.dqdiff_label[label].setStyleSheet(color_fmt) - self.dqdiff_label[label].setText(text) - - def updateRangeFromSlider(self, label, range_min, range_max): - import re - - label_match = re.search('(evprox|evdist)_([0-9]+)', label) - if label_match: - tab_name = label_match.group(1) + '_' + label_match.group(2) - else: - print("ERR: can't determine input name from parameter: %s" % label) - return - - self.dqrange_min[label] = range_min - self.dqrange_max[label] = range_max - self.dqrange_label[label].setText(format_range_str(range_min) + " - " + - format_range_str(range_max)) - self.opt_params[tab_name]['ranges'][label]['minval'] = range_min - self.opt_params[tab_name]['ranges'][label]['maxval'] = range_max - - def updateOptRanges(self, save_sliders=False): - # iterate through tabs. data is contained in grid layout - for tab_index, tab in enumerate(self.ltabs): - # now update the ranges - for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer - label = self.ltabkeys[tab_index][row_index] - self.updateRange(label, save_sliders) - - def setfromdin (self,din): - if not din: - return - - if 'dt' in din: - # din proivdes a complete parameter set - self.din = din - self.simlength = float(din['tstop']) - self.sim_dt = float(din['dt']) - - self.cleanLabels() - self.removeAllInputs() # turn off any previously set inputs - self.ltabkeys = [] - self.dtab_idx = {} - self.dtab_names = {} - - for evinput in get_inputs(din): - if 'evprox_' in evinput: - self.addProx() - elif 'evdist_' in evinput: - self.addDist() - - for k,v in din.items(): - if k in self.dparams: - try: - new_value = float(v) - except ValueError: - print("WARN: bad value for param %s: %s. Unable to convert" - " to a floating point number" % (k,v)) - continue - self.dparams[k] = new_value - elif k.count('gbar') > 0 and \ - (k.count('evprox') > 0 or \ - k.count('evdist') > 0): - # NOTE: will be deprecated in future release - # for back-compat with old-style specification which didn't have ampa,nmda in evoked gbar - try: - new_value = float(v) - except ValueError: - print("WARN: bad value for param %s: %s. Unable to convert" - " to a floating point number" % (k,v)) - continue - lks = k.split('_') - eloc = lks[1] - enum = lks[2] - base_key_str = 'gbar_' + eloc + '_' + enum + '_' - if eloc == 'evprox': - for ct in ['L2Pyr','L2Basket','L5Pyr','L5Basket']: - # ORIGINAL MODEL/PARAM: only ampa for prox evoked inputs - key_str = base_key_str + ct + '_ampa' - self.dparams[key_str] = new_value - elif eloc == 'evdist': - for ct in ['L2Pyr','L2Basket','L5Pyr']: - # ORIGINAL MODEL/PARAM: both ampa and nmda for distal evoked inputs - key_str = base_key_str + ct + '_ampa' - self.dparams[key_str] = new_value - key_str = base_key_str + ct + '_nmda' - self.dparams[key_str] = new_value - - if not self.optimization_running: - self.createOptParams() - self.rebuildOptStepInfo() - self.updateOptRanges(save_sliders=True) - - self.updateOptDeltas() - - def __str__ (self): - # don't write any values to param file - return '' - # widget to specify run params (tstop, dt, etc.) -- not many params here class RunParamDialog (DictDialog): def __init__ (self, parent, din = None): @@ -2135,64 +873,6 @@ def initUI (self): setscalegeom(self, 100, 100, 300, 100) self.setWindowTitle('Help') -# dialog for visualizing model -class VisnetDialog (QDialog): - def __init__ (self, parent): - super(VisnetDialog, self).__init__(parent) - self.initUI() - - def showcells3D (self): Popen([getPyComm(), 'visnet.py', 'cells', paramf]) # nonblocking - def showEconn (self): Popen([getPyComm(), 'visnet.py', 'Econn', paramf]) # nonblocking - def showIconn (self): Popen([getPyComm(), 'visnet.py', 'Iconn', paramf]) # nonblocking - - def runvisnet (self): - lcmd = [getPyComm(), 'visnet.py', 'cells'] - #if self.chkcells.isChecked(): lcmd.append('cells') - #if self.chkE.isChecked(): lcmd.append('Econn') - #if self.chkI.isChecked(): lcmd.append('Iconn') - lcmd.append(paramf) - Popen(lcmd) # nonblocking - - def initUI (self): - - self.layout = QVBoxLayout(self) - - # Add stretch to separate the form layout from the button - # self.layout.addStretch(1) - - """ - self.chkcells = QCheckBox('Cells in 3D',self) - self.chkcells.resize(self.chkcells.sizeHint()) - self.chkcells.setChecked(True) - self.layout.addWidget(self.chkcells) - self.chkE = QCheckBox('Excitatory Connections',self) - self.chkE.resize(self.chkE.sizeHint()) - self.layout.addWidget(self.chkE) - - self.chkI = QCheckBox('Inhibitory Connections',self) - self.chkI.resize(self.chkI.sizeHint()) - self.layout.addWidget(self.chkI) - """ - - # Create a horizontal box layout to hold the buttons - self.button_box = QHBoxLayout() - - self.btnok = QPushButton('Visualize',self) - self.btnok.resize(self.btnok.sizeHint()) - self.btnok.clicked.connect(self.runvisnet) - self.button_box.addWidget(self.btnok) - - self.btncancel = QPushButton('Cancel',self) - self.btncancel.resize(self.btncancel.sizeHint()) - self.btncancel.clicked.connect(self.hide) - self.button_box.addWidget(self.btncancel) - - self.layout.addLayout(self.button_box) - - setscalegeom(self, 100, 100, 300, 100) - - self.setWindowTitle('Visualize Model') - class SchematicDialog (QDialog): # class for holding model schematics (and parameter shortcuts) def __init__ (self, parent): @@ -2224,11 +904,6 @@ def initUI (self): self.distbtn.clicked.connect(self.parent().showdistparamwin) self.grid.addWidget(self.distbtn,gRow,2,1,1) - self.netbtn = QPushButton('Model'+os.linesep+'Visualization',self) - self.netbtn.setIcon(QIcon(lookupresource('netfig'))) - self.netbtn.clicked.connect(self.parent().showvisnet) - self.grid.addWidget(self.netbtn,gRow,3,1,1) - gRow = 1 # for schematic dialog box @@ -2254,13 +929,6 @@ def initUI (self): # self.pixDistlbl.clicked.connect(self.showdistparamwin) self.grid.addWidget(self.pixDistlbl,gRow,2,1,1) - self.pixNet = QPixmap(lookupresource('netfig')) - self.pixNetlbl = ClickLabel(self) - self.pixNetlbl.setScaledContents(True) - self.pixNetlbl.setPixmap(self.pixNet) - # self.pixNetlbl.clicked.connect(self.showvisnet) - self.grid.addWidget(self.pixNetlbl,gRow,3,1,1) - self.setLayout(grid) class BaseParamDialog (QDialog): @@ -2455,34 +1123,6 @@ def __str__ (self): for win in self.lsubwin: s += str(win) return s -# clickable label -class ClickLabel (QLabel): - """ - def __init__(self, *args, **kwargs): - QLabel.__init__(self) - # self._pixmap = QPixmap(self.pixmap()) - # spolicy = QSizePolicy(QSizePolicy.MinimumExpanding,QSizePolicy.MinimumExpanding) - spolicy = QSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed) - # spolicy = QSizePolicy(QSizePolicy.Preferred,QSizePolicy.Preferred) - # spolicy.setHorizontalStretch(0) - # spolicy.setVerticalStretch(0) - self.setSizePolicy(spolicy) - self.setMinimumWidth(150) - self.setMinimumHeight(150) - def setPixmap (self, pm): - QLabel.setPixmap(self,pm) - self._pixmap = pm - """ - clicked = pyqtSignal() - def mousePressEvent(self, event): - self.clicked.emit() - """ - def resizeEvent(self, event): - self.setPixmap(self._pixmap.scaled( - self.width(), self.height(), - QtCore.Qt.KeepAspectRatio)) - """ - class WaitSimDialog (QDialog): def __init__ (self, parent): super(WaitSimDialog, self).__init__(parent) @@ -2537,7 +1177,6 @@ def __init__ (self): self.baseparamwin = BaseParamDialog(self, paramfn, self.startoptmodel) self.optMode = False self.initUI() - self.visnetwin = VisnetDialog(self) self.helpwin = HelpDialog(self) self.erselectdistal = EvokedOrRhythmicDialog(self, True, self.baseparamwin.evparamwin, self.baseparamwin.distparamwin) self.erselectprox = EvokedOrRhythmicDialog(self, False, self.baseparamwin.evparamwin, self.baseparamwin.proxparamwin) @@ -2675,7 +1314,7 @@ def loadDataFileDialog (self): hnn_root_dir = os.path.dirname(os.path.realpath(__file__)) qfd = QFileDialog() - qfd.setHistory([os.path.join(get_output_dir, 'data'), + qfd.setHistory([os.path.join(get_output_dir(), 'data'), os.path.join(hnn_root_dir, 'data')]) fn = qfd.getOpenFileName(self, 'Open data file', os.path.join(hnn_root_dir,'data'), @@ -3041,10 +1680,6 @@ def initMenu (self): viewSchemAction.setStatusTip('View Model Schematics') viewSchemAction.triggered.connect(self.showschematics) viewMenu.addAction(viewSchemAction) - viewNetAction = QAction('View Local Network (3D)',self) - viewNetAction.setStatusTip('View Local Network Model (3D)') - viewNetAction.triggered.connect(self.showvisnet) - viewMenu.addAction(viewNetAction) viewSimLogAction = QAction('View Simulation Log',self) viewSimLogAction.setStatusTip('View Detailed Simulation Log') viewSimLogAction.triggered.connect(self.showwaitsimwin) @@ -3111,30 +1746,29 @@ def addButtons (self, gRow): pbtn.setToolTip('Set Parameters') pbtn.resize(pbtn.sizeHint()) pbtn.clicked.connect(self.setparams) - self.grid.addWidget(self.pbtn, gRow, 0, 1, 1) + self.grid.addWidget(self.pbtn, gRow, 0, 1, 3) self.pfbtn = pfbtn = QPushButton('Set Parameters From File', self) pfbtn.setToolTip('Set Parameters From File') pfbtn.resize(pfbtn.sizeHint()) pfbtn.clicked.connect(self.selParamFileDialog) - self.grid.addWidget(self.pfbtn, gRow, 1, 1, 1) + self.grid.addWidget(self.pfbtn, gRow, 3, 1, 3) self.btnsim = btn = QPushButton('Run Simulation', self) btn.setToolTip('Run Simulation') btn.resize(btn.sizeHint()) btn.clicked.connect(self.controlsim) - self.grid.addWidget(self.btnsim, gRow, 2, 1, 1) + self.grid.addWidget(self.btnsim, gRow, 6, 1, 3) self.qbtn = qbtn = QPushButton('Quit', self) qbtn.clicked.connect(QCoreApplication.instance().quit) qbtn.resize(qbtn.sizeHint()) - self.grid.addWidget(self.qbtn, gRow, 3, 1, 1) + self.grid.addWidget(self.qbtn, gRow, 9, 1, 3) def shownetparamwin (self): bringwintotop(self.baseparamwin.netparamwin) def showoptparamwin (self): bringwintotop(self.baseparamwin.optparamwin) def showdistparamwin (self): bringwintotop(self.erselectdistal) def showproxparamwin (self): bringwintotop(self.erselectprox) - def showvisnet (self): Popen([getPyComm(), 'visnet.py', 'cells', paramf]) # nonblocking def showschematics (self): bringwintotop(self.schemwin) def addParamImageButtons (self,gRow): @@ -3143,58 +1777,20 @@ def addParamImageButtons (self,gRow): self.locbtn = QPushButton('Local Network'+os.linesep+'Connections',self) self.locbtn.setIcon(QIcon(lookupresource('connfig'))) self.locbtn.clicked.connect(self.shownetparamwin) - self.grid.addWidget(self.locbtn,gRow,0,1,1) + self.grid.addWidget(self.locbtn,gRow,0,1,4) self.proxbtn = QPushButton('Proximal Drive'+os.linesep+'Thalamus',self) self.proxbtn.setIcon(QIcon(lookupresource('proxfig'))) self.proxbtn.clicked.connect(self.showproxparamwin) - self.grid.addWidget(self.proxbtn,gRow,1,1,1) + self.grid.addWidget(self.proxbtn,gRow,4,1,4) - self.distbtn = QPushButton('Distal Drive NonLemniscal'+os.linesep+'Thal./Cortical Feedback',self) + self.distbtn = QPushButton('Distal Drive Non3Lemniscal'+os.linesep+'Thal./Cortical Feedback',self) self.distbtn.setIcon(QIcon(lookupresource('distfig'))) self.distbtn.clicked.connect(self.showdistparamwin) - self.grid.addWidget(self.distbtn,gRow,2,1,1) - - self.netbtn = QPushButton('Model'+os.linesep+'Visualization',self) - self.netbtn.setIcon(QIcon(lookupresource('netfig'))) - self.netbtn.clicked.connect(self.showvisnet) - self.grid.addWidget(self.netbtn,gRow,3,1,1) + self.grid.addWidget(self.distbtn,gRow,8,1,4) gRow += 1 - return - - # for schematic dialog box - self.pixConn = QPixmap(lookupresource('connfig')) - self.pixConnlbl = ClickLabel(self) - self.pixConnlbl.setScaledContents(True) - #self.pixConnlbl.resize(self.pixConnlbl.size()) - self.pixConnlbl.setPixmap(self.pixConn) - # self.pixConnlbl.clicked.connect(self.shownetparamwin) - self.grid.addWidget(self.pixConnlbl,gRow,0,1,1) - - self.pixProx = QPixmap(lookupresource('proxfig')) - self.pixProxlbl = ClickLabel(self) - self.pixProxlbl.setScaledContents(True) - self.pixProxlbl.setPixmap(self.pixProx) - # self.pixProxlbl.clicked.connect(self.showproxparamwin) - self.grid.addWidget(self.pixProxlbl,gRow,1,1,1) - - self.pixDist = QPixmap(lookupresource('distfig')) - self.pixDistlbl = ClickLabel(self) - self.pixDistlbl.setScaledContents(True) - self.pixDistlbl.setPixmap(self.pixDist) - # self.pixDistlbl.clicked.connect(self.showdistparamwin) - self.grid.addWidget(self.pixDistlbl,gRow,2,1,1) - - self.pixNet = QPixmap(lookupresource('netfig')) - self.pixNetlbl = ClickLabel(self) - self.pixNetlbl.setScaledContents(True) - self.pixNetlbl.setPixmap(self.pixNet) - # self.pixNetlbl.clicked.connect(self.showvisnet) - self.grid.addWidget(self.pixNetlbl,gRow,3,1,1) - - def initUI (self): # initialize the user interface (UI) @@ -3235,12 +1831,12 @@ def initUI (self): self.cbsim = QComboBox(self) self.populateSimCB() # populate the combobox self.cbsim.activated[str].connect(self.onActivateSimCB) - self.grid.addWidget(self.cbsim, gRow, 0, 1, 3)#, 1, 3) + self.grid.addWidget(self.cbsim, gRow, 0, 1, 8)#, 1, 3) self.btnrmsim = QPushButton('Remove Simulation',self) self.btnrmsim.resize(self.btnrmsim.sizeHint()) self.btnrmsim.clicked.connect(self.removeSim) self.btnrmsim.setToolTip('Remove Currently Selected Simulation') - self.grid.addWidget(self.btnrmsim, gRow, 3)#, 4, 1) + self.grid.addWidget(self.btnrmsim, gRow, 8, 1, 4) gRow += 1 self.addParamImageButtons(gRow) @@ -3311,7 +1907,7 @@ def initSimCanvas (self,recalcErr=True,optMode=False,gRow=1,reInit=True): # this is the Navigation widget # it takes the Canvas widget and a parent self.toolbar = NavigationToolbar(self.m, self) - gWidth = 4 + gWidth = 12 self.grid.addWidget(self.toolbar, gRow, gCol, 1, gWidth) self.grid.addWidget(self.m, gRow + 1, gCol, 1, gWidth) if len(self.dextdata.keys()) > 0: diff --git a/morphology.py b/morphology.py deleted file mode 100644 index afe9ddb51..000000000 --- a/morphology.py +++ /dev/null @@ -1,624 +0,0 @@ -# from https://github.com/ahwillia/PyNeuron-Toolbox 4/2017 -# adjusted for python3 -from __future__ import division -import numpy as np -import pylab as plt -from matplotlib.pyplot import cm -import string -from neuron import h -import numbers - -# a helper library, included with NEURON -h.load_file('stdlib.hoc') -h.load_file('import3d.hoc') - -class Cell: - def __init__(self,name='neuron',soma=None,apic=None,dend=None,axon=None): - self.soma = soma if soma is not None else [] - self.apic = apic if apic is not None else [] - self.dend = dend if dend is not None else [] - self.axon = axon if axon is not None else [] - self.all = self.soma + self.apic + self.dend + self.axon - - def delete(self): - self.soma = None - self.apic = None - self.dend = None - self.axon = None - self.all = None - - def __str__(self): - return self.name - -def load(filename, fileformat=None, cell=None, use_axon=True, xshift=0, yshift=0, zshift=0): - """ - Load an SWC from filename and instantiate inside cell. Code kindly provided - by @ramcdougal. - - Args: - filename = .swc file containing morphology - cell = Cell() object. (Default: None, creates new object) - filename = the filename of the SWC file - use_axon = include the axon? Default: True (yes) - xshift, yshift, zshift = use to position the cell - - Returns: - Cell() object with populated soma, axon, dend, & apic fields - - Minimal example: - # pull the morphology for the demo from NeuroMorpho.Org - from PyNeuronToolbox import neuromorphoorg - with open('c91662.swc', 'w') as f: - f.write(neuromorphoorg.morphology('c91662')) - cell = load_swc(filename) - - """ - - if cell is None: - cell = Cell(name=string.join(filename.split('.')[:-1])) - - if fileformat is None: - fileformat = filename.split('.')[-1] - - name_form = {1: 'soma[%d]', 2: 'axon[%d]', 3: 'dend[%d]', 4: 'apic[%d]'} - - # load the data. Use Import3d_SWC_read for swc, Import3d_Neurolucida3 for - # Neurolucida V3, Import3d_MorphML for MorphML (level 1 of NeuroML), or - # Import3d_Eutectic_read for Eutectic. - if fileformat == 'swc': - morph = h.Import3d_SWC_read() - elif fileformat == 'asc': - morph = h.Import3d_Neurolucida3() - else: - raise Exception('file format `%s` not recognized'%(fileformat)) - morph.input(filename) - - # easiest to instantiate by passing the loaded morphology to the Import3d_GUI - # tool; with a second argument of 0, it won't display the GUI, but it will allow - # use of the GUI's features - i3d = h.Import3d_GUI(morph, 0) - - # get a list of the swc section objects - swc_secs = i3d.swc.sections - swc_secs = [swc_secs.object(i) for i in range(int(swc_secs.count()))] - - # initialize the lists of sections - sec_list = {1: cell.soma, 2: cell.axon, 3: cell.dend, 4: cell.apic} - - # name and create the sections - real_secs = {} - for swc_sec in swc_secs: - cell_part = int(swc_sec.type) - - # skip everything else if it's an axon and we're not supposed to - # use it... or if is_subsidiary - if (not(use_axon) and cell_part == 2) or swc_sec.is_subsidiary: - continue - - # figure out the name of the new section - if cell_part not in name_form: - raise Exception('unsupported point type') - name = name_form[cell_part] % len(sec_list[cell_part]) - - # create the section - sec = h.Section(name=name) - - # connect to parent, if any - if swc_sec.parentsec is not None: - sec.connect(real_secs[swc_sec.parentsec.hname()](swc_sec.parentx)) - - # define shape - if swc_sec.first == 1: - h.pt3dstyle(1, swc_sec.raw.getval(0, 0), swc_sec.raw.getval(1, 0), - swc_sec.raw.getval(2, 0), sec=sec) - - j = swc_sec.first - xx, yy, zz = [swc_sec.raw.getrow(i).c(j) for i in range(3)] - dd = swc_sec.d.c(j) - if swc_sec.iscontour_: - # never happens in SWC files, but can happen in other formats supported - # by NEURON's Import3D GUI - raise Exception('Unsupported section style: contour') - - if dd.size() == 1: - # single point soma; treat as sphere - x, y, z, d = [dim.x[0] for dim in [xx, yy, zz, dd]] - for xprime in [x - d / 2., x, x + d / 2.]: - h.pt3dadd(xprime + xshift, y + yshift, z + zshift, d, sec=sec) - else: - for x, y, z, d in zip(xx, yy, zz, dd): - h.pt3dadd(x + xshift, y + yshift, z + zshift, d, sec=sec) - - # store the section in the appropriate list in the cell and lookup table - sec_list[cell_part].append(sec) - real_secs[swc_sec.hname()] = sec - - cell.all = cell.soma + cell.apic + cell.dend + cell.axon - return cell - -def sequential_spherical(xyz): - """ - Converts sequence of cartesian coordinates into a sequence of - line segments defined by spherical coordinates. - - Args: - xyz = 2d numpy array, each row specifies a point in - cartesian coordinates (x,y,z) tracing out a - path in 3D space. - - Returns: - r = lengths of each line segment (1D array) - theta = angles of line segments in XY plane (1D array) - phi = angles of line segments down from Z axis (1D array) - """ - d_xyz = np.diff(xyz,axis=0) - - r = np.linalg.norm(d_xyz,axis=1) - theta = np.arctan2(d_xyz[:,1], d_xyz[:,0]) - hyp = d_xyz[:,0]**2 + d_xyz[:,1]**2 - phi = np.arctan2(np.sqrt(hyp), d_xyz[:,2]) - - return (r,theta,phi) - -def spherical_to_cartesian(r,theta,phi): - """ - Simple conversion of spherical to cartesian coordinates - - Args: - r,theta,phi = scalar spherical coordinates - - Returns: - x,y,z = scalar cartesian coordinates - """ - x = r * np.sin(phi) * np.cos(theta) - y = r * np.sin(phi) * np.sin(theta) - z = r * np.cos(phi) - return (x,y,z) - -def find_coord(targ_length,xyz,rcum,theta,phi): - """ - Find (x,y,z) ending coordinate of segment path along section - path. - - Args: - targ_length = scalar specifying length of segment path, starting - from the begining of the section path - xyz = coordinates specifying the section path - rcum = cumulative sum of section path length at each node in xyz - theta, phi = angles between each coordinate in xyz - """ - # [1] Find spherical coordinates for the line segment containing - # the endpoint. - # [2] Find endpoint in spherical coords and convert to cartesian - i = np.nonzero(rcum <= targ_length)[0][-1] - if i == len(theta): - return xyz[-1,:] - else: - r_lcl = targ_length-rcum[i] # remaining length along line segment - (dx,dy,dz) = spherical_to_cartesian(r_lcl,theta[i],phi[i]) - return xyz[i,:] + [dx,dy,dz] - -def interpolate_jagged(xyz,nseg): - """ - Interpolates along a jagged path in 3D - - Args: - xyz = section path specified in cartesian coordinates - nseg = number of segment paths in section path - - Returns: - interp_xyz = interpolated path - """ - - # Spherical coordinates specifying the angles of all line - # segments that make up the section path - (r,theta,phi) = sequential_spherical(xyz) - - # cumulative length of section path at each coordinate - rcum = np.append(0,np.cumsum(r)) - - # breakpoints for segment paths along section path - breakpoints = np.linspace(0,rcum[-1],nseg+1) - np.delete(breakpoints,0) - - # Find segment paths - seg_paths = [] - for a in range(nseg): - path = [] - - # find (x,y,z) starting coordinate of path - if a == 0: - start_coord = xyz[0,:] - else: - start_coord = end_coord # start at end of last path - path.append(start_coord) - - # find all coordinates between the start and end points - start_length = breakpoints[a] - end_length = breakpoints[a+1] - mid_boolean = (rcum > start_length) & (rcum < end_length) - mid_indices = np.nonzero(mid_boolean)[0] - for mi in mid_indices: - path.append(xyz[mi,:]) - - # find (x,y,z) ending coordinate of path - end_coord = find_coord(end_length,xyz,rcum,theta,phi) - path.append(end_coord) - - # Append path to list of segment paths - seg_paths.append(np.array(path)) - - # Return all segment paths - return seg_paths - -def get_section_path(h,sec): - n3d = int(h.n3d(sec=sec)) - xyz = [] - for i in range(0,n3d): - xyz.append([h.x3d(i,sec=sec),h.y3d(i,sec=sec),h.z3d(i,sec=sec)]) - xyz = np.array(xyz) - return xyz - -def shapeplot(h,ax,sections=None,order='pre',cvals=None,\ - clim=None,cmap=cm.YlOrBr_r,**kwargs): - """ - Plots a 3D shapeplot - - Args: - h = hocObject to interface with neuron - ax = matplotlib axis for plotting - sections = list of h.Section() objects to be plotted - order = { None= use h.allsec() to get sections - 'pre'= pre-order traversal of morphology } - cvals = list/array with values mapped to color by cmap; useful - for displaying voltage, calcium or some other state - variable across the shapeplot. - **kwargs passes on to matplotlib (e.g. color='r' for red lines) - - Returns: - lines = list of line objects making up shapeplot - """ - - # Default is to plot all sections. - if sections is None: - if order == 'pre': - sections = allsec_preorder(h) # Get sections in "pre-order" - else: - sections = list(h.allsec()) - - # Determine color limits - if cvals is not None and clim is None: - cn = [ isinstance(cv, numbers.Number) for cv in cvals ] - if any(cn): - clim = [np.min(cvals[cn]), np.max(cvals[cn])] - - # Plot each segement as a line - lines = [] - i = 0 - for sec in sections: - xyz = get_section_path(h,sec) - seg_paths = interpolate_jagged(xyz,sec.nseg) - - for (j,path) in enumerate(seg_paths): - line, = plt.plot(path[:,0], path[:,1], path[:,2], '-k',**kwargs) - if cvals is not None: - if isinstance(cvals[i], numbers.Number): - # map number to colormap - col = cmap(int((cvals[i]-clim[0])*255/(clim[1]-clim[0]))) - else: - # use input directly. E.g. if user specified color with a string. - col = cvals[i] - line.set_color(col) - lines.append(line) - i += 1 - - return lines - -def getshapecoords (h,sections=None,order='pre',**kwargs): - if sections is None: - if order == 'pre': - sections = allsec_preorder(h) # Get sections in "pre-order" - else: - sections = list(h.allsec()) - i = 0 - lx,ly,lz=[],[],[] - for sec in sections: - xyz = get_section_path(h,sec) - seg_paths = interpolate_jagged(xyz,sec.nseg) - for path in seg_paths: - for i in [0,1]: - lx.append(path[i][0]) - ly.append(path[i][1]) - lz.append(path[i][2]) - return lx,ly,lz - - -def shapeplot_animate(v,lines,nframes=None,tscale='linear',\ - clim=[-80,50],cmap=cm.YlOrBr_r): - """ Returns animate function which updates color of shapeplot """ - if nframes is None: - nframes = v.shape[0] - if tscale == 'linear': - def animate(i): - i_t = int((i/nframes)*v.shape[0]) - for i_seg in range(v.shape[1]): - lines[i_seg].set_color(cmap(int((v[i_t,i_seg]-clim[0])*255/(clim[1]-clim[0])))) - return [] - elif tscale == 'log': - def animate(i): - i_t = int(np.round((v.shape[0] ** (1.0/(nframes-1))) ** i - 1)) - for i_seg in range(v.shape[1]): - lines[i_seg].set_color(cmap(int((v[i_t,i_seg]-clim[0])*255/(clim[1]-clim[0])))) - return [] - else: - raise ValueError("Unrecognized option '%s' for tscale" % tscale) - - return animate - -def mark_locations(h,section,locs,markspec='or',**kwargs): - """ - Marks one or more locations on along a section. Could be used to - mark the location of a recording or electrical stimulation. - - Args: - h = hocObject to interface with neuron - section = reference to section - locs = float between 0 and 1, or array of floats - optional arguments specify details of marker - - Returns: - line = reference to plotted markers - """ - - # get list of cartesian coordinates specifying section path - xyz = get_section_path(h,section) - (r,theta,phi) = sequential_spherical(xyz) - rcum = np.append(0,np.cumsum(r)) - - # convert locs into lengths from the beginning of the path - if type(locs) is float or type(locs) is np.float64: - locs = np.array([locs]) - if type(locs) is list: - locs = np.array(locs) - lengths = locs*rcum[-1] - - # find cartesian coordinates for markers - xyz_marks = [] - for targ_length in lengths: - xyz_marks.append(find_coord(targ_length,xyz,rcum,theta,phi)) - xyz_marks = np.array(xyz_marks) - - # plot markers - line, = plt.plot(xyz_marks[:,0], xyz_marks[:,1], \ - xyz_marks[:,2], markspec, **kwargs) - return line - -def root_sections(h): - """ - Returns a list of all sections that have no parent. - """ - roots = [] - for section in h.allsec(): - sref = h.SectionRef(sec=section) - # has_parent returns a float... cast to bool - if sref.has_parent() < 0.9: - roots.append(section) - return roots - -def leaf_sections(h): - """ - Returns a list of all sections that have no children. - """ - leaves = [] - for section in h.allsec(): - sref = h.SectionRef(sec=section) - # nchild returns a float... cast to bool - if sref.nchild() < 0.9: - leaves.append(section) - return leaves - -def root_indices(sec_list): - """ - Returns the index of all sections without a parent. - """ - roots = [] - for i,section in enumerate(sec_list): - sref = h.SectionRef(sec=section) - # has_parent returns a float... cast to bool - if sref.has_parent() < 0.9: - roots.append(i) - return roots - -def allsec_preorder(h): - """ - Alternative to using h.allsec(). This returns all sections in order from - the root. Traverses the topology each neuron in "pre-order" - """ - #Iterate over all sections, find roots - roots = root_sections(h) - - # Build list of all sections - sec_list = [] - for r in roots: - add_pre(h,sec_list,r) - return sec_list - -def add_pre(h,sec_list,section,order_list=None,branch_order=None): - """ - A helper function that traverses a neuron's morphology (or a sub-tree) - of the morphology in pre-order. This is usually not necessary for the - user to import. - """ - - sec_list.append(section) - sref = h.SectionRef(sec=section) - - if branch_order is not None: - order_list.append(branch_order) - if len(sref.child) > 1: - branch_order += 1 - - for next_node in sref.child: - add_pre(h,sec_list,next_node,order_list,branch_order) - -def dist_between(h,seg1,seg2): - """ - Calculates the distance between two segments. I stole this function from - a post by Michael Hines on the NEURON forum - (www.neuron.yale.edu/phpbb/viewtopic.php?f=2&t=2114) - """ - h.distance(0, seg1.x, sec=seg1.sec) - return h.distance(seg2.x, sec=seg2.sec) - -def all_branch_orders(h): - """ - Produces a list branch orders for each section (following pre-order tree - traversal) - """ - #Iterate over all sections, find roots - roots = [] - for section in h.allsec(): - sref = h.SectionRef(sec=section) - # has_parent returns a float... cast to bool - if sref.has_parent() < 0.9: - roots.append(section) - - # Build list of all sections - order_list = [] - for r in roots: - add_pre(h,[],r,order_list,0) - return order_list - -def branch_order(h,section, path=[]): - """ - Returns the branch order of a section - """ - path.append(section) - sref = h.SectionRef(sec=section) - # has_parent returns a float... cast to bool - if sref.has_parent() < 0.9: - return 0 # section is a root - else: - nchild = len(list(h.SectionRef(sec=sref.parent).child)) - if nchild <= 1.1: - return branch_order(h,sref.parent,path) - else: - return 1+branch_order(h,sref.parent,path) - -def dist_to_mark(h, section, secdict, path=[]): - path.append(section) - sref = h.SectionRef(sec=section) - # print 'current : '+str(section) - # print 'parent : '+str(sref.parent) - if secdict[sref.parent] is None: - # print '-> go to parent' - s = section.L + dist_to_mark(h, sref.parent, secdict, path) - # print 'summing, '+str(s) - return s - else: - # print 'end <- start summing: '+str(section.L) - return section.L # parent is marked - -def branch_precedence(h): - roots = root_sections(h) - leaves = leaf_sections(h) - seclist = allsec_preorder(h) - secdict = { sec:None for sec in seclist } - - for r in roots: - secdict[r] = 0 - - precedence = 1 - while len(leaves)>0: - # build list of distances of all paths to remaining leaves - d = [] - for leaf in leaves: - p = [] - dist = dist_to_mark(h, leaf, secdict, path=p) - d.append((dist,[pp for pp in p])) - - # longest path index - i = np.argmax([ dd[0] for dd in d ]) - leaves.pop(i) # this leaf will be marked - - # mark all sections in longest path - for sec in d[i][1]: - if secdict[sec] is None: - secdict[sec] = precedence - - # increment precedence across iterations - precedence += 1 - - #prec = secdict.values() - #return [0 if p is None else 1 for p in prec], d[i][1] - return [ secdict[sec] for sec in seclist ] - - -from neuron import h -import json - -def parent(sec): - seg = sec.trueparentseg() - if seg is None: - return None - else: - return seg.sec - -def parent_loc(sec, trueparent): - seg = sec.trueparentseg() - if seg is None: - return None - else: - return seg.x - -def morphology_to_dict(sections, outfile=None): - section_map = {sec: i for i, sec in enumerate(sections)} - result = [] - h.define_shape() - - for sec in sections: - my_parent = parent(sec) - my_parent_loc = -1 if my_parent is None else parent_loc(sec, my_parent) - my_parent = -1 if my_parent is None else section_map[my_parent] - n3d = int(h.n3d(sec=sec)) - result.append({ - 'section_orientation': h.section_orientation(sec=sec), - 'parent': my_parent, - 'parent_loc': my_parent_loc, - 'x': [h.x3d(i, sec=sec) for i in range(n3d)], - 'y': [h.y3d(i, sec=sec) for i in range(n3d)], - 'z': [h.z3d(i, sec=sec) for i in range(n3d)], - 'diam': [h.diam3d(i, sec=sec) for i in range(n3d)], - 'name': sec.hname() - }) - - if outfile is not None: - with open(outfile, 'w') as f: - json.dump(result, f) - - return result - - -def load_json(morphfile): - - with open(morphfile, 'r') as f: - secdata = json.load(morphfile) - - seclist = [] - for sd in secdata: - # make section - sec = h.Section(name=sd['name']) - seclist.append(sec) - - - # make 3d morphology - for x,y,z,d in zip(sd['x'], sd['y'], sd['z'], sd('diam')): - h.pt3dadd(x, y, z, d, sec=sec) - - # connect children to parent compartments - for sec,sd in zip(seclist,secdata): - if sd['parent_loc'] >= 0: - parent_sec = sec_list[sd['parent']] - sec.connect(parent_sec(sd['parent_loc']), sd['section_orientation']) - - return seclist diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0d9ff76cc..000000000 --- a/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -MPI -Matplotlib -MNE-Python -NEURON compiled with MPI, Python support - PYTHONPATH must point to Python 3 -Numpy -PyOpenGL -Python 3 -PyQt5 -pyqtgraph -Scipy - From 75cdfd1800cc0b6f2e59c5492e1b240b3102d570 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 21:49:18 -0400 Subject: [PATCH 027/107] MAINT: move evoked Qt dialog code to qt_evoked.py --- DataViewGUI.py | 24 +- qt_evoked.py | 1281 +++++++++++++++++++++++++++++++++++++ hnn_qtlib.py => qt_lib.py | 128 +++- simdat.py | 2 +- visdipole.py | 9 +- vispsd.py | 7 +- visrast.py | 22 +- visspec.py | 7 +- visvolt.py | 32 +- 9 files changed, 1450 insertions(+), 62 deletions(-) create mode 100644 qt_evoked.py rename hnn_qtlib.py => qt_lib.py (77%) diff --git a/DataViewGUI.py b/DataViewGUI.py index 8611afae6..6887e41d8 100644 --- a/DataViewGUI.py +++ b/DataViewGUI.py @@ -7,18 +7,20 @@ from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, pyqtSlot from PyQt5 import QtCore from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar -from gutils import getmplDPI +from qt_lib import getmplDPI import matplotlib.pyplot as plt -from conf import dconf +from paramrw import get_output_dir -if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] -else: plt.rcParams['font.size'] = dconf['fontsize'] = 10 +fontsize = plt.rcParams['font.size'] = 10 # GUI for viewing data from individual/all trials class DataViewGUI (QMainWindow): def __init__ (self, CanvasType, params, title): - super().__init__() - self.fontsize = dconf['fontsize'] + super().__init__() + + global fontsize + + self.fontsize = fontsize self.linewidth = plt.rcParams['lines.linewidth'] = 1 self.markersize = plt.rcParams['lines.markersize'] = 5 self.CanvasType = CanvasType @@ -53,9 +55,11 @@ def initMenu (self): viewMenu.addAction(changeMarkerSizeAction) def changeFontSize (self): + global fontsize + i, okPressed = QInputDialog.getInt(self, "Set Font Size","Font Size:", plt.rcParams['font.size'], 1, 100, 1) if okPressed: - self.fontsize = plt.rcParams['font.size'] = dconf['fontsize'] = i + self.fontsize = plt.rcParams['font.size'] = fontsize = i self.initCanvas() self.m.plot() @@ -90,8 +94,8 @@ def initCanvas (self): # this is the Navigation widget # it takes the Canvas widget and a parent self.toolbar = NavigationToolbar(self.m, self) - self.grid.addWidget(self.toolbar, 0, 0, 1, 4); - self.grid.addWidget(self.m, 1, 0, 1, 4); + self.grid.addWidget(self.toolbar, 0, 0, 1, 4) + self.grid.addWidget(self.m, 1, 0, 1, 4) def updateCB (self): self.cb.clear() @@ -107,7 +111,7 @@ def initUI (self): self.initMenu() self.statusBar() self.setGeometry(300, 300, 1300, 1100) - self.setWindowTitle(self.title + ' - ' + os.path.join(dconf['datdir'], self.params['sim_prefix'] + '.param')) + self.setWindowTitle(self.title + ' - ' + os.path.join(get_output_dir(), 'data', self.params['sim_prefix'] + '.param')) self.grid = grid = QGridLayout() self.index = 0 self.initCanvas() diff --git a/qt_evoked.py b/qt_evoked.py new file mode 100644 index 000000000..bb4bf5a78 --- /dev/null +++ b/qt_evoked.py @@ -0,0 +1,1281 @@ +"""Class for creating the optimization configuration window""" + +# Authors: Sam Neymotin +# Blake Caldwell + +import os +from math import isclose +from copy import deepcopy + +from PyQt5.QtWidgets import QPushButton, QTabWidget, QWidget, QDialog +from PyQt5.QtWidgets import QGridLayout, QLabel, QFrame, QSpacerItem +from PyQt5.QtWidgets import QCheckBox, QSizePolicy, QLineEdit +from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QFormLayout +from PyQt5.QtGui import QPixmap +from PyQt5.QtCore import Qt + +from qt_lib import QRangeSlider, MyLineEdit, ClickLabel, setscalegeom +from qt_lib import lookupresource +from paramrw import chunk_evinputs, get_inputs, trans_input, countEvokedInputs + +decay_multiplier = 1.6 + + +def format_range_str(value): + if value == 0: + value_str = "0.000" + elif value < 0.1: + value_str = ("%6f" % value) + else: + value_str = ("%.3f" % value) + + return value_str + + +def get_prox_dict(nprox): + # evprox feed strength + + dprox = { + 't_evprox_' + str(nprox): 0., + 'sigma_t_evprox_' + str(nprox): 2.5, + 'numspikes_evprox_' + str(nprox): 1, + 'gbar_evprox_' + str(nprox) + '_L2Pyr_ampa': 0., + 'gbar_evprox_' + str(nprox) + '_L2Pyr_nmda': 0., + 'gbar_evprox_' + str(nprox) + '_L2Basket_ampa': 0., + 'gbar_evprox_' + str(nprox) + '_L2Basket_nmda': 0., + 'gbar_evprox_' + str(nprox) + '_L5Pyr_ampa': 0., + 'gbar_evprox_' + str(nprox) + '_L5Pyr_nmda': 0., + 'gbar_evprox_' + str(nprox) + '_L5Basket_ampa': 0., + 'gbar_evprox_' + str(nprox) + '_L5Basket_nmda': 0. + } + return dprox + + +def get_dist_dict(ndist): + # evdist feed strength + + ddist = { + 't_evdist_' + str(ndist): 0., + 'sigma_t_evdist_' + str(ndist): 6., + 'numspikes_evdist_' + str(ndist): 1, + 'gbar_evdist_' + str(ndist) + '_L2Pyr_ampa': 0., + 'gbar_evdist_' + str(ndist) + '_L2Pyr_nmda': 0., + 'gbar_evdist_' + str(ndist) + '_L2Basket_ampa': 0., + 'gbar_evdist_' + str(ndist) + '_L2Basket_nmda': 0., + 'gbar_evdist_' + str(ndist) + '_L5Pyr_ampa': 0., + 'gbar_evdist_' + str(ndist) + '_L5Pyr_nmda': 0., + } + return ddist + + +class EvokedInputBaseDialog(QDialog): + def __init__(self): + super(EvokedInputBaseDialog, self).__init__() + + self.nprox = self.ndist = 0 # number of proximal,distal inputs + self.ld = [] # list of dictionaries for proximal/distal inputs + self.dqline = {} + # for translating model variable name to more human-readable form + self.dtransvar = {} + + def transvar(self, k): + if k in self.dtransvar: + return self.dtransvar[k] + return k + + def addtransvarfromdict(self, d): + dtmp = {'L2': 'L2/3 ', 'L5': 'L5 '} + for k in d.keys(): + if k.startswith('gbar'): + ks = k.split('_') + stmp = ks[-2] + self.addtransvar(k, dtmp[stmp[0:2]] + stmp[2:] + ' ' + + ks[-1].upper() + u' weight (µS)') + elif k.startswith('t'): + self.addtransvar(k, 'Start time mean (ms)') + elif k.startswith('sigma'): + self.addtransvar(k, 'Start time stdev (ms)') + elif k.startswith('numspikes'): + self.addtransvar(k, 'Number spikes') + + def addtransvar(self, k, strans): + self.dtransvar[k] = strans + self.dtransvar[strans] = k + + def IsProx(self, idx): + d = self.ld[idx] + for k in d.keys(): + if k.count('evprox'): + return True + + return False + + def getInputID(self, idx): + """get evoked input number associated with idx""" + d = self.ld[idx] + for k in d.keys(): + lk = k.split('_') + if len(lk) >= 3: + return int(lk[2]) + + def downShift(self, idx): + """downshift the evoked input ID, keys, values""" + d = self.ld[idx] + dnew = {} # new dictionary + newidx = 0 # new evoked input ID + for k, v in d.items(): + lk = k.split('_') + if len(lk) >= 3: + if lk[0] == 'sigma': + newidx = int(lk[3]) - 1 + lk[3] = str(newidx) + else: + newidx = int(lk[2]) - 1 + lk[2] = str(newidx) + newkey = '_'.join(lk) + dnew[newkey] = v + if k in self.dqline: + self.dqline[newkey] = self.dqline[k] + del self.dqline[k] + self.ld[idx] = dnew + currtxt = self.tabs.tabText(idx) + newtxt = currtxt.split(' ')[0] + ' ' + str(newidx) + self.tabs.setTabText(idx, newtxt) + + def removeInput(self, idx): + # remove the evoked input specified by idx + if idx < 0 or idx > len(self.ltabs): + return + self.tabs.removeTab(idx) + tab = self.ltabs[idx] + self.ltabs.remove(tab) + d = self.ld[idx] + + isprox = self.IsProx(idx) # is it a proximal input? + isdist = not isprox # is it a distal input? + + # what's the proximal/distal input number? + inputID = self.getInputID(idx) + + for k in d.keys(): + if k in self.dqline: + del self.dqline[k] + self.ld.remove(d) + tab.setParent(None) + + # now downshift the evoked inputs (only proximal or only distal) that + # came after this one. + # first get the IDs of the evoked inputs to downshift + lds = [] # list of inputs to downshift + for jdx in range(len(self.ltabs)): + if isprox and self.IsProx(jdx) and self.getInputID(jdx) > inputID: + lds.append(jdx) + elif isdist and not self.IsProx(jdx): + if self.getInputID(jdx) > inputID: + lds.append(jdx) + for jdx in lds: + self.downShift(jdx) # then do the downshifting + + def removeCurrentInput(self): + """ removes currently selected input""" + idx = self.tabs.currentIndex() + if idx < 0: + return + self.removeInput(idx) + + def removeAllInputs(self): + for _ in range(len(self.ltabs)): + self.removeCurrentInput() + self.nprox = self.ndist = 0 + + +class EvokedInputParamDialog (EvokedInputBaseDialog): + """ Evoked Input Dialog + allows adding/removing arbitrary number of evoked inputs""" + + def __init__ (self, parent, din): + super(EvokedInputParamDialog, self).__init__() + self.initUI() + self.setfromdin(din) + + # TODO: add back tooltips + # def addtips (self): + # for ktip in dconf.keys(): + # if ktip in self.dqline: + # self.dqline[ktip].setToolTip(dconf[ktip]) + + def transvar (self,k): + if k in self.dtransvar: return self.dtransvar[k] + return k + + def set_qline_float (self, key_str, value): + try: + new_value = float(value) + except ValueError: + print("WARN: bad value for param %s: %s. Unable to convert" + " to a floating point number" % (key_str, value)) + return + + # Enforce no sci. not. + limit field len + remove trailing 0's + self.dqline[key_str].setText(("%7f" % new_value).rstrip('0').rstrip('.')) + + def setfromdin (self,din): + if not din: return + + if 'dt' in din: + + # Optimization feature introduces the case where din just contains optimization + # relevant parameters. In that case, we don't want to remove all inputs, just + # modify existing inputs. + self.removeAllInputs() # turn off any previously set inputs + + nprox, ndist = countEvokedInputs(din) + for i in range(nprox+ndist): + if i % 2 == 0: + if self.nprox < nprox: + self.addProx() + elif self.ndist < ndist: + self.addDist() + else: + if self.ndist < ndist: + self.addDist() + elif self.nprox < nprox: + self.addProx() + + for k,v in din.items(): + if k == 'sync_evinput': + try: + new_value = bool(int(v)) + except ValueError: + print("WARN: bad value for param %s: %s. Unable to convert" + " to a boolean value" % (k,v)) + continue + if new_value: + self.chksync.setChecked(True) + else: + self.chksync.setChecked(False) + elif k == 'inc_evinput': + try: + new_value = float(v) + except ValueError: + print("WARN: bad value for param %s: %s. Unable to convert" + " to a floating point number" % (k,v)) + continue + self.incedit.setText(str(new_value).strip()) + elif k in self.dqline: + if k.startswith('numspikes'): + try: + new_value = int(v) + except ValueError: + print("WARN: bad value for param %s: %s. Unable to convert" + " to a integer" % (k, v)) + continue + self.dqline[k].setText(str(new_value)) + else: + self.set_qline_float(k, v) + elif k.count('gbar') > 0 and \ + (k.count('evprox') > 0 or \ + k.count('evdist') > 0): + # NOTE: will be deprecated in future release + # for back-compat with old-style specification which didn't have ampa,nmda in evoked gbar + lks = k.split('_') + eloc = lks[1] + enum = lks[2] + base_key_str = 'gbar_' + eloc + '_' + enum + '_' + if eloc == 'evprox': + for ct in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: + # ORIGINAL MODEL/PARAM: only ampa for prox evoked inputs + key_str = base_key_str + ct + '_ampa' + self.set_qline_float(key_str, v) + elif eloc == 'evdist': + for ct in ['L2Pyr', 'L2Basket', 'L5Pyr']: + # ORIGINAL MODEL/PARAM: both ampa and nmda for distal evoked inputs + key_str = base_key_str + ct + '_ampa' + self.set_qline_float(key_str, v) + key_str = base_key_str + ct + '_nmda' + self.set_qline_float(key_str, v) + + def initUI (self): + self.layout = QVBoxLayout(self) + + # Add stretch to separate the form layout from the button + self.layout.addStretch(1) + + self.ltabs = [] + self.tabs = QTabWidget() + self.layout.addWidget(self.tabs) + + self.button_box = QVBoxLayout() + self.btnprox = QPushButton('Add Proximal Input',self) + self.btnprox.resize(self.btnprox.sizeHint()) + self.btnprox.clicked.connect(self.addProx) + self.btnprox.setToolTip('Add Proximal Input') + self.button_box.addWidget(self.btnprox) + + self.btndist = QPushButton('Add Distal Input',self) + self.btndist.resize(self.btndist.sizeHint()) + self.btndist.clicked.connect(self.addDist) + self.btndist.setToolTip('Add Distal Input') + self.button_box.addWidget(self.btndist) + + self.chksync = QCheckBox('Synchronous Inputs',self) + self.chksync.resize(self.chksync.sizeHint()) + self.chksync.setChecked(True) + self.button_box.addWidget(self.chksync) + + self.incbox = QHBoxLayout() + self.inclabel = QLabel(self) + self.inclabel.setText('Increment start time (ms)') + self.inclabel.adjustSize() + self.inclabel.setToolTip('Increment mean evoked input start time(s) by this amount on each trial.') + self.incedit = QLineEdit(self) + self.incedit.setText('0.0') + self.incbox.addWidget(self.inclabel) + self.incbox.addWidget(self.incedit) + + self.layout.addLayout(self.button_box) + self.layout.addLayout(self.incbox) + + self.tabs.resize(425,200) + + # Add tabs to widget + self.layout.addWidget(self.tabs) + self.setLayout(self.layout) + + self.setWindowTitle('Evoked Inputs') + + self.addRemoveInputButton() + self.addHideButton() + # self.addtips() + + def lines2val (self,ksearch,val): + for k in self.dqline.keys(): + if k.count(ksearch) > 0: + self.dqline[k].setText(str(val)) + + def allOff (self): self.lines2val('gbar',0.0) + + def __str__ (self): + s = '' + for k,v in self.dqline.items(): s += k + ': ' + v.text().strip() + os.linesep + if self.chksync.isChecked(): s += 'sync_evinput: 1' + os.linesep + else: s += 'sync_evinput: 0' + os.linesep + s += 'inc_evinput: ' + self.incedit.text().strip() + os.linesep + return s + + def addRemoveInputButton (self): + self.bbremovebox = QHBoxLayout() + self.btnremove = QPushButton('Remove Input',self) + self.btnremove.resize(self.btnremove.sizeHint()) + self.btnremove.clicked.connect(self.removeCurrentInput) + self.btnremove.setToolTip('Remove This Input') + self.bbremovebox.addWidget(self.btnremove) + self.layout.addLayout(self.bbremovebox) + + def addHideButton (self): + self.bbhidebox = QHBoxLayout() + self.btnhide = QPushButton('Hide Window',self) + self.btnhide.resize(self.btnhide.sizeHint()) + self.btnhide.clicked.connect(self.hide) + self.btnhide.setToolTip('Hide Window') + self.bbhidebox.addWidget(self.btnhide) + self.layout.addLayout(self.bbhidebox) + + def addTab (self,s): + tab = QWidget() + self.ltabs.append(tab) + self.tabs.addTab(tab,s) + tab.layout = QFormLayout() + tab.setLayout(tab.layout) + return tab + + def addFormToTab (self,d,tab): + for k,v in d.items(): + self.dqline[k] = QLineEdit(self) + self.dqline[k].setText(str(v)) + tab.layout.addRow(self.transvar(k),self.dqline[k]) # adds label,QLineEdit to the tab + + def makePixLabel (self,fn): + pix = QPixmap(fn) + pixlbl = ClickLabel(self) + pixlbl.setPixmap(pix) + return pixlbl + + def addProx (self): + self.nprox += 1 # starts at 1 + dprox = get_prox_dict(self.nprox) + self.ld.append(dprox) + self.addtransvarfromdict(dprox) + self.addFormToTab(dprox, self.addTab('Proximal ' + str(self.nprox))) + self.ltabs[-1].layout.addRow(self.makePixLabel(lookupresource('proxfig'))) + #print('index to', len(self.ltabs)-1) + self.tabs.setCurrentIndex(len(self.ltabs)-1) + #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) + # self.addtips() + + def addDist (self): + self.ndist += 1 + ddist = get_dist_dict(self.ndist) + self.ld.append(ddist) + self.addtransvarfromdict(ddist) + self.addFormToTab(ddist,self.addTab('Distal ' + str(self.ndist))) + self.ltabs[-1].layout.addRow(self.makePixLabel(lookupresource('distfig'))) + #print('index to', len(self.ltabs)-1) + self.tabs.setCurrentIndex(len(self.ltabs)-1) + #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) + # self.addtips() + + +class OptEvokedInputParamDialog (EvokedInputBaseDialog): + def __init__ (self, parent, optrun_func): + super(OptEvokedInputParamDialog, self).__init__() + self.nprox = self.ndist = 0 # number of proximal,distal inputs + self.ld = [] # list of dictionaries for proximal/distal inputs + self.dqline = {} # not used, prevents failure in removeInput + + self.dtab_idx = {} # for translating input names to tab indices + self.dtab_names = {} # for translating tab indices to input names + self.dparams = {} # actual values + + # these store values used in grid + self.dqchkbox = {} # optimize + self.dqparam_name = {} # parameter name + self.dqinitial_label = {} # initial + self.dqopt_label = {} # optimtized + self.dqdiff_label = {} # delta + self.dqrange_multiplier = {} # user-defined multiplier + self.dqrange_mode = {} # range mode (stdev, %, absolute) + self.dqrange_slider = {} # slider + self.dqrange_label = {} # defined range + self.dqrange_max = {} + self.dqrange_min = {} + + self.chunk_list = [] + self.lqnumsim = [] + self.lqnumparams = [] + self.lqinputs = [] + self.opt_params = {} + self.initial_opt_ranges = [] + self.dtabdata = [] + self.simlength = 0.0 + self.sim_dt = 0.0 + self.default_num_step_sims = 30 + self.default_num_total_sims = 50 + self.optrun_func = optrun_func + self.optimization_running = False + self.initUI() + self.parent = parent + self.old_num_steps = 0 + + def initUI (self): + # start with a reasonable size + setscalegeom(self, 150, 150, 475, 300) + + self.ltabs = [] + self.ltabkeys = [] + self.tabs = QTabWidget() + self.din = {} + + self.grid = QGridLayout() + + row = 0 + self.sublayout = QGridLayout() + self.old_numsims = [] + self.grid.addLayout(self.sublayout, row, 0) + + row += 1 + self.grid.addWidget(self.tabs, row, 0) + + row += 1 + self.btnrunop = QPushButton('Run Optimization', self) + self.btnrunop.resize(self.btnrunop.sizeHint()) + self.btnrunop.setToolTip('Run Optimization') + self.btnrunop.clicked.connect(self.runOptimization) + self.grid.addWidget(self.btnrunop, row, 0) + + row += 1 + self.btnreset = QPushButton('Reset Ranges',self) + self.btnreset.resize(self.btnreset.sizeHint()) + self.btnreset.clicked.connect(self.updateOptRanges) + self.btnreset.setToolTip('Reset Ranges') + self.grid.addWidget(self.btnreset, row, 0) + + row += 1 + btnhide = QPushButton('Hide Window',self) + btnhide.resize(btnhide.sizeHint()) + btnhide.clicked.connect(self.hide) + btnhide.setToolTip('Hide Window') + self.grid.addWidget(btnhide, row, 0) + + self.setLayout(self.grid) + + self.setWindowTitle("Configure Optimization") + + # the largest horizontal component will be column 0 (headings) + self.resize(self.minimumSizeHint()) + + def toggle_enable_param(self, label): + import re + + widget_dict_list = [self.dqinitial_label, self.dqopt_label, + self.dqdiff_label, self.dqparam_name, + self.dqrange_mode, self.dqrange_multiplier, + self.dqrange_label, self.dqrange_slider] + + if self.dqchkbox[label].isChecked(): + # set all other fields in the row to enabled + for widget_dict in widget_dict_list: + widget_dict[label].setEnabled(True) + toEnable = True + else: + # disable all other fields in the row + for widget_dict in widget_dict_list: + widget_dict[label].setEnabled(False) + toEnable = False + + self.changeParamEnabledStatus(label, toEnable) + + def addTab (self,id_str): + tab = QWidget() + self.ltabs.append(tab) + + name_str = trans_input(id_str) + self.tabs.addTab(tab, name_str) + + tab_index = len(self.ltabs)-1 + self.dtab_idx[id_str] = tab_index + self.dtab_names[tab_index] = id_str + + return tab + + def cleanLabels(self): + """ + To avoid memory leaks we need to delete all widgets when we recreate grid. + Go through all tabs and check for each var name (k) + """ + for idx in range(len(self.ltabs)): + for k in self.ld[idx].keys(): + if k in self.dqinitial_label: + del self.dqinitial_label[k] + if k in self.dqopt_label: + del self.dqopt_label[k] + if k in self.dqdiff_label: + del self.dqdiff_label[k] + if k in self.dqparam_name: + del self.dqparam_name[k] + if not self.optimization_running: + if k in self.dqrange_mode: + del self.dqrange_mode[k] + if k in self.dqrange_multiplier: + del self.dqrange_multiplier[k] + if k in self.dqrange_label: + del self.dqrange_label[k] + if k in self.dqrange_slider: + del self.dqrange_slider[k] + if k in self.dqrange_min: + del self.dqrange_min[k] + if k in self.dqrange_max: + del self.dqrange_max[k] + + def addGridToTab (self, d, tab): + from functools import partial + import re + + current_tab = len(self.ltabs)-1 + tab.layout = QGridLayout() + #tab.layout.setSpacing(10) + + self.ltabkeys.append([]) + + # The first row has column headings + row = 0 + self.ltabkeys[current_tab].append("") + for column_index, column_name in enumerate(["Optimize", "Parameter name", + "Initial", "Optimized", "Delta"]): + widget = QLabel(column_name) + widget.resize(widget.sizeHint()) + tab.layout.addWidget(widget, row, column_index) + + column_index += 1 + widget = QLabel("Range specifier") + widget.setMinimumWidth(100) + tab.layout.addWidget(widget, row, column_index, 1, 2) + + column_index += 2 + widget = QLabel("Range slider") + # widget.setMinimumWidth(160) + tab.layout.addWidget(widget, row, column_index) + + column_index += 1 + widget = QLabel("Defined range") + tab.layout.addWidget(widget, row, column_index) + + # The second row is a horizontal line + row = 1 + self.ltabkeys[current_tab].append("") + qthline = QFrame() + qthline.setFrameShape(QFrame.HLine) + qthline.setFrameShadow(QFrame.Sunken) + tab.layout.addWidget(qthline, row, 0, 1, 9) + + # The rest are the parameters + row = 2 + for k,v in d.items(): + self.ltabkeys[current_tab].append(k) + + # create and format widgets + self.dparams[k] = float(v) + self.dqchkbox[k] = QCheckBox() + self.dqchkbox[k].setStyleSheet(""" + .QCheckBox { + spacing: 20px; + } + .QCheckBox::unchecked { + color: grey; + } + .QCheckBox::checked { + color: black; + } + """) + self.dqchkbox[k].setChecked(True) + # use partial instead of lamda (so args won't be evaluated ahead of time?) + self.dqchkbox[k].clicked.connect(partial(self.toggle_enable_param, k)) + self.dqparam_name[k] = QLabel(self) + self.dqparam_name[k].setText(self.transvar(k)) + self.dqinitial_label[k] = QLabel() + self.dqopt_label[k] = QLabel() + self.dqdiff_label[k] = QLabel() + + # add widgets to grid + tab.layout.addWidget(self.dqchkbox[k], row, 0, alignment = Qt.AlignBaseline | Qt.AlignCenter) + tab.layout.addWidget(self.dqparam_name[k], row, 1) + tab.layout.addWidget(self.dqinitial_label[k], row, 2) # initial value + tab.layout.addWidget(self.dqopt_label[k], row, 3) # optimized value + tab.layout.addWidget(self.dqdiff_label[k], row, 4) # delta + + if k.startswith('t'): + range_mode = "(stdev)" + range_multiplier = "3.0" + elif k.startswith('sigma'): + range_mode = "(%)" + range_multiplier = "50.0" + else: + range_mode = "(%)" + range_multiplier = "500.0" + + if not self.optimization_running: + self.dqrange_slider[k] = QRangeSlider(k,self) + self.dqrange_slider[k].setMinimumWidth(140) + self.dqrange_label[k] = QLabel() + self.dqrange_multiplier[k] = MyLineEdit(range_multiplier, k) + self.dqrange_multiplier[k].textModified.connect(self.updateRange) + self.dqrange_multiplier[k].setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred) + self.dqrange_multiplier[k].setMinimumWidth(50) + self.dqrange_multiplier[k].setMaximumWidth(50) + self.dqrange_mode[k] = QLabel(range_mode) + tab.layout.addWidget(self.dqrange_multiplier[k], row, 5) # range specifier + tab.layout.addWidget(self.dqrange_mode[k], row, 6) # range mode + tab.layout.addWidget(self.dqrange_slider[k], row, 7) # range slider + tab.layout.addWidget(self.dqrange_label[k], row, 8) # calculated range + + row += 1 + + # A spacer in the last row stretches to fill remaining space. + # For inputs with fewer parameters than the rest, this pushes parameters + # to the top with the same spacing as the other inputs. + tab.layout.addItem(QSpacerItem(0, 0), row, 0, 1, 9) + tab.layout.setRowStretch(row,1) + tab.setLayout(tab.layout) + + + def addProx (self): + self.nprox += 1 + dprox = get_prox_dict(self.nprox) + self.ld.append(dprox) + self.addtransvarfromdict(dprox) + tab = self.addTab('evprox_' + str(self.nprox)) + self.addGridToTab(dprox, tab) + + def addDist (self): + self.ndist += 1 + ddist = get_dist_dict(self.ndist) + self.ld.append(ddist) + self.addtransvarfromdict(ddist) + tab = self.addTab('evdist_' + str(self.ndist)) + self.addGridToTab(ddist, tab) + + def changeParamEnabledStatus(self, label, toEnable): + import re + + label_match = re.search('(evprox|evdist)_([0-9]+)', label) + if label_match: + my_input_name = label_match.group(1) + '_' + label_match.group(2) + else: + print("ERR: can't determine input name from parameter: %s" % label) + return + + # decrease the count of num params + for chunk_index in range(self.old_num_steps): + for input_name in self.chunk_list[chunk_index]['inputs']: + if input_name == my_input_name: + try: + num_params = int(self.lqnumparams[chunk_index].text()) + except ValueError: + print("ERR: could not get number of params for step %d"%chunk_index) + + if toEnable: + num_params += 1 + else: + num_params -= 1 + self.lqnumparams[chunk_index].setText(str(num_params)) + self.opt_params[input_name]['ranges'][label]['enabled'] = toEnable + + def updateRange(self, label, save_slider=True): + import re + + max_width = 0 + + label_match = re.search('(evprox|evdist)_([0-9]+)', label) + if label_match: + tab_name = label_match.group(1) + '_' + label_match.group(2) + else: + print("ERR: can't determine input name from parameter: %s" % label) + return + + if self.dqchkbox[label].isChecked(): + self.opt_params[tab_name]['ranges'][label]['enabled'] = True + else: + self.opt_params[tab_name]['ranges'][label]['enabled'] = False + return + + if tab_name not in self.initial_opt_ranges or \ + label not in self.initial_opt_ranges[tab_name]: + value = self.dparams[label] + else: + value = float(self.initial_opt_ranges[tab_name][label]['initial']) + + range_type = self.dqrange_mode[label].text() + if range_type == "(%)" and value == 0.0: + # change to range from 0 to 1 + range_type = "(max)" + self.dqrange_mode[label].setText(range_type) + self.dqrange_multiplier[label].setText("1.0") + elif range_type == "(max)" and value > 0.0: + # change back to % + range_type = "(%)" + self.dqrange_mode[label].setText(range_type) + self.dqrange_multiplier[label].setText("500.0") + + try: + range_multiplier = float(self.dqrange_multiplier[label].text()) + except ValueError: + range_multiplier = 0.0 + self.dqrange_multiplier[label].setText(str(range_multiplier)) + + if range_type == "(max)": + range_min = 0 + try: + range_max = float(self.dqrange_multiplier[label].text()) + except ValueError: + range_max = 1.0 + elif range_type == "(stdev)": # timing + timing_sigma = self.get_input_timing_sigma(tab_name) + timing_bound = timing_sigma * range_multiplier + range_min = max(0, value - timing_bound) + range_max = min(self.simlength, value + timing_bound) + else: # range_type == "(%)" + range_min = max(0, value - (value * range_multiplier / 100.0)) + range_max = value + (value * range_multiplier / 100.0) + + # set up the slider + self.dqrange_slider[label].setLine(value) + self.dqrange_slider[label].setMin(range_min) + self.dqrange_slider[label].setMax(range_max) + + if not save_slider: + self.dqrange_min.pop(label, None) + self.dqrange_max.pop(label, None) + + self.opt_params[tab_name]['ranges'][label]['initial'] = value + if label in self.dqrange_min and label in self.dqrange_max: + range_min = self.dqrange_min[label] + range_max = self.dqrange_max[label] + + self.opt_params[tab_name]['ranges'][label]['minval'] = range_min + self.opt_params[tab_name]['ranges'][label]['maxval'] = range_max + self.dqrange_slider[label].setRange(range_min, range_max) + + if range_min == range_max: + self.dqrange_label[label].setText(format_range_str(range_min)) # use the exact value + self.dqrange_label[label].setEnabled(False) + # uncheck because invalid range + self.dqchkbox[label].setChecked(False) + # disable slider + self.dqrange_slider[label].setEnabled(False) + self.changeParamEnabledStatus(label, False) + else: + self.dqrange_label[label].setText(format_range_str(range_min) + + " - " + + format_range_str(range_max)) + + if self.dqrange_label[label].sizeHint().width() > max_width: + max_width = self.dqrange_label[label].sizeHint().width() + 15 + # fix the size for the defined range so that changing the slider doesn't change + # the dialog's width + self.dqrange_label[label].setMinimumWidth(max_width) + self.dqrange_label[label].setMaximumWidth(max_width) + + def prepareOptimization(self): + self.createOptParams() + self.rebuildOptStepInfo() + self.updateOptDeltas() + self.updateOptRanges(save_sliders=True) + self.btnreset.setEnabled(True) + self.btnrunop.setText('Run Optimization') + self.btnrunop.clicked.disconnect() + self.btnrunop.clicked.connect(self.runOptimization) + + def runOptimization(self): + self.current_opt_step = 0 + + # update the ranges to find which parameters have been disabled (unchecked) + self.updateOptRanges(save_sliders=True) + + # update the opt info dict to capture num_sims from GUI + self.rebuildOptStepInfo() + self.optimization_running = True + + # run the actual optimization. optrun_func comes from HNNGUI.startoptmodel(): + # passed to BaseParamDialog then finally OptEvokedInputParamDialog + self.optrun_func() + + def get_chunk_start(self, step): + return self.chunk_list[step]['opt_start'] + + def get_chunk_end(self, step): + return self.chunk_list[step]['opt_end'] + + def get_chunk_weights(self, step): + return self.chunk_list[step]['weights'] + + def get_num_chunks(self): + return len(self.chunk_list) + + def get_sims_for_chunk(self, step): + try: + num_sims = int(self.lqnumsim[step].text()) + except KeyError: + print("ERR: number of sims not found for step %d"%step) + num_sims = 0 + except ValueError: + if step == self.old_num_steps - 1: + num_sims = self.default_num_total_sims + else: + num_sims = self.default_num_step_sims + + return num_sims + + def get_chunk_ranges(self, step): + ranges = {} + for input_name in self.chunk_list[step]['inputs']: + # make sure initial value is between minval or maxval before returning + # ranges to the optimization + for label in self.opt_params[input_name]['ranges'].keys(): + if not self.opt_params[input_name]['ranges'][label]['enabled']: + continue + range_min = self.opt_params[input_name]['ranges'][label]['minval'] + range_max = self.opt_params[input_name]['ranges'][label]['maxval'] + if range_min > self.opt_params[input_name]['ranges'][label]['initial']: + self.opt_params[input_name]['ranges'][label]['initial'] = range_min + if range_max < self.opt_params[input_name]['ranges'][label]['initial']: + self.opt_params[input_name]['ranges'][label]['initial'] = range_max + + # copy the values to the ranges dict to be returned + # to optimization + ranges[label] = self.opt_params[input_name]['ranges'][label].copy() + + return ranges + + def get_num_params(self, step): + num_params = 0 + + for input_name in self.chunk_list[step]['inputs']: + for label in self.opt_params[input_name]['ranges'].keys(): + if not self.opt_params[input_name]['ranges'][label]['enabled']: + continue + else: + num_params += 1 + + return num_params + + def push_chunk_ranges(self, step, ranges): + import re + + for label, value in ranges.items(): + for tab_name in self.opt_params.keys(): + if label in self.opt_params[tab_name]['ranges']: + self.opt_params[tab_name]['ranges'][label]['initial'] = float(value) + + def clean_opt_grid(self): + # This is the top part of the Configure Optimization dialog. + + column_count = self.sublayout.columnCount() + row = 0 + while True: + try: + self.sublayout.itemAtPosition(row,0).widget() + except AttributeError: + # no more rows + break + + for column in range(column_count): + try: + # Use deleteLater() to avoid memory leaks. + self.sublayout.itemAtPosition(row, column).widget().deleteLater() + except AttributeError: + # if item wasn't found + pass + row += 1 + + # reset data for number of sims per chunk (step) + self.lqnumsim = [] + self.lqnumparams = [] + self.lqinputs = [] + self.old_num_steps = 0 + + def rebuildOptStepInfo(self): + # split chunks from paramter file + self.chunk_list = chunk_evinputs(self.opt_params, self.simlength, self.sim_dt) + + if len(self.chunk_list) == 0: + self.clean_opt_grid() + + qlabel = QLabel("No valid evoked inputs to optimize!") + qlabel.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) + qlabel.resize(qlabel.minimumSizeHint()) + self.sublayout.addWidget(qlabel, 0, 0) + self.btnrunop.setEnabled(False) + self.btnreset.setEnabled(False) + else: + self.btnrunop.setEnabled(True) + self.btnreset.setEnabled(True) + + if len(self.chunk_list) < self.old_num_steps or \ + self.old_num_steps == 0: + # clean up the old grid sublayout + self.clean_opt_grid() + + # keep track of inputs to optimize over (check against self.opt_params later) + all_inputs = [] + + # create a new grid sublayout with a row for each optimization step + for chunk_index, chunk in enumerate(self.chunk_list): + chunk['num_params'] = self.get_num_params(chunk_index) + + inputs = [] + for input_name in chunk['inputs']: + all_inputs.append(input_name) + inputs.append(trans_input(input_name)) + + if chunk_index >= self.old_num_steps: + qlabel = QLabel("Optimization step %d:"%(chunk_index+1)) + qlabel.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) + qlabel.resize(qlabel.minimumSizeHint()) + self.sublayout.addWidget(qlabel,chunk_index, 0) + + self.lqinputs.append(QLabel("Inputs: %s"%', '.join(inputs))) + self.lqinputs[chunk_index].setAlignment(Qt.AlignBaseline | Qt.AlignLeft) + self.lqinputs[chunk_index].resize(self.lqinputs[chunk_index].minimumSizeHint()) + self.sublayout.addWidget(self.lqinputs[chunk_index], chunk_index, 1) + + # spacer here for readability of input names and reduce size + # of "Num simulations:" + self.sublayout.addItem(QSpacerItem(0, 0, hPolicy = QSizePolicy.MinimumExpanding), chunk_index, 2) + + qlabel_params = QLabel("Num params:") + qlabel_params.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) + qlabel_params.resize(qlabel_params.minimumSizeHint()) + self.sublayout.addWidget(qlabel_params,chunk_index, 3) + + self.lqnumparams.append(QLabel(str(chunk['num_params']))) + self.lqnumparams[chunk_index].setAlignment(Qt.AlignBaseline | Qt.AlignLeft) + self.lqnumparams[chunk_index].resize(self.lqnumparams[chunk_index].minimumSizeHint()) + self.sublayout.addWidget(self.lqnumparams[chunk_index],chunk_index, 4) + + qlabel_sims = QLabel("Num simulations:") + qlabel_sims.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) + qlabel_sims.resize(qlabel_sims.minimumSizeHint()) + self.sublayout.addWidget(qlabel_sims,chunk_index, 5) + + if chunk_index == len(self.chunk_list) - 1: + chunk['num_sims'] = self.default_num_total_sims + else: + chunk['num_sims'] = self.default_num_step_sims + self.lqnumsim.append(QLineEdit(str(chunk['num_sims']))) + self.lqnumsim[chunk_index].resize( + self.lqnumsim[chunk_index].minimumSizeHint()) + self.sublayout.addWidget(self.lqnumsim[chunk_index], + chunk_index, 6) + else: + self.lqinputs[chunk_index].setText("Inputs: %s"%', '.join(inputs)) + self.lqnumparams[chunk_index].setText(str(chunk['num_params'])) + + self.old_num_steps = len(self.chunk_list) + + remove_list = [] + # remove a tab if necessary + for input_name in self.opt_params.keys(): + if input_name not in all_inputs and input_name in self.dtab_idx: + remove_list.append(input_name) + + while len(remove_list) > 0: + tab_name = remove_list.pop() + tab_index = self.dtab_idx[tab_name] + + self.removeInput(tab_index) + del self.dtab_idx[tab_name] + del self.dtab_names[tab_index] + self.ltabkeys.pop(tab_index) + + # rebuild dtab_idx and dtab_names + temp_dtab_names = {} + temp_dtab_idx = {} + for new_tab_index, old_tab_index in enumerate(self.dtab_idx.values()): + # self.dtab_idx[id_str] = tab_index + id_str = self.dtab_names[old_tab_index] + temp_dtab_names[new_tab_index] = id_str + temp_dtab_idx[id_str] = new_tab_index + self.dtab_names = temp_dtab_names + self.dtab_idx = temp_dtab_idx + + def toggleEnableUserFields(self, step, enable=True): + if not enable: + # the optimization called this to disable parameters on + # for the step passed in to this function + self.current_opt_step = step + + for input_name in self.chunk_list[step]['inputs']: + tab_index = self.dtab_idx[input_name] + tab = self.ltabs[tab_index] + + for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer + label = self.ltabkeys[tab_index][row_index] + self.dqchkbox[label].setEnabled(enable) + self.dqrange_slider[label].setEnabled(enable) + self.dqrange_multiplier[label].setEnabled(enable) + + def get_input_timing_sigma(self, tab_name): + """ get timing_sigma from already loaded values """ + + label = 'sigma_t_' + tab_name + try: + timing_sigma = self.dparams[label] + except KeyError: + timing_sigma = 3.0 + print("ERR: Couldn't fing %s. Using default %f" % + (label,timing_sigma)) + + if timing_sigma == 0.0: + # sigma of 0 will not produce a CDF + timing_sigma = 0.01 + + return timing_sigma + + def createOptParams(self): + global decay_multiplier + + self.opt_params = {} + + # iterate through tabs. data is contained in grid layout + for tab_index, tab in enumerate(self.ltabs): + tab_name = self.dtab_names[tab_index] + + # before optimization has started update 'mean', 'sigma', + # 'start', and 'user_end' + start_time_label = 't_' + tab_name + try: + try: + range_multiplier = float(self.dqrange_multiplier[start_time_label].text()) + except ValueError: + range_multiplier = 0.0 + value = self.dparams[start_time_label] + except KeyError: + print("ERR: could not find start time parameter: %s" % start_time_label) + continue + + timing_sigma = self.get_input_timing_sigma(tab_name) + self.opt_params[tab_name] = {'ranges': {}, + 'mean' : value, + 'sigma': timing_sigma, + 'decay_multiplier': decay_multiplier} + + timing_bound = timing_sigma * range_multiplier + self.opt_params[tab_name]['user_start'] = max(0, value - timing_bound) + self.opt_params[tab_name]['user_end'] = min(self.simlength, value + timing_bound) + + # add an empty dictionary so that rebuildOptStepInfo() can determine + # how many parameters + for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer + label = self.ltabkeys[tab_index][row_index] + self.opt_params[tab_name]['ranges'][label] = {'enabled': True} + + def clear_initial_opt_ranges(self): + self.initial_opt_ranges = {} + + def populate_initial_opt_ranges(self): + self.initial_opt_ranges = {} + + for input_name in self.opt_params.keys(): + self.initial_opt_ranges[input_name] = deepcopy(self.opt_params[input_name]['ranges']) + + def updateOptDeltas(self): + # iterate through tabs. data is contained in grid layout + for tab_index, tab in enumerate(self.ltabs): + tab_name = self.dtab_names[tab_index] + + # update the initial value + for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer + label = self.ltabkeys[tab_index][row_index] + value = self.dparams[label] + + # Calculate value to put in "Delta" column. When possible, use + # percentages, but when initial value is 0, use absolute changes + if tab_name not in self.initial_opt_ranges or \ + not self.dqchkbox[label].isChecked(): + self.dqdiff_label[label].setEnabled(False) + self.dqinitial_label[label].setText(("%6f"%self.dparams[label]).rstrip('0').rstrip('.')) + text = '--' + color_fmt = "QLabel { color : black; }" + self.dqopt_label[label].setText(text) + self.dqopt_label[label].setStyleSheet(color_fmt) + self.dqopt_label[label].setAlignment(Qt.AlignHCenter) + self.dqdiff_label[label].setAlignment(Qt.AlignHCenter) + else: + initial_value = float(self.initial_opt_ranges[tab_name][label]['initial']) + self.dqinitial_label[label].setText(("%6f"%initial_value).rstrip('0').rstrip('.')) + self.dqopt_label[label].setText(("%6f"%self.dparams[label]).rstrip('0').rstrip('.')) + self.dqopt_label[label].setAlignment(Qt.AlignVCenter|Qt.AlignLeft) + self.dqdiff_label[label].setAlignment(Qt.AlignVCenter|Qt.AlignLeft) + + if isclose(value, initial_value, abs_tol=1e-7): + diff = 0 + text = "0.0" + color_fmt = "QLabel { color : black; }" + else: + diff = value - initial_value + + if initial_value == 0: + # can't calculate % + if diff < 0: + text = ("%6f"%diff).rstrip('0').rstrip('.') + color_fmt = "QLabel { color : red; }" + elif diff > 0: + text = ("+%6f"%diff).rstrip('0').rstrip('.') + color_fmt = "QLabel { color : green; }" + else: + # calculate percent difference + percent_diff = 100 * diff/abs(initial_value) + if percent_diff < 0: + text = ("%2.2f %%"%percent_diff) + color_fmt = "QLabel { color : red; }" + elif percent_diff > 0: + text = ("+%2.2f %%"%percent_diff) + color_fmt = "QLabel { color : green; }" + + self.dqdiff_label[label].setStyleSheet(color_fmt) + self.dqdiff_label[label].setText(text) + + def updateRangeFromSlider(self, label, range_min, range_max): + import re + + label_match = re.search('(evprox|evdist)_([0-9]+)', label) + if label_match: + tab_name = label_match.group(1) + '_' + label_match.group(2) + else: + print("ERR: can't determine input name from parameter: %s" % label) + return + + self.dqrange_min[label] = range_min + self.dqrange_max[label] = range_max + self.dqrange_label[label].setText(format_range_str(range_min) + " - " + + format_range_str(range_max)) + self.opt_params[tab_name]['ranges'][label]['minval'] = range_min + self.opt_params[tab_name]['ranges'][label]['maxval'] = range_max + + def updateOptRanges(self, save_sliders=False): + # iterate through tabs. data is contained in grid layout + for tab_index, tab in enumerate(self.ltabs): + # now update the ranges + for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer + label = self.ltabkeys[tab_index][row_index] + self.updateRange(label, save_sliders) + + def setfromdin (self,din): + if not din: + return + + if 'dt' in din: + # din proivdes a complete parameter set + self.din = din + self.simlength = float(din['tstop']) + self.sim_dt = float(din['dt']) + + self.cleanLabels() + self.removeAllInputs() # turn off any previously set inputs + self.ltabkeys = [] + self.dtab_idx = {} + self.dtab_names = {} + + for evinput in get_inputs(din): + if 'evprox_' in evinput: + self.addProx() + elif 'evdist_' in evinput: + self.addDist() + + for k,v in din.items(): + if k in self.dparams: + try: + new_value = float(v) + except ValueError: + print("WARN: bad value for param %s: %s. Unable to convert" + " to a floating point number" % (k,v)) + continue + self.dparams[k] = new_value + elif k.count('gbar') > 0 and \ + (k.count('evprox') > 0 or \ + k.count('evdist') > 0): + # NOTE: will be deprecated in future release + # for back-compat with old-style specification which didn't have ampa,nmda in evoked gbar + try: + new_value = float(v) + except ValueError: + print("WARN: bad value for param %s: %s. Unable to convert" + " to a floating point number" % (k,v)) + continue + lks = k.split('_') + eloc = lks[1] + enum = lks[2] + base_key_str = 'gbar_' + eloc + '_' + enum + '_' + if eloc == 'evprox': + for ct in ['L2Pyr','L2Basket','L5Pyr','L5Basket']: + # ORIGINAL MODEL/PARAM: only ampa for prox evoked inputs + key_str = base_key_str + ct + '_ampa' + self.dparams[key_str] = new_value + elif eloc == 'evdist': + for ct in ['L2Pyr','L2Basket','L5Pyr']: + # ORIGINAL MODEL/PARAM: both ampa and nmda for distal evoked inputs + key_str = base_key_str + ct + '_ampa' + self.dparams[key_str] = new_value + key_str = base_key_str + ct + '_nmda' + self.dparams[key_str] = new_value + + if not self.optimization_running: + self.createOptParams() + self.rebuildOptStepInfo() + self.updateOptRanges(save_sliders=True) + + self.updateOptDeltas() + + def __str__ (self): + # don't write any values to param file + return '' diff --git a/hnn_qtlib.py b/qt_lib.py similarity index 77% rename from hnn_qtlib.py rename to qt_lib.py index 23fdf0e47..86e9503e3 100644 --- a/hnn_qtlib.py +++ b/qt_lib.py @@ -1,7 +1,14 @@ +"""Miscellaneous Qt functions for HNN GUI""" + +# Authors: Sam Neymotin +# Blake Caldwell + +import os + from PyQt5.QtWidgets import QWidget, QGridLayout, QLineEdit, QSplitter -from PyQt5.QtWidgets import QHBoxLayout, QGroupBox +from PyQt5.QtWidgets import QHBoxLayout, QGroupBox, QLabel from PyQt5.QtGui import QColor, QPainter, QFont, QPen -from PyQt5.QtCore import QCoreApplication, pyqtSignal, QObject, Qt, QSize +from PyQt5.QtCore import QCoreApplication, pyqtSignal, Qt, QSize from PyQt5.QtCore import QMetaObject DEFAULT_CSS = """ @@ -35,11 +42,96 @@ } """ + +def getscreengeom(): + """use pyqt5 to get screen resolution""" + + width, height = 2880, 1620 # default width,height - used for development + app = QCoreApplication.instance() # can only have 1 instance of qtapp + app.setDesktopSettingsAware(True) + if len(app.screens()) > 0: + screen = app.screens()[0] + geom = screen.geometry() + return geom.width(), geom.height() + else: + return width, height + + +def lowresdisplay(): + """check if display has low resolution""" + w, h = getscreengeom() + return w < 1400 or h < 700 + + +def getmplDPI(): + """get DPI for use in matplotlib figures + + used in simulation output canvas - in simdat.py + """ + if lowresdisplay(): + return 60 + return 120 + + +def scalegeom(width, height): + """get new window width, height + + scaled by current screen resolution relative to original + development resolution + """ + devwidth, devheight = 2880.0, 1620.0 # resolution used for development + screenwidth, screenheight = getscreengeom() + widthnew = int((screenwidth / devwidth) * width) + heightnew = int((screenheight / devheight) * height) + if widthnew > 1000 or heightnew > 850: + widthnew = 1000 + heightnew = 850 + return widthnew, heightnew + + +def setscalegeom(dlg, x, y, origw, origh): + """set dialog's position (x,y) and rescale geometry + + based on original width and height and development resolution + """ + + nw, nh = scalegeom(origw, origh) + dlg.setGeometry(x, y, int(nw), int(nh)) + return int(nw), int(nh) + + +def setscalegeomcenter(dlg, origw, origh): + """set dialog in center of screen width + + rescale size based on original width and height and development resolution + """ + + nw, nh = scalegeom(origw, origh) + sw, _ = getscreengeom() + x = (sw - nw) / 2 + y = 0 + dlg.setGeometry(x, y, int(nw), int(nh)) + return int(nw), int(nh) + + def scale(val, src, dst): - try: - return ((val - src[0]) / float(src[1]-src[0]) * (dst[1]-dst[0]) + dst[0]) - except ZeroDivisionError: - return 0 + numerator = val - src[0] + denominator = float(src[1]-src[0]) * (dst[1]-dst[0]) + dst[0] + + if denominator == 0: + return 0 + + return numerator / denominator + + +def lookupresource(fn): + """look up resource adjusted for screen resolution""" + lowres = lowresdisplay() # low resolution display + if lowres: + return os.path.join('res', fn + '2.png') + else: + return os.path.join('res', fn + '.png') + class Ui_Form(object): def setupUi(self, Form): @@ -108,7 +200,7 @@ def __init__(self, parent, main): def drawText(self, event, qp): qp.setPen(self.textColor()) qp.setFont(QFont('Arial', 10)) - qp.drawText(event.rect(), Qt.AlignLeft, ("%.3f"%self.main.min())) + qp.drawText(event.rect(), Qt.AlignLeft, ("%.3f" % self.main.min())) class Tail(Element): @@ -118,7 +210,7 @@ def __init__(self, parent, main): def drawText(self, event, qp): qp.setPen(self.textColor()) qp.setFont(QFont('Arial', 10)) - qp.drawText(event.rect(), Qt.AlignRight, ("%.3f"%self.main.max())) + qp.drawText(event.rect(), Qt.AlignRight, ("%.3f" % self.main.max())) class LineBox(Element): @@ -129,7 +221,7 @@ def drawText(self, event, qp): qp.setPen(QPen(Qt.red, 2, Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin)) pos = self.main.valueToPos(self.main.line_value) if (pos == 0): - pos += 1 + pos += 1 qp.drawLine(pos, 0, pos, 50) @@ -300,13 +392,16 @@ def _posToValue(self, xpos): def _handleMoveSplitter(self, xpos, index): self._splitter.handleWidth() + def _lockWidth(widget): width = widget.size().width() widget.setMinimumWidth(width) widget.setMaximumWidth(width) + def _unlockWidth(widget): widget.setMinimumWidth(0) widget.setMaximumWidth(16777215) + if index == self._SPLIT_START: v = self._posToValue(xpos) _lockWidth(self._tail) @@ -327,9 +422,9 @@ def _unlockWidth(widget): _unlockWidth(self._head) _unlockWidth(self._handle) -# see https://stackoverflow.com/questions/12182133/pyqt4-combine-textchanged-and-editingfinished-for-qlineedit + class MyLineEdit(QLineEdit): - textModified = pyqtSignal(str) # (label) + textModified = pyqtSignal(str) # (label) def __init__(self, contents, label, parent=None): super(MyLineEdit, self).__init__(contents, parent) @@ -346,4 +441,13 @@ def __handleEditingFinished(self): before, after = self._before, self.text() if before != after: self._before = after - self.textModified.emit(self._label) \ No newline at end of file + self.textModified.emit(self._label) + + +class ClickLabel(QLabel): + """clickable label""" + + clicked = pyqtSignal() + + def mousePressEvent(self, event): + self.clicked.emit() diff --git a/simdat.py b/simdat.py index 675df8a47..99664e829 100644 --- a/simdat.py +++ b/simdat.py @@ -11,7 +11,7 @@ from paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs from paramrw import usingTonicInputs, countEvokedInputs, get_output_dir from scipy import signal -from gutils import getscreengeom +from qt_lib import getscreengeom import traceback from hnn_core import read_spikes diff --git a/visdipole.py b/visdipole.py index 198448cc6..24056db7c 100644 --- a/visdipole.py +++ b/visdipole.py @@ -15,15 +15,13 @@ import pylab as plt import matplotlib.gridspec as gridspec from DataViewGUI import DataViewGUI -import paramrw +from paramrw import get_output_dir import spikefn from simdat import readdpltrials -from conf import dconf from hnn_core import read_params -if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] -else: dconf['fontsize'] = 10 +fontsize = plt.rcParams['font.size'] = 10 tstop = -1; ntrial = 1; scalefctr = 30e3; dplpath = ''; paramf = '' for i in range(len(sys.argv)): @@ -37,9 +35,8 @@ tstop = params['tstop'] ntrial = params['N_trials'] -basedir = os.path.join(dconf['datdir'], params['sim_prefix']) - ddat = {} +basedir = os.path.join(get_output_dir(), 'data', params['sim_prefix']) ddat['dpltrials'] = readdpltrials(basedir) try: ddat['dpl'] = np.loadtxt(os.path.join(basedir,'dpl.txt')) diff --git a/vispsd.py b/vispsd.py index e943ba24f..3da4585c7 100644 --- a/vispsd.py +++ b/vispsd.py @@ -17,12 +17,11 @@ from DataViewGUI import DataViewGUI from math import sqrt from specfn import MorletSpec -from conf import dconf +from paramrw import get_output_dir from hnn_core import read_params -if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] -else: dconf['fontsize'] = 10 +fontsize = plt.rcParams['font.size'] = 10 ddat = {} @@ -228,7 +227,7 @@ def clearDataFile (self): params = read_params(paramf) ntrial = params['N_trials'] - basedir = os.path.join(dconf['datdir'],params['sim_prefix']) + basedir = os.path.join(get_output_dir(), 'data', params['sim_prefix']) specpath = os.path.join(basedir,'rawspec.npz') print('specpath',specpath) ddat['spec'] = np.load(specpath) diff --git a/visrast.py b/visrast.py index 295d0cda0..2387f8d92 100644 --- a/visrast.py +++ b/visrast.py @@ -19,16 +19,14 @@ import paramrw import spikefn from math import ceil -from conf import dconf -from gutils import getmplDPI +from qt_lib import getmplDPI from hnn_core import read_params, read_spikes #plt.rcParams['lines.markersize'] = 15 plt.rcParams['lines.linewidth'] = 1 rastmarksz = 5 # raster dot size -if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] -else: plt.rcParams['font.size'] = dconf['fontsize'] = 10 +fontsize = plt.rcParams['font.size'] = 10 # colors for the different cell types dclr = {'L2_pyramidal' : 'g', @@ -43,13 +41,15 @@ spkpath = sys.argv[i] elif sys.argv[i].endswith('.param'): paramf = sys.argv[i] + print(paramf) params = read_params(paramf) tstop = params['tstop'] ntrial = params['N_trials'] EvokedInputs = paramrw.usingEvokedInputs(params) OngoingInputs = paramrw.usingOngoingInputs(params) PoissonInputs = paramrw.usingPoissonInputs(params) - outparamf = os.path.join(dconf['datdir'],params['sim_prefix'],'param.txt') + outparamf = os.path.join(paramrw.get_output_dir(), 'data', + params['sim_prefix'], 'param.txt') extinputs = spikefn.ExtInputs(spkpath, outparamf, params) extinputs.add_delay_times() @@ -247,8 +247,10 @@ def loadspk (self,idx): alldat[idx]['dhist'] = dhist alldat[idx]['extinputs'] = extinputs else: - spkpathtrial = os.path.join(dconf['datdir'], params['sim_prefix'], 'spk_'+str(self.index-1)+'.txt') - dspktrial,haveinputs,dhisttrial = getdspk(spkpathtrial) # show spikes from first trial + spkpathtrial = os.path.join(paramrw.get_output_dir(), 'data', + params['sim_prefix'], + 'spk_' + str(self.index-1) + '.txt') + dspktrial, haveinputs, dhisttrial = getdspk(spkpathtrial) # show spikes from first trial try: extinputs = spikefn.ExtInputs(spkpathtrial, outparamf, params) except ValueError: @@ -337,9 +339,11 @@ def toggleHist (self): self.m.plot() def changeFontSize (self): + global fontsize + i, okPressed = QInputDialog.getInt(self, "Set Font Size","Font Size:", plt.rcParams['font.size'], 1, 100, 1) if okPressed: - plt.rcParams['font.size'] = dconf['fontsize'] = i + plt.rcParams['font.size'] = fontsize = i self.initCanvas() self.m.plot() @@ -396,7 +400,7 @@ def initUI (self): # need a separate widget to put grid on widget = QWidget(self) widget.setLayout(grid) - self.setCentralWidget(widget); + self.setCentralWidget(widget) try: self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) except: pass diff --git a/visspec.py b/visspec.py index 541ab7bb4..6f6dcd4f8 100644 --- a/visspec.py +++ b/visspec.py @@ -16,14 +16,13 @@ import matplotlib.gridspec as gridspec from DataViewGUI import DataViewGUI from specfn import MorletSpec -from conf import dconf import simdat from simdat import readdpltrials -import paramrw +from paramrw import get_output_dir from hnn_core import read_params -if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] +fontsize = plt.rcParams['font.size'] = 10 # assumes column 0 is time, rest of columns are time-series def extractspec (dat, fmax=40.0): @@ -65,7 +64,7 @@ def loaddat_txt (fname): return dat def loaddat (sim_prefix, ntrial): - basedir = os.path.join(dconf['datdir'], sim_prefix) + basedir = os.path.join(get_output_dir(), 'data', sim_prefix) if ntrial > 1: ddat = readdpltrials(basedir) #print('read dpl trials',ddat[0].shape) diff --git a/visvolt.py b/visvolt.py index 76d926dec..54275cd80 100644 --- a/visvolt.py +++ b/visvolt.py @@ -15,13 +15,12 @@ import pylab as plt import matplotlib.gridspec as gridspec import pickle -from conf import dconf -from gutils import getmplDPI +from qt_lib import getmplDPI +from paramrw import get_output_dir from hnn_core import read_params -if dconf['fontsize'] > 0: plt.rcParams['font.size'] = dconf['fontsize'] -else: dconf['fontsize'] = 10 +fontsize = plt.rcParams['font.size'] = 10 # colors for the different cell types dclr = {'L2_pyramidal' : 'g', @@ -39,14 +38,14 @@ params = read_params(paramf) tstop = params['tstop'] ntrial = params['N_trials'] - outparamf = os.path.join(dconf['datdir'],params['sim_prefix'],'param.txt') + outparamf = os.path.join(get_output_dir(), 'data', params['sim_prefix'], 'param.txt') elif sys.argv[i] == 'maxperty': maxperty = int(sys.argv[i]) if ntrial <= 1: - voltpath = os.path.join(dconf['datdir'],params['sim_prefix'],'vsoma.pkl') + voltpath = os.path.join(get_output_dir(), 'data', params['sim_prefix'], 'vsoma.pkl') else: - voltpath = os.path.join(dconf['datdir'],params['sim_prefix'],'vsoma_1.pkl') + voltpath = os.path.join(get_output_dir(), 'data', params['sim_prefix'], 'vsoma_1.pkl') class VoltCanvas (FigureCanvas): def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, title='Voltage Viewer'): @@ -100,7 +99,7 @@ def drawvolt (self, dvolt, fig, G, sz=8, ltextra=''): ax.grid(True) if tstop != -1: ax.set_xlim((0,tstop)) if i ==0: ax.set_title(ltextra) - ax.set_xlabel('Time (ms)'); + ax.set_xlabel('Time (ms)') self.figure.subplots_adjust(bottom=0.01, left=0.01, right=0.99, top=0.99, wspace=0.1, hspace=0.09) @@ -116,24 +115,25 @@ def plot (self): dvolt = pickle.load(open(voltpath,'rb')) self.lax = self.drawvolt(dvolt,self.figure, self.G, 5, ltextra='All Trials') else: - voltpathtrial = os.path.join(dconf['datdir'],params['sim_prefix'],'vsoma_'+str(self.index)+'.pkl') + voltpathtrial = os.path.join(get_output_dir(), 'data', params['sim_prefix'], 'vsoma_'+str(self.index)+'.pkl') dvolttrial = pickle.load(open(voltpathtrial,'rb')) - self.lax=self.drawvolt(dvolttrial,self.figure, self.G, 5, ltextra='Trial '+str(self.index)); + self.lax=self.drawvolt(dvolttrial,self.figure, self.G, 5, ltextra='Trial '+str(self.index)) self.draw() class VoltGUI (QMainWindow): def __init__ (self): - global dfile, ddat, paramf + global dfile, ddat, paramf, fontsize super().__init__() - self.fontsize = dconf['fontsize'] + self.fontsize = fontsize self.linewidth = plt.rcParams['lines.linewidth'] = 1 self.markersize = plt.rcParams['lines.markersize'] = 5 self.initUI() def changeFontSize (self): + global fontsize i, okPressed = QInputDialog.getInt(self, "Set Font Size","Font Size:", plt.rcParams['font.size'], 1, 100, 1) if okPressed: - self.fontsize = plt.rcParams['font.size'] = dconf['fontsize'] = i + self.fontsize = plt.rcParams['font.size'] = fontsize = i self.initCanvas() self.m.plot() @@ -191,8 +191,8 @@ def initCanvas (self): # this is the Navigation widget # it takes the Canvas widget and a parent self.toolbar = NavigationToolbar(self.m, self) - self.grid.addWidget(self.toolbar, 0, 0, 1, 4); - self.grid.addWidget(self.m, 1, 0, 1, 4); + self.grid.addWidget(self.toolbar, 0, 0, 1, 4) + self.grid.addWidget(self.m, 1, 0, 1, 4) def initUI (self): self.initMenu() @@ -211,7 +211,7 @@ def initUI (self): # need a separate widget to put grid on widget = QWidget(self) widget.setLayout(grid) - self.setCentralWidget(widget); + self.setCentralWidget(widget) try: self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) except: pass From 08003f3095124ff4b29c00c9a6c519c7766437ad Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 23:31:46 -0400 Subject: [PATCH 028/107] MAINT: reorganize as hnn package This is a large commit, with the major changes: 1. hnn is now a proper module. code is in the directory hnn, there is a setup.py, hnn/__init__.py, and relative imports are used 2. installation instructions were updated to complete the shift to pip install NEURON except for windows. 3. Travis CI is updated to install NEURON, but tests have not been migrated yet (they will fail) --- .travis.yml | 9 ++- __init__.py | 1 - environment.yml | 16 +++++ hnn.py | 2 +- hnn => hnn.sh | 0 DataViewGUI.py => hnn/DataViewGUI.py | 0 hnn/__init__.py | 3 + hnn_qt5.py => hnn/hnn_qt5.py | 84 ++++++++++------------- paramrw.py => hnn/paramrw.py | 0 qt_evoked.py => hnn/qt_evoked.py | 6 +- qt_lib.py => hnn/qt_lib.py | 0 run.py => hnn/run.py | 56 +++++++-------- simdat.py => hnn/simdat.py | 10 +-- specfn.py => hnn/specfn.py | 1 - spikefn.py => hnn/spikefn.py | 4 +- {tests => hnn/tests}/test_compare_hnn.py | 25 ++----- {tests => hnn/tests}/test_view_windows.py | 0 visdipole.py => hnn/visdipole.py | 0 vispsd.py => hnn/vispsd.py | 0 visrast.py => hnn/visrast.py | 0 visspec.py => hnn/visspec.py | 0 visvolt.py => hnn/visvolt.py | 0 installer/aws/aws-build.sh | 84 +---------------------- installer/brown_ccv/oscar_setup.sh | 25 +------ installer/centos/hnn-centos6.sh | 5 +- installer/docker/Dockerfile | 6 +- installer/ubuntu/hnn-ubuntu.sh | 4 +- installer/windows/hnn-windows.ps1 | 6 +- scripts/setup-travis-mac.sh | 4 +- setup.py | 18 +++++ 30 files changed, 135 insertions(+), 234 deletions(-) delete mode 100644 __init__.py create mode 100644 environment.yml rename hnn => hnn.sh (100%) rename DataViewGUI.py => hnn/DataViewGUI.py (100%) create mode 100644 hnn/__init__.py rename hnn_qt5.py => hnn/hnn_qt5.py (97%) rename paramrw.py => hnn/paramrw.py (100%) rename qt_evoked.py => hnn/qt_evoked.py (99%) rename qt_lib.py => hnn/qt_lib.py (100%) rename run.py => hnn/run.py (93%) rename simdat.py => hnn/simdat.py (98%) rename specfn.py => hnn/specfn.py (99%) rename spikefn.py => hnn/spikefn.py (99%) rename {tests => hnn/tests}/test_compare_hnn.py (66%) rename {tests => hnn/tests}/test_view_windows.py (100%) rename visdipole.py => hnn/visdipole.py (100%) rename vispsd.py => hnn/vispsd.py (100%) rename visrast.py => hnn/visrast.py (100%) rename visspec.py => hnn/visspec.py (100%) rename visvolt.py => hnn/visvolt.py (100%) create mode 100644 setup.py diff --git a/.travis.yml b/.travis.yml index 0236087f1..8ada8576e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -161,12 +161,11 @@ before_install: fi install: - - | # for mac build HNN .mod files - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then - make -j2 - fi - | # testing packages pip install flake8 pytest pytest-cov coverage coveralls mne + - | + cd $TRAVIS_BUILD_DIR + python setup.py install script: - | # Check that the GUI starts on host OS @@ -174,7 +173,7 @@ script: $PYTHON hnn.py - | # Run py.test that includes running a simulation and verifying results echo "Running Python tests on host OS..." - py.test --cov=. tests/ + py.test --cov=. hnn/tests/ - | # Test WSL-based version on windows (needs VcXsrv) if [[ "${TRAVIS_OS_NAME}" == "windows" ]]; then find_command_suggested_path "vcxsrv" "/c/Program Files/VcXsrv" && \ diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 3189e6389..000000000 --- a/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '0.1.3' diff --git a/environment.yml b/environment.yml new file mode 100644 index 000000000..5f9f30732 --- /dev/null +++ b/environment.yml @@ -0,0 +1,16 @@ +name: hnn +channels: +- defaults +dependencies: +- python=3.7 +- pip +- openmpi +- numpy +- scipy +- matplotlib +- psutil +- pip: + - NEURON + - https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master + - PyQt5>=5.10,<5.14; platform_system == "Darwin" + - PyQt5>=5.10; platform_system != "Darwin" diff --git a/hnn.py b/hnn.py index ad4e3994c..25d777f65 100755 --- a/hnn.py +++ b/hnn.py @@ -1,7 +1,7 @@ import sys from PyQt5.QtWidgets import QApplication -from hnn_qt5 import HNNGUI +from hnn import HNNGUI def runqt5 (): app = QApplication(sys.argv) diff --git a/hnn b/hnn.sh similarity index 100% rename from hnn rename to hnn.sh diff --git a/DataViewGUI.py b/hnn/DataViewGUI.py similarity index 100% rename from DataViewGUI.py rename to hnn/DataViewGUI.py diff --git a/hnn/__init__.py b/hnn/__init__.py new file mode 100644 index 000000000..f22111efa --- /dev/null +++ b/hnn/__init__.py @@ -0,0 +1,3 @@ +__version__ = "1.4.0" + +from .hnn_qt5 import HNNGUI \ No newline at end of file diff --git a/hnn_qt5.py b/hnn/hnn_qt5.py similarity index 97% rename from hnn_qt5.py rename to hnn/hnn_qt5.py index 72f297caf..203cf1fe4 100644 --- a/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -31,14 +31,15 @@ from hnn_core import read_params # HNN modules -import spikefn -from paramrw import usingOngoingInputs, usingEvokedInputs -from paramrw import write_legacy_paramf, get_output_dir -from simdat import SIMCanvas, getinputfiles, updatedat -from run import RunSimThread, ParamSignal -from qt_lib import setscalegeom, setscalegeomcenter, getmplDPI, getscreengeom -from qt_lib import lookupresource, ClickLabel -from qt_evoked import EvokedInputParamDialog, OptEvokedInputParamDialog +from .paramrw import usingOngoingInputs, usingEvokedInputs +from .paramrw import write_legacy_paramf, get_output_dir +from .simdat import SIMCanvas, getinputfiles, updatedat +from .simdat import updatelsimdat, ddat, updateoptdat, lsimdat, lsimidx +from .simdat import weighted_rmse, initial_ddat, calcerr +from .run import RunSimThread, ParamSignal +from .qt_lib import setscalegeom, setscalegeomcenter, getmplDPI, getscreengeom +from .qt_lib import lookupresource, ClickLabel +from .qt_evoked import EvokedInputParamDialog, OptEvokedInputParamDialog # TODO: These globals should be made configurable via the GUI drawindivrast = 0 @@ -1163,7 +1164,7 @@ def __init__ (self): global fontsize - hnn_root_dir = os.path.dirname(os.path.realpath(__file__)) + hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) self.runningsim = False self.runthread = None @@ -1240,9 +1241,7 @@ def changeMarkerSize (self): def selParamFileDialog (self): # bring up window to select simulation parameter file - import simdat - - hnn_root_dir = os.path.dirname(os.path.realpath(__file__)) + hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) qfd = QFileDialog() qfd.setHistory([os.path.join(get_output_dir(), 'param'), @@ -1273,9 +1272,9 @@ def selParamFileDialog (self): self.setWindowTitle(self.baseparamwin.paramfn) # store the sim just loaded in simdat's list - is this the desired behavior? or should we first erase prev sims? - if 'dpl' in simdat.ddat: + if 'dpl' in ddat: # update lsimdat and its current sim index - simdat.updatelsimdat(self.baseparamwin.paramfn, self.baseparamwin.params, simdat.ddat['dpl']) + updatelsimdat(self.baseparamwin.paramfn, self.baseparamwin.params, ddat['dpl']) self.populateSimCB() # populate the combobox @@ -1284,7 +1283,6 @@ def selParamFileDialog (self): def loadDataFile (self, fn): # load a dipole data file - import simdat try: self.dextdata[fn] = np.loadtxt(fn) @@ -1299,7 +1297,7 @@ def loadDataFile (self, fn): QMessageBox.information(self, "HNN", "WARNING: could not load data file %s" % fn) return False - simdat.ddat['dextdata'] = self.dextdata + ddat['dextdata'] = self.dextdata print('Loaded data in ', fn) self.m.plot() @@ -1311,7 +1309,7 @@ def loadDataFile (self, fn): def loadDataFileDialog (self): # bring up window to select/load external dipole data file - hnn_root_dir = os.path.dirname(os.path.realpath(__file__)) + hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) qfd = QFileDialog() qfd.setHistory([os.path.join(get_output_dir(), 'data'), @@ -1327,10 +1325,8 @@ def loadDataFileDialog (self): def clearDataFile (self): # clear external dipole data - import simdat - self.m.clearlextdatobj() - self.dextdata = simdat.ddat['dextdata'] = {} + self.dextdata = ddat['dextdata'] = {} self.toggleEnableOptimization(False) self.m.plot() # recreate canvas self.m.draw() @@ -1343,7 +1339,7 @@ def setparams (self): def showAboutDialog (self): # show HNN's about dialog box - from __init__ import __version__ + from hnn import __version__ msgBox = QMessageBox(self) msgBox.setTextFormat(Qt.RichText) msgBox.setWindowTitle('About') @@ -1491,11 +1487,9 @@ def updateDatCanv(self, params): def updateSelectedSim(self, sim_idx): """Update the sim shown in the ComboBox and update globals""" - import simdat - # update globals - simdat.lsimidx = sim_idx - paramfn = simdat.lsimdat[sim_idx]['paramfn'] + lsimidx = sim_idx + paramfn = lsimdat[sim_idx]['paramfn'] try: params = read_params(paramfn) @@ -1509,20 +1503,18 @@ def updateSelectedSim(self, sim_idx): # update GUI self.updateDatCanv(params) - self.cbsim.setCurrentIndex(simdat.lsimidx) + self.cbsim.setCurrentIndex(lsimidx) def removeSim(self): """Remove the currently selected simulation""" - import simdat - cidx = self.cbsim.currentIndex() self.cbsim.removeItem(cidx) - del simdat.lsimdat[cidx] + del lsimdat[cidx] # go to last entry new_simidx = self.cbsim.count() - 1 if new_simidx < 0: - simdat.lsimidx = 0 + lsimidx = 0 self.clearSimulations() else: self.updateSelectedSim(new_simidx) @@ -1549,13 +1541,11 @@ def nextSim (self): def clearSimulationData (self): # clear the simulation data - import simdat - self.baseparamwin.params = None self.baseparamwin.paramfn = None - simdat.ddat = {} # clear data in simdat.ddat - simdat.lsimdat = [] - simdat.lsimidx = 0 + ddat = {} # clear data in simdat.ddat + lsimdat = [] + lsimidx = 0 self.populateSimCB() # un-populate the combobox self.toggleEnableOptimization(False) @@ -1569,10 +1559,9 @@ def clearSimulations (self): def clearCanvas (self): # clear all simulation & external data and erase everything from the canvas - import simdat self.clearSimulationData() self.m.clearlextdatobj() # clear the external data - self.dextdata = simdat.ddat['dextdata'] = {} + self.dextdata = ddat['dextdata'] = {} self.initSimCanvas() # recreate canvas self.m.draw() self.setWindowTitle('') @@ -1821,12 +1810,11 @@ def initUI (self): gRow += 2 # store any sim just loaded in simdat's list - is this the desired behavior? or should we start empty? - import simdat - if 'dpl' in simdat.ddat: + if 'dpl' in ddat: # update lsimdat and its current sim index - simdat.updatelsimdat(self.baseparamwin.paramfn, + updatelsimdat(self.baseparamwin.paramfn, self.baseparamwin.params, - simdat.ddat['dpl']) + ddat['dpl']) self.cbsim = QComboBox(self) self.populateSimCB() # populate the combobox @@ -1861,9 +1849,9 @@ def initUI (self): def onActivateSimCB (self, paramfn): # load simulation when activating simulation combobox - import simdat - if self.cbsim.currentIndex() != simdat.lsimidx: + global lsimidx + if self.cbsim.currentIndex() != lsimidx: try: params = read_params(paramfn) except ValueError: @@ -1873,17 +1861,16 @@ def onActivateSimCB (self, paramfn): return self.baseparamwin.paramfn = paramfn - simdat.lsimidx = self.cbsim.currentIndex() + lsimidx = self.cbsim.currentIndex() self.updateDatCanv(params) def populateSimCB (self): # populate the simulation combobox self.cbsim.clear() - import simdat - for sim in simdat.lsimdat: + for sim in lsimdat: sim_paramfn = sim['paramfn'] self.cbsim.addItem(sim_paramfn) - self.cbsim.setCurrentIndex(simdat.lsimidx) + self.cbsim.setCurrentIndex(lsimidx) def initSimCanvas (self,recalcErr=True,optMode=False,gRow=1,reInit=True): # initialize the simulation canvas, loading any required data @@ -1911,8 +1898,7 @@ def initSimCanvas (self,recalcErr=True,optMode=False,gRow=1,reInit=True): self.grid.addWidget(self.toolbar, gRow, gCol, 1, gWidth) self.grid.addWidget(self.m, gRow + 1, gCol, 1, gWidth) if len(self.dextdata.keys()) > 0: - import simdat - simdat.ddat['dextdata'] = self.dextdata + ddat['dextdata'] = self.dextdata self.m.plot(recalcErr) self.m.draw() diff --git a/paramrw.py b/hnn/paramrw.py similarity index 100% rename from paramrw.py rename to hnn/paramrw.py diff --git a/qt_evoked.py b/hnn/qt_evoked.py similarity index 99% rename from qt_evoked.py rename to hnn/qt_evoked.py index bb4bf5a78..9da2f08fe 100644 --- a/qt_evoked.py +++ b/hnn/qt_evoked.py @@ -14,9 +14,9 @@ from PyQt5.QtGui import QPixmap from PyQt5.QtCore import Qt -from qt_lib import QRangeSlider, MyLineEdit, ClickLabel, setscalegeom -from qt_lib import lookupresource -from paramrw import chunk_evinputs, get_inputs, trans_input, countEvokedInputs +from .qt_lib import QRangeSlider, MyLineEdit, ClickLabel, setscalegeom +from .qt_lib import lookupresource +from .paramrw import chunk_evinputs, get_inputs, trans_input, countEvokedInputs decay_multiplier = 1.6 diff --git a/qt_lib.py b/hnn/qt_lib.py similarity index 100% rename from qt_lib.py rename to hnn/qt_lib.py diff --git a/run.py b/hnn/run.py similarity index 93% rename from run.py rename to hnn/run.py index 6c51caae9..d7dd1d227 100755 --- a/run.py +++ b/hnn/run.py @@ -20,10 +20,11 @@ import nlopt from psutil import wait_procs, process_iter, NoSuchProcess -from paramrw import usingOngoingInputs, write_gids_param -import specfn -import simdat -from paramrw import write_legacy_paramf, get_output_dir +from .paramrw import usingOngoingInputs, write_gids_param +from .specfn import analysis_simp +from .simdat import updatelsimdat, updatedat, ddat, updateoptdat +from .simdat import weighted_rmse, initial_ddat, calcerr +from .paramrw import write_legacy_paramf, get_output_dir def get_fname(sim_dir, key, trial=0, ntrial=1): @@ -203,7 +204,7 @@ def simulate(params, n_procs=None): 'runtype': 'parallel'} # run the spectral analysis - specfn.analysis_simp(spec_opts, params, dpl, + analysis_simp(spec_opts, params, dpl, get_fname(sim_dir, 'rawspec', trial_idx, params['N_trials'])) @@ -379,15 +380,14 @@ def runsim (self, is_opt=False, banner=True, simlength=None): self.updatewaitsimwin(txt) # should have good data written to files at this point - simdat.updatedat(self.params) + updatedat(self.params) if not is_opt: # update lsimdat and its current sim index - simdat.updatelsimdat(self.paramfn, self.params, simdat.ddat['dpl']) + updatelsimdat(self.paramfn, self.params, ddat['dpl']) def optmodel (self): - import simdat - + global initial_ddat need_initial_ddat = False # initialize RNG with seed from config @@ -396,9 +396,9 @@ def optmodel (self): # initial_ddat stores the initial fit (from "Run Simulation"). # To be displayed in final dipole plot as black dashed line. - if len(simdat.ddat) > 0: - simdat.initial_ddat['dpl'] = deepcopy(simdat.ddat['dpl']) - simdat.initial_ddat['errtot'] = deepcopy(simdat.ddat['errtot']) + if len(ddat) > 0: + initial_ddat['dpl'] = deepcopy(ddat['dpl']) + initial_ddat['errtot'] = deepcopy(ddat['errtot']) else: need_initial_ddat = True @@ -441,15 +441,15 @@ def optmodel (self): self.runOptStep(step) if 'dpl' in self.best_ddat: - simdat.ddat['dpl'] = deepcopy(self.best_ddat['dpl']) + ddat['dpl'] = deepcopy(self.best_ddat['dpl']) if 'errtot' in self.best_ddat: - simdat.ddat['errtot'] = deepcopy(self.best_ddat['errtot']) + ddat['errtot'] = deepcopy(self.best_ddat['errtot']) if need_initial_ddat: - simdat.initial_ddat = deepcopy(simdat.ddat) + initial_ddat = deepcopy(ddat) # update optdat with best from this step - simdat.updateoptdat(self.paramfn, self.params, simdat.ddat['dpl']) + updateoptdat(self.paramfn, self.params, ddat['dpl']) # put best opt results into GUI and save to param file push_values = {} @@ -466,10 +466,10 @@ def optmodel (self): self.runsim(is_opt=True, banner=False) # update lsimdat and its current sim index - simdat.updatelsimdat(self.paramfn, self.params, simdat.ddat['dpl']) + updatelsimdat(self.paramfn, self.params, ddat['dpl']) # update optdat with the final best - simdat.updateoptdat(self.paramfn, self.params, simdat.ddat['dpl']) + updateoptdat(self.paramfn, self.params, ddat['dpl']) # re-enable all the range sliders self.baseparamwin.optparamwin.toggleEnableUserFields(step, enable=True) @@ -518,24 +518,24 @@ def optrun (new_params, grad=0): self.runsim(is_opt=True, banner=False, simlength=self.opt_end) # calculate wRMSE for all steps - simdat.weighted_rmse(simdat.ddat, + weighted_rmse(ddat, self.opt_end, self.opt_weights, tstart=self.opt_start) - err = simdat.ddat['werrtot'] + err = ddat['werrtot'] if self.last_step: # weighted RMSE with weights of all 1's is the same as # regular RMSE - simdat.ddat['errtot'] = simdat.ddat['werrtot'] + ddat['errtot'] = ddat['werrtot'] txt = "RMSE = %f"%err else: # calculate regular RMSE for displaying on plot - simdat.calcerr(simdat.ddat, + calcerr(ddat, self.opt_end, tstart=self.opt_start) - txt = "weighted RMSE = %f, RMSE = %f"% (err,simdat.ddat['errtot']) + txt = "weighted RMSE = %f, RMSE = %f"% (err,ddat['errtot']) print(txt) self.updatewaitsimwin(os.linesep+'Simulation finished: ' + txt + os.linesep) # print error @@ -545,7 +545,7 @@ def optrun (new_params, grad=0): fnoptinf = os.path.join(sim_dir,'optinf.txt') with open(fnoptinf,'a') as fpopt: - fpopt.write(str(simdat.ddat['errtot'])+os.linesep) # write error + fpopt.write(str(ddat['errtot'])+os.linesep) # write error # save params numbered by optsim param_out = os.path.join(sim_dir,'step_%d_sim_%d.param'%(self.cur_step,self.optsim)) @@ -558,10 +558,10 @@ def optrun (new_params, grad=0): # save best param file param_out = os.path.join(sim_dir,'step_%d_best.param'%self.cur_step) write_legacy_paramf(param_out, self.params) - if 'dpl' in simdat.ddat: - self.best_ddat['dpl'] = simdat.ddat['dpl'] - if 'errtot' in simdat.ddat: - self.best_ddat['errtot'] = simdat.ddat['errtot'] + if 'dpl' in ddat: + self.best_ddat['dpl'] = ddat['dpl'] + if 'errtot' in ddat: + self.best_ddat['errtot'] = ddat['errtot'] if self.optsim == 0 and not self.first_step: # Update plots for the first simulation only of this step (best results from last round) diff --git a/simdat.py b/hnn/simdat.py similarity index 98% rename from simdat.py rename to hnn/simdat.py index 99664e829..e313d9bb5 100644 --- a/simdat.py +++ b/hnn/simdat.py @@ -7,11 +7,11 @@ import matplotlib.gridspec as gridspec import numpy as np from math import ceil -import spikefn -from paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs -from paramrw import usingTonicInputs, countEvokedInputs, get_output_dir +from .spikefn import ExtInputs +from .paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs +from .paramrw import usingTonicInputs, countEvokedInputs, get_output_dir from scipy import signal -from qt_lib import getscreengeom +from .qt_lib import getscreengeom import traceback from hnn_core import read_spikes @@ -284,7 +284,7 @@ def plotinputhist(self, xl, dinty): times = np.linspace(0, sim_tstop, num_step) try: - extinputs = spikefn.ExtInputs(dfile['spk'], dfile['outparam'], + extinputs = ExtInputs(dfile['spk'], dfile['outparam'], self.params) extinputs.add_delay_times() dinput = extinputs.inputs diff --git a/specfn.py b/hnn/specfn.py similarity index 99% rename from specfn.py rename to hnn/specfn.py index 644af01c5..aa8a58ca1 100644 --- a/specfn.py +++ b/hnn/specfn.py @@ -9,7 +9,6 @@ import numpy as np import scipy.signal as sps import matplotlib.pyplot as plt -import paramrw # MorletSpec class based on a time vec tvec and a time series vec tsvec class MorletSpec(): diff --git a/spikefn.py b/hnn/spikefn.py similarity index 99% rename from spikefn.py rename to hnn/spikefn.py index c0161d8df..03445666e 100644 --- a/spikefn.py +++ b/hnn/spikefn.py @@ -9,7 +9,7 @@ import matplotlib.pyplot as plt import itertools as it import os -import paramrw +from .paramrw import read_gids_param from hnn_core import read_spikes @@ -65,7 +65,7 @@ def __init__ (self, fspk, fgids, params, evoked=False): self.p_dict = params try: - self.gid_dict = paramrw.read_gids_param(fgids) + self.gid_dict = read_gids_param(fgids) except FileNotFoundError: raise ValueError diff --git a/tests/test_compare_hnn.py b/hnn/tests/test_compare_hnn.py similarity index 66% rename from tests/test_compare_hnn.py rename to hnn/tests/test_compare_hnn.py index 732155679..90579ad9c 100644 --- a/tests/test_compare_hnn.py +++ b/hnn/tests/test_compare_hnn.py @@ -5,31 +5,14 @@ from mne.utils import _fetch_file +from ... import run def test_hnn(): """Test to check that HNN produces consistent results""" - # small snippet of data on data branch for now. To be deleted - # later. Data branch should have only commit so it does not - # pollute the history. - from subprocess import Popen, PIPE - import shlex - import os - import sys + self.runthread = RunSimThread(self.p, self.d, ntrial, ncore, + self.waitsimwin, params, opt=False, + baseparamwin=None, mainwin=None) - ntrials = 3 - paramf = op.join('param', 'default.param') - - nrniv_str = 'nrniv -python -nobanner' - cmd = nrniv_str + ' ' + sys.executable + ' run.py ' + paramf \ - + ' ntrial ' + str(ntrials) - - # Split the command into shell arguments for passing to Popen - cmdargs = shlex.split(cmd, posix="win" not in sys.platform) - - # Start the simulation - proc = Popen(cmdargs, stdin=PIPE, stdout=PIPE, stderr=PIPE, - cwd=os.getcwd(), universal_newlines=True) - out, err = proc.communicate() # print all messages (including error messages) print('STDOUT', out) diff --git a/tests/test_view_windows.py b/hnn/tests/test_view_windows.py similarity index 100% rename from tests/test_view_windows.py rename to hnn/tests/test_view_windows.py diff --git a/visdipole.py b/hnn/visdipole.py similarity index 100% rename from visdipole.py rename to hnn/visdipole.py diff --git a/vispsd.py b/hnn/vispsd.py similarity index 100% rename from vispsd.py rename to hnn/vispsd.py diff --git a/visrast.py b/hnn/visrast.py similarity index 100% rename from visrast.py rename to hnn/visrast.py diff --git a/visspec.py b/hnn/visspec.py similarity index 100% rename from visspec.py rename to hnn/visspec.py diff --git a/visvolt.py b/hnn/visvolt.py similarity index 100% rename from visvolt.py rename to hnn/visvolt.py diff --git a/installer/aws/aws-build.sh b/installer/aws/aws-build.sh index 1b3801740..b8e2015c5 100644 --- a/installer/aws/aws-build.sh +++ b/installer/aws/aws-build.sh @@ -15,94 +15,12 @@ sudo apt-get install -y git python3-dev python3-pip python3-psutil \ git vim iputils-ping net-tools iproute2 nano sudo \ telnet language-pack-en-base sudo pip3 install pip --upgrade -sudo pip install PyOpenGL matplotlib pyqt5 pyqtgraph scipy numpy nlopt - -# build MESA from source (for software 3D rendering) -# this part is optional if the 'Model Visualization' feature is not needed -sudo apt-get update && \ - sudo apt-get upgrade -y && \ - sudo apt-get install --no-install-recommends -y \ - wget \ - bzip2 \ - curl \ - python \ - libllvm6.0 \ - llvm-6.0-dev \ - zlib1g-dev \ - xserver-xorg-dev \ - build-essential \ - libxcb-dri2-0-dev \ - libxcb-xfixes0-dev \ - libxext-dev \ - libx11-xcb-dev \ - pkg-config && \ - update-alternatives --install \ - /usr/bin/llvm-config llvm-config /usr/bin/llvm-config-6.0 200 \ ---slave /usr/bin/llvm-ar llvm-ar /usr/bin/llvm-ar-6.0 \ ---slave /usr/bin/llvm-as llvm-as /usr/bin/llvm-as-6.0 \ ---slave /usr/bin/llvm-bcanalyzer llvm-bcanalyzer /usr/bin/llvm-bcanalyzer-6.0 \ ---slave /usr/bin/llvm-cov llvm-cov /usr/bin/llvm-cov-6.0 \ ---slave /usr/bin/llvm-diff llvm-diff /usr/bin/llvm-diff-6.0 \ ---slave /usr/bin/llvm-dis llvm-dis /usr/bin/llvm-dis-6.0 \ ---slave /usr/bin/llvm-dwarfdump llvm-dwarfdump /usr/bin/llvm-dwarfdump-6.0 \ ---slave /usr/bin/llvm-extract llvm-extract /usr/bin/llvm-extract-6.0 \ ---slave /usr/bin/llvm-link llvm-link /usr/bin/llvm-link-6.0 \ ---slave /usr/bin/llvm-mc llvm-mc /usr/bin/llvm-mc-6.0 \ ---slave /usr/bin/llvm-mcmarkup llvm-mcmarkup /usr/bin/llvm-mcmarkup-6.0 \ ---slave /usr/bin/llvm-nm llvm-nm /usr/bin/llvm-nm-6.0 \ ---slave /usr/bin/llvm-objdump llvm-objdump /usr/bin/llvm-objdump-6.0 \ ---slave /usr/bin/llvm-ranlib llvm-ranlib /usr/bin/llvm-ranlib-6.0 \ ---slave /usr/bin/llvm-readobj llvm-readobj /usr/bin/llvm-readobj-6.0 \ ---slave /usr/bin/llvm-rtdyld llvm-rtdyld /usr/bin/llvm-rtdyld-6.0 \ ---slave /usr/bin/llvm-size llvm-size /usr/bin/llvm-size-6.0 \ ---slave /usr/bin/llvm-stress llvm-stress /usr/bin/llvm-stress-6.0 \ ---slave /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-6.0 \ ---slave /usr/bin/llvm-tblgen llvm-tblgen /usr/bin/llvm-tblgen-6.0 && \ - set -xe; \ - mkdir -p /var/tmp/build; \ - cd /var/tmp/build; \ - wget -q --no-check-certificate "https://mesa.freedesktop.org/archive/mesa-18.0.1.tar.gz"; \ - tar xf mesa-18.0.1.tar.gz; \ - rm mesa-18.0.1.tar.gz; \ - cd mesa-18.0.1; \ - ./configure --enable-glx=gallium-xlib --with-gallium-drivers=swrast,swr --disable-dri --disable-gbm --disable-egl --enable-gallium-osmesa --enable-llvm --prefix=/usr/local; \ - make; \ - sudo make install; \ - cd .. ; \ - rm -rf mesa-18.0.1; \ - sudo apt-get -y remove --purge llvm-6.0-dev \ - zlib1g-dev \ - xserver-xorg-dev \ - python3-dev \ - python \ - pkg-config \ - libxext-dev \ - libx11-xcb-dev \ - libxcb-xfixes0-dev \ - libxcb-dri2-0-dev && \ - sudo apt autoremove -y --purge && \ - sudo apt clean - -cd $HOME && \ - git clone https://github.com/neuronsimulator/nrn.git && \ - cd nrn && \ - git checkout 7.7 && \ - ./build.sh && \ - ./configure --with-nrnpython=python3 \ - --with-paranrn --without-iv --disable-rx3d && \ - make && \ - sudo make install +sudo pip install matplotlib pyqt5 scipy numpy nlopt NEURON echo '# these lines define global session variables for HNN' >> ~/.bashrc -echo 'export CPU=$(uname -m)' >> ~/.bashrc -echo 'export PATH=$PATH:/usr/local/nrn/$CPU/bin' >> ~/.bashrc echo 'export OMPI_MCA_btl_base_warn_component_unused=0' >> ~/.bashrc -echo 'export PYTHONPATH=/usr/local/nrn/lib/python:$PYTHONPATH' >> ~/.bashrc -export CPU=$(uname -m) -export PATH=$PATH:/usr/local/nrn/$CPU/bin export OMPI_MCA_btl_base_warn_component_unused=0 -export PYTHONPATH=/usr/local/nrn/lib/python:$PYTHONPATH cd $HOME && \ git clone https://github.com/jonescompneurolab/hnn && \ diff --git a/installer/brown_ccv/oscar_setup.sh b/installer/brown_ccv/oscar_setup.sh index 05fe8a6dd..685e1423e 100644 --- a/installer/brown_ccv/oscar_setup.sh +++ b/installer/brown_ccv/oscar_setup.sh @@ -6,30 +6,10 @@ mkdir -p $HOME/HNN # Clone the source code for HNN and prerequisites cd $HOME/HNN -git clone https://github.com/neuronsimulator/nrn -git clone https://github.com/neuronsimulator/iv git clone https://github.com/jonescompneurolab/hnn -# Build HNN prerequisites - -# Build NEURON - -cd $HOME/HNN/nrn && \ - ./build.sh && \ - ./configure --with-nrnpython=python3 --with-paranrn --disable-rx3d \ - --without-iv --with-mpi --prefix=$(pwd)/build && \ - make -j2 && \ - make install -j2 && \ - cd src/nrnpython && \ - python3 setup.py install --home=$HOME/HNN/nrn/build/x86_64/python - -# Cleanup compiled prerequisites - -cd $HOME/HNN/nrn && \ - make clean - # Install python modules. Ignore the errors -pip3 install --user PyOpenGL pyqtgraph psutil nlopt >/dev/null 2>&1 +pip3 install --user psutil nlopt NEURON >/dev/null 2>&1 # Build HNN cd $HOME/HNN/hnn && \ @@ -38,8 +18,7 @@ cd $HOME/HNN/hnn && \ # Set commands to run at login for future logins cat < /dev/null -export PATH="\$PATH:\$HOME/HNN/nrn/build/x86_64/bin" -export PYTHONPATH="/gpfs/runtime/opt/hnn/1.0/pyqt:\$HOME/HNN/nrn/build/x86_64/python/lib/python" +export PYTHONPATH="/gpfs/runtime/opt/hnn/1.0/pyqt:" export OMPI_MCA_btl_openib_allow_ib=1 # HNN settings if [[ ! "\$(ulimit -l)" =~ "unlimited" ]]; then diff --git a/installer/centos/hnn-centos6.sh b/installer/centos/hnn-centos6.sh index 86b335ca7..81c2fd827 100644 --- a/installer/centos/hnn-centos6.sh +++ b/installer/centos/hnn-centos6.sh @@ -16,6 +16,7 @@ sudo yum -y install python34-setuptools sudo easy_install-3.4 pip pip3 install --upgrade matplotlib --user pip3 install --upgrade nlopt scipy --user +pip3 install NEURON sudo yum -y install ncurses-devel sudo yum -y install openmpi openmpi-devel sudo yum -y install libXext libXext-devel @@ -40,7 +41,7 @@ cd .. # create the global session variables, make available for all users echo '# these lines define global session variables for HNN' | sudo tee -a /etc/profile.d/hnn.sh echo 'export CPU=$(uname -m)' | sudo tee -a /etc/profile.d/hnn.sh -echo "export PATH=\$PATH::/usr/lib64/openmpi/bin:$startdir/nrn/build/\$CPU/bin" | sudo tee -a /etc/profile.d/hnn.sh +echo "export PATH=\$PATH::/usr/lib64/openmpi/bin" | sudo tee -a /etc/profile.d/hnn.sh # qt, pyqt, and supporting packages - needed for GUI # SIP unforutnately not available as a wheel for Python 3.4, so have to compile @@ -71,4 +72,4 @@ rm -f PyQt5_gpl-5.8.2.tar.gz # needed for matplotlib sudo yum -y install python34-tkinter -pip3 install psutil --user \ No newline at end of file +pip3 install psutil --user diff --git a/installer/docker/Dockerfile b/installer/docker/Dockerfile index f9b98ea8d..7dbb90f61 100644 --- a/installer/docker/Dockerfile +++ b/installer/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM caldweba/opengl-docker +FROM ubuntu:18.04 # avoid questions from debconf ENV DEBIAN_FRONTEND noninteractive @@ -32,8 +32,8 @@ RUN sudo pip3 install --no-cache-dir --upgrade pip && \ sudo apt-get update && \ sudo apt-get install --no-install-recommends -y \ gcc python3-dev && \ - sudo pip install --no-cache-dir matplotlib PyOpenGL \ - pyqt5 pyqtgraph scipy numpy nlopt psutil && \ + sudo pip install --no-cache-dir matplotlib \ + pyqt5 scipy numpy nlopt psutil && \ sudo apt-get -y remove --purge \ gcc python3-dev && \ sudo apt-get autoremove -y --purge && \ diff --git a/installer/ubuntu/hnn-ubuntu.sh b/installer/ubuntu/hnn-ubuntu.sh index 4af9990d8..d62a12bd1 100755 --- a/installer/ubuntu/hnn-ubuntu.sh +++ b/installer/ubuntu/hnn-ubuntu.sh @@ -101,8 +101,8 @@ if ! which nrnivmodl &> /dev/null; then fi echo "Installing python packages for HNN with pip..." | tee -a "$LOGFILE" -$PIP install --no-cache-dir --user matplotlib PyOpenGL \ - pyqt5 pyqtgraph scipy numpy nlopt psutil &>> "$LOGFILE" +$PIP install --no-cache-dir --user matplotlib \ + pyqt5 scipy numpy nlopt psutil &>> "$LOGFILE" # save dir installing hnn to startdir=$(pwd) diff --git a/installer/windows/hnn-windows.ps1 b/installer/windows/hnn-windows.ps1 index ccb092abf..4b0c5847a 100644 --- a/installer/windows/hnn-windows.ps1 +++ b/installer/windows/hnn-windows.ps1 @@ -390,7 +390,7 @@ if ($null -ne $script:VIRTUALENV) { $script:PYTHON = "$HOME\venv\hnn\Scripts\python.exe" if (Test-Python-3($script:PYTHON)) { # use pip3 for good measure - Start-Process "$HOME\venv\hnn\Scripts\pip3" "install PyOpenGL pyqtgraph matplotlib scipy PyQt5 psutil nlopt" -Wait + Start-Process "$HOME\venv\hnn\Scripts\pip3" "install matplotlib scipy PyQt5 psutil nlopt" -Wait } else { Write-Warning "Virtualenv failed to create a valid python3 environment" @@ -415,8 +415,10 @@ elseif ($null -ne $script:CONDA_PATH) { if (!$script:env_exists) { Write-Host "Setting up anaconda hnn environment..." - conda create -y -n hnn python=3.7 PyOpenGL pyqtgraph matplotlib scipy conda psutil + conda create -y -f environment.yml conda install -y -n hnn -c conda-forge nlopt + + pip install --upgrade https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master Set-Location $CONDA_ENV mkdir .\etc\conda\activate.d 2>&1>$null mkdir .\etc\conda\deactivate.d 2>&1>$null diff --git a/scripts/setup-travis-mac.sh b/scripts/setup-travis-mac.sh index 226b12471..c1a147fda 100755 --- a/scripts/setup-travis-mac.sh +++ b/scripts/setup-travis-mac.sh @@ -15,12 +15,10 @@ chmod +x "$HOME/miniconda.sh" export PATH=${HOME}/Miniconda3/bin:$PATH # create conda environment -conda create -n hnn --yes python=${PYTHON_VERSION} pip openmpi scipy numpy matplotlib pyqtgraph pyopengl psutil +conda create -y -f environment.yml source activate hnn && echo "activated conda HNN environment" # conda is faster to install nlopt conda install -y -n hnn -c conda-forge nlopt -pip install NEURON flake8 pytest pytest-cov coverage coveralls mne - echo "Install finished" diff --git a/setup.py b/setup.py new file mode 100644 index 000000000..61bb70ba0 --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +import setuptools + +setuptools.setup( + name="hnn", # Replace with your own username + version="1.4.0", + author="Blake Caldwell", + author_email="blake_caldwell@brown.edu", + description="Human Neocortical Neurosolver", + long_description='', + url="https://github.com/jonescompneurolab/hnn", + packages=setuptools.find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: Brown CS License", + "Operating System :: OS Independent", + ], + python_requires='>=3.6', +) From 154316117de3d0b34db3a9ccdf64be1d2f28fe36 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 3 Oct 2020 23:57:50 -0400 Subject: [PATCH 029/107] TST: cleanup testing after hnn-core switch --- .travis.yml | 178 ++++++++---------------------- environment.yml | 4 +- installer/ubuntu/hnn-ubuntu.sh | 62 +++++++---- installer/windows/hnn-windows.ps1 | 33 ++---- scripts/run-travis-wsl.sh | 6 +- scripts/setup-travis-linux.sh | 13 +++ scripts/setup-travis-mac.sh | 24 ---- scripts/setup-travis-osx.sh | 21 ++++ scripts/setup-travis-windows.sh | 83 ++------------ scripts/setup-travis-wsl.sh | 78 +++++++++++++ 10 files changed, 220 insertions(+), 282 deletions(-) create mode 100755 scripts/setup-travis-linux.sh delete mode 100755 scripts/setup-travis-mac.sh create mode 100755 scripts/setup-travis-osx.sh create mode 100755 scripts/setup-travis-wsl.sh diff --git a/.travis.yml b/.travis.yml index 8ada8576e..6e9218a46 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,68 +4,28 @@ matrix: include: # OSX - os: osx - name: "MacOS sierra" - osx_image: xcode9.2 - env: - - PYTHON_VERSION=3.7 - - NEURON_VERSION=7.7 - - - os: osx - name: "MacOS el capitan" - osx_image: xcode8 - env: - - PYTHON_VERSION=3.7 - - NEURON_VERSION=7.7 + name: "MacOS Catalina" + osx_image: xcode12 - - os: osx - name: "MacOS mojave" - osx_image: xcode11.3 - env: - - PYTHON_VERSION=3.7 - - NEURON_VERSION=7.7 - - - os: osx - name: "MacOS high sierra" - osx_image: xcode10.1 + # WSL + - os: windows + name: "WSL" env: - - PYTHON_VERSION=3.7 - - NEURON_VERSION=7.7 + - WSL_INSTALL=1 + - USE_CONDA=0 # Windows - os: windows name: "Windows" env: - - PYTHON_VERSION=3.7 - - NEURON_VERSION=7.7 + - WSL_INSTALL=0 # Linux - - os: linux - dist: xenial - name: "Ubuntu xenial" - env: - - NEURON_VERSION=7.7 - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - xvfb - - os: linux dist: bionic - name: "Ubuntu bionic" + name: "Ubuntu Bionic" env: - - NEURON_VERSION=7.7 - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - xvfb - - - os: linux - dist: disco - name: "Ubuntu disco" - env: - - NEURON_VERSION=7.7 + - USE_CONDA=1 apt: sources: - ubuntu-toolchain-r-test @@ -84,102 +44,52 @@ matrix: - xvfb before_install: - - set -e # error on any command failure - - | # function exports + - | # common environment exports for all subshells (scripts) export TRAVIS_TESTING=1 - - if [[ "${TRAVIS_OS_NAME}" == "windows" ]]; then - # for start_vcxsrv_print and stop_vcxsrv - source "scripts/docker_functions.sh" - set_globals - fi - - # source utility functions + export DISPLAY=:0 export LOGFILE="hnn_travis.log" - source scripts/utils.sh - export -f cleanup - - - | - if [ "${TRAVIS_OS_NAME}" == "osx" ]; then # install osx prerequisites - echo "Installing macOS prerequisites" - - scripts/setup-travis-mac.sh - export PATH=${HOME}/Miniconda3/bin:$PATH - export PATH=$PATH:/Applications/NEURON-${NEURON_VERSION}/nrn/x86_64/bin - export PYTHONPATH=/Applications/NEURON-${NEURON_VERSION}/nrn/lib/python:$PYTHONPATH - export PYTHON=python3 - - source activate hnn && echo "activated conda HNN environment" + - | # Run prerequisite installation script + if [ "${WSL_INSTALL}" ] && [ "${WSL_INSTALL}" -eq 1 ]; then + echo "Installing WSL prerequisites" + scripts/setup-travis-wsl.sh + else + echo "Installing ${TRAVIS_OS_NAME} prerequisites" + scripts/setup-travis-${TRAVIS_OS_NAME}.sh + + source "$HOME/Miniconda3/etc/profile.d/conda.sh" + conda activate hnn fi - - | # windows - if [ "${TRAVIS_OS_NAME}" == "windows" ]; then - echo "Installing windows prerequisites" - - scripts/setup-travis-windows.sh - - # add miniconda python to the path - export PATH=$PATH:$HOME/Miniconda3/Scripts - export PATH=$HOME/Miniconda3/envs/hnn/:$PATH - export PATH=$HOME/Miniconda3/envs/hnn/Scripts:$PATH - export PATH=$HOME/Miniconda3/envs/hnn/Library/bin:$PATH - - # for using X server - export PATH="$PATH:/c/Program\ Files/VcXsrv" - # for MESA dll's - export PATH=$PATH:/c/tools/msys64/mingw64/bin - - # for sharing with WSL environment - export WSLENV=TRAVIS_TESTING/u - - # set other variables for neuron and HNN - export PATH=$PATH:/c/nrn/bin - export DISPLAY="localhost:0" - export NEURONHOME=/c/nrn - export PYTHON=python +install: + - | # set up hnn module and testing packages + if [[ "${WSL_INSTALL}" -ne 1 ]]; then + pip install flake8 pytest pytest-cov coverage coveralls mne pytest-qt + command cd $TRAVIS_BUILD_DIR && python setup.py install + else + wsl -- bash -ec "pip install flake8 pytest pytest-cov coverage coveralls mne pytest-qt" + wsl -- bash -ec "python setup.py install" fi - - | # Linux - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then - echo "Installing Linux prerequisites" - - export DISPLAY=:0 - export PATH=/usr/bin:/usr/local/bin:$PATH - - echo "Starting fake Xserver" - Xvfb $DISPLAY -listen tcp -screen 0 1024x768x24 > /dev/null & - - echo "Starting Ubuntu install script" - installer/ubuntu/hnn-ubuntu.sh - - NLOPT_LIB=$(ls -d $HOME/.local/lib/python*/site-packages) - echo $NLOPT_LIB - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$NLOPT_LIB - export PYTHON=python3 - # test X server - xset -display $DISPLAY -q > /dev/null; - fi +script: + - | # run tests + if [[ "${WSL_INSTALL}" -eq 1 ]]; then + # for start_vcxsrv_print and stop_vcxsrv + source "scripts/docker_functions.sh" + set_globals -install: - - | # testing packages - pip install flake8 pytest pytest-cov coverage coveralls mne - - | - cd $TRAVIS_BUILD_DIR - python setup.py install + # source utility functions + source scripts/utils.sh + export -f cleanup -script: - - | # Check that the GUI starts on host OS - echo "Testing GUI on host OS..." - $PYTHON hnn.py - - | # Run py.test that includes running a simulation and verifying results - echo "Running Python tests on host OS..." - py.test --cov=. hnn/tests/ - - | # Test WSL-based version on windows (needs VcXsrv) - if [[ "${TRAVIS_OS_NAME}" == "windows" ]]; then find_command_suggested_path "vcxsrv" "/c/Program Files/VcXsrv" && \ start_vcxsrv_print || script_fail wsl -- bash -e //home/hnn_user/hnn/scripts/run-travis-wsl.sh stop_vcxsrv || script_fail + else + echo "Testing GUI on host OS..." + python hnn.py + echo "Running Python tests on host OS..." + py.test --cov=. hnn/tests/ fi after_success: diff --git a/environment.yml b/environment.yml index 5f9f30732..2682ca83a 100644 --- a/environment.yml +++ b/environment.yml @@ -4,7 +4,6 @@ channels: dependencies: - python=3.7 - pip -- openmpi - numpy - scipy - matplotlib @@ -12,5 +11,4 @@ dependencies: - pip: - NEURON - https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master - - PyQt5>=5.10,<5.14; platform_system == "Darwin" - - PyQt5>=5.10; platform_system != "Darwin" + - PyQt5 \ No newline at end of file diff --git a/installer/ubuntu/hnn-ubuntu.sh b/installer/ubuntu/hnn-ubuntu.sh index d62a12bd1..abfaedbd8 100755 --- a/installer/ubuntu/hnn-ubuntu.sh +++ b/installer/ubuntu/hnn-ubuntu.sh @@ -61,17 +61,17 @@ echo "Using python version $PYTHON_VERSION" | tee -a "$LOGFILE" export DEBIAN_FRONTEND=noninteractive echo "Updating package repository..." | tee -a "$LOGFILE" -sudo -E apt-get update &>> "$LOGFILE" +sudo -E apt-get update &> "$LOGFILE" echo "Updating OS python packages..." | tee -a "$LOGFILE" if [[ "${PYTHON_VERSION}" =~ "3.7" ]] && [[ "$DISTRIB" =~ "bionic" ]]; then - sudo -E apt-get install --no-install-recommends -y python3.7 python3-pip python3.7-tk python3.7-dev &>> "$LOGFILE" && \ - sudo python3.7 -m pip install --upgrade pip setuptools &>> "$LOGFILE" + sudo -E apt-get install --no-install-recommends -y python3.7 python3-pip python3.7-dev &> "$LOGFILE" && \ + sudo python3.7 -m pip install --upgrade pip setuptools &> "$LOGFILE" sudo ln -s /usr/lib/python3/dist-packages/apt_pkg.cpython-36m-x86_64-linux-gnu.so \ /usr/lib/python3/dist-packages/apt_pkg.so else - sudo -E apt-get install --no-install-recommends -y python3 python3-pip python3-tk python3-setuptools &>> "$LOGFILE" && \ - sudo pip3 install --upgrade pip &>> "$LOGFILE" + sudo -E apt-get install --no-install-recommends -y python3 python3-pip python3-setuptools &> "$LOGFILE" && \ + sudo pip3 install --upgrade pip &> "$LOGFILE" fi if which python3 &> /dev/null; then @@ -90,7 +90,25 @@ echo "Using python: $PYTHON with pip: $PIP" | tee -a "$LOGFILE" echo "Installing OS compilation toolchain..." | tee -a "$LOGFILE" # get prerequisites from pip. requires gcc to build psutil sudo -E apt-get install --no-install-recommends -y \ - make gcc g++ python3-dev &>> "$LOGFILE" + make gcc g++ python3-dev &> "$LOGFILE" + +URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh" +FILENAME="$HOME/miniconda.sh" +start_download "$FILENAME" "$URL" + +echo "Installing miniconda..." +chmod +x "$HOME/miniconda.sh" +"$HOME/miniconda.sh" -b -p "${HOME}/Miniconda3" +export PATH=${HOME}/Miniconda3/bin:$PATH + +# create conda environment +conda env create -f environment.yml + +# conda is faster to install nlopt +conda install -y -n hnn -c conda-forge nlopt +conda install -y -n hnn mpi4py + +source activate hnn && echo "activated conda HNN environment" $PIP install --no-cache-dir NEURON @@ -102,7 +120,17 @@ fi echo "Installing python packages for HNN with pip..." | tee -a "$LOGFILE" $PIP install --no-cache-dir --user matplotlib \ - pyqt5 scipy numpy nlopt psutil &>> "$LOGFILE" + pyqt5 scipy numpy nlopt psutil &> "$LOGFILE" +echo "Downloading runtime prerequisite packages..." | tee -a "$LOGFILE" +apt-get download \ + openmpi-bin lsof libfontconfig1 libxext6 libx11-xcb1 libxcb-glx0 \ + libxkbcommon-x11-0 libgl1-mesa-glx \ + libc6-dev libtinfo-dev libncurses5-dev \ + libx11-dev libreadline-dev \ + libxcb-icccm4 libxcb-util1 libxcb-image0 libxcb-keysyms1 \ + libxcb-render0 libxcb-shape0 libxcb-randr0 libxcb-render-util0 \ + libxcb-xinerama0 &> "$LOGFILE" & +APT_DOWNLOAD=$! # save dir installing hnn to startdir=$(pwd) @@ -115,11 +143,11 @@ if [[ $TRAVIS_TESTING -ne 1 ]]; then cd hnn_source_code if [ -d "$source_code_dir/.git" ]; then - git pull origin master &>> "$LOGFILE" + git pull origin master &> "$LOGFILE" fi else echo "Downloading and extracting HNN..." | tee -a "$LOGFILE" - wget --no-hsts --no-check-certificate -O hnn.tar.gz https://github.com/jonescompneurolab/hnn/releases/latest/download/hnn.tar.gz | tee -a "$LOGFILE" + wget -O hnn.tar.gz https://github.com/jonescompneurolab/hnn/releases/latest/download/hnn.tar.gz | tee -a "$LOGFILE" mkdir hnn_source_code tar -x --strip-components 1 -f hnn.tar.gz -C hnn_source_code &>> "$LOGFILE" && \ cd hnn_source_code &>> "$LOGFILE" @@ -132,10 +160,6 @@ else fi fi -echo "Building HNN..." | tee -a "$LOGFILE" -make -j4 &>> "$LOGFILE" -MAKE_PID=$! - # create the global session variables echo '# these lines define global session variables for HNN' >> ~/.bashrc echo "export PATH=\$PATH:\"$source_code_dir\"" >> ~/.bashrc @@ -147,7 +171,7 @@ if [[ -d "$HOME/Desktop" ]]; then cp -f hnn.desktop "$HOME/Desktop" && \ sed -i "s~/home/hnn_user\(.*\)$~\"$startdir\"\1~g" "$HOME/Desktop/hnn.desktop" && \ chmod +x "$HOME/Desktop/hnn.desktop" - } &>> "$LOGFILE" + } &> "$LOGFILE" fi echo "Installing prerequisites..." | tee -a "$LOGFILE" @@ -160,17 +184,11 @@ sudo -E apt-get install --no-install-recommends -y \ libx11-dev libreadline-dev \ libxcb-icccm4 libxcb-util1 libxcb-image0 libxcb-keysyms1 \ libxcb-render0 libxcb-shape0 libxcb-randr0 libxcb-render-util0 \ - libxcb-xinerama0 libxcb-xfixes0 &>> "$LOGFILE" + libxcb-xinerama0 libxcb-xfixes0 &> "$LOGFILE" # Clean up a little echo "Cleaning up..." | tee -a "$LOGFILE" -sudo -E apt-get clean &>> "$LOGFILE" - -if [[ $TRAVIS_TESTING -ne 1 ]]; then - echo "Waiting for HNN module build to finish..." - NAME="building HNN modules" - wait_for_pid "${MAKE_PID}" "$NAME" -fi +sudo -E apt-get clean &> "$LOGFILE" echo "HNN installation successful" | tee -a "$LOGFILE" echo "Source code is at $source_code_dir" | tee -a "$LOGFILE" diff --git a/installer/windows/hnn-windows.ps1 b/installer/windows/hnn-windows.ps1 index 4b0c5847a..1c377ff31 100644 --- a/installer/windows/hnn-windows.ps1 +++ b/installer/windows/hnn-windows.ps1 @@ -379,6 +379,13 @@ if ($proc1) { Write-Host "Miniconda is finished" } +if ($proc2) { + Write-Host "Waiting for NEURON install to finish..." + $proc2.WaitForExit() 2>$null + Update-User-Paths("$script:NEURON_PATH\bin") + Write-Host "NEURON is finished" +} + # setup python with virtualenv or 'conda if ($null -ne $script:VIRTUALENV) { Write-Host "Creating Python virtualenv at $HOME\venv\hnn..." @@ -391,6 +398,7 @@ if ($null -ne $script:VIRTUALENV) { if (Test-Python-3($script:PYTHON)) { # use pip3 for good measure Start-Process "$HOME\venv\hnn\Scripts\pip3" "install matplotlib scipy PyQt5 psutil nlopt" -Wait + Start-Process "$HOME\venv\hnn\Scripts\pip3" "install --upgrade https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master" -Wait } else { Write-Warning "Virtualenv failed to create a valid python3 environment" @@ -415,10 +423,10 @@ elseif ($null -ne $script:CONDA_PATH) { if (!$script:env_exists) { Write-Host "Setting up anaconda hnn environment..." - conda create -y -f environment.yml + conda env create -f environment.yml conda install -y -n hnn -c conda-forge nlopt - pip install --upgrade https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master + pip install mpi4py Set-Location $CONDA_ENV mkdir .\etc\conda\activate.d 2>&1>$null mkdir .\etc\conda\deactivate.d 2>&1>$null @@ -435,27 +443,6 @@ else { } - -if ($proc2) { - Write-Host "Waiting for NEURON install to finish..." - $proc2.WaitForExit() 2>$null - Update-User-Paths("$script:NEURON_PATH\bin") - Write-Host "NEURON is finished" -} - -if (!(Test-Path "$HNN_PATH\nrnmech.dll" -PathType Leaf)) { - Write-Host "Creating nrnmech.dll" - Set-Location $HNN_PATH\mod - Start-Process "$script:NEURON_PATH\mingw\usr\bin\sh.exe" "$script:NEURON_ESC_PATH/lib/mknrndll.sh C:\nrn\" - $obj = New-Object -com Wscript.Shell - sleep -s 10 - $obj.SendKeys("{ENTER}") - Copy-Item $HNN_PATH\mod\nrnmech.dll -Destination $HNN_PATH -} -else { - Write-Host "nrnmech.dll already exists $HNN_PATH\nrnmech.dll" -} - Write-Host "" Write-Host "Finished installing HNN and prerequisites." Write-Host "Activate the environment from cmd.exe (not Powershell):" diff --git a/scripts/run-travis-wsl.sh b/scripts/run-travis-wsl.sh index 9fdca1133..23517a0af 100755 --- a/scripts/run-travis-wsl.sh +++ b/scripts/run-travis-wsl.sh @@ -1,7 +1,7 @@ #!/bin/bash +set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -export TRAVIS_TESTING=1 export PATH="$PATH:$HOME/.local/bin" export OMPI_MCA_btl_vader_single_copy_mechanism=none @@ -11,5 +11,5 @@ cd $DIR/../ export DISPLAY=:0 python3 hnn.py -echo "Testing MPI in WSL..." -mpiexec -np 2 nrniv -mpi -python run.py \ No newline at end of file +echo "Running Python tests on WSL..." +py.test --cov=. hnn/tests/ \ No newline at end of file diff --git a/scripts/setup-travis-linux.sh b/scripts/setup-travis-linux.sh new file mode 100755 index 000000000..222c7bacc --- /dev/null +++ b/scripts/setup-travis-linux.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e + +export PATH=/usr/bin:/usr/local/bin:$PATH + +echo "Starting fake Xserver" +Xvfb $DISPLAY -listen tcp -screen 0 1024x768x24 > /dev/null & + +echo "Starting Ubuntu install script" +installer/ubuntu/hnn-ubuntu.sh + +# test X server +xset -display $DISPLAY -q > /dev/null; diff --git a/scripts/setup-travis-mac.sh b/scripts/setup-travis-mac.sh deleted file mode 100755 index c1a147fda..000000000 --- a/scripts/setup-travis-mac.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -set -e - -export TRAVIS_TESTING=1 - -source scripts/utils.sh - -URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh" -FILENAME="$HOME/miniconda.sh" -start_download "$FILENAME" "$URL" - -echo "Installing miniconda..." -chmod +x "$HOME/miniconda.sh" -"$HOME/miniconda.sh" -b -p "${HOME}/Miniconda3" -export PATH=${HOME}/Miniconda3/bin:$PATH - -# create conda environment -conda create -y -f environment.yml -source activate hnn && echo "activated conda HNN environment" - -# conda is faster to install nlopt -conda install -y -n hnn -c conda-forge nlopt - -echo "Install finished" diff --git a/scripts/setup-travis-osx.sh b/scripts/setup-travis-osx.sh new file mode 100755 index 000000000..5cc9193f9 --- /dev/null +++ b/scripts/setup-travis-osx.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +# we use start_download from utils.sh +source "$DIR/utils.sh" + +URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh" +FILENAME="$HOME/miniconda.sh" +start_download "$FILENAME" "$URL" + +echo "Installing miniconda..." +bash "$HOME/miniconda.sh" -b -p "${HOME}/Miniconda3" +source "$HOME/Miniconda3/etc/profile.d/conda.sh" + +# create conda environment +conda env create -f environment.yml +conda install -y -n hnn openmpi mpi4py +# conda is faster to install nlopt +conda install -y -n hnn -c conda-forge nlopt \ No newline at end of file diff --git a/scripts/setup-travis-windows.sh b/scripts/setup-travis-windows.sh index 5508fb027..fa8af0628 100755 --- a/scripts/setup-travis-windows.sh +++ b/scripts/setup-travis-windows.sh @@ -1,50 +1,6 @@ #!/bin/bash set -e -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -# we use wait_for_pid and start_download from utils.sh -source "$DIR/utils.sh" - -[[ $LOGFILE ]] || LOGFILE="hnn_travis.log" - -echo "Installing Ubuntu WSL..." -powershell.exe -ExecutionPolicy Bypass -File ./scripts/setup-travis-wsl.ps1 & -WSL_PID=$! - -# prepare for installing msys2 -[[ ! -f C:/tools/msys64/msys2_shell.cmd ]] && rm -rf C:/tools/msys64 -choco uninstall -y mingw - -# enable windows remoting service to log in as a different user to run tests -powershell -Command 'Start-Service -Name WinRM' > /dev/null -powershell -Command 'Start-Service -Name seclogon' > /dev/null - -# change settings to allow a blank password for TEST_USER -reg add HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Lsa //t REG_DWORD //v LimitBlankPasswordUse //d 0 //f 2>&1 > /dev/null -net accounts /minpwlen:0 > /dev/null - -# create the test user with a space in the username -echo "Creating user: \"test user\"..." -TEST_USER="test user" -echo > "$HOME/test_user_creds" -net user "$TEST_USER" //ADD "//homedir:\\\\%computername%\\users\\$TEST_USER" > /dev/null < "$HOME/test_user_creds" - -# add to administrators group -net localgroup administrators "$TEST_USER" //add > /dev/null - -# run a command as new user to create home directory -runas //user:"$TEST_USER" "cmd /C whoami" > /dev/null < "$HOME/test_user_creds" - -# copy hnn source to test user's home directory -TEST_USER_DIR="/c/Users/$TEST_USER" -if [ -d "$TEST_USER_DIR" ]; then - cp -r "$(pwd)" "$TEST_USER_DIR/" -else - echo "No user home directory created at $TEST_USER_DIR" - exit 2 -fi - echo "Installing Microsoft MPI" powershell -command "(New-Object System.Net.WebClient).DownloadFile('https://github.com/microsoft/Microsoft-MPI/releases/download/v10.1.1/msmpisetup.exe', 'msmpisetup.exe')" && \ ./msmpisetup.exe -unattend && \ @@ -52,35 +8,16 @@ powershell -command "(New-Object System.Net.WebClient).DownloadFile('https://git echo "Running HNN Windows install script..." powershell.exe -ExecutionPolicy Bypass -File ./installer/windows/hnn-windows.ps1 -# add miniconda python to the path -export PATH=$PATH:$HOME/Miniconda3/Scripts -export PATH=$HOME/Miniconda3/envs/hnn/:$PATH -export PATH=$HOME/Miniconda3/envs/hnn/Scripts:$PATH -export PATH=$HOME/Miniconda3/envs/hnn/Library/bin:$PATH - -echo "Installing msys2 with choco..." -choco upgrade --no-progress -y msys2 &> /dev/null - -echo "Downloading VcXsrv..." -URL="https://downloads.sourceforge.net/project/vcxsrv/vcxsrv/1.20.8.1/vcxsrv-64.1.20.8.1.installer.exe" -FILENAME="$HOME/vcxsrv-64.1.20.8.1.installer.exe" -start_download "$FILENAME" "$URL" > /dev/null - -echo "Installing VcXsrv..." -cmd //c "$HOME/vcxsrv-64.1.20.8.1.installer.exe /S" -# get opengl32.dll from mesa -# this is needed to be able to start vcxsrv -export msys2='cmd //C RefreshEnv.cmd ' -export msys2+='& set MSYS=winsymlinks:nativestrict ' -export msys2+='& C:\\tools\\msys64\\msys2_shell.cmd -defterm -no-start' -export mingw64="$msys2 -mingw64 -full-path -here -c "\"\$@"\" --" -export msys2+=" -msys2 -c "\"\$@"\" --" -$msys2 pacman --sync --noconfirm --needed mingw-w64-x86_64-mesa +source "$HOME/Miniconda3/etc/profile.d/conda.sh" +conda activate hnn -echo "Downloading python test packages..." -pip download flake8 pytest pytest-cov coverage coveralls mne +# # add miniconda python to the path +# export PATH=$PATH:$HOME/Miniconda3/Scripts +# export PATH=$HOME/Miniconda3/envs/hnn/:$PATH +# export PATH=$HOME/Miniconda3/envs/hnn/Scripts:$PATH +# export PATH=$HOME/Miniconda3/envs/hnn/Library/bin:$PATH -echo "Waiting for WSL install to finish..." -NAME="installing WSL" -wait_for_pid "${WSL_PID}" "$NAME" || script_fail +# set other variables for neuron and HNN +export PATH=$PATH:/c/nrn/bin +export NEURONHOME=/c/nrn diff --git a/scripts/setup-travis-wsl.sh b/scripts/setup-travis-wsl.sh new file mode 100755 index 000000000..0d360b3bf --- /dev/null +++ b/scripts/setup-travis-wsl.sh @@ -0,0 +1,78 @@ +#!/bin/bash +set -e + +# for sharing with WSL environment +export WSLENV=TRAVIS_TESTING/u + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +# we use wait_for_pid and start_download from utils.sh +source "$DIR/utils.sh" + +echo "Installing Ubuntu WSL..." +powershell.exe -ExecutionPolicy Bypass -File ./scripts/setup-travis-wsl.ps1 & +WSL_PID=$! + +# prepare for installing msys2 +[[ ! -f C:/tools/msys64/msys2_shell.cmd ]] && rm -rf C:/tools/msys64 +choco uninstall -y mingw + +# enable windows remoting service to log in as a different user to run tests +powershell -Command 'Start-Service -Name WinRM' > /dev/null +powershell -Command 'Start-Service -Name seclogon' > /dev/null + +# change settings to allow a blank password for TEST_USER +reg add HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Lsa //t REG_DWORD //v LimitBlankPasswordUse //d 0 //f 2>&1 > /dev/null +net accounts /minpwlen:0 > /dev/null + +# create the test user with a space in the username +echo "Creating user: \"test user\"..." +TEST_USER="test user" +echo > "$HOME/test_user_creds" +net user "$TEST_USER" //ADD "//homedir:\\\\%computername%\\users\\$TEST_USER" > /dev/null < "$HOME/test_user_creds" + +# add to administrators group +net localgroup administrators "$TEST_USER" //add > /dev/null + +# run a command as new user to create home directory +runas //user:"$TEST_USER" "cmd /C whoami" > /dev/null < "$HOME/test_user_creds" + +# copy hnn source to test user's home directory +TEST_USER_DIR="/c/Users/$TEST_USER" +if [ -d "$TEST_USER_DIR" ]; then + cp -r "$(pwd)" "$TEST_USER_DIR/" +else + echo "No user home directory created at $TEST_USER_DIR" + exit 2 +fi + +echo "Installing msys2 with choco..." +choco upgrade --no-progress -y msys2 &> /dev/null + +echo "Downloading VcXsrv..." +URL="https://downloads.sourceforge.net/project/vcxsrv/vcxsrv/1.20.8.1/vcxsrv-64.1.20.8.1.installer.exe" +FILENAME="$HOME/vcxsrv-64.1.20.8.1.installer.exe" +start_download "$FILENAME" "$URL" > /dev/null + +echo "Installing VcXsrv..." +cmd //c "$HOME/vcxsrv-64.1.20.8.1.installer.exe /S" + +# # get opengl32.dll from mesa +# # this is needed to be able to start vcxsrv +# export msys2='cmd //C RefreshEnv.cmd ' +# export msys2+='& set MSYS=winsymlinks:nativestrict ' +# export msys2+='& C:\\tools\\msys64\\msys2_shell.cmd -defterm -no-start' +# export mingw64="$msys2 -mingw64 -full-path -here -c "\"\$@"\" --" +# export msys2+=" -msys2 -c "\"\$@"\" --" +# $msys2 pacman --sync --noconfirm --needed mingw-w64-x86_64-mesa + +# # for MESA dll's +# export PATH=$PATH:/c/tools/msys64/mingw64/bin + +# for using X server +export PATH="$PATH:/c/Program\ Files/VcXsrv" + +echo "Waiting for WSL install to finish..." +NAME="installing WSL" +wait_for_pid "${WSL_PID}" "$NAME" || script_fail + From 5a247d36e36241bf8af60954dba3f9eb5bd835e4 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 4 Oct 2020 09:39:00 -0400 Subject: [PATCH 030/107] ENH: option to use conda or pip for hnn-ubuntu.sh --- installer/ubuntu/hnn-ubuntu.sh | 46 ++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/installer/ubuntu/hnn-ubuntu.sh b/installer/ubuntu/hnn-ubuntu.sh index abfaedbd8..eb7e6db7e 100755 --- a/installer/ubuntu/hnn-ubuntu.sh +++ b/installer/ubuntu/hnn-ubuntu.sh @@ -3,6 +3,7 @@ set -e [[ "$LOGFILE" ]] || LOGFILE="ubuntu_install.log" +[[ "$USE_CONDA" ]] || USE_CONDA=0 function start_download { echo "Downloading $2" @@ -65,12 +66,12 @@ sudo -E apt-get update &> "$LOGFILE" echo "Updating OS python packages..." | tee -a "$LOGFILE" if [[ "${PYTHON_VERSION}" =~ "3.7" ]] && [[ "$DISTRIB" =~ "bionic" ]]; then - sudo -E apt-get install --no-install-recommends -y python3.7 python3-pip python3.7-dev &> "$LOGFILE" && \ + sudo -E apt-get install --no-install-recommends -y python3.7 python3-pip python3-tk python3.7-dev &> "$LOGFILE" && \ sudo python3.7 -m pip install --upgrade pip setuptools &> "$LOGFILE" sudo ln -s /usr/lib/python3/dist-packages/apt_pkg.cpython-36m-x86_64-linux-gnu.so \ /usr/lib/python3/dist-packages/apt_pkg.so else - sudo -E apt-get install --no-install-recommends -y python3 python3-pip python3-setuptools &> "$LOGFILE" && \ + sudo -E apt-get install --no-install-recommends -y python3 python3-pip python3-tk python3-setuptools &> "$LOGFILE" && \ sudo pip3 install --upgrade pip &> "$LOGFILE" fi @@ -87,28 +88,41 @@ elif which python &> /dev/null; then fi echo "Using python: $PYTHON with pip: $PIP" | tee -a "$LOGFILE" +if [[ "$USE_CONDA" -eq 0 ]]; then + echo "Downloading python packages for HNN with pip..." | tee -a "$LOGFILE" + $PIP download matplotlib PyOpenGL \ + pyqt5 pyqtgraph scipy numpy nlopt psutil &> "$LOGFILE" & + PIP_PID=$! +fi + echo "Installing OS compilation toolchain..." | tee -a "$LOGFILE" # get prerequisites from pip. requires gcc to build psutil sudo -E apt-get install --no-install-recommends -y \ make gcc g++ python3-dev &> "$LOGFILE" -URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh" -FILENAME="$HOME/miniconda.sh" -start_download "$FILENAME" "$URL" +if [[ "$USE_CONDA" -eq 0 ]]; then + echo "Waiting for python packages for HNN downloads to finish..." + NAME="downloading python packages for HNN " + wait_for_pid "${PIP_PID}" "$NAME" +else + URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh" + FILENAME="$HOME/miniconda.sh" + start_download "$FILENAME" "$URL" -echo "Installing miniconda..." -chmod +x "$HOME/miniconda.sh" -"$HOME/miniconda.sh" -b -p "${HOME}/Miniconda3" -export PATH=${HOME}/Miniconda3/bin:$PATH + echo "Installing miniconda..." + chmod +x "$HOME/miniconda.sh" + "$HOME/miniconda.sh" -b -p "${HOME}/Miniconda3" + export PATH=${HOME}/Miniconda3/bin:$PATH -# create conda environment -conda env create -f environment.yml + # create conda environment + conda env create -f environment.yml -# conda is faster to install nlopt -conda install -y -n hnn -c conda-forge nlopt -conda install -y -n hnn mpi4py + # conda is faster to install nlopt + conda install -y -n hnn -c conda-forge nlopt + conda install -y -n hnn mpi4py -source activate hnn && echo "activated conda HNN environment" + source activate hnn && echo "activated conda HNN environment" +fi $PIP install --no-cache-dir NEURON @@ -116,7 +130,7 @@ $PIP install --no-cache-dir NEURON if ! which nrnivmodl &> /dev/null; then export PATH="$PATH:$HOME/.local/bin" echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc -fi +fi echo "Installing python packages for HNN with pip..." | tee -a "$LOGFILE" $PIP install --no-cache-dir --user matplotlib \ From b6c446e197ad412811b4d205777812402ae88ad4 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 4 Oct 2020 12:51:47 -0400 Subject: [PATCH 031/107] TST: keep handles of GUI dialog boxes for testing --- hnn/hnn_qt5.py | 68 +++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 203cf1fe4..bee134dbf 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -1092,14 +1092,13 @@ def saveparams (self, checkok = True): oktosave = True if os.path.isfile(tmpf) and checkok: self.show() - oktosave = False msg = QMessageBox() - msg.setIcon(QMessageBox.Warning) - msg.setText(tmpf + ' already exists. Over-write?') - msg.setWindowTitle('Over-write file(s)?') - msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) - if msg.exec_() == QMessageBox.Ok: - oktosave = True + ret = msg.warning(self, 'Over-write file(s)?', + tmpf + ' already exists. Over-write?', + QMessageBox.Ok | QMessageBox.Cancel, + QMessageBox.Ok) + if ret == QMessageBox.Cancel: + oktosave = False if oktosave: with open(tmpf,'w') as fp: @@ -1185,11 +1184,6 @@ def __init__ (self): default_param = os.path.join(get_output_dir(), 'data', 'default') first_load = not (os.path.exists(default_param)) - if "TRAVIS_TESTING" in os.environ and os.environ["TRAVIS_TESTING"] == "1": - print("Exiting because HNN was started with TRAVIS_TESTING=1") - qApp.quit() - exit(0) - if first_load: QMessageBox.information(self, "HNN", "Welcome to HNN! Default parameter file loaded. " "Press 'Run Simulation' to display simulation output") @@ -1641,28 +1635,28 @@ def initMenu (self): # view menu - to view drawing/visualizations viewMenu = self.menubar.addMenu('&View') - viewDipoleAction = QAction('View Simulation Dipoles',self) - viewDipoleAction.setStatusTip('View Simulation Dipoles') - viewDipoleAction.triggered.connect(self.showDipolePlot) - viewMenu.addAction(viewDipoleAction) - viewRasterAction = QAction('View Simulation Spiking Activity',self) - viewRasterAction.setStatusTip('View Simulation Raster Plot') - viewRasterAction.triggered.connect(self.showRasterPlot) - viewMenu.addAction(viewRasterAction) - viewPSDAction = QAction('View PSD',self) - viewPSDAction.setStatusTip('View PSD') - viewPSDAction.triggered.connect(self.showPSDPlot) - viewMenu.addAction(viewPSDAction) - - viewSomaVAction = QAction('View Somatic Voltage',self) - viewSomaVAction.setStatusTip('View Somatic Voltage') - viewSomaVAction.triggered.connect(self.showSomaVPlot) - viewMenu.addAction(viewSomaVAction) - - viewSpecAction = QAction('View Spectrograms',self) - viewSpecAction.setStatusTip('View Spectrograms/Dipoles from Experimental Data') - viewSpecAction.triggered.connect(self.showSpecPlot) - viewMenu.addAction(viewSpecAction) + self.viewDipoleAction = QAction('View Simulation Dipoles',self) + self.viewDipoleAction.setStatusTip('View Simulation Dipoles') + self.viewDipoleAction.triggered.connect(self.showDipolePlot) + viewMenu.addAction(self.viewDipoleAction) + self.viewRasterAction = QAction('View Simulation Spiking Activity',self) + self.viewRasterAction.setStatusTip('View Simulation Raster Plot') + self.viewRasterAction.triggered.connect(self.showRasterPlot) + viewMenu.addAction(self.viewRasterAction) + self.viewPSDAction = QAction('View PSD',self) + self.viewPSDAction.setStatusTip('View PSD') + self.viewPSDAction.triggered.connect(self.showPSDPlot) + viewMenu.addAction(self.viewPSDAction) + + self.viewSomaVAction = QAction('View Somatic Voltage',self) + self.viewSomaVAction.setStatusTip('View Somatic Voltage') + self.viewSomaVAction.triggered.connect(self.showSomaVPlot) + viewMenu.addAction(self.viewSomaVAction) + + self.viewSpecAction = QAction('View Spectrograms',self) + self.viewSpecAction.setStatusTip('View Spectrograms/Dipoles from Experimental Data') + self.viewSpecAction.triggered.connect(self.showSpecPlot) + viewMenu.addAction(self.viewSpecAction) viewMenu.addSeparator() viewSchemAction = QAction('View Model Schematics',self) @@ -1750,10 +1744,10 @@ def addButtons (self, gRow): self.grid.addWidget(self.btnsim, gRow, 6, 1, 3) self.qbtn = qbtn = QPushButton('Quit', self) - qbtn.clicked.connect(QCoreApplication.instance().quit) + qbtn.clicked.connect(QApplication.exit) qbtn.resize(qbtn.sizeHint()) self.grid.addWidget(self.qbtn, gRow, 9, 1, 3) - + def shownetparamwin (self): bringwintotop(self.baseparamwin.netparamwin) def showoptparamwin (self): bringwintotop(self.baseparamwin.optparamwin) def showdistparamwin (self): bringwintotop(self.erselectdistal) @@ -2039,7 +2033,7 @@ def done (self, optMode, except_msg): msg += "running sim " if failed: - QMessageBox.information(self, "Failed!", msg + "using " + self.baseparamwin.paramfn + '. Check simulation log or console for error messages') + QMessageBox.critical(self, "Failed!", msg + "using " + self.baseparamwin.paramfn + '. Check simulation log or console for error messages') else: data_dir = os.path.join(get_output_dir(), 'data') sim_dir = os.path.join(data_dir, self.baseparamwin.params['sim_prefix']) From 408229006fb3c400b088d5073472220f15b60dfb Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 4 Oct 2020 12:55:01 -0400 Subject: [PATCH 032/107] TST: use pytest-qt for testing GUI --- hnn/tests/test_compare_hnn.py | 38 +++++++++++---- hnn/tests/test_gui.py | 18 ++++++++ hnn/tests/test_view_windows.py | 84 +++++++++++++++++----------------- 3 files changed, 90 insertions(+), 50 deletions(-) create mode 100644 hnn/tests/test_gui.py diff --git a/hnn/tests/test_compare_hnn.py b/hnn/tests/test_compare_hnn.py index 90579ad9c..49bc18456 100644 --- a/hnn/tests/test_compare_hnn.py +++ b/hnn/tests/test_compare_hnn.py @@ -1,22 +1,44 @@ import os.path as op +import os +import sys from numpy import loadtxt from numpy.testing import assert_allclose from mne.utils import _fetch_file +from PyQt5 import QtWidgets, QtCore -from ... import run +from hnn import HNNGUI -def test_hnn(): + +def test_hnn(qtbot, monkeypatch): """Test to check that HNN produces consistent results""" - self.runthread = RunSimThread(self.p, self.d, ntrial, ncore, - self.waitsimwin, params, opt=False, - baseparamwin=None, mainwin=None) + # for pressing exit button + exit_calls = [] + monkeypatch.setattr(QtWidgets.QApplication, "exit", + lambda: exit_calls.append(1)) + + # skip in warning messages + monkeypatch.setattr(QtWidgets.QMessageBox, "warning", + lambda *args: QtWidgets.QMessageBox.Ok) + monkeypatch.setattr(QtWidgets.QMessageBox, "information", + lambda *args: QtWidgets.QMessageBox.Ok) + + main = HNNGUI() + qtbot.addWidget(main) + + # start the simulation by pressing the button + qtbot.mouseClick(main.btnsim, QtCore.Qt.LeftButton) + qtbot.waitUntil(lambda: main.runningsim) + + # wait up to 100 seconds for simulation to finish + qtbot.waitUntil(lambda: not main.runningsim, 100000) + qtbot.mouseClick(main.qbtn, QtCore.Qt.LeftButton) + assert exit_calls == [1] - # print all messages (including error messages) - print('STDOUT', out) - print('STDERR', err) + # only testing default configuration with 1 trial + ntrials = 1 for trial in range(ntrials): print("Checking data for trial %d" % trial) diff --git a/hnn/tests/test_gui.py b/hnn/tests/test_gui.py new file mode 100644 index 000000000..bd11c8642 --- /dev/null +++ b/hnn/tests/test_gui.py @@ -0,0 +1,18 @@ +from PyQt5 import QtWidgets, QtCore + +from hnn import HNNGUI + + +def test_HNNGUI(qtbot): + main = HNNGUI() + qtbot.addWidget(main) + + +def test_exit_button(qtbot, monkeypatch): + exit_calls = [] + monkeypatch.setattr(QtWidgets.QApplication, "exit", + lambda: exit_calls.append(1)) + main = HNNGUI() + qtbot.addWidget(main) + qtbot.mouseClick(main.qbtn, QtCore.Qt.LeftButton) + assert exit_calls == [1] diff --git a/hnn/tests/test_view_windows.py b/hnn/tests/test_view_windows.py index 87f111970..03a152a36 100644 --- a/hnn/tests/test_view_windows.py +++ b/hnn/tests/test_view_windows.py @@ -1,11 +1,9 @@ import os.path as op -import os -import sys -import shlex -from subprocess import Popen, PIPE from mne.utils import _fetch_file +from hnn import HNNGUI + def fetch_file(fname): data_dir = ('https://raw.githubusercontent.com/jonescompneurolab/' @@ -16,58 +14,60 @@ def fetch_file(fname): _fetch_file(data_url, fname) -def view_window(code_fname, paramf, data_fname=None): - """Test to check that viewer displays without error""" +def test_view_rast(qtbot): + """Show the spiking activity window""" + fname = 'spk.txt' + fetch_file(fname) - nrniv_str = 'nrniv -python -nobanner' - cmd = nrniv_str + ' ' + sys.executable + ' ' + code_fname + ' ' + \ - paramf - if data_fname is not None: - cmd += ' ' + data_fname + # start the GUI + main = HNNGUI() + qtbot.addWidget(main) - # Windows will fail to load the correct Qt plugin when launched with nrniv - # This is a temporary fix until separate windows are no longer launched - # as different processes - basedir = os.path.expanduser('~') - plugin_dir = op.join(basedir, 'Miniconda3', 'envs', 'hnn', 'Library', - 'plugins', 'platforms') - os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_dir + main.viewRasterAction.trigger() - # Split the command into shell arguments for passing to Popen - cmdargs = shlex.split(cmd, posix="win" not in sys.platform) - # Start the simulation - proc = Popen(cmdargs, stdin=PIPE, stdout=PIPE, stderr=PIPE, - cwd=os.getcwd(), universal_newlines=True) - out, err = proc.communicate() +def test_view_dipole(qtbot): + """Show the dipole window""" + fname = 'dpl.txt' + fetch_file(fname) - # print all messages (including error messages) - print('STDOUT', out) - print('STDERR', err) + # start the GUI + main = HNNGUI() + qtbot.addWidget(main) - if proc.returncode != 0: - raise RuntimeError("Running command %s failed" % cmd) + main.viewDipoleAction.trigger() -def test_view_rast(): - fname = 'spk.txt' +def test_view_psd(qtbot): + """Show the PSD window""" + fname = 'dpl.txt' fetch_file(fname) - paramf = op.join('param', 'default.param') - view_window('visrast.py', paramf, fname) + + # start the GUI + main = HNNGUI() + qtbot.addWidget(main) + + main.viewPSDAction.trigger() -def test_view_dipole(): +def test_view_spec(qtbot): + """Show the pectrogram window""" fname = 'dpl.txt' fetch_file(fname) - paramf = op.join('param', 'default.param') - view_window('visdipole.py', paramf, fname) + + # start the GUI + main = HNNGUI() + qtbot.addWidget(main) + + main.viewSpecAction.trigger() -def test_view_psd(): - paramf = op.join('param', 'default.param') - view_window('vispsd.py', paramf) +# def test_view_soma(qtbot): +# fname = 'spike.txt' +# fetch_file(fname) +# # start the GUI +# main = HNNGUI() +# qtbot.addWidget(main) -def test_view_spec(): - paramf = op.join('param', 'default.param') - view_window('visspec.py', paramf) +# main.viewSomaVAction.trigger() From 39ae2c3a6c9257bb78e829882180abc9567d5be6 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 4 Oct 2020 12:57:14 -0400 Subject: [PATCH 033/107] MAINT: flake8 progress on hnn_qt5.py --- hnn.py | 15 +- hnn/hnn_qt5.py | 965 +++++++++++++++++++++++++------------------------ 2 files changed, 513 insertions(+), 467 deletions(-) diff --git a/hnn.py b/hnn.py index 25d777f65..eda7c7bf0 100755 --- a/hnn.py +++ b/hnn.py @@ -1,12 +1,19 @@ +"""Main file to launch HNN GUI""" + +# Authors: Sam Neymotin +# Blake Caldwell + import sys -from PyQt5.QtWidgets import QApplication +from PyQt5 import QtWidgets from hnn import HNNGUI -def runqt5 (): - app = QApplication(sys.argv) - ex = HNNGUI() + +def runqt5(): + app = QtWidgets.QApplication(sys.argv) + HNNGUI() sys.exit(app.exec_()) + if __name__ == '__main__': runqt5() diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index bee134dbf..ea3013767 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -8,36 +8,31 @@ import sys import os import multiprocessing -from subprocess import Popen, PIPE -import shlex, shutil +from subprocess import Popen from collections import namedtuple, OrderedDict -from copy import deepcopy -from time import time, sleep import numpy as np -from math import ceil import traceback from psutil import cpu_count # External libraries -from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout -from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QDialog, QGridLayout, QLineEdit, QLabel -from PyQt5.QtWidgets import QCheckBox, QTextEdit, QInputDialog, QSpacerItem, QFrame +from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication +from PyQt5.QtWidgets import QFileDialog, QComboBox, QTabWidget +from PyQt5.QtWidgets import QToolTip, QPushButton, QFormLayout +from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QDialog, QGridLayout +from PyQt5.QtWidgets import QLineEdit, QLabel, QTextEdit, QInputDialog +from PyQt5.QtWidgets import QMenu, QMessageBox, QWidget, QDialog from PyQt5.QtGui import QIcon, QPixmap, QFont -from PyQt5.QtCore import QCoreApplication, pyqtSignal, QObject, Qt, QSize -from PyQt5.QtCore import QMetaObject -from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar +from PyQt5.QtCore import pyqtSignal, QObject, Qt +from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT import matplotlib.pyplot as plt from hnn_core import read_params # HNN modules -from .paramrw import usingOngoingInputs, usingEvokedInputs -from .paramrw import write_legacy_paramf, get_output_dir -from .simdat import SIMCanvas, getinputfiles, updatedat -from .simdat import updatelsimdat, ddat, updateoptdat, lsimdat, lsimidx -from .simdat import weighted_rmse, initial_ddat, calcerr +from .paramrw import usingOngoingInputs, usingEvokedInputs, get_output_dir +from .simdat import SIMCanvas, getinputfiles +from .simdat import updatelsimdat, ddat, lsimdat, lsimidx from .run import RunSimThread, ParamSignal -from .qt_lib import setscalegeom, setscalegeomcenter, getmplDPI, getscreengeom +from .qt_lib import setscalegeom, setscalegeomcenter, getmplDPI, getscreengeom from .qt_lib import lookupresource, ClickLabel from .qt_evoked import EvokedInputParamDialog, OptEvokedInputParamDialog @@ -46,480 +41,517 @@ drawavgdpl = 0 fontsize = plt.rcParams['font.size'] = 10 -def isWindows (): - # are we on windows? or linux/mac ? - return sys.platform.startswith('win') -def getPyComm (): - # get the python command - Windows only has python linux/mac have python3 - if sys.executable is not None: # check python command interpreter path - if available - pyc = sys.executable - if pyc.count('python') > 0 and len(pyc) > 0: - return pyc # full path to python - if isWindows(): - return 'python' - return 'python3' +def isWindows(): + # are we on windows? or linux/mac ? + return sys.platform.startswith('win') -def _add_missing_frames(tb): - fake_tb = namedtuple( - 'fake_tb', ('tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next') - ) - result = fake_tb(tb.tb_frame, tb.tb_lasti, tb.tb_lineno, tb.tb_next) - frame = tb.tb_frame.f_back - while frame: - result = fake_tb(frame, frame.f_lasti, frame.f_lineno, result) - frame = frame.f_back - return result - -def _get_defncore(): - """get default number of cores """ - - try: - defncore = len(os.sched_getaffinity(0)) - except AttributeError: - defncore = cpu_count(logical=False) - - if defncore is None or defncore == 0: - # in case psutil is not supported (e.g. BSD) - defncore = multiprocessing.cpu_count() - - return defncore - -# for signaling -class DoneSignal (QObject): - finishSim = pyqtSignal(bool, str) - -def bringwintobot (win): - #win.show() - #win.lower() - win.hide() - -def bringwintotop (win): - # bring a pyqt5 window to the top (parents still stay behind children) - # based on examples from https://www.programcreek.com/python/example/101663/PyQt5.QtCore.Qt.WindowActive - #win.show() - #win.setWindowState(win.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) - #win.raise_() - win.showNormal() - win.activateWindow() - #win.setWindowState((win.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive) - #win.activateWindow() - #win.raise_() - #win.show() - -# DictDialog - dictionary-based dialog with tabs - should make all dialogs -# specifiable via cfg file format - then can customize gui without changing py code -# and can reduce code explosion / overlap between dialogs -class DictDialog (QDialog): - - def __init__ (self, parent, din): - super(DictDialog, self).__init__(parent) - self.ldict = [] # subclasses should override - self.ltitle = [] - self.dtransvar = {} # for translating model variable name to more human-readable form - self.stitle = '' - self.initd() - self.initUI() - self.initExtra() - self.setfromdin(din) # set values from input dictionary - # self.addtips() - - # TODO: add back tooltips - # def addtips (self): - # for ktip in dconf.keys(): - # if ktip in self.dqline: - # self.dqline[ktip].setToolTip(dconf[ktip]) - # elif ktip in self.dqextra: - # self.dqextra[ktip].setToolTip(dconf[ktip]) - - def __str__ (self): - s = '' - for k,v in self.dqline.items(): s += k + ': ' + v.text().strip() + os.linesep - return s - def saveparams (self): self.hide() +def getPyComm(): + """get the python command""" - def initd (self): pass # implemented in subclass + # check python command interpreter path - if available + if sys.executable is not None: + pyc = sys.executable + if pyc.count('python') > 0 and len(pyc) > 0: + return pyc # full path to python + if isWindows(): + return 'python' + return 'python3' - def getval (self,k): - if k in self.dqline.keys(): - return self.dqline[k].text().strip() - def lines2val (self,ksearch,val): - for k in self.dqline.keys(): - if k.count(ksearch) > 0: - self.dqline[k].setText(str(val)) - - def setfromdin (self,din): - if not din: return - for k,v in din.items(): - if k in self.dqline: - self.dqline[k].setText(str(v).strip()) - - def transvar (self,k): - if k in self.dtransvar: return self.dtransvar[k] - return k +def _add_missing_frames(tb): + fake_tb = namedtuple( + 'fake_tb', ('tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next') + ) + result = fake_tb(tb.tb_frame, tb.tb_lasti, tb.tb_lineno, tb.tb_next) + frame = tb.tb_frame.f_back + while frame: + result = fake_tb(frame, frame.f_lasti, frame.f_lineno, result) + frame = frame.f_back + return result - def addtransvar (self,k,strans): - self.dtransvar[k] = strans - self.dtransvar[strans] = k - def initExtra (self): - # extra items not written to param file - self.dqextra = {} +def _get_defncore(): + """get default number of cores """ - def initUI (self): - self.layout = QVBoxLayout(self) + try: + defncore = len(os.sched_getaffinity(0)) + except AttributeError: + defncore = cpu_count(logical=False) + + if defncore is None or defncore == 0: + # in case psutil is not supported (e.g. BSD) + defncore = multiprocessing.cpu_count() + + return defncore + + +class DoneSignal(QObject): + finishSim = pyqtSignal(bool, str) + + +def bringwintobot(win): + # win.show() + # win.lower() + win.hide() + + +def bringwintotop(win): + # bring a pyqt5 window to the top (parents still stay behind children) + # from https://www.programcreek.com/python/example/ + # 101663/PyQt5.QtCore.Qt.WindowActive + # win.show() + # win.setWindowState(win.windowState() & ~Qt.WindowMinimized | + # Qt.WindowActive) + # win.raise_() + win.showNormal() + win.activateWindow() + # win.setWindowState((win.windowState() & ~Qt.WindowMinimized) | + # Qt.WindowActive) + # win.activateWindow() + # win.raise_() + # win.show() + + +class DictDialog(QDialog): + """dictionary-based dialog with tabs + + should make all dialogs specifiable via cfg file format - + then can customize gui without changing py code + and can reduce code explosion / overlap between dialogs + """ + + def __init__(self, parent, din): + super(DictDialog, self).__init__(parent) + self.ldict = [] # subclasses should override + self.ltitle = [] + # for translating model variable name to more human-readable form + self.dtransvar = {} + self.stitle = '' + self.initd() + self.initUI() + self.initExtra() + self.setfromdin(din) # set values from input dictionary + # self.addtips() + + # TODO: add back tooltips + # def addtips (self): + # for ktip in dconf.keys(): + # if ktip in self.dqline: + # self.dqline[ktip].setToolTip(dconf[ktip]) + # elif ktip in self.dqextra: + # self.dqextra[ktip].setToolTip(dconf[ktip]) + + def __str__(self): + s = '' + for k, v in self.dqline.items(): + s += k + ': ' + v.text().strip() + os.linesep + return s + + def saveparams(self): + self.hide() + + def initd(self): + pass # implemented in subclass + + def getval(self, k): + if k in self.dqline.keys(): + return self.dqline[k].text().strip() + + def lines2val(self, ksearch, val): + for k in self.dqline.keys(): + if k.count(ksearch) > 0: + self.dqline[k].setText(str(val)) + + def setfromdin(self, din): + if not din: + return + for k, v in din.items(): + if k in self.dqline: + self.dqline[k].setText(str(v).strip()) + + def transvar(self, k): + if k in self.dtransvar: + return self.dtransvar[k] + return k + + def addtransvar(self, k, strans): + self.dtransvar[k] = strans + self.dtransvar[strans] = k + + def initExtra(self): + # extra items not written to param file + self.dqextra = {} + + def initUI(self): + self.layout = QVBoxLayout(self) + + # Add stretch to separate the form layout from the button + self.layout.addStretch(1) + + # Initialize tab screen + self.ltabs = [] + self.tabs = QTabWidget() + self.layout.addWidget(self.tabs) + + for _ in range(len(self.ldict)): + self.ltabs.append(QWidget()) + + self.tabs.resize(575, 200) + + # create tabs and their layouts + for tab, s in zip(self.ltabs, self.ltitle): + self.tabs.addTab(tab, s) + tab.layout = QFormLayout() + tab.setLayout(tab.layout) + + self.dqline = {} # QLineEdits dict; key is model variable + for d, tab in zip(self.ldict, self.ltabs): + for k, v in d.items(): + self.dqline[k] = QLineEdit(self) + self.dqline[k].setText(str(v)) + # add label,QLineEdit to the tab + tab.layout.addRow(self.transvar(k), self.dqline[k]) + + # Add tabs to widget + self.layout.addWidget(self.tabs) + self.setLayout(self.layout) + self.setWindowTitle(self.stitle) + + def TurnOff(self): + pass - # Add stretch to separate the form layout from the button - self.layout.addStretch(1) + def addOffButton(self): + """Create a horizontal box layout to hold the button""" + self.button_box = QHBoxLayout() + self.btnoff = QPushButton('Turn Off Inputs', self) + self.btnoff.resize(self.btnoff.sizeHint()) + self.btnoff.clicked.connect(self.TurnOff) + self.btnoff.setToolTip('Turn Off Inputs') + self.button_box.addWidget(self.btnoff) + self.layout.addLayout(self.button_box) + + def addHideButton(self): + self.bbhidebox = QHBoxLayout() + self.btnhide = QPushButton('Hide Window', self) + self.btnhide.resize(self.btnhide.sizeHint()) + self.btnhide.clicked.connect(self.hide) + self.btnhide.setToolTip('Hide Window') + self.bbhidebox.addWidget(self.btnhide) + self.layout.addLayout(self.bbhidebox) - # Initialize tab screen - self.ltabs = [] - self.tabs = QTabWidget(); self.layout.addWidget(self.tabs) - - for _ in range(len(self.ldict)): - self.ltabs.append(QWidget()) - - self.tabs.resize(575,200) - - # create tabs and their layouts - for tab,s in zip(self.ltabs,self.ltitle): - self.tabs.addTab(tab,s) - tab.layout = QFormLayout() - tab.setLayout(tab.layout) - - self.dqline = {} # QLineEdits dict; key is model variable - for d,tab in zip(self.ldict, self.ltabs): - for k,v in d.items(): - self.dqline[k] = QLineEdit(self) - self.dqline[k].setText(str(v)) - tab.layout.addRow(self.transvar(k),self.dqline[k]) # adds label,QLineEdit to the tab - - # Add tabs to widget - self.layout.addWidget(self.tabs) - self.setLayout(self.layout) - self.setWindowTitle(self.stitle) - #nw, nh = setscalegeom(self, 150, 150, 625, 300) - #nx = parent.rect().x()+parent.rect().width()/2-nw/2 - #ny = parent.rect().y()+parent.rect().height()/2-nh/2 - #print(parent.rect(),nx,ny) - #self.move(nx, ny) - #self.move(self.parent. - #self.move(self.parent.widget.rect().x+self.parent.widget.rect().width()/2-nw, - # self.parent.widget.rect().y+self.parent.widget.rect().height()/2-nh) - - def TurnOff (self): pass - - def addOffButton (self): - # Create a horizontal box layout to hold the button - self.button_box = QHBoxLayout() - self.btnoff = QPushButton('Turn Off Inputs',self) - self.btnoff.resize(self.btnoff.sizeHint()) - self.btnoff.clicked.connect(self.TurnOff) - self.btnoff.setToolTip('Turn Off Inputs') - self.button_box.addWidget(self.btnoff) - self.layout.addLayout(self.button_box) - - def addHideButton (self): - self.bbhidebox = QHBoxLayout() - self.btnhide = QPushButton('Hide Window',self) - self.btnhide.resize(self.btnhide.sizeHint()) - self.btnhide.clicked.connect(self.hide) - self.btnhide.setToolTip('Hide Window') - self.bbhidebox.addWidget(self.btnhide) - self.layout.addLayout(self.bbhidebox) -# widget to specify ongoing input params (proximal, distal) class OngoingInputParamDialog (DictDialog): - def __init__ (self, parent, inty, din=None): - self.inty = inty - if self.inty.startswith('Proximal'): - self.prefix = 'input_prox_A_' - self.postfix = '_prox' - self.isprox = True - else: - self.prefix = 'input_dist_A_' - self.postfix = '_dist' - self.isprox = False - super(OngoingInputParamDialog, self).__init__(parent,din) - self.addOffButton() - self.addImages() - self.addHideButton() - - # add png cartoons to tabs - def addImages (self): - if self.isprox: self.pix = QPixmap(lookupresource('proxfig')) - else: self.pix = QPixmap(lookupresource('distfig')) - for tab in self.ltabs: - pixlbl = ClickLabel(self) - pixlbl.setPixmap(self.pix) - tab.layout.addRow(pixlbl) - - - # turn off by setting all weights to 0.0 - def TurnOff (self): self.lines2val('weight',0.0) - - def initd (self): - self.dtiming = OrderedDict([#('distribution' + self.postfix, 'normal'), - ('t0_input' + self.postfix, 1000.), - ('t0_input_stdev' + self.postfix, 0.), - ('tstop_input' + self.postfix, 250.), - ('f_input' + self.postfix, 10.), - ('f_stdev' + self.postfix, 20.), - ('events_per_cycle' + self.postfix, 2), - ('repeats' + self.postfix, 10)]) - - self.dL2 = OrderedDict([(self.prefix + 'weight_L2Pyr_ampa', 0.), - (self.prefix + 'weight_L2Pyr_nmda', 0.), - (self.prefix + 'weight_L2Basket_ampa', 0.), - (self.prefix + 'weight_L2Basket_nmda',0.), - (self.prefix + 'delay_L2', 0.1),]) - - self.dL5 = OrderedDict([(self.prefix + 'weight_L5Pyr_ampa', 0.), - (self.prefix + 'weight_L5Pyr_nmda', 0.)]) - - if self.isprox: - self.dL5[self.prefix + 'weight_L5Basket_ampa'] = 0.0 - self.dL5[self.prefix + 'weight_L5Basket_nmda'] = 0.0 - self.dL5[self.prefix + 'delay_L5'] = 0.1 - - self.ldict = [self.dtiming, self.dL2, self.dL5] - self.ltitle = ['Timing', 'Layer 2/3', 'Layer 5'] - self.stitle = 'Set Rhythmic '+self.inty+' Inputs' - - dtmp = {'L2':'L2/3 ','L5':'L5 '} - for d in [self.dL2, self.dL5]: - for k in d.keys(): - lk = k.split('_') - if k.count('weight') > 0: - self.addtransvar(k, dtmp[lk[-2][0:2]] + lk[-2][2:]+' '+lk[-1].upper()+u' weight (µS)') + """widget to specify ongoing input params (proximal, distal)""" + def __init__(self, parent, inty, din=None): + self.inty = inty + if self.inty.startswith('Proximal'): + self.prefix = 'input_prox_A_' + self.postfix = '_prox' + self.isprox = True + else: + self.prefix = 'input_dist_A_' + self.postfix = '_dist' + self.isprox = False + super(OngoingInputParamDialog, self).__init__(parent, din) + self.addOffButton() + self.addImages() + self.addHideButton() + + def addImages(self): + """add png cartoons to tabs""" + if self.isprox: + self.pix = QPixmap(lookupresource('proxfig')) else: - self.addtransvar(k, 'Delay (ms)') + self.pix = QPixmap(lookupresource('distfig')) + for tab in self.ltabs: + pixlbl = ClickLabel(self) + pixlbl.setPixmap(self.pix) + tab.layout.addRow(pixlbl) + + def TurnOff(self): + """ turn off by setting all weights to 0.0""" + self.lines2val('weight', 0.0) + + def initd(self): + self.dtiming = OrderedDict([('t0_input' + self.postfix, 1000.), + ('t0_input_stdev' + self.postfix, 0.), + ('tstop_input' + self.postfix, 250.), + ('f_input' + self.postfix, 10.), + ('f_stdev' + self.postfix, 20.), + ('events_per_cycle' + self.postfix, 2), + ('repeats' + self.postfix, 10)]) + + self.dL2 = OrderedDict([(self.prefix + 'weight_L2Pyr_ampa', 0.), + (self.prefix + 'weight_L2Pyr_nmda', 0.), + (self.prefix + 'weight_L2Basket_ampa', 0.), + (self.prefix + 'weight_L2Basket_nmda', 0.), + (self.prefix + 'delay_L2', 0.1)]) + + self.dL5 = OrderedDict([(self.prefix + 'weight_L5Pyr_ampa', 0.), + (self.prefix + 'weight_L5Pyr_nmda', 0.)]) + + if self.isprox: + self.dL5[self.prefix + 'weight_L5Basket_ampa'] = 0.0 + self.dL5[self.prefix + 'weight_L5Basket_nmda'] = 0.0 + self.dL5[self.prefix + 'delay_L5'] = 0.1 + + self.ldict = [self.dtiming, self.dL2, self.dL5] + self.ltitle = ['Timing', 'Layer 2/3', 'Layer 5'] + self.stitle = 'Set Rhythmic ' + self.inty + ' Inputs' + + dtmp = {'L2': 'L2/3 ', 'L5': 'L5 '} + for d in [self.dL2, self.dL5]: + for k in d.keys(): + lk = k.split('_') + if k.count('weight') > 0: + self.addtransvar(k, dtmp[lk[-2][0:2]] + lk[-2][2:] + ' ' + + lk[-1].upper() + u' weight (µS)') + else: + self.addtransvar(k, 'Delay (ms)') + + self.addtransvar('t0_input' + self.postfix, 'Start time mean (ms)') + self.addtransvar('t0_input_stdev' + self.postfix, + 'Start time stdev (ms)') + self.addtransvar('tstop_input' + self.postfix, 'Stop time (ms)') + self.addtransvar('f_input' + self.postfix, 'Burst frequency (Hz)') + self.addtransvar('f_stdev' + self.postfix, 'Burst stdev (ms)') + self.addtransvar('events_per_cycle' + self.postfix, 'Spikes/burst') + self.addtransvar('repeats' + self.postfix, 'Number bursts') - #self.addtransvar('distribution'+self.postfix,'Distribution') - self.addtransvar('t0_input'+self.postfix,'Start time mean (ms)') - self.addtransvar('t0_input_stdev'+self.postfix,'Start time stdev (ms)') - self.addtransvar('tstop_input'+self.postfix,'Stop time (ms)') - self.addtransvar('f_input'+self.postfix,'Burst frequency (Hz)') - self.addtransvar('f_stdev'+self.postfix,'Burst stdev (ms)') - self.addtransvar('events_per_cycle'+self.postfix,'Spikes/burst') - self.addtransvar('repeats'+self.postfix,'Number bursts') class EvokedOrRhythmicDialog (QDialog): - def __init__ (self, parent, distal, evwin, rhythwin): - super(EvokedOrRhythmicDialog, self).__init__(parent) - if distal: self.prefix = 'Distal' - else: self.prefix = 'Proximal' - self.evwin = evwin - self.rhythwin = rhythwin - self.initUI() - - def initUI (self): - self.layout = QVBoxLayout(self) - # Add stretch to separate the form layout from the button - self.layout.addStretch(1) - - self.btnrhythmic = QPushButton('Rhythmic ' + self.prefix + ' Inputs',self) - self.btnrhythmic.resize(self.btnrhythmic.sizeHint()) - self.btnrhythmic.clicked.connect(self.showrhythmicwin) - self.layout.addWidget(self.btnrhythmic) - - self.btnevoked = QPushButton('Evoked Inputs',self) - self.btnevoked.resize(self.btnevoked.sizeHint()) - self.btnevoked.clicked.connect(self.showevokedwin) - self.layout.addWidget(self.btnevoked) - - self.addHideButton() - - setscalegeom(self, 150, 150, 270, 120) - self.setWindowTitle("Pick Input Type") - - def showevokedwin (self): - bringwintotop(self.evwin) - self.hide() - - def showrhythmicwin (self): - bringwintotop(self.rhythwin) - self.hide() - - def addHideButton (self): - self.bbhidebox = QHBoxLayout() - self.btnhide = QPushButton('Hide Window',self) - self.btnhide.resize(self.btnhide.sizeHint()) - self.btnhide.clicked.connect(self.hide) - self.btnhide.setToolTip('Hide Window') - self.bbhidebox.addWidget(self.btnhide) - self.layout.addLayout(self.bbhidebox) - - -class SynGainParamDialog (QDialog): - def __init__ (self, parent, netparamwin): - super(SynGainParamDialog, self).__init__(parent) - self.netparamwin = netparamwin - self.initUI() - - def scalegain (self, k, fctr): - oldval = float(self.netparamwin.dqline[k].text().strip()) - newval = oldval * fctr - self.netparamwin.dqline[k].setText(str(newval)) - return newval - - def isE (self,ty): return ty.count('Pyr') > 0 - def isI (self,ty): return ty.count('Basket') > 0 - - def tounity (self): - for k in self.dqle.keys(): self.dqle[k].setText('1.0') - - def scalegains (self): - for _, k in enumerate(self.dqle.keys()): - fctr = float(self.dqle[k].text().strip()) - if fctr < 0.: - fctr = 0. - self.dqle[k].setText(str(fctr)) - elif fctr == 1.0: - continue - for k2 in self.netparamwin.dqline.keys(): - l = k2.split('_') - ty1,ty2 = l[1],l[2] - if self.isE(ty1) and self.isE(ty2) and k == 'E -> E': - self.scalegain(k2,fctr) - elif self.isE(ty1) and self.isI(ty2) and k == 'E -> I': - self.scalegain(k2,fctr) - elif self.isI(ty1) and self.isE(ty2) and k == 'I -> E': - self.scalegain(k2,fctr) - elif self.isI(ty1) and self.isI(ty2) and k == 'I -> I': - self.scalegain(k2,fctr) - self.tounity() # go back to unity since pressed OK - next call to this dialog will reset new values - self.hide() - - def initUI (self): - grid = QGridLayout() - grid.setSpacing(10) + def __init__(self, parent, distal, evwin, rhythwin): + super(EvokedOrRhythmicDialog, self).__init__(parent) + if distal: + self.prefix = 'Distal' + else: + self.prefix = 'Proximal' + self.evwin = evwin + self.rhythwin = rhythwin + self.initUI() + + def initUI(self): + self.layout = QVBoxLayout(self) + # Add stretch to separate the form layout from the button + self.layout.addStretch(1) + + self.btnrhythmic = QPushButton('Rhythmic ' + self.prefix + ' Inputs', + self) + self.btnrhythmic.resize(self.btnrhythmic.sizeHint()) + self.btnrhythmic.clicked.connect(self.showrhythmicwin) + self.layout.addWidget(self.btnrhythmic) + + self.btnevoked = QPushButton('Evoked Inputs', self) + self.btnevoked.resize(self.btnevoked.sizeHint()) + self.btnevoked.clicked.connect(self.showevokedwin) + self.layout.addWidget(self.btnevoked) + + self.addHideButton() + + setscalegeom(self, 150, 150, 270, 120) + self.setWindowTitle("Pick Input Type") + + def showevokedwin(self): + bringwintotop(self.evwin) + self.hide() + + def showrhythmicwin(self): + bringwintotop(self.rhythwin) + self.hide() + + def addHideButton(self): + self.bbhidebox = QHBoxLayout() + self.btnhide = QPushButton('Hide Window', self) + self.btnhide.resize(self.btnhide.sizeHint()) + self.btnhide.clicked.connect(self.hide) + self.btnhide.setToolTip('Hide Window') + self.bbhidebox.addWidget(self.btnhide) + self.layout.addLayout(self.bbhidebox) + + +class SynGainParamDialog(QDialog): + def __init__(self, parent, netparamwin): + super(SynGainParamDialog, self).__init__(parent) + self.netparamwin = netparamwin + self.initUI() + + def scalegain(self, k, fctr): + oldval = float(self.netparamwin.dqline[k].text().strip()) + newval = oldval * fctr + self.netparamwin.dqline[k].setText(str(newval)) + return newval + + def isE(self, ty): + return ty.count('Pyr') > 0 + + def isI(self, ty): + return ty.count('Basket') > 0 + + def tounity(self): + for k in self.dqle.keys(): + self.dqle[k].setText('1.0') + + def scalegains(self): + for _, k in enumerate(self.dqle.keys()): + fctr = float(self.dqle[k].text().strip()) + if fctr < 0.: + fctr = 0. + self.dqle[k].setText(str(fctr)) + elif fctr == 1.0: + continue + for k2 in self.netparamwin.dqline.keys(): + types = k2.split('_') + ty1, ty2 = types[1], types[2] + if self.isE(ty1) and self.isE(ty2) and k == 'E -> E': + self.scalegain(k2, fctr) + elif self.isE(ty1) and self.isI(ty2) and k == 'E -> I': + self.scalegain(k2, fctr) + elif self.isI(ty1) and self.isE(ty2) and k == 'I -> E': + self.scalegain(k2, fctr) + elif self.isI(ty1) and self.isI(ty2) and k == 'I -> I': + self.scalegain(k2, fctr) + + # go back to unity since pressed OK - next call to this dialog will + # reset new values + self.tounity() + self.hide() + + def initUI(self): + grid = QGridLayout() + grid.setSpacing(10) + + self.dqle = {} + for row, k in enumerate(['E -> E', 'E -> I', 'I -> E', 'I -> I']): + lbl = QLabel(self) + lbl.setText(k) + lbl.adjustSize() + grid.addWidget(lbl, row, 0) + qle = QLineEdit(self) + qle.setText('1.0') + grid.addWidget(qle, row, 1) + self.dqle[k] = qle + + row += 1 + self.btnok = QPushButton('OK', self) + self.btnok.resize(self.btnok.sizeHint()) + self.btnok.clicked.connect(self.scalegains) + grid.addWidget(self.btnok, row, 0, 1, 1) + self.btncancel = QPushButton('Cancel', self) + self.btncancel.resize(self.btncancel.sizeHint()) + self.btncancel.clicked.connect(self.hide) + grid.addWidget(self.btncancel, row, 1, 1, 1) + + self.setLayout(grid) + setscalegeom(self, 150, 150, 270, 180) + self.setWindowTitle("Synaptic Gains") - self.dqle = {} - for row,k in enumerate(['E -> E', 'E -> I', 'I -> E', 'I -> I']): - lbl = QLabel(self) - lbl.setText(k) - lbl.adjustSize() - grid.addWidget(lbl,row, 0) - qle = QLineEdit(self) - qle.setText('1.0') - grid.addWidget(qle,row, 1) - self.dqle[k] = qle - - row += 1 - self.btnok = QPushButton('OK',self) - self.btnok.resize(self.btnok.sizeHint()) - self.btnok.clicked.connect(self.scalegains) - grid.addWidget(self.btnok, row, 0, 1, 1) - self.btncancel = QPushButton('Cancel',self) - self.btncancel.resize(self.btncancel.sizeHint()) - self.btncancel.clicked.connect(self.hide) - grid.addWidget(self.btncancel, row, 1, 1, 1) - - self.setLayout(grid) - setscalegeom(self, 150, 150, 270, 180) - self.setWindowTitle("Synaptic Gains") # widget to specify tonic inputs -class TonicInputParamDialog (DictDialog): - def __init__ (self, parent, din): - super(TonicInputParamDialog, self).__init__(parent,din) - self.addOffButton() - self.addHideButton() - - # turn off by setting all weights to 0.0 - def TurnOff (self): self.lines2val('A',0.0) - - def initd (self): +class TonicInputParamDialog(DictDialog): + def __init__(self, parent, din): + super(TonicInputParamDialog, self).__init__(parent, din) + self.addOffButton() + self.addHideButton() + + # turn off by setting all weights to 0.0 + def TurnOff(self): + self.lines2val('A', 0.0) + + def initd(self): + self.dL2 = OrderedDict([ + # IClamp params for L2Pyr + ('Itonic_A_L2Pyr_soma', 0.), + ('Itonic_t0_L2Pyr_soma', 0.), + ('Itonic_T_L2Pyr_soma', -1.), + # IClamp param for L2Basket + ('Itonic_A_L2Basket', 0.), + ('Itonic_t0_L2Basket', 0.), + ('Itonic_T_L2Basket', -1.)]) + + self.dL5 = OrderedDict([ + # IClamp params for L5Pyr + ('Itonic_A_L5Pyr_soma', 0.), + ('Itonic_t0_L5Pyr_soma', 0.), + ('Itonic_T_L5Pyr_soma', -1.), + # IClamp param for L5Basket + ('Itonic_A_L5Basket', 0.), + ('Itonic_t0_L5Basket', 0.), + ('Itonic_T_L5Basket', -1.)]) + + # temporary dictionary for string translation + dtmp = {'L2': 'L2/3 ', 'L5': 'L5 '} + for d in [self.dL2, self.dL5]: + for k in d.keys(): + cty = k.split('_')[2] # cell type + tcty = dtmp[cty[0:2]] + cty[2:] # translated cell type + if k.count('A') > 0: + self.addtransvar(k, tcty + ' amplitude (nA)') + elif k.count('t0') > 0: + self.addtransvar(k, tcty + ' start time (ms)') + elif k.count('T') > 0: + self.addtransvar(k, tcty + ' stop time (ms)') + + self.ldict = [self.dL2, self.dL5] + self.ltitle = ['Layer 2/3', 'Layer 5'] + self.stitle = 'Set Tonic Inputs' - self.dL2 = OrderedDict([ - # IClamp params for L2Pyr - ('Itonic_A_L2Pyr_soma', 0.), - ('Itonic_t0_L2Pyr_soma', 0.), - ('Itonic_T_L2Pyr_soma', -1.), - # IClamp param for L2Basket - ('Itonic_A_L2Basket', 0.), - ('Itonic_t0_L2Basket', 0.), - ('Itonic_T_L2Basket', -1.)]) - - self.dL5 = OrderedDict([ - # IClamp params for L5Pyr - ('Itonic_A_L5Pyr_soma', 0.), - ('Itonic_t0_L5Pyr_soma', 0.), - ('Itonic_T_L5Pyr_soma', -1.), - # IClamp param for L5Basket - ('Itonic_A_L5Basket', 0.), - ('Itonic_t0_L5Basket', 0.), - ('Itonic_T_L5Basket', -1.)]) - - dtmp = {'L2':'L2/3 ','L5':'L5 '} # temporary dictionary for string translation - for d in [self.dL2, self.dL5]: - for k in d.keys(): - cty = k.split('_')[2] # cell type - tcty = dtmp[cty[0:2]] + cty[2:] # translated cell type - if k.count('A') > 0: - self.addtransvar(k, tcty + ' amplitude (nA)') - elif k.count('t0') > 0: - self.addtransvar(k, tcty + ' start time (ms)') - elif k.count('T') > 0: - self.addtransvar(k, tcty + ' stop time (ms)') - - self.ldict = [self.dL2, self.dL5] - self.ltitle = ['Layer 2/3', 'Layer 5'] - self.stitle = 'Set Tonic Inputs' # widget to specify ongoing poisson inputs -class PoissonInputParamDialog (DictDialog): - def __init__ (self, parent, din): - super(PoissonInputParamDialog, self).__init__(parent,din) - self.addOffButton() - self.addHideButton() - - # turn off by setting all weights to 0.0 - def TurnOff (self): self.lines2val('weight',0.0) - - def initd (self): - - self.dL2,self.dL5 = {},{} - ld = [self.dL2,self.dL5] +class PoissonInputParamDialog(DictDialog): + def __init__(self, parent, din): + super(PoissonInputParamDialog, self).__init__(parent, din) + self.addOffButton() + self.addHideButton() + + def TurnOff(self): + """turn off by setting all weights to 0.0""" + self.lines2val('weight', 0.0) + + def initd(self): + self.dL2, self.dL5 = {}, {} + ld = [self.dL2, self.dL5] + + for i, lyr in enumerate(['L2', 'L5']): + d = ld[i] + for ty in ['Pyr', 'Basket']: + for sy in ['ampa', 'nmda']: + d[lyr + ty + '_Pois_A_weight' + '_' + sy] = 0. + d[lyr + ty + '_Pois_lamtha'] = 0. + + self.dtiming = OrderedDict([('t0_pois', 0.), + ('T_pois', -1)]) + + self.addtransvar('t0_pois', 'Start time (ms)') + self.addtransvar('T_pois', 'Stop time (ms)') + + # temporary dictionary for string translation + dtmp = {'L2': 'L2/3 ', 'L5': 'L5 '} + for d in [self.dL2, self.dL5]: + for k in d.keys(): + ks = k.split('_') + cty = ks[0] # cell type + tcty = dtmp[cty[0:2]] + cty[2:] # translated cell type + if k.count('weight'): + self.addtransvar(k, tcty + ' ' + ks[-1].upper() + + u' weight (µS)') + elif k.endswith('lamtha'): + self.addtransvar(k, tcty + ' freq (Hz)') + + self.ldict = [self.dL2, self.dL5, self.dtiming] + self.ltitle = ['Layer 2/3', 'Layer 5', 'Timing'] + self.stitle = 'Set Poisson Inputs' - for i,lyr in enumerate(['L2','L5']): - d = ld[i] - for ty in ['Pyr', 'Basket']: - for sy in ['ampa','nmda']: d[lyr+ty+'_Pois_A_weight'+'_'+sy]=0. - d[lyr+ty+'_Pois_lamtha']=0. - - self.dtiming = OrderedDict([('t0_pois', 0.), - ('T_pois', -1)]) - - self.addtransvar('t0_pois','Start time (ms)') - self.addtransvar('T_pois','Stop time (ms)') - - dtmp = {'L2':'L2/3 ','L5':'L5 '} # temporary dictionary for string translation - for d in [self.dL2, self.dL5]: - for k in d.keys(): - ks = k.split('_') - cty = ks[0] # cell type - tcty = dtmp[cty[0:2]] + cty[2:] # translated cell type - if k.count('weight'): - self.addtransvar(k, tcty+ ' ' + ks[-1].upper() + u' weight (µS)') - elif k.endswith('lamtha'): - self.addtransvar(k, tcty+ ' freq (Hz)') - - self.ldict = [self.dL2, self.dL5, self.dtiming] - self.ltitle = ['Layer 2/3', 'Layer 5', 'Timing'] - self.stitle = 'Set Poisson Inputs' # widget to specify run params (tstop, dt, etc.) -- not many params here -class RunParamDialog (DictDialog): - def __init__ (self, parent, din = None): +class RunParamDialog(DictDialog): + def __init__(self, parent, din = None): super(RunParamDialog, self).__init__(parent,din) self.addHideButton() self.parent = parent - def initd (self): + def initd(self): self.drun = OrderedDict([('tstop', 250.), # simulation end time (ms) ('dt', 0.025), # timestep @@ -1482,6 +1514,8 @@ def updateDatCanv(self, params): def updateSelectedSim(self, sim_idx): """Update the sim shown in the ComboBox and update globals""" # update globals + global lsimidx + lsimidx = sim_idx paramfn = lsimdat[sim_idx]['paramfn'] @@ -1501,6 +1535,8 @@ def updateSelectedSim(self, sim_idx): def removeSim(self): """Remove the currently selected simulation""" + global lsimidx + cidx = self.cbsim.currentIndex() self.cbsim.removeItem(cidx) del lsimdat[cidx] @@ -1515,6 +1551,7 @@ def removeSim(self): def prevSim(self): """Go to previous simulation""" + global lsimidx new_simidx = self.cbsim.currentIndex() - 1 if new_simidx < 0: @@ -1534,6 +1571,8 @@ def nextSim (self): self.updateSelectedSim(new_simidx) def clearSimulationData (self): + global lsimidx, ddat, lsimdat + # clear the simulation data self.baseparamwin.params = None self.baseparamwin.paramfn = None @@ -1887,7 +1926,7 @@ def initSimCanvas (self,recalcErr=True,optMode=False,gRow=1,reInit=True): self.m = SIMCanvas(self.baseparamwin.params, parent = self, width=10, height=1, dpi=getmplDPI(), optMode=optMode) # this is the Navigation widget # it takes the Canvas widget and a parent - self.toolbar = NavigationToolbar(self.m, self) + self.toolbar = NavigationToolbar2QT(self.m, self) gWidth = 12 self.grid.addWidget(self.toolbar, gRow, gCol, 1, gWidth) self.grid.addWidget(self.m, gRow + 1, gCol, 1, gWidth) From 46834d364911b27fd33308bdc06352ff0e25651a Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 4 Oct 2020 14:28:27 -0400 Subject: [PATCH 034/107] MAINT: flake8 run.py --- hnn/run.py | 715 ++++++++++++++++++++++++++--------------------------- 1 file changed, 351 insertions(+), 364 deletions(-) diff --git a/hnn/run.py b/hnn/run.py index d7dd1d227..44bc0140e 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -6,10 +6,8 @@ import os.path as op import os -import sys import numpy as np from threading import Lock -import traceback from time import sleep from copy import deepcopy from math import ceil, isclose @@ -194,7 +192,7 @@ def simulate(params, n_procs=None): # save dipole for each trial and perform spectral analysis for trial_idx, dpl in enumerate(dpls): dipole_fn = get_fname(sim_dir, 'normdpl', trial_idx, - params['N_trials']) + params['N_trials']) dpl.write(dipole_fn) if params['save_spec_data'] or usingOngoingInputs(params): @@ -205,8 +203,8 @@ def simulate(params, n_procs=None): # run the spectral analysis analysis_simp(spec_opts, params, dpl, - get_fname(sim_dir, 'rawspec', trial_idx, - params['N_trials'])) + get_fname(sim_dir, 'rawspec', trial_idx, + params['N_trials'])) # TODO: the savefigs functionality is quite complicated and rewriting # from scratch in hnn-core is probably a better option that will allow @@ -240,383 +238,372 @@ def simulate(params, n_procs=None): # based on https://nikolak.com/pyqt-threading-tutorial/ -class RunSimThread (QThread): - def __init__ (self,p,d,ntrial,ncore,waitsimwin,params,opt=False,baseparamwin=None,mainwin=None): - QThread.__init__(self) - self.p = p - self.d = d - self.killed = False - self.ntrial = ntrial - self.ncore = ncore - self.waitsimwin = waitsimwin - self.params = params - self.opt = opt - self.baseparamwin = baseparamwin - self.mainwin = mainwin - self.paramfn = os.path.join(get_output_dir(), 'param', self.params['sim_prefix'] + '.param') - - - # it would be ideal to display a dialog box, but we have to get that event back to the main window - # the next best thing is to print to the console and not crash the application - sys.excepthook = traceback.print_exception - - self.txtComm = TextSignal() - self.txtComm.tsig.connect(self.waitsimwin.updatetxt) - - self.prmComm = ParamSignal() - if self.baseparamwin is not None: - self.prmComm.psig.connect(self.baseparamwin.updatesaveparams) - - self.canvComm = CanvSignal() - if self.mainwin is not None: - self.canvComm.csig.connect(self.mainwin.initSimCanvas) - - self.lock = Lock() - - def updatewaitsimwin (self, txt): - # print('RunSimThread updatewaitsimwin, txt=',txt) - self.txtComm.tsig.emit(txt) - - def updatebaseparamwin (self, d): - self.prmComm.psig.emit(d) - - def updatedispparam (self): - self.p.psig.emit(self.params) - - def updatedrawerr (self): - self.canvComm.csig.emit(False, self.opt) # False means do not recalculate error - - def stop (self): - self.killproc() - - def __del__ (self): - self.quit() - self.wait() - - def run (self): - msg='' - - if self.opt and self.baseparamwin is not None: - try: - self.optmodel() # run optimization - except RuntimeError as e: - msg = str(e) - self.baseparamwin.optparamwin.toggleEnableUserFields(self.cur_step, enable=True) - self.baseparamwin.optparamwin.clear_initial_opt_ranges() - self.baseparamwin.optparamwin.optimization_running = False - else: - try: - self.runsim() # run simulation - self.updatedispparam() # update params in all windows (optimization) - except RuntimeError as e: - msg = str(e) +class RunSimThread(QThread): + def __init__(self, p, d, ntrial, ncore, waitsimwin, params, opt=False, + baseparamwin=None, mainwin=None): + QThread.__init__(self) + self.p = p + self.d = d + self.killed = False + self.ntrial = ntrial + self.ncore = ncore + self.waitsimwin = waitsimwin + self.params = params + self.opt = opt + self.baseparamwin = baseparamwin + self.mainwin = mainwin + self.paramfn = os.path.join(get_output_dir(), 'param', + self.params['sim_prefix'] + '.param') + + self.txtComm = TextSignal() + self.txtComm.tsig.connect(self.waitsimwin.updatetxt) + + self.prmComm = ParamSignal() + if self.baseparamwin is not None: + self.prmComm.psig.connect(self.baseparamwin.updatesaveparams) + + self.canvComm = CanvSignal() + if self.mainwin is not None: + self.canvComm.csig.connect(self.mainwin.initSimCanvas) + + self.lock = Lock() + + def updatewaitsimwin(self, txt): + self.txtComm.tsig.emit(txt) + + def updatebaseparamwin(self, d): + self.prmComm.psig.emit(d) + + def updatedispparam(self): + self.p.psig.emit(self.params) + + def updatedrawerr(self): + # False means do not recalculate error + self.canvComm.csig.emit(False, self.opt) + + def stop(self): + self.killproc() + + def __del__(self): + self.quit() + self.wait() + + def run(self): + msg = '' + + if self.opt and self.baseparamwin is not None: + try: + self.optmodel() # run optimization + except RuntimeError as e: + msg = str(e) + self.baseparamwin.optparamwin.toggleEnableUserFields( + self.cur_step, enable=True) + self.baseparamwin.optparamwin.clear_initial_opt_ranges() + self.baseparamwin.optparamwin.optimization_running = False + else: + try: + self.runsim() # run simulation + # update params in all windows (optimization) + self.updatedispparam() + except RuntimeError as e: + msg = str(e) - self.d.finishSim.emit(self.opt, msg) # send the finish signal + self.d.finishSim.emit(self.opt, msg) # send the finish signal + def killproc(self): + """make sure all nrniv procs have been killed""" + _kill_and_check_nrniv_procs() - def killproc (self): - # make absolute sure all nrniv procs have been killed - _kill_and_check_nrniv_procs() + self.lock.acquire() + self.killed = True + self.lock.release() - self.lock.acquire() - self.killed = True - self.lock.release() + def get_proc_stream(self, stream, print_to_console=False): + for line in iter(stream.readline, ""): + if print_to_console: + print(line.strip()) + # send a signal to waitsimwin, which updates its textedit + self.updatewaitsimwin(line.strip()) + stream.close() + + # run sim command via mpi, then delete the temp file. + def runsim(self, is_opt=False, banner=True, simlength=None): + self.lock.acquire() + self.killed = False + self.lock.release() - def get_proc_stream (self, stream, print_to_console=False): - try: - for line in iter(stream.readline, ""): - if print_to_console: - print(line.strip()) - try: # see https://stackoverflow.com/questions/2104779/qobject-qplaintextedit-multithreading-issues - self.updatewaitsimwin(line.strip()) # sends a pyqtsignal to waitsimwin, which updates its textedit - except: - pass # catch exception in case anything else goes wrong - except ValueError: - # if process is killed and stream.readline() gives I/O error - pass - stream.close() - - # run sim command via mpi, then delete the temp file. - def runsim (self, is_opt=False, banner=True, simlength=None): - self.lock.acquire() - self.killed = False - self.lock.release() - - while True: - if self.ncore == 0: - raise RuntimeError("No cores available for simulation") - - try: - simulate(self.params, self.ncore) - break - except RuntimeError as e: - if self.ncore == 1: - # can't reduce ncore any more - print(str(e)) - self.updatewaitsimwin(str(e)) - _kill_and_check_nrniv_procs() - raise RuntimeError("Simulation failed to start") - except: - # if it's something else we still want to print the error and then - # handle it like a RuntimeError instead of crashing the whole application - txt = traceback.format_exc() - sys.stderr.write(txt) - self.updatewaitsimwin(txt) - # pop up dialog pop - raise RuntimeError("Unknown error") + while True: + if self.ncore == 0: + raise RuntimeError("No cores available for simulation") + + try: + simulate(self.params, self.ncore) + break + except RuntimeError as e: + if self.ncore == 1: + # can't reduce ncore any more + print(str(e)) + self.updatewaitsimwin(str(e)) + _kill_and_check_nrniv_procs() + raise RuntimeError("Simulation failed to start") + + # check if proc was killed before retrying with fewer cores + self.lock.acquire() + if self.killed: + self.lock.release() + # exit using RuntimeError + raise RuntimeError("Terminated") + else: + self.lock.release() + + self.ncore = ceil(self.ncore/2) + txt = "INFO: Failed starting simulation, retrying with %d cores" \ + % self.ncore + print(txt) + self.updatewaitsimwin(txt) + + # should have good data written to files at this point + updatedat(self.params) + + if not is_opt: + # update lsimdat and its current sim index + updatelsimdat(self.paramfn, self.params, ddat['dpl']) + + def optmodel(self): + need_initial_ddat = False + + # initialize RNG with seed from config + seed = self.params['prng_seedcore_opt'] + nlopt.srand(seed) + + # initial_ddat stores the initial fit (from "Run Simulation"). + # To be displayed in final dipole plot as black dashed line. + if len(ddat) > 0: + initial_ddat['dpl'] = deepcopy(ddat['dpl']) + initial_ddat['errtot'] = deepcopy(ddat['errtot']) + else: + need_initial_ddat = True - # check if proc was killed before retrying with fewer cores - self.lock.acquire() - if self.killed: - self.lock.release() - # exit using RuntimeError - raise RuntimeError("Terminated") - else: - self.lock.release() + self.baseparamwin.optparamwin.populate_initial_opt_ranges() - self.ncore = ceil(self.ncore/2) - txt = "INFO: Failed starting simulation, retrying with %d cores" % self.ncore - print(txt) - self.updatewaitsimwin(txt) + # save initial parameters file + data_dir = op.join(get_output_dir(), 'data') + sim_dir = op.join(data_dir, self.params['sim_prefix']) + param_out = os.path.join(sim_dir, 'before_opt.param') + write_legacy_paramf(param_out, self.params) - # should have good data written to files at this point - updatedat(self.params) + self.updatewaitsimwin('Optimizing model. . .') - if not is_opt: - # update lsimdat and its current sim index - updatelsimdat(self.paramfn, self.params, ddat['dpl']) + self.last_step = False + self.first_step = True + num_steps = self.baseparamwin.optparamwin.get_num_chunks() + for step in range(num_steps): + self.cur_step = step + if step == num_steps - 1: + self.last_step = True - def optmodel (self): - global initial_ddat - need_initial_ddat = False + # disable range sliders for each step once that step has begun + self.baseparamwin.optparamwin.toggleEnableUserFields(step, + enable=False) - # initialize RNG with seed from config - seed = self.params['prng_seedcore_opt'] - nlopt.srand(seed) + self.step_ranges = \ + self.baseparamwin.optparamwin.get_chunk_ranges(step) + self.step_sims = \ + self.baseparamwin.optparamwin.get_sims_for_chunk(step) - # initial_ddat stores the initial fit (from "Run Simulation"). - # To be displayed in final dipole plot as black dashed line. - if len(ddat) > 0: - initial_ddat['dpl'] = deepcopy(ddat['dpl']) - initial_ddat['errtot'] = deepcopy(ddat['errtot']) - else: - need_initial_ddat = True + if self.step_sims == 0: + txt = "Skipping optimization step %d (0 simulations)" % \ + (step + 1) + self.updatewaitsimwin(txt) + continue - self.baseparamwin.optparamwin.populate_initial_opt_ranges() + if len(self.step_ranges) == 0: + txt = "Skipping optimization step %d (0 parameters)" % \ + (step + 1) + self.updatewaitsimwin(txt) + continue - # save initial parameters file - data_dir = op.join(get_output_dir(), 'data') - sim_dir = op.join(data_dir, self.params['sim_prefix']) - param_out = os.path.join(sim_dir,'before_opt.param') - write_legacy_paramf(param_out, self.params) + txt = "Starting optimization step %d/%d" % (step + 1, num_steps) + self.updatewaitsimwin(txt) + self.runOptStep(step) - self.updatewaitsimwin('Optimizing model. . .') + if 'dpl' in self.best_ddat: + ddat['dpl'] = deepcopy(self.best_ddat['dpl']) + if 'errtot' in self.best_ddat: + ddat['errtot'] = deepcopy(self.best_ddat['errtot']) - self.last_step = False - self.first_step = True - num_steps = self.baseparamwin.optparamwin.get_num_chunks() - for step in range(num_steps): - self.cur_step = step - if step == num_steps - 1: - self.last_step = True + if need_initial_ddat: + initial_ddat = deepcopy(ddat) - # disable range sliders for each step once that step has begun - self.baseparamwin.optparamwin.toggleEnableUserFields(step, enable=False) + # update optdat with best from this step + updateoptdat(self.paramfn, self.params, ddat['dpl']) - self.step_ranges = self.baseparamwin.optparamwin.get_chunk_ranges(step) - self.step_sims = self.baseparamwin.optparamwin.get_sims_for_chunk(step) + # put best opt results into GUI and save to param file + push_values = {} + for param_name in self.step_ranges.keys(): + push_values[param_name] = self.step_ranges[param_name]['final'] + self.updatebaseparamwin(push_values) + self.baseparamwin.optparamwin.push_chunk_ranges(step, push_values) - if self.step_sims == 0: - txt = "Skipping optimization step %d (0 simulations)"%(step+1) - self.updatewaitsimwin(txt) - continue + sleep(1) + + self.first_step = False + + # one final sim with the best parameters to update display + self.runsim(is_opt=True, banner=False) + + # update lsimdat and its current sim index + updatelsimdat(self.paramfn, self.params, ddat['dpl']) - if len(self.step_ranges) == 0: - txt = "Skipping optimization step %d (0 parameters)"%(step+1) + # update optdat with the final best + updateoptdat(self.paramfn, self.params, ddat['dpl']) + + # re-enable all the range sliders + self.baseparamwin.optparamwin.toggleEnableUserFields(step, + enable=True) + + self.baseparamwin.optparamwin.clear_initial_opt_ranges() + self.baseparamwin.optparamwin.optimization_running = False + + def runOptStep(self, step): + self.optsim = 0 + self.minopterr = 1e9 + self.stepminopterr = self.minopterr + self.best_ddat = {} + self.opt_start = self.baseparamwin.optparamwin.get_chunk_start(step) + self.opt_end = self.baseparamwin.optparamwin.get_chunk_end(step) + self.opt_weights = \ + self.baseparamwin.optparamwin.get_chunk_weights(step) + + def optrun(new_params, grad=0): + txt = "Optimization step %d, simulation %d" % (step + 1, + self.optsim + 1) + self.updatewaitsimwin(txt) + print(txt) + + dtest = {} + for param_name, test_value in zip(self.step_ranges.keys(), + new_params): + if test_value >= self.step_ranges[param_name]['minval'] and \ + test_value <= self.step_ranges[param_name]['maxval']: + dtest[param_name] = test_value + else: + # This test is not strictly necessary with COBYLA, but in + # case the algorithm is changed at some point in the future + print('INFO: optimization chose ' + '%.3f for %s outside of [%.3f-%.3f].' + % (test_value, param_name, + self.step_ranges[param_name]['minval'], + self.step_ranges[param_name]['maxval'])) + return 1e9 # invalid param value -> large error + + # put new param values into GUI and save params to file + self.updatebaseparamwin(dtest) + sleep(1) + + # run the simulation, but stop early if possible + self.runsim(is_opt=True, banner=False, simlength=self.opt_end) + + # calculate wRMSE for all steps + weighted_rmse(ddat, self.opt_end, self.opt_weights, + tstart=self.opt_start) + err = ddat['werrtot'] + + if self.last_step: + # weighted RMSE with weights of all 1's is the same as + # regular RMSE + ddat['errtot'] = ddat['werrtot'] + txt = "RMSE = %f" % err + else: + # calculate regular RMSE for displaying on plot + calcerr(ddat, self.opt_end, tstart=self.opt_start) + txt = "weighted RMSE = %f, RMSE = %f" % (err, ddat['errtot']) + + print(txt) + self.updatewaitsimwin(os.linesep + 'Simulation finished: ' + txt + + os.linesep) + + data_dir = op.join(get_output_dir(), 'data') + sim_dir = op.join(data_dir, self.params['sim_prefix']) + + fnoptinf = os.path.join(sim_dir, 'optinf.txt') + with open(fnoptinf, 'a') as fpopt: + fpopt.write(str(ddat['errtot']) + os.linesep) # write error + + # save params numbered by optsim + param_out = os.path.join(sim_dir, 'step_%d_sim_%d.param' % + (self.cur_step, self.optsim)) + write_legacy_paramf(param_out, self.params) + + if err < self.stepminopterr: + self.updatewaitsimwin("new best with RMSE %f" % err) + + self.stepminopterr = err + # save best param file + param_out = os.path.join(sim_dir, 'step_%d_best.param' % + self.cur_step) + write_legacy_paramf(param_out, self.params) + if 'dpl' in ddat: + self.best_ddat['dpl'] = ddat['dpl'] + if 'errtot' in ddat: + self.best_ddat['errtot'] = ddat['errtot'] + + if self.optsim == 0 and not self.first_step: + # Update plots for the first simulation only of this step + # (best results from last round). Skip the first step because + # there are no optimization results to show yet. + self.updatedrawerr() # send event to draw updated RMSE + + self.optsim += 1 + + return err # return RMSE + + def optimize(params_input, evals, algorithm): + opt_params = [] + lb = [] + ub = [] + + for param_name in params_input.keys(): + upper = params_input[param_name]['maxval'] + lower = params_input[param_name]['minval'] + if upper == lower: + continue + + ub.append(upper) + lb.append(lower) + opt_params.append(params_input[param_name]['initial']) + + if algorithm == nlopt.G_MLSL_LDS or algorithm == nlopt.G_MLSL: + # In case these mixed mode (global + local) algorithms are + # used in the future + local_opt = nlopt.opt(nlopt.LN_COBYLA, num_params) + opt.set_local_optimizer(local_opt) + + opt.set_lower_bounds(lb) + opt.set_upper_bounds(ub) + opt.set_min_objective(optrun) + opt.set_xtol_rel(1e-4) + opt.set_maxeval(evals) + opt_results = opt.optimize(opt_params) + + return opt_results + + txt = 'Optimizing from [%3.3f-%3.3f] ms' % (self.opt_start, + self.opt_end) self.updatewaitsimwin(txt) - continue - - txt = "Starting optimization step %d/%d" % (step + 1, num_steps) - self.updatewaitsimwin(txt) - self.runOptStep(step) - - if 'dpl' in self.best_ddat: - ddat['dpl'] = deepcopy(self.best_ddat['dpl']) - if 'errtot' in self.best_ddat: - ddat['errtot'] = deepcopy(self.best_ddat['errtot']) - - if need_initial_ddat: - initial_ddat = deepcopy(ddat) - - # update optdat with best from this step - updateoptdat(self.paramfn, self.params, ddat['dpl']) - - # put best opt results into GUI and save to param file - push_values = {} - for param_name in self.step_ranges.keys(): - push_values[param_name] = self.step_ranges[param_name]['final'] - self.updatebaseparamwin(push_values) - self.baseparamwin.optparamwin.push_chunk_ranges(step,push_values) - - sleep(1) - - self.first_step = False - - # one final sim with the best parameters to update display - self.runsim(is_opt=True, banner=False) - - # update lsimdat and its current sim index - updatelsimdat(self.paramfn, self.params, ddat['dpl']) - - # update optdat with the final best - updateoptdat(self.paramfn, self.params, ddat['dpl']) - - # re-enable all the range sliders - self.baseparamwin.optparamwin.toggleEnableUserFields(step, enable=True) - - self.baseparamwin.optparamwin.clear_initial_opt_ranges() - self.baseparamwin.optparamwin.optimization_running = False - - - def runOptStep (self, step): - self.optsim = 0 - self.minopterr = 1e9 - self.stepminopterr = self.minopterr - self.best_ddat = {} - self.opt_start = self.baseparamwin.optparamwin.get_chunk_start(step) - self.opt_end = self.baseparamwin.optparamwin.get_chunk_end(step) - self.opt_weights = self.baseparamwin.optparamwin.get_chunk_weights(step) - def optrun (new_params, grad=0): - txt = "Optimization step %d, simulation %d" % (step + 1, - self.optsim + 1) - self.updatewaitsimwin(txt) - print(txt) - - dtest = {} - for param_name, test_value in zip(self.step_ranges.keys(), new_params): # set parameters - if test_value >= self.step_ranges[param_name]['minval'] and \ - test_value <= self.step_ranges[param_name]['maxval']: - # print('optrun prm:', self.step_ranges[param_name]['initial'], - # self.step_ranges[param_name]['minval'], - # self.step_ranges[param_name]['maxval'], - # test_value) - dtest[param_name] = test_value - else: - # This test is not strictly necessary with COBYLA, but in case the algorithm - # is changed at some point in the future - print('INFO: optimization chose %.3f for %s outside of [%.3f-%.3f].' - % (test_value, param_name, - self.step_ranges[param_name]['minval'], - self.step_ranges[param_name]['maxval'])) - return 1e9 # invalid param value -> large error - - # put new param values into GUI and save params to file - self.updatebaseparamwin(dtest) - sleep(1) - - # run the simulation, but stop early if possible - self.runsim(is_opt=True, banner=False, simlength=self.opt_end) - - # calculate wRMSE for all steps - weighted_rmse(ddat, - self.opt_end, - self.opt_weights, - tstart=self.opt_start) - err = ddat['werrtot'] - - if self.last_step: - # weighted RMSE with weights of all 1's is the same as - # regular RMSE - ddat['errtot'] = ddat['werrtot'] - txt = "RMSE = %f"%err - else: - # calculate regular RMSE for displaying on plot - calcerr(ddat, - self.opt_end, - tstart=self.opt_start) - - txt = "weighted RMSE = %f, RMSE = %f"% (err,ddat['errtot']) - - print(txt) - self.updatewaitsimwin(os.linesep+'Simulation finished: ' + txt + os.linesep) # print error - - data_dir = op.join(get_output_dir(), 'data') - sim_dir = op.join(data_dir, self.params['sim_prefix']) - - fnoptinf = os.path.join(sim_dir,'optinf.txt') - with open(fnoptinf,'a') as fpopt: - fpopt.write(str(ddat['errtot'])+os.linesep) # write error - - # save params numbered by optsim - param_out = os.path.join(sim_dir,'step_%d_sim_%d.param'%(self.cur_step,self.optsim)) - write_legacy_paramf(param_out, self.params) - - if err < self.stepminopterr: - self.updatewaitsimwin("new best with RMSE %f"%err) - - self.stepminopterr = err - # save best param file - param_out = os.path.join(sim_dir,'step_%d_best.param'%self.cur_step) - write_legacy_paramf(param_out, self.params) - if 'dpl' in ddat: - self.best_ddat['dpl'] = ddat['dpl'] - if 'errtot' in ddat: - self.best_ddat['errtot'] = ddat['errtot'] - - if self.optsim == 0 and not self.first_step: - # Update plots for the first simulation only of this step (best results from last round) - # Skip the first step because there are no optimization results to show yet. - self.updatedrawerr() # send event to draw updated error (asynchronously) - - self.optsim += 1 - - return err # return error - - def optimize(params_input, evals, algorithm): - opt_params = [] - lb = [] - ub = [] - - for param_name in params_input.keys(): - upper = params_input[param_name]['maxval'] - lower = params_input[param_name]['minval'] - if upper == lower: - continue - - ub.append(upper) - lb.append(lower) - opt_params.append(params_input[param_name]['initial']) - - if algorithm == nlopt.G_MLSL_LDS or algorithm == nlopt.G_MLSL: - # In case these mixed mode (global + local) algorithms are used in the future - local_opt = nlopt.opt(nlopt.LN_COBYLA, num_params) - opt.set_local_optimizer(local_opt) - - opt.set_lower_bounds(lb) - opt.set_upper_bounds(ub) - opt.set_min_objective(optrun) - opt.set_xtol_rel(1e-4) - opt.set_maxeval(evals) - opt_results = opt.optimize(opt_params) - - return opt_results - - txt = 'Optimizing from [%3.3f-%3.3f] ms' % (self.opt_start, - self.opt_end) - self.updatewaitsimwin(txt) - - num_params = len(self.step_ranges) - algorithm = nlopt.LN_COBYLA - opt = nlopt.opt(algorithm, num_params) - opt_results = optimize(self.step_ranges, self.step_sims, algorithm) - - # update opt params for the next round - for var_name, new_value in zip(self.step_ranges, opt_results): - old_value = self.step_ranges[var_name]['initial'] - - # only change the parameter value if it changed significantly - if not isclose(old_value, new_value, abs_tol=1e-9): - self.step_ranges[var_name]['final'] = new_value - else: - self.step_ranges[var_name]['final'] = \ - self.step_ranges[var_name]['initial'] + + num_params = len(self.step_ranges) + algorithm = nlopt.LN_COBYLA + opt = nlopt.opt(algorithm, num_params) + opt_results = optimize(self.step_ranges, self.step_sims, algorithm) + + # update opt params for the next round + for var_name, new_value in zip(self.step_ranges, opt_results): + old_value = self.step_ranges[var_name]['initial'] + + # only change the parameter value if it changed significantly + if not isclose(old_value, new_value, abs_tol=1e-9): + self.step_ranges[var_name]['final'] = new_value + else: + self.step_ranges[var_name]['final'] = \ + self.step_ranges[var_name]['initial'] From 4dfb704fa1e06aded98cebf292e5165aa440880a Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 4 Oct 2020 14:36:31 -0400 Subject: [PATCH 035/107] MAINT: remove use of catchall except blocks Resolves #236 --- hnn/DataViewGUI.py | 17 +++++++---------- hnn/hnn_qt5.py | 12 +++--------- hnn/paramrw.py | 24 +++++++++++------------- hnn/simdat.py | 8 ++++---- hnn/specfn.py | 8 ++++---- hnn/spikefn.py | 8 +++----- hnn/visdipole.py | 16 +++++----------- hnn/vispsd.py | 10 ++++------ hnn/visrast.py | 31 ++++++++++++------------------- hnn/visvolt.py | 18 ++++++++---------- 10 files changed, 61 insertions(+), 91 deletions(-) diff --git a/hnn/DataViewGUI.py b/hnn/DataViewGUI.py index 6887e41d8..15fa194ad 100644 --- a/hnn/DataViewGUI.py +++ b/hnn/DataViewGUI.py @@ -82,14 +82,12 @@ def printStat (self,s): self.statusBar().showMessage(s) def initCanvas (self): - try: # to avoid memory leaks remove any pre-existing widgets before adding new ones - self.grid.removeWidget(self.m) - self.grid.removeWidget(self.toolbar) - self.m.setParent(None) - self.toolbar.setParent(None) - self.m = self.toolbar = None - except: - pass + self.grid.removeWidget(self.m) + self.grid.removeWidget(self.toolbar) + self.m.setParent(None) + self.toolbar.setParent(None) + self.m = self.toolbar = None + self.m = self.CanvasType(self.params, self.index, parent = self, width=12, height=10, dpi=getmplDPI()) # this is the Navigation widget # it takes the Canvas widget and a parent @@ -125,8 +123,7 @@ def initUI (self): widget.setLayout(grid) self.setCentralWidget(widget) - try: self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) - except: pass + self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) self.show() diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index ea3013767..39a6133b0 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -1501,10 +1501,8 @@ def distribsubwin (self): def updateDatCanv(self, params): # update the simulation data and canvas - try: - getinputfiles(self.baseparamwin.paramfn) # reset input data - if already exists - except: - pass + # reset input data - if already exists + getinputfiles(self.baseparamwin.paramfn) # now update the GUI components to reflect the param file selected self.baseparamwin.updateDispParam(params) @@ -1873,8 +1871,7 @@ def initUI (self): self.d = DoneSignal() self.d.finishSim.connect(self.done) - try: self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) - except: pass + self.setWindowIcon(QIcon(os.path.join('res', 'icon.png'))) self.schemwin.show() # so it's underneath main window @@ -1942,11 +1939,8 @@ def setcursors (self,cursor): kids = self.children() kids.append(self.m) # matplotlib simcanvas for k in kids: - try: k.setCursor(cursor) k.update() - except: - pass def startoptmodel (self): # start model optimization diff --git a/hnn/paramrw.py b/hnn/paramrw.py index 383b64156..65d256476 100644 --- a/hnn/paramrw.py +++ b/hnn/paramrw.py @@ -39,19 +39,17 @@ def usingOngoingInputs (params, lty = ['_prox', '_dist']): return False dpref = {'_prox':'input_prox_A_','_dist':'input_dist_A_'} - try: - for postfix in lty: - if float(params['t0_input'+postfix])<= tstop and \ - float(params['tstop_input'+postfix])>=float(params['t0_input'+postfix]) and \ - float(params['f_input'+postfix])>0.: - for k in ['weight_L2Pyr_ampa','weight_L2Pyr_nmda',\ - 'weight_L5Pyr_ampa','weight_L5Pyr_nmda',\ - 'weight_inh_ampa','weight_inh_nmda']: - if float(params[dpref[postfix]+k])>0.: - # print('usingOngoingInputs:',params[dpref[postfix]+k]) - return True - except: - return False + for postfix in lty: + if float(params['t0_input'+postfix])<= tstop and \ + float(params['tstop_input'+postfix])>=float(params['t0_input'+postfix]) and \ + float(params['f_input'+postfix])>0.: + for k in ['weight_L2Pyr_ampa','weight_L2Pyr_nmda',\ + 'weight_L5Pyr_ampa','weight_L5Pyr_nmda',\ + 'weight_inh_ampa','weight_inh_nmda']: + if float(params[dpref[postfix]+k])>0.: + # print('usingOngoingInputs:',params[dpref[postfix]+k]) + return True + return False # return number of evoked inputs (proximal, distal) diff --git a/hnn/simdat.py b/hnn/simdat.py index e313d9bb5..36f0016f7 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -552,10 +552,10 @@ def hassimdata (self): def clearlextdatobj (self): # clear list of external data objects for o in self.lextdatobj: - try: - o.set_visible(False) - except: - o[0].set_visible(False) + # try: + o.set_visible(False) + # except: + # o[0].set_visible(False) del self.lextdatobj self.lextdatobj = [] # reset list of external data objects self.lpatch = [] # reset legend diff --git a/hnn/specfn.py b/hnn/specfn.py index aa8a58ca1..f6f734db6 100644 --- a/hnn/specfn.py +++ b/hnn/specfn.py @@ -213,10 +213,10 @@ def __init__(self, fspec, spec_cmap='jet', dtype='dpl'): # may be better ways of doing this... self.fspec = fspec print('Spec: fspec:',fspec) - try: - self.expmt = fspec.split('/')[6].split('.')[0] - except: - self.expmt = '' + # try: + self.expmt = fspec.split('/')[6].split('.')[0] + # except: + # self.expmt = '' self.fname = 'spec.npz' # fspec.split('/')[-1].split('-spec')[0] self.spec_cmap = spec_cmap diff --git a/hnn/spikefn.py b/hnn/spikefn.py index 03445666e..84fec70ad 100644 --- a/hnn/spikefn.py +++ b/hnn/spikefn.py @@ -211,11 +211,9 @@ def is_dist_gid (self, gid): # check if gid is associated with a Poisson input def is_pois_gid (self, gid): - try: - if len(self.inputs['pois']) > 0: - return self.pois_gid_range[0] <= gid <= self.pois_gid_range[1] - except: - pass + if len(self.inputs['pois']) > 0: + return self.pois_gid_range[0] <= gid <= self.pois_gid_range[1] + return False def truncate_ext (self, dtype, t_int): diff --git a/hnn/visdipole.py b/hnn/visdipole.py index 24056db7c..5d9f57b0f 100644 --- a/hnn/visdipole.py +++ b/hnn/visdipole.py @@ -38,11 +38,8 @@ ddat = {} basedir = os.path.join(get_output_dir(), 'data', params['sim_prefix']) ddat['dpltrials'] = readdpltrials(basedir) -try: - ddat['dpl'] = np.loadtxt(os.path.join(basedir,'dpl.txt')) -except: - print('Could not load',dplpath) - quit() +ddat['dpl'] = np.loadtxt(os.path.join(basedir,'dpl.txt')) + class DipoleCanvas (FigureCanvas): @@ -57,13 +54,10 @@ def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, ti self.paramf = paramf self.plot() - def clearaxes (self): - try: + def clearaxes(self): for ax in self.lax: - ax.set_yticks([]) - ax.cla() - except: - pass + ax.set_yticks([]) + ax.cla() def drawdipole (self, fig): diff --git a/hnn/vispsd.py b/hnn/vispsd.py index 3da4585c7..5a12fd7d6 100644 --- a/hnn/vispsd.py +++ b/hnn/vispsd.py @@ -100,13 +100,11 @@ def drawpsd (self, dspec, fig, G, ltextra=''): return lax - def clearaxes (self): - try: + def clearaxes(self): for ax in self.lax: - ax.set_yticks([]) - ax.cla() - except: - pass + ax.set_yticks([]) + ax.cla() + def clearlextdatobj (self): if hasattr(self,'lextdatobj'): diff --git a/hnn/visrast.py b/hnn/visrast.py index 2387f8d92..fca67e42b 100644 --- a/hnn/visrast.py +++ b/hnn/visrast.py @@ -97,9 +97,7 @@ def getdspk (fn): ddat['spk'] = np.r_[spikes.times, spikes.gids].T except ValueError: ddat['spk'] = np.loadtxt(fn) - except: - print('Could not load',fn) - quit() + dspk = {'Cell':([],[],[]),'Input':([],[],[])} dhist = {} for ty in dclr.keys(): dhist[ty] = [] @@ -222,13 +220,10 @@ def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, ti self.G = gridspec.GridSpec(16,1) self.plot() - def clearaxes (self): - try: + def clearaxes(self): for ax in self.lax: - ax.set_yticks([]) - ax.cla() - except: - pass + ax.set_yticks([]) + ax.cla() def loadspk (self,idx): global haveinputs,extinputs,params @@ -363,14 +358,13 @@ def changeMarkerSize (self): self.m.plot() def initCanvas (self): - try: # to avoid memory leaks remove any pre-existing widgets before adding new ones - self.grid.removeWidget(self.m) - self.grid.removeWidget(self.toolbar) - self.m.setParent(None) - self.toolbar.setParent(None) - self.m = self.toolbar = None - except: - pass + # to avoid memory leaks remove any pre-existing widgets before adding new ones + self.grid.removeWidget(self.m) + self.grid.removeWidget(self.toolbar) + self.m.setParent(None) + self.toolbar.setParent(None) + self.m = self.toolbar = None + self.m = SpikeCanvas(paramf, self.index, parent = self, width=12, height=10, dpi=getmplDPI()) # this is the Navigation widget # it takes the Canvas widget and a parent @@ -402,8 +396,7 @@ def initUI (self): widget.setLayout(grid) self.setCentralWidget(widget) - try: self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) - except: pass + self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) self.show() diff --git a/hnn/visvolt.py b/hnn/visvolt.py index 54275cd80..4ac07c26d 100644 --- a/hnn/visvolt.py +++ b/hnn/visvolt.py @@ -179,14 +179,13 @@ def initMenu (self): def initCanvas (self): self.invertedax = False - try: # to avoid memory leaks remove any pre-existing widgets before adding new ones - self.grid.removeWidget(self.m) - self.grid.removeWidget(self.toolbar) - self.m.setParent(None) - self.toolbar.setParent(None) - self.m = self.toolbar = None - except: - pass + # to avoid memory leaks remove any pre-existing widgets before adding new ones + self.grid.removeWidget(self.m) + self.grid.removeWidget(self.toolbar) + self.m.setParent(None) + self.toolbar.setParent(None) + self.m = self.toolbar = None + self.m = VoltCanvas(paramf, self.index, parent = self, width=12, height=10, dpi=getmplDPI()) # this is the Navigation widget # it takes the Canvas widget and a parent @@ -213,8 +212,7 @@ def initUI (self): widget.setLayout(grid) self.setCentralWidget(widget) - try: self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) - except: pass + self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) self.show() From f932d32971d784440883ba68d8ad5d875c284577 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 4 Oct 2020 14:46:26 -0400 Subject: [PATCH 036/107] MAINT: fixed AttributeError uncovered by removing excepts --- hnn/hnn_qt5.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 39a6133b0..e70cf50aa 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -20,7 +20,7 @@ from PyQt5.QtWidgets import QToolTip, QPushButton, QFormLayout from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QDialog, QGridLayout from PyQt5.QtWidgets import QLineEdit, QLabel, QTextEdit, QInputDialog -from PyQt5.QtWidgets import QMenu, QMessageBox, QWidget, QDialog +from PyQt5.QtWidgets import QMenu, QMessageBox, QWidget, QDialog, QLayout from PyQt5.QtGui import QIcon, QPixmap, QFont from PyQt5.QtCore import pyqtSignal, QObject, Qt from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT @@ -1932,13 +1932,16 @@ def initSimCanvas (self,recalcErr=True,optMode=False,gRow=1,reInit=True): self.m.plot(recalcErr) self.m.draw() - def setcursors (self,cursor): + def setcursors(self, cursor): # set cursors of self and children self.setCursor(cursor) self.update() kids = self.children() - kids.append(self.m) # matplotlib simcanvas + kids.append(self.m) # matplotlib simcanvas for k in kids: + if type(k) == QLayout or type(k) == QAction: + # These types don't have setCursor() + continue k.setCursor(cursor) k.update() From bacce95af8d61c69701d928dae11539af2002f47 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 4 Oct 2020 15:49:07 -0400 Subject: [PATCH 037/107] TST: no longer running regular python hnn.py --- .travis.yml | 2 -- scripts/run-travis-wsl.sh | 1 - 2 files changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6e9218a46..b756c945d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -86,8 +86,6 @@ script: wsl -- bash -e //home/hnn_user/hnn/scripts/run-travis-wsl.sh stop_vcxsrv || script_fail else - echo "Testing GUI on host OS..." - python hnn.py echo "Running Python tests on host OS..." py.test --cov=. hnn/tests/ fi diff --git a/scripts/run-travis-wsl.sh b/scripts/run-travis-wsl.sh index 23517a0af..9ff8fde44 100755 --- a/scripts/run-travis-wsl.sh +++ b/scripts/run-travis-wsl.sh @@ -9,7 +9,6 @@ echo "Testing GUI on WSL..." cd $DIR/../ export DISPLAY=:0 -python3 hnn.py echo "Running Python tests on WSL..." py.test --cov=. hnn/tests/ \ No newline at end of file From 79fbb64e35601ff36fb29dd2e4b05fabed1571be Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 4 Oct 2020 17:13:56 -0400 Subject: [PATCH 038/107] TST: tweaks to Travis build --- .travis.yml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index b756c945d..6528686fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,29 +64,34 @@ install: - | # set up hnn module and testing packages if [[ "${WSL_INSTALL}" -ne 1 ]]; then pip install flake8 pytest pytest-cov coverage coveralls mne pytest-qt - command cd $TRAVIS_BUILD_DIR && python setup.py install + command cd $TRAVIS_BUILD_DIR + python setup.py install else - wsl -- bash -ec "pip install flake8 pytest pytest-cov coverage coveralls mne pytest-qt" - wsl -- bash -ec "python setup.py install" + wsl -- pip install flake8 pytest pytest-cov coverage coveralls mne \ + pytest-qt + wsl -- python3 setup.py install fi script: - | # run tests if [[ "${WSL_INSTALL}" -eq 1 ]]; then - # for start_vcxsrv_print and stop_vcxsrv + # helper commands from docker_functions.sh and utils.sh source "scripts/docker_functions.sh" set_globals - - # source utility functions source scripts/utils.sh export -f cleanup - find_command_suggested_path "vcxsrv" "/c/Program Files/VcXsrv" && \ - start_vcxsrv_print || script_fail - wsl -- bash -e //home/hnn_user/hnn/scripts/run-travis-wsl.sh + find_command_suggested_path "vcxsrv" "/c/Program Files/VcXsrv" + + # script_fail prints out the log before quitting so we can troubleshoot + start_vcxsrv_print || script_fail + wsl -- //home/hnn_user/hnn/scripts/run-travis-wsl.sh stop_vcxsrv || script_fail else echo "Running Python tests on host OS..." + pwd + command cd $TRAVIS_BUILD_DIR + pwd py.test --cov=. hnn/tests/ fi From e1c0683086e74a5105c6b0dc1fa4af5ac124f563 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 4 Oct 2020 23:10:18 -0400 Subject: [PATCH 039/107] MAINT: removing linux uninstall scripts --- installer/centos/uninstall.sh | 1 - installer/ubuntu/uninstaller.sh | 5 ----- 2 files changed, 6 deletions(-) delete mode 100644 installer/centos/uninstall.sh delete mode 100755 installer/ubuntu/uninstaller.sh diff --git a/installer/centos/uninstall.sh b/installer/centos/uninstall.sh deleted file mode 100644 index c1636639e..000000000 --- a/installer/centos/uninstall.sh +++ /dev/null @@ -1 +0,0 @@ -sudo rm -f /etc/profile.d/hnn.sh diff --git a/installer/ubuntu/uninstaller.sh b/installer/ubuntu/uninstaller.sh deleted file mode 100755 index 8d9d3a812..000000000 --- a/installer/ubuntu/uninstaller.sh +++ /dev/null @@ -1,5 +0,0 @@ -# clean up the bashrc (sed looks like it uses regex, but I don't -# think that it does. You do have to escape the '/' character, though) -sed -i '/# these lines define global session variables for HNN/d' ~/.bashrc -sed -i '/export CPU=$(uname -m)/d' ~/.bashrc -sed -i "/export PATH=\$PATH:$startdir\/nrn\/build\/\$CPU\/bin/d" ~/.bashrc From a72e7a92df795b1bd2eed4a2f09312b55be4ffbe Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 4 Oct 2020 23:43:31 -0400 Subject: [PATCH 040/107] MAINT: update setup.py to include .params --- setup.py | 55 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/setup.py b/setup.py index 61bb70ba0..d2be1c90a 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,39 @@ -import setuptools +#! /usr/bin/env python -setuptools.setup( - name="hnn", # Replace with your own username - version="1.4.0", - author="Blake Caldwell", - author_email="blake_caldwell@brown.edu", - description="Human Neocortical Neurosolver", - long_description='', - url="https://github.com/jonescompneurolab/hnn", - packages=setuptools.find_packages(), - classifiers=[ - "Programming Language :: Python :: 3", - "License :: Brown CS License", - "Operating System :: OS Independent", - ], - python_requires='>=3.6', -) +from setuptools import setup, find_packages + +descr = """Human Neocortical Neurosolver""" + +DISTNAME = 'hnn' +DESCRIPTION = descr +MAINTAINER = 'Blake Caldwell' +MAINTAINER_EMAIL = 'blake_caldwell@brown.edu' +URL = '' +LICENSE = 'Brown CS License' +DOWNLOAD_URL = 'http://github.com/jonescompneurolab/hnn' +VERSION = '1.4.0' + +if __name__ == "__main__": + setup(name=DISTNAME, + maintainer=MAINTAINER, + maintainer_email=MAINTAINER_EMAIL, + description=DESCRIPTION, + license=LICENSE, + url=URL, + version=VERSION, + download_url=DOWNLOAD_URL, + long_description=open('README.md').read(), + classifiers=[ + 'Intended Audience :: Science/Research', + 'License :: OSI Approved', + 'Programming Language :: Python', + 'Topic :: Scientific/Engineering', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Operating System :: MacOS', + ], + platforms='any', + packages=find_packages(), + package_data={'hnn': + ['../param/*.param']}) From 2f3eca3c90e8a402dcf0c7060f3346517eee13bf Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 5 Oct 2020 15:59:52 -0400 Subject: [PATCH 041/107] MAINT: remove NEURON+hnn-core from enviornment.yml NEURON doesn't have a standard pip installation that will work for Winodws yet. Until then, we cannot install hnn-core using the default dependencies, which might pull in an incorrect version of NEURON. This makes the conda install two steps: 'conda create' and 'pip install' --- environment.yml | 2 -- installer/ubuntu/hnn-ubuntu.sh | 34 +++++++++++++++++++++---------- installer/windows/hnn-windows.ps1 | 20 +++++++++++------- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/environment.yml b/environment.yml index 2682ca83a..29c996385 100644 --- a/environment.yml +++ b/environment.yml @@ -9,6 +9,4 @@ dependencies: - matplotlib - psutil - pip: - - NEURON - - https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master - PyQt5 \ No newline at end of file diff --git a/installer/ubuntu/hnn-ubuntu.sh b/installer/ubuntu/hnn-ubuntu.sh index eb7e6db7e..51c6325a1 100755 --- a/installer/ubuntu/hnn-ubuntu.sh +++ b/installer/ubuntu/hnn-ubuntu.sh @@ -104,6 +104,21 @@ if [[ "$USE_CONDA" -eq 0 ]]; then echo "Waiting for python packages for HNN downloads to finish..." NAME="downloading python packages for HNN " wait_for_pid "${PIP_PID}" "$NAME" + + $PIP install --no-cache-dir NEURON + + # WSL may not have nrnivmodl in PATH + if ! which nrnivmodl &> /dev/null; then + export PATH="$PATH:$HOME/.local/bin" + echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc + fi + + echo "Installing python packages for HNN with pip..." | tee -a "$LOGFILE" + $PIP install --no-cache-dir --user matplotlib PyOpenGL \ + pyqt5 pyqtgraph scipy numpy nlopt psutil &> "$LOGFILE" + + pip install \ + https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master else URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh" FILENAME="$HOME/miniconda.sh" @@ -119,22 +134,19 @@ else # conda is faster to install nlopt conda install -y -n hnn -c conda-forge nlopt - conda install -y -n hnn mpi4py source activate hnn && echo "activated conda HNN environment" -fi -$PIP install --no-cache-dir NEURON + echo "Installing MPI compilation toolchain..." | tee -a "$LOGFILE" + # get prerequisites to build mpi4py + sudo -E apt-get install --no-install-recommends -y \ + libopenmpi-dev &> "$LOGFILE" -# WSL may not have nrnivmodl in PATH -if ! which nrnivmodl &> /dev/null; then - export PATH="$PATH:$HOME/.local/bin" - echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc -fi + pip install mpi4py NEURON + pip install \ + https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master +fi -echo "Installing python packages for HNN with pip..." | tee -a "$LOGFILE" -$PIP install --no-cache-dir --user matplotlib \ - pyqt5 scipy numpy nlopt psutil &> "$LOGFILE" echo "Downloading runtime prerequisite packages..." | tee -a "$LOGFILE" apt-get download \ openmpi-bin lsof libfontconfig1 libxext6 libx11-xcb1 libxcb-glx0 \ diff --git a/installer/windows/hnn-windows.ps1 b/installer/windows/hnn-windows.ps1 index 1c377ff31..c4a6bea6f 100644 --- a/installer/windows/hnn-windows.ps1 +++ b/installer/windows/hnn-windows.ps1 @@ -6,7 +6,7 @@ install Miniconda: - Miniconda3-latest-Windows-x86_64.exe Additionally the following will be installed if they are not found: - - nrn-7.7.w64-mingwsetup.exe + - nrn-7.8.w64-mingwsetup.exe Other requirements: - Only 64-bit installs are supported due to NEURON compatibility @@ -310,8 +310,8 @@ if ($script:installMiniconda) { $program = "NEURON" if (!(Test-Installed($program))) { - $file = "nrn-7.7.w64-mingwsetup.exe" - $url = "https://neuron.yale.edu/ftp/neuron/versions/v7.7/$file" + $file = "nrn-7.8.w64-mingwsetup.exe" + $url = "https://neuron.yale.edu/ftp/neuron/versions/v7.8/$file" Download-Program $program $file $url $dirpath = $script:NEURON_PATH Write-Host "Installing $program to $dirpath..." @@ -398,7 +398,8 @@ if ($null -ne $script:VIRTUALENV) { if (Test-Python-3($script:PYTHON)) { # use pip3 for good measure Start-Process "$HOME\venv\hnn\Scripts\pip3" "install matplotlib scipy PyQt5 psutil nlopt" -Wait - Start-Process "$HOME\venv\hnn\Scripts\pip3" "install --upgrade https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master" -Wait + # get hnn-core, but skip NEURON dependency + Start-Process "$HOME\venv\hnn\Scripts\pip3" "install --no-deps mpi4py https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master" -Wait } else { Write-Warning "Virtualenv failed to create a valid python3 environment" @@ -426,13 +427,18 @@ elseif ($null -ne $script:CONDA_PATH) { conda env create -f environment.yml conda install -y -n hnn -c conda-forge nlopt - pip install mpi4py + # need to call the right pip to install in miniconda environment + # get hnn-core, but skip NEURON dependency + Set-Location $HOME + Miniconda3\envs\hnn\Scripts\pip install --no-deps mpi4py https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master + Set-Location $CONDA_ENV mkdir .\etc\conda\activate.d 2>&1>$null mkdir .\etc\conda\deactivate.d 2>&1>$null - #"set NRN_PYLIB=$script:PYTHON_DLL" | Set-Content "$CONDA_ENV\etc\conda\activate.d\env_vars.bat" - "set PYTHONHOME=$CONDA_ENV" | Add-Content "$CONDA_ENV\etc\conda\activate.d\env_vars.bat" + # "set NRN_PYLIB=$script:PYTHON_DLL" | Set-Content "$CONDA_ENV\etc\conda\activate.d\env_vars.bat" + "set PYTHONPATH=$script:NEURON_PATH\lib\python" | Add-Content "$CONDA_ENV\etc\conda\activate.d\env_vars.bat" + "export PYTHONPATH=/c/nrn/lib/python" | Add-Content "$CONDA_ENV\etc\conda\activate.d\env_vars.sh" } else { Write-Host "Miniconda hnn environment already exists" From 3db313b1528d314be7ccfcae6be3fc2c2cfd47c2 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 5 Oct 2020 17:03:02 -0400 Subject: [PATCH 042/107] MAINT: start using Python 3.8 in environment.yml --- hnn/paramrw.py | 4 ++-- hnn/spikefn.py | 2 +- installer/mac/README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hnn/paramrw.py b/hnn/paramrw.py index 65d256476..3c15b1c3a 100644 --- a/hnn/paramrw.py +++ b/hnn/paramrw.py @@ -146,9 +146,9 @@ def read_gids_param (fparam): if line.startswith('#'): continue keystring, val = line.split(": ") key = keystring.strip() - if val[0] is '[': + if val[0] == '[': val_range = val[1:-1].split(', ') - if len(val_range) is 2: + if len(val_range) == 2: ind_start = int(val_range[0]) ind_end = int(val_range[1]) + 1 gid_dict[key] = np.arange(ind_start, ind_end) diff --git a/hnn/spikefn.py b/hnn/spikefn.py index 84fec70ad..a76086609 100644 --- a/hnn/spikefn.py +++ b/hnn/spikefn.py @@ -234,7 +234,7 @@ def add_delay_times (self): # extinput is either 'dist' or 'prox' def plot_hist (self, ax, extinput, tvec, bins='auto', xlim=None, color='green', hty='bar',lw=4): - if bins is 'auto': + if bins == 'auto': bins = hist_bin_opt(self.inputs[extinput], 1) if not xlim: xlim = (0., self.p_dict['tstop']) diff --git a/installer/mac/README.md b/installer/mac/README.md index 70977bb2c..49f56553f 100644 --- a/installer/mac/README.md +++ b/installer/mac/README.md @@ -45,7 +45,7 @@ The Xcode Command Line Tools package includes utilities for compiling code from 1. Create a conda environment with the Python prerequisites for HNN. ```bash - conda create -y -n hnn python=3.7 openmpi pyqtgraph pyopengl matplotlib scipy psutil + conda env create -f environment.yml ``` 2. Activate the HNN conda environment and install nlopt and NEURON From 3e0c99fbd70d50835212ecb41aff5cd3cfd162d0 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 5 Oct 2020 16:30:11 -0400 Subject: [PATCH 043/107] TST: disable WSL CI while msys2 server is down --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6528686fc..7fca55d2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,12 @@ matrix: name: "MacOS Catalina" osx_image: xcode12 - # WSL - - os: windows - name: "WSL" - env: - - WSL_INSTALL=1 - - USE_CONDA=0 + # # WSL + # - os: windows + # name: "WSL" + # env: + # - WSL_INSTALL=1 + # - USE_CONDA=0 # Windows - os: windows From a7847722af690b690ae8ea229eb2a42dec66fc07 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 5 Oct 2020 16:05:44 -0400 Subject: [PATCH 044/107] TST: Travis fixes after installer change --- .travis.yml | 30 ++++++++++++++++++++++-------- scripts/setup-travis-osx.sh | 8 +++++++- scripts/setup-travis-windows.sh | 11 ----------- scripts/setup-travis-wsl.sh | 22 +++++++++++----------- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7fca55d2e..640ebc4ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,11 +44,16 @@ matrix: - xvfb before_install: - - | # common environment exports for all subshells (scripts) + - | + # set common environment exports for all subshells (scripts) + + set -e export TRAVIS_TESTING=1 export DISPLAY=:0 export LOGFILE="hnn_travis.log" - - | # Run prerequisite installation script + - | + # Step 1: install prerequisites + if [ "${WSL_INSTALL}" ] && [ "${WSL_INSTALL}" -eq 1 ]; then echo "Installing WSL prerequisites" scripts/setup-travis-wsl.sh @@ -61,7 +66,9 @@ before_install: fi install: - - | # set up hnn module and testing packages + - | + # Step 2: install hnn Python module and modules for testing + if [[ "${WSL_INSTALL}" -ne 1 ]]; then pip install flake8 pytest pytest-cov coverage coveralls mne pytest-qt command cd $TRAVIS_BUILD_DIR @@ -69,11 +76,13 @@ install: else wsl -- pip install flake8 pytest pytest-cov coverage coveralls mne \ pytest-qt - wsl -- python3 setup.py install + wsl -- python3 setup.py install --user fi script: - - | # run tests + - | + # Step 3: run CI tests with py.test + if [[ "${WSL_INSTALL}" -eq 1 ]]; then # helper commands from docker_functions.sh and utils.sh source "scripts/docker_functions.sh" @@ -88,10 +97,15 @@ script: wsl -- //home/hnn_user/hnn/scripts/run-travis-wsl.sh stop_vcxsrv || script_fail else + if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then + # NEURON will fail to import if DISPLAY is set + unset DISPLAY + elif [[ "${TRAVIS_OS_NAME}" == "windows" ]]; then + # Python will search path to find neuron dll's + export PATH=$PATH:/c/nrn/bin + fi + echo "Running Python tests on host OS..." - pwd - command cd $TRAVIS_BUILD_DIR - pwd py.test --cov=. hnn/tests/ fi diff --git a/scripts/setup-travis-osx.sh b/scripts/setup-travis-osx.sh index 5cc9193f9..fe376002a 100755 --- a/scripts/setup-travis-osx.sh +++ b/scripts/setup-travis-osx.sh @@ -18,4 +18,10 @@ source "$HOME/Miniconda3/etc/profile.d/conda.sh" conda env create -f environment.yml conda install -y -n hnn openmpi mpi4py # conda is faster to install nlopt -conda install -y -n hnn -c conda-forge nlopt \ No newline at end of file +conda install -y -n hnn -c conda-forge nlopt + +conda activate hnn +# NEURON needs to be installed first (not in the same command) or the wheel +# build in hnn-core will fail +pip install NEURON +pip install https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master \ No newline at end of file diff --git a/scripts/setup-travis-windows.sh b/scripts/setup-travis-windows.sh index fa8af0628..df1978335 100755 --- a/scripts/setup-travis-windows.sh +++ b/scripts/setup-travis-windows.sh @@ -10,14 +10,3 @@ echo "Running HNN Windows install script..." powershell.exe -ExecutionPolicy Bypass -File ./installer/windows/hnn-windows.ps1 source "$HOME/Miniconda3/etc/profile.d/conda.sh" -conda activate hnn - -# # add miniconda python to the path -# export PATH=$PATH:$HOME/Miniconda3/Scripts -# export PATH=$HOME/Miniconda3/envs/hnn/:$PATH -# export PATH=$HOME/Miniconda3/envs/hnn/Scripts:$PATH -# export PATH=$HOME/Miniconda3/envs/hnn/Library/bin:$PATH - -# set other variables for neuron and HNN -export PATH=$PATH:/c/nrn/bin -export NEURONHOME=/c/nrn diff --git a/scripts/setup-travis-wsl.sh b/scripts/setup-travis-wsl.sh index 0d360b3bf..d7d9d9182 100755 --- a/scripts/setup-travis-wsl.sh +++ b/scripts/setup-travis-wsl.sh @@ -57,17 +57,17 @@ start_download "$FILENAME" "$URL" > /dev/null echo "Installing VcXsrv..." cmd //c "$HOME/vcxsrv-64.1.20.8.1.installer.exe /S" -# # get opengl32.dll from mesa -# # this is needed to be able to start vcxsrv -# export msys2='cmd //C RefreshEnv.cmd ' -# export msys2+='& set MSYS=winsymlinks:nativestrict ' -# export msys2+='& C:\\tools\\msys64\\msys2_shell.cmd -defterm -no-start' -# export mingw64="$msys2 -mingw64 -full-path -here -c "\"\$@"\" --" -# export msys2+=" -msys2 -c "\"\$@"\" --" -# $msys2 pacman --sync --noconfirm --needed mingw-w64-x86_64-mesa - -# # for MESA dll's -# export PATH=$PATH:/c/tools/msys64/mingw64/bin +# get opengl32.dll from mesa +# this is needed to be able to start vcxsrv +export msys2='cmd //C RefreshEnv.cmd ' +export msys2+='& set MSYS=winsymlinks:nativestrict ' +export msys2+='& C:\\tools\\msys64\\msys2_shell.cmd -defterm -no-start' +export mingw64="$msys2 -mingw64 -full-path -here -c "\"\$@"\" --" +export msys2+=" -msys2 -c "\"\$@"\" --" +$msys2 pacman --sync --noconfirm --needed mingw-w64-x86_64-mesa + +# for MESA dll's +export PATH=$PATH:/c/tools/msys64/mingw64/bin # for using X server export PATH="$PATH:/c/Program\ Files/VcXsrv" From a1cdf8fd610dca8e8bbc00f2a8b9e04e8afba57b Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 5 Oct 2020 16:07:54 -0400 Subject: [PATCH 045/107] TST: cleanup WSL testing scripts --- .travis.yml | 10 +++++++--- scripts/{run-travis-wsl.sh => run-pytest-wsl.sh} | 5 ----- scripts/setup-travis-wsl.ps1 | 8 ++++---- scripts/setup-travis-wsl.sh | 3 --- 4 files changed, 11 insertions(+), 15 deletions(-) rename scripts/{run-travis-wsl.sh => run-pytest-wsl.sh} (62%) diff --git a/.travis.yml b/.travis.yml index 640ebc4ad..3cc55988c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,12 +45,17 @@ matrix: before_install: - | - # set common environment exports for all subshells (scripts) + # Step 0: set common environment variables set -e export TRAVIS_TESTING=1 export DISPLAY=:0 export LOGFILE="hnn_travis.log" + + if [ "${WSL_INSTALL}" ] && [ "${WSL_INSTALL}" -eq 1 ]; then + # for sharing with WSL environment + export WSLENV=TRAVIS_TESTING/u + fi - | # Step 1: install prerequisites @@ -71,7 +76,6 @@ install: if [[ "${WSL_INSTALL}" -ne 1 ]]; then pip install flake8 pytest pytest-cov coverage coveralls mne pytest-qt - command cd $TRAVIS_BUILD_DIR python setup.py install else wsl -- pip install flake8 pytest pytest-cov coverage coveralls mne \ @@ -94,7 +98,7 @@ script: # script_fail prints out the log before quitting so we can troubleshoot start_vcxsrv_print || script_fail - wsl -- //home/hnn_user/hnn/scripts/run-travis-wsl.sh + wsl -- //home/hnn_user/hnn/scripts/run-pytest-wsl.sh stop_vcxsrv || script_fail else if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then diff --git a/scripts/run-travis-wsl.sh b/scripts/run-pytest-wsl.sh similarity index 62% rename from scripts/run-travis-wsl.sh rename to scripts/run-pytest-wsl.sh index 9ff8fde44..2401693c6 100755 --- a/scripts/run-travis-wsl.sh +++ b/scripts/run-pytest-wsl.sh @@ -1,13 +1,8 @@ #!/bin/bash set -e -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" export PATH="$PATH:$HOME/.local/bin" export OMPI_MCA_btl_vader_single_copy_mechanism=none - -echo "Testing GUI on WSL..." -cd $DIR/../ - export DISPLAY=:0 echo "Running Python tests on WSL..." diff --git a/scripts/setup-travis-wsl.ps1 b/scripts/setup-travis-wsl.ps1 index cd0d4a2f7..8d641ce75 100644 --- a/scripts/setup-travis-wsl.ps1 +++ b/scripts/setup-travis-wsl.ps1 @@ -13,20 +13,20 @@ $userenv = [System.Environment]::GetEnvironmentVariable("Path", "User"); [System Write-Host "Configuring Ubuntu WSL..." & .\Ubuntu\Ubuntu1804.exe install --root -# add hnn_user +# This creates "hnn_user" which will be the default user in WSL & wsl -- bash -ec "groupadd hnn_group && useradd -m -b /home/ -g hnn_group hnn_user && adduser hnn_user sudo && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && chsh -s /bin/bash hnn_user" -# copy hnn dir to hnn_user homedir and change permissions +# Copy hnn source (from Travis clone) to hnn_user homedir and change permissions & wsl -- bash -ec "cp -r build/jonescompneurolab/hnn /home/hnn_user/ && chown -R hnn_user: /home/hnn_user && apt-get update && apt-get install -y dos2unix" -# run future commands as hnn_user +# Now all future commands can be run as hnn_user & .\Ubuntu\Ubuntu1804.exe config --default-user hnn_user # remove windows newlines & wsl -- bash -ec "dos2unix /home/hnn_user/hnn/scripts/* /home/hnn_user/hnn/installer/ubuntu/hnn-ubuntu.sh /home/hnn_user/hnn/installer/docker/hnn_envs" Write-Host "Installing HNN in Ubuntu WSL..." -& wsl -- bash -ec "cd /home/hnn_user/hnn && source scripts/utils.sh && export LOGFILE=ubuntu_install.log && TRAVIS_TESTING=1 installer/ubuntu/hnn-ubuntu.sh || script_fail" +& wsl -- bash -ec "cd /home/hnn_user/hnn && source scripts/utils.sh && export LOGFILE=ubuntu_install.log && installer/ubuntu/hnn-ubuntu.sh || script_fail" if (!$?) { exit 1 diff --git a/scripts/setup-travis-wsl.sh b/scripts/setup-travis-wsl.sh index d7d9d9182..100113277 100755 --- a/scripts/setup-travis-wsl.sh +++ b/scripts/setup-travis-wsl.sh @@ -1,9 +1,6 @@ #!/bin/bash set -e -# for sharing with WSL environment -export WSLENV=TRAVIS_TESTING/u - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" # we use wait_for_pid and start_download from utils.sh From 7f389eda644112eff94456f8037f2d5c799b0b80 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 5 Oct 2020 16:56:01 -0400 Subject: [PATCH 046/107] TST: flake8 for info and skip some GUI tests Just start counting flake8 errors and set a goal for which files will should be fixed. This returns zero (for now). The dialog and GUI tests aren't working well. Try again once more work has been done on the view*.py code. --- .travis.yml | 5 +++++ hnn/tests/test_gui.py | 5 +++-- hnn/tests/test_view_windows.py | 20 +++++++++++++------- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3cc55988c..1bafcb615 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,6 +87,11 @@ script: - | # Step 3: run CI tests with py.test + # first check code style with flake8 (ignored currently) + flake8 --exit-zero --quiet --count \ + --exclude visdipole.py,visrast.py,visvolt.py,visspec.py,vispsd.py \ + --exclude spikefn.py,specfn.py,DataViewGUI.py hnn + if [[ "${WSL_INSTALL}" -eq 1 ]]; then # helper commands from docker_functions.sh and utils.sh source "scripts/docker_functions.sh" diff --git a/hnn/tests/test_gui.py b/hnn/tests/test_gui.py index bd11c8642..2d9606973 100644 --- a/hnn/tests/test_gui.py +++ b/hnn/tests/test_gui.py @@ -1,13 +1,14 @@ from PyQt5 import QtWidgets, QtCore +import pytest from hnn import HNNGUI - +@pytest.mark.skip def test_HNNGUI(qtbot): main = HNNGUI() qtbot.addWidget(main) - +@pytest.mark.skip def test_exit_button(qtbot, monkeypatch): exit_calls = [] monkeypatch.setattr(QtWidgets.QApplication, "exit", diff --git a/hnn/tests/test_view_windows.py b/hnn/tests/test_view_windows.py index 03a152a36..d9bfb80ee 100644 --- a/hnn/tests/test_view_windows.py +++ b/hnn/tests/test_view_windows.py @@ -1,5 +1,6 @@ import os.path as op +import pytest from mne.utils import _fetch_file from hnn import HNNGUI @@ -14,6 +15,7 @@ def fetch_file(fname): _fetch_file(data_url, fname) +@pytest.mark.skip(reason="Skipping until #232 improves launching view windows") def test_view_rast(qtbot): """Show the spiking activity window""" fname = 'spk.txt' @@ -26,6 +28,7 @@ def test_view_rast(qtbot): main.viewRasterAction.trigger() +@pytest.mark.skip(reason="Skipping until #232 improves launching view windows") def test_view_dipole(qtbot): """Show the dipole window""" fname = 'dpl.txt' @@ -38,6 +41,7 @@ def test_view_dipole(qtbot): main.viewDipoleAction.trigger() +@pytest.mark.skip(reason="Skipping until #232 improves launching view windows") def test_view_psd(qtbot): """Show the PSD window""" fname = 'dpl.txt' @@ -50,6 +54,7 @@ def test_view_psd(qtbot): main.viewPSDAction.trigger() +@pytest.mark.skip(reason="Skipping until #232 improves launching view windows") def test_view_spec(qtbot): """Show the pectrogram window""" fname = 'dpl.txt' @@ -62,12 +67,13 @@ def test_view_spec(qtbot): main.viewSpecAction.trigger() -# def test_view_soma(qtbot): -# fname = 'spike.txt' -# fetch_file(fname) +@pytest.mark.skip(reason="Skipping until #232 improves launching view windows") +def test_view_soma(qtbot): + fname = 'spike.txt' + fetch_file(fname) -# # start the GUI -# main = HNNGUI() -# qtbot.addWidget(main) + # start the GUI + main = HNNGUI() + qtbot.addWidget(main) -# main.viewSomaVAction.trigger() + main.viewSomaVAction.trigger() From a6e856cca4d31e8703f8ed1e8e6e5863c0a977fa Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 5 Oct 2020 18:20:40 -0400 Subject: [PATCH 047/107] TST: fix WSL testing by adding VcXsrv to PATH Move around code that creates a new user with a space "test user" to setup-travis-windows.sh run running py.test as a different user. --- .travis.yml | 22 ++++++++------ scripts/setup-travis-windows.sh | 29 ++++++++++++++++++- scripts/setup-travis-wsl.sh | 51 --------------------------------- 3 files changed, 41 insertions(+), 61 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1bafcb615..bf91f270c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,12 @@ matrix: name: "MacOS Catalina" osx_image: xcode12 - # # WSL - # - os: windows - # name: "WSL" - # env: - # - WSL_INSTALL=1 - # - USE_CONDA=0 + # WSL + - os: windows + name: "WSL" + env: + - WSL_INSTALL=1 + - USE_CONDA=0 # Windows - os: windows @@ -93,15 +93,17 @@ script: --exclude spikefn.py,specfn.py,DataViewGUI.py hnn if [[ "${WSL_INSTALL}" -eq 1 ]]; then - # helper commands from docker_functions.sh and utils.sh + # for X server DLLs + export PATH="$PATH:/c/Program\ Files/VcXsrv" + + # use helper commands from docker_functions.sh and utils.sh + # script_fail prints out the log before quitting so we can troubleshoot source "scripts/docker_functions.sh" set_globals source scripts/utils.sh export -f cleanup find_command_suggested_path "vcxsrv" "/c/Program Files/VcXsrv" - - # script_fail prints out the log before quitting so we can troubleshoot start_vcxsrv_print || script_fail wsl -- //home/hnn_user/hnn/scripts/run-pytest-wsl.sh stop_vcxsrv || script_fail @@ -112,6 +114,8 @@ script: elif [[ "${TRAVIS_OS_NAME}" == "windows" ]]; then # Python will search path to find neuron dll's export PATH=$PATH:/c/nrn/bin + + runas //user:"test user" "cmd py.test --cov=. hnn/tests/" > /dev/null < "$HOME/test_user_creds" fi echo "Running Python tests on host OS..." diff --git a/scripts/setup-travis-windows.sh b/scripts/setup-travis-windows.sh index df1978335..d85a83612 100755 --- a/scripts/setup-travis-windows.sh +++ b/scripts/setup-travis-windows.sh @@ -9,4 +9,31 @@ powershell -command "(New-Object System.Net.WebClient).DownloadFile('https://git echo "Running HNN Windows install script..." powershell.exe -ExecutionPolicy Bypass -File ./installer/windows/hnn-windows.ps1 -source "$HOME/Miniconda3/etc/profile.d/conda.sh" +# enable windows remoting service to log in as a different user to run tests +powershell -Command 'Start-Service -Name WinRM' > /dev/null +powershell -Command 'Start-Service -Name seclogon' > /dev/null + +# change settings to allow a blank password for TEST_USER +reg add HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Lsa //t REG_DWORD //v LimitBlankPasswordUse //d 0 //f 2>&1 > /dev/null +net accounts /minpwlen:0 > /dev/null + +# create the test user with a space in the username +echo "Creating user: \"test user\"..." +TEST_USER="test user" +echo > "$HOME/test_user_creds" +net user "$TEST_USER" //ADD "//homedir:\\\\%computername%\\users\\$TEST_USER" > /dev/null < "$HOME/test_user_creds" + +# add to administrators group +net localgroup administrators "$TEST_USER" //add > /dev/null + +# run a command as new user to create home directory +runas //user:"$TEST_USER" "cmd /C whoami" > /dev/null < "$HOME/test_user_creds" + +# copy hnn source to test user's home directory +TEST_USER_DIR="/c/Users/$TEST_USER" +if [ -d "$TEST_USER_DIR" ]; then + cp -r "$(pwd)" "$TEST_USER_DIR/" +else + echo "No user home directory created at $TEST_USER_DIR" + exit 2 +fi diff --git a/scripts/setup-travis-wsl.sh b/scripts/setup-travis-wsl.sh index 100113277..ff906bc21 100755 --- a/scripts/setup-travis-wsl.sh +++ b/scripts/setup-travis-wsl.sh @@ -10,42 +10,6 @@ echo "Installing Ubuntu WSL..." powershell.exe -ExecutionPolicy Bypass -File ./scripts/setup-travis-wsl.ps1 & WSL_PID=$! -# prepare for installing msys2 -[[ ! -f C:/tools/msys64/msys2_shell.cmd ]] && rm -rf C:/tools/msys64 -choco uninstall -y mingw - -# enable windows remoting service to log in as a different user to run tests -powershell -Command 'Start-Service -Name WinRM' > /dev/null -powershell -Command 'Start-Service -Name seclogon' > /dev/null - -# change settings to allow a blank password for TEST_USER -reg add HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Lsa //t REG_DWORD //v LimitBlankPasswordUse //d 0 //f 2>&1 > /dev/null -net accounts /minpwlen:0 > /dev/null - -# create the test user with a space in the username -echo "Creating user: \"test user\"..." -TEST_USER="test user" -echo > "$HOME/test_user_creds" -net user "$TEST_USER" //ADD "//homedir:\\\\%computername%\\users\\$TEST_USER" > /dev/null < "$HOME/test_user_creds" - -# add to administrators group -net localgroup administrators "$TEST_USER" //add > /dev/null - -# run a command as new user to create home directory -runas //user:"$TEST_USER" "cmd /C whoami" > /dev/null < "$HOME/test_user_creds" - -# copy hnn source to test user's home directory -TEST_USER_DIR="/c/Users/$TEST_USER" -if [ -d "$TEST_USER_DIR" ]; then - cp -r "$(pwd)" "$TEST_USER_DIR/" -else - echo "No user home directory created at $TEST_USER_DIR" - exit 2 -fi - -echo "Installing msys2 with choco..." -choco upgrade --no-progress -y msys2 &> /dev/null - echo "Downloading VcXsrv..." URL="https://downloads.sourceforge.net/project/vcxsrv/vcxsrv/1.20.8.1/vcxsrv-64.1.20.8.1.installer.exe" FILENAME="$HOME/vcxsrv-64.1.20.8.1.installer.exe" @@ -54,21 +18,6 @@ start_download "$FILENAME" "$URL" > /dev/null echo "Installing VcXsrv..." cmd //c "$HOME/vcxsrv-64.1.20.8.1.installer.exe /S" -# get opengl32.dll from mesa -# this is needed to be able to start vcxsrv -export msys2='cmd //C RefreshEnv.cmd ' -export msys2+='& set MSYS=winsymlinks:nativestrict ' -export msys2+='& C:\\tools\\msys64\\msys2_shell.cmd -defterm -no-start' -export mingw64="$msys2 -mingw64 -full-path -here -c "\"\$@"\" --" -export msys2+=" -msys2 -c "\"\$@"\" --" -$msys2 pacman --sync --noconfirm --needed mingw-w64-x86_64-mesa - -# for MESA dll's -export PATH=$PATH:/c/tools/msys64/mingw64/bin - -# for using X server -export PATH="$PATH:/c/Program\ Files/VcXsrv" - echo "Waiting for WSL install to finish..." NAME="installing WSL" wait_for_pid "${WSL_PID}" "$NAME" || script_fail From 54431f27c261fc2fdbec5d47836d6d094f22c01a Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 5 Oct 2020 18:46:15 -0400 Subject: [PATCH 048/107] TST: run-pytest.sh gets run by all OSes Renamed from run-pytest-wsl.sh --- .travis.yml | 23 +++++++++++------------ scripts/run-pytest-wsl.sh | 9 --------- scripts/run-pytest.sh | 15 +++++++++++++++ scripts/setup-travis-wsl.sh | 22 ++++++++++++++++++++-- 4 files changed, 46 insertions(+), 23 deletions(-) delete mode 100755 scripts/run-pytest-wsl.sh create mode 100755 scripts/run-pytest.sh diff --git a/.travis.yml b/.travis.yml index bf91f270c..2967f2293 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,15 +51,17 @@ before_install: export TRAVIS_TESTING=1 export DISPLAY=:0 export LOGFILE="hnn_travis.log" + export PATH="$PATH:$HOME/.local/bin" - if [ "${WSL_INSTALL}" ] && [ "${WSL_INSTALL}" -eq 1 ]; then + if [[ "${WSL_INSTALL}" -eq 1 ]]; then # for sharing with WSL environment - export WSLENV=TRAVIS_TESTING/u + export OMPI_MCA_btl_vader_single_copy_mechanism=none + export WSLENV=TRAVIS_TESTING/u:DISPLAY/u:OMPI_MCA_btl_vader_single_copy_mechanism/u:WSL_INSTALL/y fi - | # Step 1: install prerequisites - if [ "${WSL_INSTALL}" ] && [ "${WSL_INSTALL}" -eq 1 ]; then + if [[ "${WSL_INSTALL}" -eq 1 ]]; then echo "Installing WSL prerequisites" scripts/setup-travis-wsl.sh else @@ -87,14 +89,10 @@ script: - | # Step 3: run CI tests with py.test - # first check code style with flake8 (ignored currently) - flake8 --exit-zero --quiet --count \ - --exclude visdipole.py,visrast.py,visvolt.py,visspec.py,vispsd.py \ - --exclude spikefn.py,specfn.py,DataViewGUI.py hnn - if [[ "${WSL_INSTALL}" -eq 1 ]]; then # for X server DLLs export PATH="$PATH:/c/Program\ Files/VcXsrv" + export PATH=$PATH:/c/msys64/mingw64/bin # use helper commands from docker_functions.sh and utils.sh # script_fail prints out the log before quitting so we can troubleshoot @@ -105,7 +103,7 @@ script: find_command_suggested_path "vcxsrv" "/c/Program Files/VcXsrv" start_vcxsrv_print || script_fail - wsl -- //home/hnn_user/hnn/scripts/run-pytest-wsl.sh + wsl -- //home/hnn_user/hnn/scripts/run-pytest.sh stop_vcxsrv || script_fail else if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then @@ -115,11 +113,12 @@ script: # Python will search path to find neuron dll's export PATH=$PATH:/c/nrn/bin - runas //user:"test user" "cmd py.test --cov=. hnn/tests/" > /dev/null < "$HOME/test_user_creds" + # run tests first as a user with a space (TODO) + # runas //user:"test user" //wait "bash" "scripts/run-pytest.sh" < "$HOME/test_user_creds" + # echo "Finished test with 'test user'" fi - echo "Running Python tests on host OS..." - py.test --cov=. hnn/tests/ + scripts/run-pytest.sh fi after_success: diff --git a/scripts/run-pytest-wsl.sh b/scripts/run-pytest-wsl.sh deleted file mode 100755 index 2401693c6..000000000 --- a/scripts/run-pytest-wsl.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -e - -export PATH="$PATH:$HOME/.local/bin" -export OMPI_MCA_btl_vader_single_copy_mechanism=none -export DISPLAY=:0 - -echo "Running Python tests on WSL..." -py.test --cov=. hnn/tests/ \ No newline at end of file diff --git a/scripts/run-pytest.sh b/scripts/run-pytest.sh new file mode 100755 index 000000000..bd476f476 --- /dev/null +++ b/scripts/run-pytest.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -e + +if [[ "${WSL_INSTALL}" -eq 1 ]]; then + export PATH="$PATH:$HOME/.local/bin" +fi + +# first check code style with flake8 (ignored currently) +echo "Checking code style compliance with flake8..." +flake8 --exit-zero --quiet --count \ + --exclude visdipole.py,visrast.py,visvolt.py,visspec.py,vispsd.py,spikefn.py,specfn.py,DataViewGUI.py \ + hnn + +echo "Running unit tests with pytest..." +py.test --cov=. hnn/tests/ diff --git a/scripts/setup-travis-wsl.sh b/scripts/setup-travis-wsl.sh index ff906bc21..5b4cbd179 100755 --- a/scripts/setup-travis-wsl.sh +++ b/scripts/setup-travis-wsl.sh @@ -18,7 +18,25 @@ start_download "$FILENAME" "$URL" > /dev/null echo "Installing VcXsrv..." cmd //c "$HOME/vcxsrv-64.1.20.8.1.installer.exe /S" +[[ ! -f C:/tools/msys64/msys2_shell.cmd ]] && rm -rf C:/tools/msys64 +choco uninstall -y mingw +echo "Downloading msys2..." +URL="https://github.com/msys2/msys2-installer/releases/download/2020-09-03/msys2-base-x86_64-20200903.sfx.exe" +FILENAME="$HOME/msys2-base-x86_64-20200903.sfx.exe" +start_download "$FILENAME" "$URL" > /dev/null + +echo "Installing VcXsrv..." +cmd //c "$HOME/msys2-base-x86_64-20200903.sfx.exe -y -oC:\\" + +# choco upgrade --no-progress -y msys2 +export msys2='cmd //C RefreshEnv.cmd ' +export msys2+='& set MSYS=winsymlinks:nativestrict ' +export msys2+='& C:\\msys64\\msys2_shell.cmd -defterm -no-start' +export mingw64="$msys2 -mingw64 -full-path -here -c "\"\$@"\" --" +export msys2+=" -msys2 -c "\"\$@"\" --" +$msys2 pacman --sync --noconfirm --needed base-devel mingw-w64-x86_64-toolchain python +$msys2 pacman --sync --noconfirm --needed mingw-w64-x86_64-glib2 mingw-w64-cross-binutils mingw-w64-x86_64-pixman + echo "Waiting for WSL install to finish..." NAME="installing WSL" -wait_for_pid "${WSL_PID}" "$NAME" || script_fail - +wait_for_pid "${WSL_PID}" "$NAME" || script_fail \ No newline at end of file From bc70b4ebf41b8fd641cbcf584ce00838c8129e58 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 5 Oct 2020 20:34:21 -0400 Subject: [PATCH 049/107] MAINT: create param dir before writing Also use params['sim_prefix'] instead of the QLineEdit text field Also print a message to the console when the params can't be loaded --- hnn/hnn_qt5.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index e70cf50aa..de03fa784 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -1119,8 +1119,9 @@ def initUI (self): self.setWindowTitle('Set Parameters') def saveparams (self, checkok = True): - tmpf = os.path.join(get_output_dir(), 'param', - self.qle.text() + '.param') + param_dir = os.path.join(get_output_dir(), 'param') + tmpf = os.path.join(param_dir, self.params['sim_prefix'] + '.param') + oktosave = True if os.path.isfile(tmpf) and checkok: self.show() @@ -1133,6 +1134,8 @@ def saveparams (self, checkok = True): oktosave = False if oktosave: + os.makedirs(param_dir, exist_ok=True) + with open(tmpf,'w') as fp: fp.write(str(self)) @@ -1150,8 +1153,8 @@ def updatesaveparams (self, dtest): self.saveparams(checkok = False) def __str__ (self): - s = 'sim_prefix: ' + self.qle.text() + os.linesep - s += 'expmt_groups: {' + self.qle.text() + '}' + os.linesep + s = 'sim_prefix: ' + self.params['sim_prefix'] + os.linesep + s += 'expmt_groups: {' + self.params['sim_prefix'] + '}' + os.linesep for win in self.lsubwin: s += str(win) return s @@ -2014,9 +2017,10 @@ def startsim (self, ntrial, ncore): try: params = read_params(self.baseparamwin.paramfn) except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not" - "retrieve parameters from %s" % - self.baseparamwin.paramfn) + txt = "WARNING: could not retrieve parameters from %s" % \ + self.baseparamwin.paramfn + QMessageBox.information(self, "HNN", txt) + print(txt) return self.setcursors(Qt.WaitCursor) From 094cdc2073cd5d390330956fc3b52c861fea505d Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 5 Oct 2020 20:56:56 -0400 Subject: [PATCH 050/107] TST: fix up test_hnn_compare and add back test_gui disable some tests on windows --- hnn/tests/test_compare_hnn.py | 90 ++++++++++++++++++++--------------- hnn/tests/test_gui.py | 10 +++- 2 files changed, 59 insertions(+), 41 deletions(-) diff --git a/hnn/tests/test_compare_hnn.py b/hnn/tests/test_compare_hnn.py index 49bc18456..288bef6f4 100644 --- a/hnn/tests/test_compare_hnn.py +++ b/hnn/tests/test_compare_hnn.py @@ -1,19 +1,17 @@ import os.path as op -import os import sys - from numpy import loadtxt from numpy.testing import assert_allclose from mne.utils import _fetch_file from PyQt5 import QtWidgets, QtCore +import pytest from hnn import HNNGUI +from hnn.paramrw import get_output_dir -def test_hnn(qtbot, monkeypatch): - """Test to check that HNN produces consistent results""" - +def run_hnn(qtbot, monkeypatch): # for pressing exit button exit_calls = [] monkeypatch.setattr(QtWidgets.QApplication, "exit", @@ -30,41 +28,55 @@ def test_hnn(qtbot, monkeypatch): # start the simulation by pressing the button qtbot.mouseClick(main.btnsim, QtCore.Qt.LeftButton) - qtbot.waitUntil(lambda: main.runningsim) + qtbot.waitUntil(lambda: main.runningsim, 10000) - # wait up to 100 seconds for simulation to finish - qtbot.waitUntil(lambda: not main.runningsim, 100000) + # wait up to 300 seconds for simulation to finish + qtbot.waitUntil(lambda: not main.runningsim, 300000) qtbot.mouseClick(main.qbtn, QtCore.Qt.LeftButton) assert exit_calls == [1] - # only testing default configuration with 1 trial - ntrials = 1 - - for trial in range(ntrials): - print("Checking data for trial %d" % trial) - if 'SYSTEM_USER_DIR' in os.environ: - basedir = os.environ['SYSTEM_USER_DIR'] - else: - basedir = os.path.expanduser('~') - dirname = op.join(basedir, 'hnn_out', 'data', 'default') - - data_dir = ('https://raw.githubusercontent.com/jonescompneurolab/' - 'hnn/test_data/') - for data_type in ['dpl', 'rawdpl', 'i']: - sys.stdout.write("%s..." % data_type) - - fname = "%s_%d.txt" % (data_type, trial) - data_url = op.join(data_dir, fname) - if not op.exists(fname): - _fetch_file(data_url, fname) - - print("comparing %s" % fname) - pr = loadtxt(op.join(dirname, fname)) - master = loadtxt(fname) - - assert_allclose(pr[:, 1], master[:, 1], rtol=1e-8, atol=0) - if data_type in ['dpl', 'rawdpl', 'i']: - assert_allclose(pr[:, 2], master[:, 2], rtol=1e-8, atol=0) - if data_type in ['dpl', 'rawdpl']: - assert_allclose(pr[:, 3], master[:, 3], rtol=1e-8, atol=0) - print("done") +@pytest.mark.skipif(sys.platform == 'win32', + reason="does not run on windows") +def test_hnn(qtbot, monkeypatch): + """Test HNN can run a simulation""" + + run_hnn(qtbot, monkeypatch) + dirname = op.join(get_output_dir(), 'data', 'default') + + fname = "dpl.txt" + pr = loadtxt(op.join(dirname, fname)) + assert len(pr) > 0 + + +@pytest.mark.skip(reason="Skipping until #232 verification is complete") +def test_compare_hnn(qtbot, monkeypatch): + """Test simulation data are consistent with master""" + + # do we need to run a simulation? + run_sim = False + dirname = op.join(get_output_dir(), 'data', 'default') + for data_type in ['dpl', 'rawdpl', 'i']: + fname = "%s.txt" % (data_type) + if not op.exists(fname): + run_sim = True + break + + if run_sim: + run_hnn(qtbot, monkeypatch) + + data_dir = ('https://raw.githubusercontent.com/jonescompneurolab/' + 'hnn/test_data/') + for data_type in ['dpl', 'rawdpl', 'i']: + fname = "%s.txt" % (data_type) + data_url = op.join(data_dir, fname) + if not op.exists(fname): + _fetch_file(data_url, fname) + + pr = loadtxt(op.join(dirname, fname)) + master = loadtxt(fname) + + assert_allclose(pr[:, 1], master[:, 1], rtol=1e-4, atol=0) + if data_type in ['dpl', 'rawdpl', 'i']: + assert_allclose(pr[:, 2], master[:, 2], rtol=1e-4, atol=0) + if data_type in ['dpl', 'rawdpl']: + assert_allclose(pr[:, 3], master[:, 3], rtol=1e-4, atol=0) diff --git a/hnn/tests/test_gui.py b/hnn/tests/test_gui.py index 2d9606973..021ecf952 100644 --- a/hnn/tests/test_gui.py +++ b/hnn/tests/test_gui.py @@ -1,14 +1,20 @@ +import sys + from PyQt5 import QtWidgets, QtCore import pytest from hnn import HNNGUI -@pytest.mark.skip + +@pytest.mark.skipif(sys.platform == 'win32', + reason="does not run on windows") def test_HNNGUI(qtbot): main = HNNGUI() qtbot.addWidget(main) -@pytest.mark.skip + +@pytest.mark.skipif(sys.platform == 'win32', + reason="does not run on windows") def test_exit_button(qtbot, monkeypatch): exit_calls = [] monkeypatch.setattr(QtWidgets.QApplication, "exit", From d064d663bd9cc5f930704c22d551f873f05a4081 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 5 Oct 2020 20:57:44 -0400 Subject: [PATCH 051/107] TST: fix msys2 install for WSL --- .travis.yml | 16 ++++++++-------- scripts/setup-travis-wsl.sh | 19 ------------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2967f2293..d01efa597 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,7 @@ before_install: if [[ "${WSL_INSTALL}" -eq 1 ]]; then # for sharing with WSL environment export OMPI_MCA_btl_vader_single_copy_mechanism=none - export WSLENV=TRAVIS_TESTING/u:DISPLAY/u:OMPI_MCA_btl_vader_single_copy_mechanism/u:WSL_INSTALL/y + export WSLENV=TRAVIS_TESTING/u:DISPLAY/u:OMPI_MCA_btl_vader_single_copy_mechanism/u:WSL_INSTALL/u fi - | # Step 1: install prerequisites @@ -90,10 +90,6 @@ script: # Step 3: run CI tests with py.test if [[ "${WSL_INSTALL}" -eq 1 ]]; then - # for X server DLLs - export PATH="$PATH:/c/Program\ Files/VcXsrv" - export PATH=$PATH:/c/msys64/mingw64/bin - # use helper commands from docker_functions.sh and utils.sh # script_fail prints out the log before quitting so we can troubleshoot source "scripts/docker_functions.sh" @@ -101,10 +97,14 @@ script: source scripts/utils.sh export -f cleanup - find_command_suggested_path "vcxsrv" "/c/Program Files/VcXsrv" - start_vcxsrv_print || script_fail + # for X server DLLs + export PATH=$PATH:/c/Program\ Files/VcXsrv + + # find_command_suggested_path "vcxsrv" "/c/Program Files/VcXsrv" + # start_vcxsrv_print || script_fail + /c/Program\ Files/VcXsrv/vcxsrv.exe -wgl -multiwindow wsl -- //home/hnn_user/hnn/scripts/run-pytest.sh - stop_vcxsrv || script_fail + # stop_vcxsrv || script_fail else if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then # NEURON will fail to import if DISPLAY is set diff --git a/scripts/setup-travis-wsl.sh b/scripts/setup-travis-wsl.sh index 5b4cbd179..7decff91d 100755 --- a/scripts/setup-travis-wsl.sh +++ b/scripts/setup-travis-wsl.sh @@ -18,25 +18,6 @@ start_download "$FILENAME" "$URL" > /dev/null echo "Installing VcXsrv..." cmd //c "$HOME/vcxsrv-64.1.20.8.1.installer.exe /S" -[[ ! -f C:/tools/msys64/msys2_shell.cmd ]] && rm -rf C:/tools/msys64 -choco uninstall -y mingw -echo "Downloading msys2..." -URL="https://github.com/msys2/msys2-installer/releases/download/2020-09-03/msys2-base-x86_64-20200903.sfx.exe" -FILENAME="$HOME/msys2-base-x86_64-20200903.sfx.exe" -start_download "$FILENAME" "$URL" > /dev/null - -echo "Installing VcXsrv..." -cmd //c "$HOME/msys2-base-x86_64-20200903.sfx.exe -y -oC:\\" - -# choco upgrade --no-progress -y msys2 -export msys2='cmd //C RefreshEnv.cmd ' -export msys2+='& set MSYS=winsymlinks:nativestrict ' -export msys2+='& C:\\msys64\\msys2_shell.cmd -defterm -no-start' -export mingw64="$msys2 -mingw64 -full-path -here -c "\"\$@"\" --" -export msys2+=" -msys2 -c "\"\$@"\" --" -$msys2 pacman --sync --noconfirm --needed base-devel mingw-w64-x86_64-toolchain python -$msys2 pacman --sync --noconfirm --needed mingw-w64-x86_64-glib2 mingw-w64-cross-binutils mingw-w64-x86_64-pixman - echo "Waiting for WSL install to finish..." NAME="installing WSL" wait_for_pid "${WSL_PID}" "$NAME" || script_fail \ No newline at end of file From 7b0a5670874705d950a7694f86758f9b14f9409d Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 6 Oct 2020 08:00:27 -0400 Subject: [PATCH 052/107] TST: fix msys2 install for WSL --- .travis.yml | 3 ++- scripts/setup-travis-wsl.sh | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d01efa597..2111c39f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -102,7 +102,8 @@ script: # find_command_suggested_path "vcxsrv" "/c/Program Files/VcXsrv" # start_vcxsrv_print || script_fail - /c/Program\ Files/VcXsrv/vcxsrv.exe -wgl -multiwindow + which vcxsrv + vcxsrv.exe -wgl -multiwindow wsl -- //home/hnn_user/hnn/scripts/run-pytest.sh # stop_vcxsrv || script_fail else diff --git a/scripts/setup-travis-wsl.sh b/scripts/setup-travis-wsl.sh index 7decff91d..476c6d42b 100755 --- a/scripts/setup-travis-wsl.sh +++ b/scripts/setup-travis-wsl.sh @@ -18,6 +18,7 @@ start_download "$FILENAME" "$URL" > /dev/null echo "Installing VcXsrv..." cmd //c "$HOME/vcxsrv-64.1.20.8.1.installer.exe /S" +/c/Program\ Files/VcXSrv/vcxsrv.exe -wgl -multiwindow echo "Waiting for WSL install to finish..." NAME="installing WSL" wait_for_pid "${WSL_PID}" "$NAME" || script_fail \ No newline at end of file From 3c4cc76078039ef971d9041460fcec757900eac9 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 6 Oct 2020 09:54:39 -0400 Subject: [PATCH 053/107] TST: Give up launching VcXsrv for WSL --- .travis.yml | 19 ++----------------- scripts/run-pytest.sh | 2 ++ scripts/setup-travis-wsl.ps1 | 2 +- scripts/setup-travis-wsl.sh | 9 ++++++++- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2111c39f6..5db4ac2fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,8 +62,8 @@ before_install: # Step 1: install prerequisites if [[ "${WSL_INSTALL}" -eq 1 ]]; then - echo "Installing WSL prerequisites" - scripts/setup-travis-wsl.sh + powershell.exe -ExecutionPolicy Bypass -File ./scripts/setup-travis-wsl.ps1 + # scripts/setup-travis-wsl.sh else echo "Installing ${TRAVIS_OS_NAME} prerequisites" scripts/setup-travis-${TRAVIS_OS_NAME}.sh @@ -90,22 +90,7 @@ script: # Step 3: run CI tests with py.test if [[ "${WSL_INSTALL}" -eq 1 ]]; then - # use helper commands from docker_functions.sh and utils.sh - # script_fail prints out the log before quitting so we can troubleshoot - source "scripts/docker_functions.sh" - set_globals - source scripts/utils.sh - export -f cleanup - - # for X server DLLs - export PATH=$PATH:/c/Program\ Files/VcXsrv - - # find_command_suggested_path "vcxsrv" "/c/Program Files/VcXsrv" - # start_vcxsrv_print || script_fail - which vcxsrv - vcxsrv.exe -wgl -multiwindow wsl -- //home/hnn_user/hnn/scripts/run-pytest.sh - # stop_vcxsrv || script_fail else if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then # NEURON will fail to import if DISPLAY is set diff --git a/scripts/run-pytest.sh b/scripts/run-pytest.sh index bd476f476..cd7d0fdb6 100755 --- a/scripts/run-pytest.sh +++ b/scripts/run-pytest.sh @@ -3,6 +3,8 @@ set -e if [[ "${WSL_INSTALL}" -eq 1 ]]; then export PATH="$PATH:$HOME/.local/bin" + # unable to get vcxsrv to work (DLL loading problems) + unset DISPLAY fi # first check code style with flake8 (ignored currently) diff --git a/scripts/setup-travis-wsl.ps1 b/scripts/setup-travis-wsl.ps1 index 8d641ce75..790751808 100644 --- a/scripts/setup-travis-wsl.ps1 +++ b/scripts/setup-travis-wsl.ps1 @@ -2,7 +2,7 @@ $ErrorActionPreference = "Stop" Set-Location C:\Users\travis Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -Write-Host "Downloading Ubuntu image..." +Write-Host "Downloading Ubuntu WSL image..." Invoke-WebRequest -Uri https://aka.ms/wsl-ubuntu-1804 -OutFile Ubuntu.appx -UseBasicParsing Write-Host "Finished downloading Ubuntu image. Extracting..." diff --git a/scripts/setup-travis-wsl.sh b/scripts/setup-travis-wsl.sh index 476c6d42b..b3de2b8f8 100755 --- a/scripts/setup-travis-wsl.sh +++ b/scripts/setup-travis-wsl.sh @@ -10,6 +10,9 @@ echo "Installing Ubuntu WSL..." powershell.exe -ExecutionPolicy Bypass -File ./scripts/setup-travis-wsl.ps1 & WSL_PID=$! +# Note: unable to get VcXServe to start, always missing some dll. First libcrypto, then +# api-ms-win-core-delayload-l1-1-0.dll + echo "Downloading VcXsrv..." URL="https://downloads.sourceforge.net/project/vcxsrv/vcxsrv/1.20.8.1/vcxsrv-64.1.20.8.1.installer.exe" FILENAME="$HOME/vcxsrv-64.1.20.8.1.installer.exe" @@ -18,7 +21,11 @@ start_download "$FILENAME" "$URL" > /dev/null echo "Installing VcXsrv..." cmd //c "$HOME/vcxsrv-64.1.20.8.1.installer.exe /S" -/c/Program\ Files/VcXSrv/vcxsrv.exe -wgl -multiwindow +echo "Starting VcXsrv..." +# note: do not try messing with quotes and escape characters here. you will +# regret it and the time wasted cannot be regained +cmd //c "C:\\PROGRA~1\\VcXsrv\vcxsrv.exe -wgl -multiwindow" + echo "Waiting for WSL install to finish..." NAME="installing WSL" wait_for_pid "${WSL_PID}" "$NAME" || script_fail \ No newline at end of file From cf6e85401850c4a0e99472adb5d01054e56cd017 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 25 Nov 2020 07:26:32 -0500 Subject: [PATCH 054/107] MAINT: add back sim status printing to GUI --- hnn/run.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/hnn/run.py b/hnn/run.py index 44bc0140e..563f0c59d 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -6,11 +6,14 @@ import os.path as op import os +import sys +import io import numpy as np from threading import Lock from time import sleep from copy import deepcopy from math import ceil, isclose +from contextlib import redirect_stdout from PyQt5.QtCore import QThread, pyqtSignal, QObject from hnn_core import simulate_dipole, Network, MPIBackend @@ -271,6 +274,20 @@ def __init__(self, p, d, ntrial, ncore, waitsimwin, params, opt=False, def updatewaitsimwin(self, txt): self.txtComm.tsig.emit(txt) + class _log_sim_status(object): + def __init__(self, parent): + self.out = sys.stdout + self.parent = parent + + def write(self, message): + self.out.write(message) + stripped_message = message.strip() + if not stripped_message == '': + self.parent.updatewaitsimwin(stripped_message) + + def flush(self): + self.out.flush() + def updatebaseparamwin(self, d): self.prmComm.psig.emit(d) @@ -337,7 +354,9 @@ def runsim(self, is_opt=False, banner=True, simlength=None): raise RuntimeError("No cores available for simulation") try: - simulate(self.params, self.ncore) + sim_log = self._log_sim_status(parent=self) + with redirect_stdout(sim_log): + simulate(self.params, self.ncore) break except RuntimeError as e: if self.ncore == 1: From e52a7b9d46bf482e6f30b3ef1f5ce210a0949673 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Thu, 26 Nov 2020 06:59:21 -0500 Subject: [PATCH 055/107] MAINT: turn simdat into SimData class This replaces the use of global variables for storing simulation data. simdat.py is now flake8 compliant --- hnn/hnn_qt5.py | 181 +++--- hnn/run.py | 193 +++--- hnn/simdat.py | 1520 ++++++++++++++++++++++++++---------------------- hnn/visrast.py | 1 - hnn/visspec.py | 1 - hnn/visvolt.py | 4 +- 6 files changed, 1018 insertions(+), 882 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index de03fa784..6bff6e3d7 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -29,8 +29,7 @@ # HNN modules from .paramrw import usingOngoingInputs, usingEvokedInputs, get_output_dir -from .simdat import SIMCanvas, getinputfiles -from .simdat import updatelsimdat, ddat, lsimdat, lsimidx +from .simdat import SIMCanvas, SimData from .run import RunSimThread, ParamSignal from .qt_lib import setscalegeom, setscalegeomcenter, getmplDPI, getscreengeom from .qt_lib import lookupresource, ClickLabel @@ -41,7 +40,6 @@ drawavgdpl = 0 fontsize = plt.rcParams['font.size'] = 10 - def isWindows(): # are we on windows? or linux/mac ? return sys.platform.startswith('win') @@ -1120,7 +1118,7 @@ def initUI (self): def saveparams (self, checkok = True): param_dir = os.path.join(get_output_dir(), 'param') - tmpf = os.path.join(param_dir, self.params['sim_prefix'] + '.param') + tmpf = os.path.join(param_dir, self.qle.text() + '.param') oktosave = True if os.path.isfile(tmpf) and checkok: @@ -1141,7 +1139,7 @@ def saveparams (self, checkok = True): self.paramfn = tmpf data_dir = os.path.join(get_output_dir(), 'data') - sim_dir = os.path.join(data_dir, self.params['sim_prefix']) + sim_dir = os.path.join(data_dir, self.qle.text()) os.makedirs(sim_dir, exist_ok=True) return oktosave @@ -1153,8 +1151,8 @@ def updatesaveparams (self, dtest): self.saveparams(checkok = False) def __str__ (self): - s = 'sim_prefix: ' + self.params['sim_prefix'] + os.linesep - s += 'expmt_groups: {' + self.params['sim_prefix'] + '}' + os.linesep + s = 'sim_prefix: ' + self.qle.text() + os.linesep + s += 'expmt_groups: {' + self.qle.text() + '}' + os.linesep for win in self.lsubwin: s += str(win) return s @@ -1205,17 +1203,18 @@ def __init__ (self): self.fontsize = fontsize self.linewidth = plt.rcParams['lines.linewidth'] = 1 self.markersize = plt.rcParams['lines.markersize'] = 5 - self.dextdata = {} # external data self.schemwin = SchematicDialog(self) - self.m = self.toolbar = None + self.sim_canvas = self.toolbar = None paramfn = os.path.join(hnn_root_dir, 'param', 'default.param') self.baseparamwin = BaseParamDialog(self, paramfn, self.startoptmodel) self.optMode = False + self.sim_data = SimData() self.initUI() self.helpwin = HelpDialog(self) self.erselectdistal = EvokedOrRhythmicDialog(self, True, self.baseparamwin.evparamwin, self.baseparamwin.distparamwin) self.erselectprox = EvokedOrRhythmicDialog(self, False, self.baseparamwin.evparamwin, self.baseparamwin.proxparamwin) self.waitsimwin = WaitSimDialog(self) + default_param = os.path.join(get_output_dir(), 'data', 'default') first_load = not (os.path.exists(default_param)) @@ -1241,8 +1240,8 @@ def excepthook(self, exc_type, exc_value, exc_tb): def redraw (self): # redraw simulation & external data - self.m.plot() - self.m.draw() + self.sim_canvas.plot() + self.sim_canvas.draw() def changeFontSize (self): # bring up window to change font sizes @@ -1292,33 +1291,30 @@ def selParamFileDialog (self): tmpfn) return + # Now update GUI components self.baseparamwin.paramfn = tmpfn # now update the GUI components to reflect the param file selected self.baseparamwin.updateDispParam(params) self.initSimCanvas() # recreate canvas - # self.m.plot() # replot data + # self.sim_canvas.plot() # replot data self.setWindowTitle(self.baseparamwin.paramfn) - # store the sim just loaded in simdat's list - is this the desired behavior? or should we first erase prev sims? - if 'dpl' in ddat: - # update lsimdat and its current sim index - updatelsimdat(self.baseparamwin.paramfn, self.baseparamwin.params, ddat['dpl']) - self.populateSimCB() # populate the combobox - if len(self.dextdata) > 0: + if self.sim_data.get_exp_data_size() > 0: self.toggleEnableOptimization(True) def loadDataFile (self, fn): # load a dipole data file + extdata = None try: - self.dextdata[fn] = np.loadtxt(fn) + extdata = np.loadtxt(fn) except ValueError: # possible that data file is comma delimted instead of whitespace delimted try: - self.dextdata[fn] = np.loadtxt(fn, delimiter=',') + extdata = np.loadtxt(fn, delimiter=',') except ValueError: QMessageBox.information(self, "HNN", "WARNING: could not load data file %s" % fn) return False @@ -1326,11 +1322,11 @@ def loadDataFile (self, fn): QMessageBox.information(self, "HNN", "WARNING: could not load data file %s" % fn) return False - ddat['dextdata'] = self.dextdata + self.sim_data.update_exp_data(fn, extdata) print('Loaded data in ', fn) - self.m.plot() - self.m.draw() # make sure new lines show up in plot + self.sim_canvas.plot() + self.sim_canvas.draw() # make sure new lines show up in plot if self.baseparamwin.paramfn: self.toggleEnableOptimization(True) @@ -1354,11 +1350,11 @@ def loadDataFileDialog (self): def clearDataFile (self): # clear external dipole data - self.m.clearlextdatobj() - self.dextdata = ddat['dextdata'] = {} + self.sim_canvas.clearlextdatobj() + self.sim_data.clear_exp_data() self.toggleEnableOptimization(False) - self.m.plot() # recreate canvas - self.m.draw() + self.sim_canvas.plot() # recreate canvas + self.sim_canvas.draw() def setparams (self): # show set parameters dialog window @@ -1473,8 +1469,8 @@ def togAvgDpl (self): global drawavgdpl drawavgdpl = not drawavgdpl - self.m.plot() - self.m.draw() + self.sim_canvas.plot() + self.sim_canvas.draw() def hidesubwin (self): # hide GUI's sub windows @@ -1504,8 +1500,7 @@ def distribsubwin (self): def updateDatCanv(self, params): # update the simulation data and canvas - # reset input data - if already exists - getinputfiles(self.baseparamwin.paramfn) + # self.sim_data.update_sim_data(self.paramfn, params) # now update the GUI components to reflect the param file selected self.baseparamwin.updateDispParam(params) @@ -1513,13 +1508,9 @@ def updateDatCanv(self, params): self.setWindowTitle(self.baseparamwin.paramfn) def updateSelectedSim(self, sim_idx): - """Update the sim shown in the ComboBox and update globals""" - # update globals - global lsimidx - - lsimidx = sim_idx - paramfn = lsimdat[sim_idx]['paramfn'] + """Update the sim shown in the ComboBox""" + paramfn = self.cbsim.itemText(sim_idx) try: params = read_params(paramfn) except ValueError: @@ -1527,32 +1518,31 @@ def updateSelectedSim(self, sim_idx): "retrieve parameters from %s" % paramfn) return - self.baseparamwin.paramfn = paramfn # update GUI self.updateDatCanv(params) - self.cbsim.setCurrentIndex(lsimidx) + self.cbsim.setCurrentIndex(sim_idx) def removeSim(self): """Remove the currently selected simulation""" - global lsimidx - cidx = self.cbsim.currentIndex() - self.cbsim.removeItem(cidx) - del lsimdat[cidx] + sim_idx = self.cbsim.currentIndex() + paramfn = self.cbsim.itemText(sim_idx) + if not paramfn == '': + self.sim_data.remove_sim_by_fn(paramfn) + + self.cbsim.removeItem(sim_idx) # go to last entry new_simidx = self.cbsim.count() - 1 if new_simidx < 0: - lsimidx = 0 self.clearSimulations() else: self.updateSelectedSim(new_simidx) def prevSim(self): """Go to previous simulation""" - global lsimidx new_simidx = self.cbsim.currentIndex() - 1 if new_simidx < 0: @@ -1572,15 +1562,13 @@ def nextSim (self): self.updateSelectedSim(new_simidx) def clearSimulationData (self): - global lsimidx, ddat, lsimdat # clear the simulation data self.baseparamwin.params = None self.baseparamwin.paramfn = None - ddat = {} # clear data in simdat.ddat - lsimdat = [] - lsimidx = 0 - self.populateSimCB() # un-populate the combobox + + self.sim_data.clear_sim_data() + self.cbsim.clear() # un-populate the combobox self.toggleEnableOptimization(False) @@ -1588,16 +1576,16 @@ def clearSimulations (self): # clear all simulation data and erase simulations from canvas (does not clear external data) self.clearSimulationData() self.initSimCanvas() # recreate canvas - self.m.draw() + self.sim_canvas.draw() self.setWindowTitle('') def clearCanvas (self): # clear all simulation & external data and erase everything from the canvas + self.sim_canvas.clearlextdatobj() # clear the external data self.clearSimulationData() - self.m.clearlextdatobj() # clear the external data - self.dextdata = ddat['dextdata'] = {} + self.sim_data.clear_exp_data() self.initSimCanvas() # recreate canvas - self.m.draw() + self.sim_canvas.draw() self.setWindowTitle('') def initMenu (self): @@ -1843,13 +1831,6 @@ def initUI (self): self.initSimCanvas(gRow=gRow, reInit=False) gRow += 2 - # store any sim just loaded in simdat's list - is this the desired behavior? or should we start empty? - if 'dpl' in ddat: - # update lsimdat and its current sim index - updatelsimdat(self.baseparamwin.paramfn, - self.baseparamwin.params, - ddat['dpl']) - self.cbsim = QComboBox(self) self.populateSimCB() # populate the combobox self.cbsim.activated[str].connect(self.onActivateSimCB) @@ -1868,11 +1849,11 @@ def initUI (self): widget.setLayout(grid) self.setCentralWidget(widget) - self.p = ParamSignal() - self.p.psig.connect(self.baseparamwin.updateDispParam) + self.param_signal = ParamSignal() + self.param_signal.psig.connect(self.baseparamwin.updateDispParam) - self.d = DoneSignal() - self.d.finishSim.connect(self.done) + self.done_signal = DoneSignal() + self.done_signal.finishSim.connect(self.done) self.setWindowIcon(QIcon(os.path.join('res', 'icon.png'))) @@ -1880,11 +1861,10 @@ def initUI (self): self.show() - def onActivateSimCB (self, paramfn): + def onActivateSimCB(self, paramfn): # load simulation when activating simulation combobox - global lsimidx - if self.cbsim.currentIndex() != lsimidx: + if paramfn != self.baseparamwin.paramfn: try: params = read_params(paramfn) except ValueError: @@ -1894,18 +1874,25 @@ def onActivateSimCB (self, paramfn): return self.baseparamwin.paramfn = paramfn - lsimidx = self.cbsim.currentIndex() self.updateDatCanv(params) - def populateSimCB (self): + def populateSimCB(self, index=None): # populate the simulation combobox + self.cbsim.clear() - for sim in lsimdat: - sim_paramfn = sim['paramfn'] - self.cbsim.addItem(sim_paramfn) - self.cbsim.setCurrentIndex(lsimidx) + for paramfn in self.sim_data._sim_data.keys(): + self.cbsim.addItem(paramfn) + + if self.cbsim.count() == 0: + raise ValueError("No simulations to add to combo box") - def initSimCanvas (self,recalcErr=True,optMode=False,gRow=1,reInit=True): + if index is None: + # set to last entry + self.cbsim.setCurrentIndex(self.cbsim.count() - 1) + else: + self.cbsim.setCurrentIndex(index) + + def initSimCanvas(self, recalcErr=True, optMode=False, gRow=1, reInit=True): # initialize the simulation canvas, loading any required data gCol = 0 @@ -1923,24 +1910,25 @@ def initSimCanvas (self,recalcErr=True,optMode=False,gRow=1,reInit=True): self.baseparamwin.paramfn) return - self.m = SIMCanvas(self.baseparamwin.params, parent = self, width=10, height=1, dpi=getmplDPI(), optMode=optMode) + self.sim_canvas = SIMCanvas(self.baseparamwin.paramfn, self.baseparamwin.params, + parent=self, width=10, height=1, dpi=getmplDPI(), + optMode=optMode) # this is the Navigation widget # it takes the Canvas widget and a parent - self.toolbar = NavigationToolbar2QT(self.m, self) + self.toolbar = NavigationToolbar2QT(self.sim_canvas, self) gWidth = 12 self.grid.addWidget(self.toolbar, gRow, gCol, 1, gWidth) - self.grid.addWidget(self.m, gRow + 1, gCol, 1, gWidth) - if len(self.dextdata.keys()) > 0: - ddat['dextdata'] = self.dextdata - self.m.plot(recalcErr) - self.m.draw() + self.grid.addWidget(self.sim_canvas, gRow + 1, gCol, 1, gWidth) + if self.sim_data.get_exp_data_size() > 0: + self.sim_canvas.plot(recalcErr) + self.sim_canvas.draw() def setcursors(self, cursor): # set cursors of self and children self.setCursor(cursor) self.update() kids = self.children() - kids.append(self.m) # matplotlib simcanvas + kids.append(self.sim_canvas) # matplotlib simcanvas for k in kids: if type(k) == QLayout or type(k) == QAction: # These types don't have setCursor() @@ -1955,7 +1943,7 @@ def startoptmodel (self): else: self.optMode = True try: - self.optmodel(self.baseparamwin.runparamwin.getntrial(),self.baseparamwin.runparamwin.getncore()) + self.optmodel(self.baseparamwin.runparamwin.getncore()) except RuntimeError: print("ERR: Optimization aborted") @@ -1965,7 +1953,7 @@ def controlsim (self): self.stopsim() # stop sim works but leaves subproc as zombie until this main GUI thread exits else: self.optMode = False - self.startsim(self.baseparamwin.runparamwin.getntrial(),self.baseparamwin.runparamwin.getncore()) + self.startsim(self.baseparamwin.runparamwin.getncore()) def stopsim (self): # stop the simulation @@ -1980,7 +1968,7 @@ def stopsim (self): self.statusBar().showMessage('') self.setcursors(Qt.ArrowCursor) - def optmodel (self, ntrial, ncore): + def optmodel (self, ncore): # make sure params saved and ok to run if not self.baseparamwin.saveparams(): return @@ -1998,7 +1986,10 @@ def optmodel (self, ntrial, ncore): self.statusBar().showMessage("Optimizing model. . .") - self.runthread = RunSimThread(self.p, self.d, ntrial, ncore, self.waitsimwin, self.baseparamwin.params, opt=True, baseparamwin=self.baseparamwin, mainwin=self) + self.runthread = RunSimThread(ncore, self.baseparamwin.params, + self.param_signal, self.done_signal, + self.waitsimwin, self.baseparamwin, + mainwin=self, opt=True) # We have all the events we need connected we can start the thread self.runthread.start() @@ -2008,7 +1999,7 @@ def optmodel (self, ntrial, ncore): self.qbtn.setEnabled(False) bringwintotop(self.waitsimwin) - def startsim (self, ntrial, ncore): + def startsim (self, ncore): # start the simulation if not self.baseparamwin.saveparams(self.baseparamwin.paramfn): return # make sure params saved and ok to run @@ -2030,9 +2021,10 @@ def startsim (self, ntrial, ncore): self.statusBar().showMessage("Running simulation. . .") - self.runthread = RunSimThread(self.p, self.d, ntrial, ncore, - self.waitsimwin, params, opt=False, - baseparamwin=None, mainwin=None) + self.runthread = RunSimThread(ncore, params, + self.param_signal, self.done_signal, + self.waitsimwin, self.baseparamwin, + mainwin=self, opt=False) # We have all the events we need connected we can start the thread self.runthread.start() @@ -2054,7 +2046,7 @@ def done (self, optMode, except_msg): self.btnsim.setText("Run Simulation") self.qbtn.setEnabled(True) self.initSimCanvas(optMode=optMode) # recreate canvas (plots too) to avoid incorrect axes - # self.m.plot() + # self.sim_canvas.plot() self.setcursors(Qt.ArrowCursor) failed=False @@ -2079,7 +2071,12 @@ def done (self, optMode, except_msg): sim_dir = os.path.join(data_dir, self.baseparamwin.params['sim_prefix']) QMessageBox.information(self, "Done!", msg + "using " + self.baseparamwin.paramfn + '. Saved data/figures in: ' + sim_dir) self.setWindowTitle(self.baseparamwin.paramfn) + self.populateSimCB() # populate the combobox + cb_index = self.cbsim.findText(self.baseparamwin.paramfn) + if cb_index < 0: + raise ValueError("Couldn't find simulation in combobox: %s" % self.baseparamwin.paramfn) + self.cbsim.setCurrentIndex(cb_index) if __name__ == '__main__': app = QApplication(sys.argv) diff --git a/hnn/run.py b/hnn/run.py index 563f0c59d..2eb2ca968 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -7,9 +7,7 @@ import os.path as op import os import sys -import io import numpy as np -from threading import Lock from time import sleep from copy import deepcopy from math import ceil, isclose @@ -23,8 +21,6 @@ from .paramrw import usingOngoingInputs, write_gids_param from .specfn import analysis_simp -from .simdat import updatelsimdat, updatedat, ddat, updateoptdat -from .simdat import weighted_rmse, initial_ddat, calcerr from .paramrw import write_legacy_paramf, get_output_dir @@ -79,6 +75,11 @@ class TextSignal (QObject): tsig = pyqtSignal(str) +class DataSignal (QObject): + """for signalling data read""" + dsig = pyqtSignal(str, dict) + + class ParamSignal (QObject): """for updating GUI & param file during optimization""" psig = pyqtSignal(dict) @@ -242,39 +243,86 @@ def simulate(params, n_procs=None): # based on https://nikolak.com/pyqt-threading-tutorial/ class RunSimThread(QThread): - def __init__(self, p, d, ntrial, ncore, waitsimwin, params, opt=False, - baseparamwin=None, mainwin=None): + """The RunSimThread class. + + Parameters + ---------- + + ncore : int + Number of cores to run this simulation over + params : dict + Dictionary of params describing simulation config + param_signal : ParamSignal + Signal to main process to send back params + done_signal : DoneSignal + Signal to main process that the simulation has finished + waitsimwin : WaitSimDialog + Handle to the Qt dialog during a simulation + baseparamwin : BaseParamDialog + Handle to the Qt dialog with parameter values + mainwin : HNNGUI + Handle to the main application window + opt : bool + Whether this simulation thread is running an optimization + + Attributes + ---------- + ncore : int + Number of cores to run this simulation over + params : dict + Dictionary of params describing simulation config + param_signal : ParamSignal + Signal to main process to send back params + done_signal : DoneSignal + Signal to main process that the simulation has finished + waitsimwin : WaitSimDialog + Handle to the Qt dialog during a simulation + baseparamwin : BaseParamDialog + Handle to the Qt dialog with parameter values + mainwin : HNNGUI + Handle to the main application window + opt : bool + Whether this simulation thread is running an optimization + killed : bool + Whether this simulation was forcefully terminated + killed : bool + Whether this simulation was forcefully terminated + """ + + def __init__(self, ncore, params, param_signal, done_signal, waitsimwin, + baseparamwin, mainwin, opt=False): QThread.__init__(self) - self.p = p - self.d = d - self.killed = False - self.ntrial = ntrial self.ncore = ncore - self.waitsimwin = waitsimwin self.params = params - self.opt = opt + self.param_signal = param_signal + self.done_signal = done_signal + self.waitsimwin = waitsimwin self.baseparamwin = baseparamwin self.mainwin = mainwin + self.opt = opt + self.killed = False + self.paramfn = os.path.join(get_output_dir(), 'param', self.params['sim_prefix'] + '.param') + self.dataComm = DataSignal() + self.dataComm.dsig.connect(self.mainwin.sim_data.update_sim_data) + self.txtComm = TextSignal() self.txtComm.tsig.connect(self.waitsimwin.updatetxt) self.prmComm = ParamSignal() - if self.baseparamwin is not None: - self.prmComm.psig.connect(self.baseparamwin.updatesaveparams) + self.prmComm.psig.connect(self.baseparamwin.updatesaveparams) self.canvComm = CanvSignal() - if self.mainwin is not None: - self.canvComm.csig.connect(self.mainwin.initSimCanvas) - - self.lock = Lock() + self.canvComm.csig.connect(self.mainwin.initSimCanvas) - def updatewaitsimwin(self, txt): + def _updatewaitsimwin(self, txt): + """Used to write messages to simulation window""" self.txtComm.tsig.emit(txt) class _log_sim_status(object): + """Replaces sys.stdout.write() to write message to simulation window""" def __init__(self, parent): self.out = sys.stdout self.parent = parent @@ -283,32 +331,43 @@ def write(self, message): self.out.write(message) stripped_message = message.strip() if not stripped_message == '': - self.parent.updatewaitsimwin(stripped_message) + self.parent._updatewaitsimwin(stripped_message) def flush(self): self.out.flush() - def updatebaseparamwin(self, d): + def _read_sim_data(self, paramfn, params): + """Signals main window to read sim data from files""" + self.dataComm.dsig.emit(paramfn, params) + + def _updatebaseparamwin(self, d): + """Signals baseparamwin to update its parameter from passed dict""" self.prmComm.psig.emit(d) - def updatedispparam(self): - self.p.psig.emit(self.params) + def _updatedispparam(self): + """Signals baseparamwin to run updateDispParam""" + self.param_signal.psig.emit(self.params) - def updatedrawerr(self): - # False means do not recalculate error + def _updatedrawerr(self): + """Signals mainwin to redraw canvas with RMSE""" + # When self.opt is false, do not recalculate error self.canvComm.csig.emit(False, self.opt) def stop(self): - self.killproc() + """Terminate running simulation""" + _kill_and_check_nrniv_procs() + self.killed = True def __del__(self): self.quit() self.wait() def run(self): + """Start simulation""" + msg = '' - if self.opt and self.baseparamwin is not None: + if self.opt: try: self.optmodel() # run optimization except RuntimeError as e: @@ -319,35 +378,17 @@ def run(self): self.baseparamwin.optparamwin.optimization_running = False else: try: - self.runsim() # run simulation + self._runsim() # run simulation # update params in all windows (optimization) - self.updatedispparam() + self._updatedispparam() except RuntimeError as e: msg = str(e) - self.d.finishSim.emit(self.opt, msg) # send the finish signal - - def killproc(self): - """make sure all nrniv procs have been killed""" - _kill_and_check_nrniv_procs() - - self.lock.acquire() - self.killed = True - self.lock.release() - - def get_proc_stream(self, stream, print_to_console=False): - for line in iter(stream.readline, ""): - if print_to_console: - print(line.strip()) - # send a signal to waitsimwin, which updates its textedit - self.updatewaitsimwin(line.strip()) - stream.close() + self.done_signal.finishSim.emit(self.opt, msg) # run sim command via mpi, then delete the temp file. - def runsim(self, is_opt=False, banner=True, simlength=None): - self.lock.acquire() + def _runsim(self, is_opt=False, banner=True, simlength=None): self.killed = False - self.lock.release() while True: if self.ncore == 0: @@ -362,31 +403,24 @@ def runsim(self, is_opt=False, banner=True, simlength=None): if self.ncore == 1: # can't reduce ncore any more print(str(e)) - self.updatewaitsimwin(str(e)) + self._updatewaitsimwin(str(e)) _kill_and_check_nrniv_procs() raise RuntimeError("Simulation failed to start") # check if proc was killed before retrying with fewer cores - self.lock.acquire() if self.killed: - self.lock.release() # exit using RuntimeError raise RuntimeError("Terminated") - else: - self.lock.release() self.ncore = ceil(self.ncore/2) txt = "INFO: Failed starting simulation, retrying with %d cores" \ % self.ncore print(txt) - self.updatewaitsimwin(txt) - - # should have good data written to files at this point - updatedat(self.params) + self._updatewaitsimwin(txt) if not is_opt: - # update lsimdat and its current sim index - updatelsimdat(self.paramfn, self.params, ddat['dpl']) + # send signal to read data from files and save + self._read_sim_data(self.paramfn, self.params) def optmodel(self): need_initial_ddat = False @@ -411,7 +445,7 @@ def optmodel(self): param_out = os.path.join(sim_dir, 'before_opt.param') write_legacy_paramf(param_out, self.params) - self.updatewaitsimwin('Optimizing model. . .') + self._updatewaitsimwin('Optimizing model. . .') self.last_step = False self.first_step = True @@ -433,17 +467,17 @@ def optmodel(self): if self.step_sims == 0: txt = "Skipping optimization step %d (0 simulations)" % \ (step + 1) - self.updatewaitsimwin(txt) + self._updatewaitsimwin(txt) continue if len(self.step_ranges) == 0: txt = "Skipping optimization step %d (0 parameters)" % \ (step + 1) - self.updatewaitsimwin(txt) + self._updatewaitsimwin(txt) continue txt = "Starting optimization step %d/%d" % (step + 1, num_steps) - self.updatewaitsimwin(txt) + self._updatewaitsimwin(txt) self.runOptStep(step) if 'dpl' in self.best_ddat: @@ -452,16 +486,17 @@ def optmodel(self): ddat['errtot'] = deepcopy(self.best_ddat['errtot']) if need_initial_ddat: + save_initial_sim_data() initial_ddat = deepcopy(ddat) # update optdat with best from this step - updateoptdat(self.paramfn, self.params, ddat['dpl']) + update_opt_data(self.paramfn, self.params, ddat['dpl']) # put best opt results into GUI and save to param file push_values = {} for param_name in self.step_ranges.keys(): push_values[param_name] = self.step_ranges[param_name]['final'] - self.updatebaseparamwin(push_values) + self._updatebaseparamwin(push_values) self.baseparamwin.optparamwin.push_chunk_ranges(step, push_values) sleep(1) @@ -469,13 +504,13 @@ def optmodel(self): self.first_step = False # one final sim with the best parameters to update display - self.runsim(is_opt=True, banner=False) + self._runsim(is_opt=True, banner=False) # update lsimdat and its current sim index - updatelsimdat(self.paramfn, self.params, ddat['dpl']) + update_sim_data(self.paramfn, self.params, ddat['dpl']) # update optdat with the final best - updateoptdat(self.paramfn, self.params, ddat['dpl']) + update_opt_data(self.paramfn, self.params, ddat['dpl']) # re-enable all the range sliders self.baseparamwin.optparamwin.toggleEnableUserFields(step, @@ -497,7 +532,7 @@ def runOptStep(self, step): def optrun(new_params, grad=0): txt = "Optimization step %d, simulation %d" % (step + 1, self.optsim + 1) - self.updatewaitsimwin(txt) + self._updatewaitsimwin(txt) print(txt) dtest = {} @@ -517,15 +552,15 @@ def optrun(new_params, grad=0): return 1e9 # invalid param value -> large error # put new param values into GUI and save params to file - self.updatebaseparamwin(dtest) + self._updatebaseparamwin(dtest) sleep(1) # run the simulation, but stop early if possible - self.runsim(is_opt=True, banner=False, simlength=self.opt_end) + self._runsim(is_opt=True, banner=False, simlength=self.opt_end) # calculate wRMSE for all steps - weighted_rmse(ddat, self.opt_end, self.opt_weights, - tstart=self.opt_start) + calcerr(self.paramfn, self.opt_end, tstart=self.opt_start, + weights=self.opt_weights) err = ddat['werrtot'] if self.last_step: @@ -535,11 +570,11 @@ def optrun(new_params, grad=0): txt = "RMSE = %f" % err else: # calculate regular RMSE for displaying on plot - calcerr(ddat, self.opt_end, tstart=self.opt_start) + calcerr(self.paramfn, self.opt_end, tstart=self.opt_start) txt = "weighted RMSE = %f, RMSE = %f" % (err, ddat['errtot']) print(txt) - self.updatewaitsimwin(os.linesep + 'Simulation finished: ' + txt + + self._updatewaitsimwin(os.linesep + 'Simulation finished: ' + txt + os.linesep) data_dir = op.join(get_output_dir(), 'data') @@ -555,7 +590,7 @@ def optrun(new_params, grad=0): write_legacy_paramf(param_out, self.params) if err < self.stepminopterr: - self.updatewaitsimwin("new best with RMSE %f" % err) + self._updatewaitsimwin("new best with RMSE %f" % err) self.stepminopterr = err # save best param file @@ -571,7 +606,7 @@ def optrun(new_params, grad=0): # Update plots for the first simulation only of this step # (best results from last round). Skip the first step because # there are no optimization results to show yet. - self.updatedrawerr() # send event to draw updated RMSE + self._updatedrawerr() # send event to draw updated RMSE self.optsim += 1 @@ -609,7 +644,7 @@ def optimize(params_input, evals, algorithm): txt = 'Optimizing from [%3.3f-%3.3f] ms' % (self.opt_start, self.opt_end) - self.updatewaitsimwin(txt) + self._updatewaitsimwin(txt) num_params = len(self.step_ranges) algorithm = nlopt.LN_COBYLA diff --git a/hnn/simdat.py b/hnn/simdat.py index 36f0016f7..31bc532b7 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -1,736 +1,844 @@ import os from PyQt5.QtWidgets import QSizePolicy -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure import matplotlib.pyplot as plt import matplotlib.patches as mpatches import matplotlib.gridspec as gridspec import numpy as np from math import ceil +from glob import glob +from scipy import signal + +from hnn_core import read_spikes + from .spikefn import ExtInputs from .paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs from .paramrw import usingTonicInputs, countEvokedInputs, get_output_dir -from scipy import signal from .qt_lib import getscreengeom -import traceback - -from hnn_core import read_spikes drawindivdpl = 1 drawavgdpl = 1 fontsize = plt.rcParams['font.size'] = 10 -ddat = {} # current simulation data -dfile = {} # data file information for current simulation - -lsimdat = [] # list of simulation data -lsimidx = 0 # index into lsimdat - -initial_ddat = {} -optdat = [] # single optimization run - -def updatelsimdat(paramf, params, dpl): - # update lsimdat with paramf and dipole dpl - # but if the specific sim already run put dipole at that location in list - global lsimdat, lsimidx - - for idx, sim in enumerate(lsimdat): - if paramf in sim['paramfn']: - lsimdat[idx]['params'] = params - lsimdat[idx]['dpl'] = dpl - lsimidx = idx - return - - lsimdat.append({'paramfn': paramf, 'params': params, 'dpl': dpl}) # if not found, append to end of the list - lsimidx = len(lsimdat) - 1 - -def updateoptdat(paramfn, params, dpl): - global optdat - - optdat = {'paramfn': paramfn, 'params': params, 'dpl': dpl} - -def rmse (a1, a2): - # return root mean squared error between a1, a2; assumes same lengths, sampling rates - len1,len2 = len(a1),len(a2) - sz = min(len1,len2) - return np.sqrt(((a1[0:sz] - a2[0:sz]) ** 2).mean()) - -def readdpltrials(sim_dir): - # read dipole data files for individual trials - ldpl = [] - - i = 0 - while True: - fn = os.path.join(sim_dir, 'dpl_' + str(i) + '.txt') - if not os.path.exists(fn): - break - - ldpl.append(np.loadtxt(fn)) - - # try reading another trial - i += 1 - - return ldpl - -def getinputfiles (sim_prefix): - # get a dictionary of input files based on simulation parameter file paramf - global dfile - dfile = {} - data_dir = os.path.join(get_output_dir(), 'data') - sim_dir = os.path.join(data_dir, sim_prefix) - dfile['dpl'] = os.path.join(sim_dir,'dpl.txt') - dfile['spec'] = os.path.join(sim_dir,'rawspec.npz') - dfile['spk'] = os.path.join(sim_dir,'spk.txt') - dfile['outparam'] = os.path.join(sim_dir,'param.txt') - return dfile - -def readtxt (fn): - contents = [] - - try: - contents = np.loadtxt(fn) - except OSError: - print('Warning: could not read file:', fn) - except ValueError: - print('Warning: error reading data from:', fn) - - return contents - -def updatedat (params): - # update data dictionary (ddat) from the param file - - data_dir = os.path.join(get_output_dir(), 'data') - sim_dir = os.path.join(data_dir, params['sim_prefix']) - getinputfiles(params['sim_prefix']) - - for k in ['dpl','spk']: - if k in ddat: - del ddat[k] - - if os.path.exists(sim_dir): - ddat['dpl'] = readtxt(dfile['dpl']) - if len(ddat['dpl']) == 0: - del ddat['dpl'] - print('WARN: could not read dipole data for simulation %s' %params['sim_prefix']) - - try: - spikes = read_spikes(dfile['spk']) - ddat['spk'] = np.r_[spikes.times, spikes.gids].T - except ValueError: - ddat['spk'] = readtxt(dfile['spk']) - except IndexError: - # incorrect dimensions (bad spike file) - ddat['spk'] = None - - ddat['dpltrials'] = readdpltrials(sim_dir) - - if os.path.isfile(dfile['spec']): - ddat['spec'] = np.load(dfile['spec']) - else: - ddat['spec'] = None - -def calcerr (ddat, tstop, tstart=0.0): - # calculates RMSE error from ddat dictionary - NSig = errtot = 0.0; lerr = [] - ddat['errtot']=None; ddat['lerr']=None - for _, dat in ddat['dextdata'].items(): - shp = dat.shape - - exp_times = dat[:,0] - sim_times = ddat['dpl'][:,0] - - # do tstart and tstop fall within both datasets? - # if not, use the closest data point as the new tstop/tstart - for tseries in [exp_times, sim_times]: - if tstart < tseries[0]: - tstart = tseries[0] - if tstop > tseries[-1]: - tstop = tseries[-1] - - # make sure start and end times are valid for both dipoles - exp_start_index = (np.abs(exp_times - tstart)).argmin() - exp_end_index = (np.abs(exp_times - tstop)).argmin() - exp_length = exp_end_index - exp_start_index - - sim_start_index = (np.abs(sim_times - tstart)).argmin() - sim_end_index = (np.abs(sim_times - tstop)).argmin() - sim_length = sim_end_index - sim_start_index - - for c in range(1,shp[1],1): - dpl1 = ddat['dpl'][sim_start_index:sim_end_index,1] - dpl2 = dat[exp_start_index:exp_end_index,c] - - if (sim_length > exp_length): - # downsample simulation timeseries to match exp data - dpl1 = signal.resample(dpl1, exp_length) - elif (sim_length < exp_length): - # downsample exp timeseries to match simulation data - dpl2 = signal.resample(dpl2, sim_length) - err0 = np.sqrt(((dpl1 - dpl2) ** 2).mean()) - lerr.append(err0) - errtot += err0 - #print('RMSE: ',err0) - NSig += 1 - if not NSig == 0.0: - errtot /= NSig - #print('Avg. RMSE:' + str(round(errtot,2))) - ddat['errtot'] = errtot - ddat['lerr'] = lerr - return lerr, errtot - -def weighted_rmse(ddat, tstop, weights, tstart=0.0): - from numpy import sqrt - from scipy import signal - - # calculates RMSE error from ddat dictionary - NSig = errtot = 0.0; lerr = [] - ddat['werrtot']=None; ddat['lerr']=None - for _, dat in ddat['dextdata'].items(): - shp = dat.shape - exp_times = dat[:,0] - sim_times = ddat['dpl'][:,0] - - # do tstart and tstop fall within both datasets? - # if not, use the closest data point as the new tstop/tstart - for tseries in [exp_times, sim_times]: - if tstart < tseries[0]: - tstart = tseries[0] - if tstop > tseries[-1]: - tstop = tseries[-1] - - # make sure start and end times are valid for both dipoles - exp_start_index = (np.abs(exp_times - tstart)).argmin() - exp_end_index = (np.abs(exp_times - tstop)).argmin() - exp_length = exp_end_index - exp_start_index - - sim_start_index = (np.abs(sim_times - tstart)).argmin() - sim_end_index = (np.abs(sim_times - tstop)).argmin() - sim_length = sim_end_index - sim_start_index - - weight = weights[sim_start_index:sim_end_index] - - for c in range(1,shp[1],1): - dpl1 = ddat['dpl'][sim_start_index:sim_end_index,1] - dpl2 = dat[exp_start_index:exp_end_index,c] - - if (sim_length > exp_length): - # downsample simulation timeseries to match exp data - dpl1 = signal.resample(dpl1, exp_length) - weight = signal.resample(weight, exp_length) - indices = np.where(weight < 1e-4) - weight[indices] = 0 - elif (sim_length < exp_length): - # downsample exp timeseries to match simulation data - dpl2 = signal.resample(dpl2, sim_length) - - err0 = np.sqrt((weight * ((dpl1 - dpl2) ** 2)).sum()/weight.sum()) - lerr.append(err0) - errtot += err0 - #print('RMSE: ',err0) - NSig += 1 - errtot /= NSig - #print('Avg. RMSE:' + str(round(errtot,2))) - ddat['werrtot'] = errtot - ddat['wlerr'] = lerr - return lerr, errtot - - -class SIMCanvas (FigureCanvas): - # matplotlib/pyqt-compatible canvas for drawing simulation & external data - # based on https://pythonspot.com/en/pyqt5-matplotlib/ - - def __init__ (self, params, parent=None, width=5, height=4, dpi=40, optMode=False, title='Simulation Viewer'): - FigureCanvas.__init__(self, Figure(figsize=(width, height), dpi=dpi)) - - self.title = title - self.lextdatobj = [] # external data object - self.clridx = 5 # index for next color for drawing external data - self.lpatch = [mpatches.Patch(color='black', label='Sim.')] # legend for dipole signals - self.setParent(parent) - self.gui = parent - FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding) - FigureCanvas.updateGeometry(self) - self.params = params - self.initaxes() - self.G = gridspec.GridSpec(10,1) - - global initial_ddat, optdat - self.optMode = optMode - if not optMode: - initial_ddat = {} - optdat = {} - self.plot() - - def initaxes (self): - # initialize the axes - self.axdist = self.axprox = self.axdipole = self.axspec = self.axpois = None - - def plotinputhist(self, xl, dinty): - """ plot input histograms - xl = x axis limits - dinty = dict of input types used, - determines how many/which axes created/displayed - """ - extinputs = None - plot_distribs = False - - if self.params is None: - raise ValueError("No valid params found") - - sim_tstop = self.params['tstop'] - sim_dt = self.params['tstop'] - num_step = ceil(sim_tstop / sim_dt) + 1 - times = np.linspace(0, sim_tstop, num_step) - - try: - extinputs = ExtInputs(dfile['spk'], dfile['outparam'], - self.params) - extinputs.add_delay_times() - dinput = extinputs.inputs - except ValueError: - dinput = self.getInputDistrib() - plot_distribs = True - - if len(dinput['dist']) <= 0 and len(dinput['prox']) <= 0 and \ - len(dinput['evdist']) <= 0 and len(dinput['evprox']) <= 0 and \ - len(dinput['pois']) <= 0: - return False - - self.hist = {'feed_dist': None, - 'feed_prox': None, - 'feed_evdist': None, - 'feed_evprox': None, - 'feed_pois': None} - - # dinty ensures synaptic weight > 0 - hasPois = len(dinput['pois']) > 0 and dinty['Poisson'] - gRow = 0 - self.axdist = self.axprox = self.axpois = None # axis objects - - # check poisson inputs, create subplot - if hasPois: - self.axpois = self.figure.add_subplot(self.G[gRow, 0]) - gRow += 1 - - # check distal inputs, create subplot - if (len(dinput['dist']) > 0 and dinty['OngoingDist']) or \ - (len(dinput['evdist']) > 0 and dinty['EvokedDist']): - self.axdist = self.figure.add_subplot(self.G[gRow, 0]) - gRow += 1 - - # check proximal inputs, create subplot - if (len(dinput['prox']) > 0 and dinty['OngoingProx']) or \ - (len(dinput['evprox']) > 0 and dinty['EvokedProx']): - self.axprox = self.figure.add_subplot(self.G[gRow, 0]) - gRow += 1 - - # check input types provided in simulation - if extinputs is not None and self.hassimdata(): - if hasPois: - extinputs.plot_hist(self.axpois, 'pois', times, 'auto', xl, color='k', - hty='step', lw=self.gui.linewidth+1) - - # dinty condition ensures synaptic weight > 0 - if len(dinput['dist']) > 0 and dinty['OngoingDist']: - extinputs.plot_hist(self.axdist, 'dist', times, 'auto', xl, color='g', - lw=self.gui.linewidth+1) - - if len(dinput['prox']) > 0 and dinty['OngoingProx']: - extinputs.plot_hist(self.axprox, 'prox', times, 'auto', xl, color='r', - lw=self.gui.linewidth+1) - - if len(dinput['evdist']) > 0 and dinty['EvokedDist']: - extinputs.plot_hist(self.axdist, 'evdist', times, 'auto', xl, - color='g', hty='step', lw=self.gui.linewidth+1) - - if len(dinput['evprox']) > 0 and dinty['EvokedProx']: - extinputs.plot_hist(self.axprox, 'evprox', times, 'auto', xl, - color='r', hty='step', lw=self.gui.linewidth+1) - elif plot_distribs: - if len(dinput['evprox']) > 0 and dinty['EvokedProx']: - prox_tot = np.zeros(len(dinput['evprox'][0][0])) - for prox in dinput['evprox']: - prox_tot += prox[1] - self.axprox.plot(dinput['evprox'][0][0], prox_tot, color='r', - lw=self.gui.linewidth, label='evprox distribution') - self.axprox.set_xlim(dinput['evprox'][0][0][0], - dinput['evprox'][0][0][-1]) - if len(dinput['evdist']) > 0 and dinty['EvokedDist']: - dist_tot = np.zeros(len(dinput['evdist'][0][0])) - for dist in dinput['evdist']: - dist_tot += dist[1] - self.axdist.plot(dinput['evdist'][0][0], dist_tot, color='g', - lw=self.gui.linewidth, label='evdist distribution') - self.axprox.set_xlim(dinput['evdist'][0][0][0], - dinput['evdist'][0][0][-1]) - - ymax = 0 - for ax in [self.axpois, self.axdist, self.axprox]: - if ax is not None: - if ax.get_ylim()[1] > ymax: - ymax = ax.get_ylim()[1] - - if ymax == 0: - return False - else: - for ax in [self.axpois, self.axdist, self.axprox]: - if ax is not None: - ax.set_ylim(0, ymax) - if self.axdist: - self.axdist.invert_yaxis() - for ax in [self.axpois, self.axdist, self.axprox]: - if ax: - ax.set_xlim(xl) - ax.legend(loc=1) # legend in upper right - return True, gRow - - def clearaxes (self): - # clear the figures axes - for ax in self.figure.get_axes(): - if ax: - ax.cla() - - def getInputDistrib (self): - import scipy.stats as stats - - dinput = {'evprox': [], 'evdist': [], 'prox': [], 'dist': [], 'pois': []} - try: - sim_tstop = self.params['tstop'] - sim_dt = self.params['dt'] - except KeyError: - return dinput - - num_step = ceil(sim_tstop / sim_dt) + 1 - times = np.linspace(0, sim_tstop, num_step) - ltprox, ltdist = self.getEVInputTimes() - for prox in ltprox: - pdf = stats.norm.pdf(times, prox[0], prox[1]) - dinput['evprox'].append((times,pdf)) - for dist in ltdist: - pdf = stats.norm.pdf(times, dist[0], dist[1]) - dinput['evdist'].append((times,pdf)) - return dinput - - def getEVInputTimes (self): - # get the evoked input times - - if self.params is None: - raise ValueError("No valid params found") - - nprox, ndist = countEvokedInputs(self.params) - ltprox, ltdist = [], [] - for i in range(nprox): - input_mu = self.params['t_evprox_' + str(i+1)] - input_sigma = self.params['sigma_t_evprox_' + str(i+1)] - ltprox.append((input_mu, input_sigma)) - for i in range(ndist): - input_mu = self.params['t_evdist_' + str(i+1)] - input_sigma = self.params['sigma_t_evdist_' + str(i+1)] - ltdist.append((input_mu, input_sigma)) - return ltprox, ltdist - - def drawEVInputTimes (self, ax, yl, h=0.1, hw=15, hl=15): - # draw the evoked input times using arrows - ltprox, ltdist = self.getEVInputTimes() - yrange = abs(yl[1] - yl[0]) - #print('drawEVInputTimes:',yl,yrange,h,hw,hl,h*yrange,-h*yrange,yl[0]+h*yrange,yl[1]-h*yrange) - for tt in ltprox: ax.arrow(tt[0],yl[0],0,h*yrange,fc='r',ec='r', head_width=hw,head_length=hl)#head_length=w,head_width=1.)#w/4)#length_includes_head=True, - for tt in ltdist: ax.arrow(tt[0],yl[1],0,-h*yrange,fc='g',ec='g',head_width=hw,head_length=hl)#head_length=w,head_width=1.)#w/4) - - def getInputs (self): +def read_dpltrials(sim_dir): + """read dipole data files for individual trials""" + ldpl = [] + + dpl_fname_pattern = os.path.join(sim_dir, 'dpl_*.txt') + for dipole_fn in sorted(glob(str(dpl_fname_pattern))): + dpl_trial = None + try: + dpl_trial = np.loadtxt(dipole_fn) + except OSError: + print('Warning: could not read file:', dipole_fn) + except ValueError: + print('Warning: could not read file:', dipole_fn) + + ldpl.append(dpl_trial) + + return ldpl + + +def get_inputs(params): """ get a dictionary of input types used in simulation with distal/proximal specificity for evoked,ongoing inputs """ - dinty = {'Evoked':False,'Ongoing':False,'Poisson':False,'Tonic':False,'EvokedDist':False,\ - 'EvokedProx':False,'OngoingDist':False,'OngoingProx':False} + dinty = {'Evoked': False, 'Ongoing': False, 'Poisson': False, + 'Tonic': False, 'EvokedDist': False, 'EvokedProx': False, + 'OngoingDist': False, 'OngoingProx': False} - dinty['Evoked'] = usingEvokedInputs(self.params) - dinty['EvokedDist'] = usingEvokedInputs(self.params, lsuffty = ['_evdist_']) - dinty['EvokedProx'] = usingEvokedInputs(self.params, lsuffty = ['_evprox_']) - dinty['Ongoing'] = usingOngoingInputs(self.params) - dinty['OngoingDist'] = usingOngoingInputs(self.params, lty = ['_dist']) - dinty['OngoingProx'] = usingOngoingInputs(self.params, lty = ['_prox']) - dinty['Poisson'] = usingPoissonInputs(self.params) - dinty['Tonic'] = usingTonicInputs(self.params) + dinty['Evoked'] = usingEvokedInputs(params) + dinty['EvokedDist'] = usingEvokedInputs(params, lsuffty=['_evdist_']) + dinty['EvokedProx'] = usingEvokedInputs(params, lsuffty=['_evprox_']) + dinty['Ongoing'] = usingOngoingInputs(params) + dinty['OngoingDist'] = usingOngoingInputs(params, lty=['_dist']) + dinty['OngoingProx'] = usingOngoingInputs(params, lty=['_prox']) + dinty['Poisson'] = usingPoissonInputs(params) + dinty['Tonic'] = usingTonicInputs(params) return dinty - def getnextcolor (self): - # get next color for external data (colors selected in order) - self.clridx += 5 - if self.clridx > 100: self.clridx = 5 - return self.clridx - def plotextdat (self, recalcErr=True): - global fontsize - - if not 'dextdata' in ddat or len(ddat['dextdata']) == 0: - return - - lerr = None - errtot = None - initial_err = None - # plot 'external' data (e.g. from experiment/other simulation) - hassimdata = self.hassimdata() # has the simulation been run yet? - if hassimdata: - if recalcErr: - calcerr(ddat, ddat['dpl'][-1,0]) # recalculate/save the error? - - try: - lerr, errtot = ddat['lerr'], ddat['errtot'] +class SimData(object): + """The SimData class""" + + def __init__(self): + self._sim_data = {} + self._opt_data = {} + self._exp_data = {} + + def remove_sim_by_fn(self, paramfn): + """Deletes sim from SimData + + Parameters + ---------- + paramfn : str + Filename of parameter file to remove + """ + del self._sim_data[paramfn] + + def _update_sim_list(self, paramfn, params, avg_dpl, dpl_trials, + spikes=None, spec=None): + self._sim_data[paramfn] = {'params': params, + 'data': {'avg_dpl': avg_dpl, + 'dpl_trials': dpl_trials, + 'spk': spikes, + 'spec': spec}} + + def clear_exp_data(self): + """Clear all experimental data from SimData""" + + self._exp_data = {} + + def clear_sim_data(self): + """Clear all simulation data from SimData""" + + self._sim_data.clear() + + def update_exp_data(self, exp_fn, exp_data): + """Adds experimental data to SimData + + Parameters + ---------- + exp_fn : str + Filename of experimental data + exp_data : array + Data from np.loadtxt() on experimental data file + """ + self._exp_data[exp_fn] = exp_data + + def get_exp_data_size(self): + """Adds experimental data to SimData + + Returns + ---------- + length: int + The number of experimental data files in SimData + """ + + return len(self._exp_data) + + def update_sim_data(self, paramfn, params): + """Adds simulation data to SimData + + Parameters + ---------- + paramfn : str + Simulation paramter filename + params : dict + Dictionary containing parameters + """ + + data_dir = os.path.join(get_output_dir(), 'data') + sim_dir = os.path.join(data_dir, params['sim_prefix']) + warn_nofile = False + if os.path.exists(sim_dir): + warn_nofile = True + + warning_message = 'Warning: could not read file:' + + dipole_fn = os.path.join(sim_dir, 'dpl.txt') + avg_dpl = None + try: + avg_dpl = np.loadtxt(dipole_fn) + except OSError: + if warn_nofile: + print(warning_message, dipole_fn) + except ValueError: + if warn_nofile: + print(warning_message, dipole_fn) + + spike_fn = os.path.join(sim_dir, 'spk.txt') + spikes_array = None + try: + raw_spikes = read_spikes(spike_fn) + spikes_array = np.r_[raw_spikes.times, raw_spikes.gids].T + if len(spikes_array) == 0 and warn_nofile: + print(warning_message, spike_fn) + except ValueError: + try: + spikes_array = np.loadtxt(spike_fn) + except OSError: + if warn_nofile: + print(warning_message, spike_fn) + except ValueError: + if warn_nofile: + print(warning_message, spike_fn) + except IndexError: + print('Warning: incorrect dimensions for spike file: %s' % + spike_fn) + + warn_nospec = False + dinty = get_inputs(params) + if params['save_spec_data'] or dinty['Ongoing'] or \ + dinty['Poisson'] or dinty['Tonic']: + warn_nospec = True + + spec_fn = os.path.join(sim_dir, 'rawspec.npz') + spec = None + try: + spec = np.load(spec_fn) + except OSError: + if warn_nospec: + print(warning_message, spec_fn) + except ValueError: + if warn_nospec: + print(warning_message, spec_fn) + + dpl_trials = read_dpltrials(sim_dir) + + self._update_sim_list(paramfn, params, avg_dpl, dpl_trials, + spikes_array, spec) + + def calcerr(self, paramfn, tstop, tstart=0.0, weights=None): + """Calculate root mean squared error using SimData + + Parameters + ---------- + paramfn : str + Simulation parameter filename to calculate RMSE for against + experimental data previously loaded in SimData + tstop : float + Time in ms defining the end of the region to calculate RMSE + tstart : float | None + Time in ms defining the start of the region to calculate RMSE. + If None is provided, this defaults to 0.0 ms. + weights : array | None + An array containing weights for each data point of the simulation. + If weights is provided, then the weighted root mean square error + will be returned. If None is provided, then standard RMSE will be + returned. + + Returns + ---------- + lerr : list of floats + A list of RMSE values between the simulation and each experimental + data files stored in SimData + errtot : float + Average RMSE over all experimental data files + """ + + NSig = errtot = 0.0 + lerr = [] + for _, dat in self._exp_data.items(): + shp = dat.shape + + exp_times = dat[:, 0] + sim_times = self._sim_data[paramfn]['data']['avg_dpl'][:, 0] + + # do tstart and tstop fall within both datasets? + # if not, use the closest data point as the new tstop/tstart + for tseries in [exp_times, sim_times]: + if tstart < tseries[0]: + tstart = tseries[0] + if tstop > tseries[-1]: + tstop = tseries[-1] + + # make sure start and end times are valid for both dipoles + exp_start_index = (np.abs(exp_times - tstart)).argmin() + exp_end_index = (np.abs(exp_times - tstop)).argmin() + exp_length = exp_end_index - exp_start_index + + sim_start_index = (np.abs(sim_times - tstart)).argmin() + sim_end_index = (np.abs(sim_times - tstop)).argmin() + sim_length = sim_end_index - sim_start_index + + if weights is not None: + weight = weights[sim_start_index:sim_end_index] + + for c in range(1, shp[1], 1): + sim_dpl = self._sim_data[paramfn]['data']['avg_dpl'] + dpl1 = sim_dpl[sim_start_index:sim_end_index, 1] + dpl2 = dat[exp_start_index:exp_end_index, c] + + if (sim_length > exp_length): + # downsample simulation timeseries to match exp data + dpl1 = signal.resample(dpl1, exp_length) + if weights is not None: + weight = signal.resample(weight, exp_length) + indices = np.where(weight < 1e-4) + weight[indices] = 0 + + elif (sim_length < exp_length): + # downsample exp timeseries to match simulation data + dpl2 = signal.resample(dpl2, sim_length) + + if weights is not None: + err0 = np.sqrt((weight * ((dpl1 - dpl2) ** 2)).sum() / + weight.sum()) + else: + err0 = np.sqrt(((dpl1 - dpl2) ** 2).mean()) + lerr.append(err0) + errtot += err0 + NSig += 1 + + if not NSig == 0.0: + errtot /= NSig + return lerr, errtot + + def clear_opt_data(self): + self._initial_opt = {} + self._opt_data = {} + + def update_opt_data(self, paramfn, params, avg_dpl): + self._opt_data = {'paramfn': paramfn, 'params': params, + 'data': {'avg_dpl': avg_dpl}} + + +class SIMCanvas (FigureCanvasQTAgg): + # matplotlib/pyqt-compatible canvas for drawing simulation & external data + # based on https://pythonspot.com/en/pyqt5-matplotlib/ + + def __init__(self, paramfn, params, parent=None, width=5, height=4, + dpi=40, optMode=False, title='Simulation Viewer'): + FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), + dpi=dpi)) + + self.title = title + self.sim_data = parent.sim_data + self.lextdatobj = [] # external data object + self.clridx = 5 # index for next color for drawing external data + + # legend for dipole signals + self.lpatch = [mpatches.Patch(color='black', label='Sim.')] + self.setParent(parent) + self.linewidth = parent.linewidth + FigureCanvasQTAgg.setSizePolicy(self, QSizePolicy.Expanding, + QSizePolicy.Expanding) + FigureCanvasQTAgg.updateGeometry(self) + self.params = params + self.paramfn = paramfn + self.initaxes() + self.G = gridspec.GridSpec(10, 1) + + self.optMode = optMode + if not optMode: + self.sim_data.clear_opt_data() + self.plot() + + def initaxes(self): + # initialize the axes + self.axdist = self.axprox = self.axdipole = self.axspec = None + self.axpois = None + + def plotinputhist(self, xl, dinty): + """ plot input histograms + xl = x axis limits + dinty = dict of input types used, + determines how many/which axes created/displayed + """ + + extinputs = None + plot_distribs = False + + if self.params is None: + raise ValueError("No valid params found") + + sim_tstop = self.params['tstop'] + sim_dt = self.params['tstop'] + num_step = ceil(sim_tstop / sim_dt) + 1 + times = np.linspace(0, sim_tstop, num_step) + + data_dir = os.path.join(get_output_dir(), 'data') + sim_dir = os.path.join(data_dir, self.params['sim_prefix']) + spike_fn = os.path.join(sim_dir, 'spk.txt') + out_paramfn = os.path.join(sim_dir, 'param.txt') + try: + extinputs = ExtInputs(spike_fn, out_paramfn, self.params) + extinputs.add_delay_times() + dinput = extinputs.inputs + except ValueError: + dinput = self.getInputDistrib() + plot_distribs = True + + if len(dinput['dist']) <= 0 and len(dinput['prox']) <= 0 and \ + len(dinput['evdist']) <= 0 and len(dinput['evprox']) <= 0 and \ + len(dinput['pois']) <= 0: + return False + + self.hist = {'feed_dist': None, + 'feed_prox': None, + 'feed_evdist': None, + 'feed_evprox': None, + 'feed_pois': None} + + # dinty ensures synaptic weight > 0 + hasPois = len(dinput['pois']) > 0 and dinty['Poisson'] + gRow = 0 + self.axdist = self.axprox = self.axpois = None # axis objects + + # check poisson inputs, create subplot + if hasPois: + self.axpois = self.figure.add_subplot(self.G[gRow, 0]) + gRow += 1 + + # check distal inputs, create subplot + if (len(dinput['dist']) > 0 and dinty['OngoingDist']) or \ + (len(dinput['evdist']) > 0 and dinty['EvokedDist']): + self.axdist = self.figure.add_subplot(self.G[gRow, 0]) + gRow += 1 + + # check proximal inputs, create subplot + if (len(dinput['prox']) > 0 and dinty['OngoingProx']) or \ + (len(dinput['evprox']) > 0 and dinty['EvokedProx']): + self.axprox = self.figure.add_subplot(self.G[gRow, 0]) + gRow += 1 + + # check input types provided in simulation + if extinputs is not None and self._has_simdata(): + if hasPois: + extinputs.plot_hist(self.axpois, 'pois', times, 'auto', xl, + color='k', hty='step', + lw=self.linewidth+1) + + # dinty condition ensures synaptic weight > 0 + if len(dinput['dist']) > 0 and dinty['OngoingDist']: + extinputs.plot_hist(self.axdist, 'dist', times, 'auto', xl, + color='g', lw=self.linewidth+1) + + if len(dinput['prox']) > 0 and dinty['OngoingProx']: + extinputs.plot_hist(self.axprox, 'prox', times, 'auto', xl, + color='r', lw=self.linewidth+1) + + if len(dinput['evdist']) > 0 and dinty['EvokedDist']: + extinputs.plot_hist(self.axdist, 'evdist', times, 'auto', xl, + color='g', hty='step', + lw=self.linewidth+1) + + if len(dinput['evprox']) > 0 and dinty['EvokedProx']: + extinputs.plot_hist(self.axprox, 'evprox', times, 'auto', xl, + color='r', hty='step', + lw=self.linewidth+1) + elif plot_distribs: + if len(dinput['evprox']) > 0 and dinty['EvokedProx']: + prox_tot = np.zeros(len(dinput['evprox'][0][0])) + for prox in dinput['evprox']: + prox_tot += prox[1] + self.axprox.plot(dinput['evprox'][0][0], prox_tot, color='r', + lw=self.linewidth, + label='evprox distribution') + self.axprox.set_xlim(dinput['evprox'][0][0][0], + dinput['evprox'][0][0][-1]) + if len(dinput['evdist']) > 0 and dinty['EvokedDist']: + dist_tot = np.zeros(len(dinput['evdist'][0][0])) + for dist in dinput['evdist']: + dist_tot += dist[1] + self.axdist.plot(dinput['evdist'][0][0], dist_tot, color='g', + lw=self.linewidth, + label='evdist distribution') + self.axprox.set_xlim(dinput['evdist'][0][0][0], + dinput['evdist'][0][0][-1]) + + ymax = 0 + for ax in [self.axpois, self.axdist, self.axprox]: + if ax is not None: + if ax.get_ylim()[1] > ymax: + ymax = ax.get_ylim()[1] + + if ymax == 0: + return False + else: + for ax in [self.axpois, self.axdist, self.axprox]: + if ax is not None: + ax.set_ylim(0, ymax) + if self.axdist: + self.axdist.invert_yaxis() + for ax in [self.axpois, self.axdist, self.axprox]: + if ax: + ax.set_xlim(xl) + ax.legend(loc=1) # legend in upper right + return True, gRow + + def clearaxes(self): + # clear the figures axes + for ax in self.figure.get_axes(): + if ax: + ax.cla() + + def getInputDistrib(self): + import scipy.stats as stats + + dinput = {'evprox': [], 'evdist': [], 'prox': [], 'dist': [], + 'pois': []} + try: + sim_tstop = self.params['tstop'] + sim_dt = self.params['dt'] + except KeyError: + return dinput + + num_step = ceil(sim_tstop / sim_dt) + 1 + times = np.linspace(0, sim_tstop, num_step) + ltprox, ltdist = self.getEVInputTimes() + for prox in ltprox: + pdf = stats.norm.pdf(times, prox[0], prox[1]) + dinput['evprox'].append((times, pdf)) + for dist in ltdist: + pdf = stats.norm.pdf(times, dist[0], dist[1]) + dinput['evdist'].append((times, pdf)) + return dinput + + def getEVInputTimes(self): + # get the evoked input times + + if self.params is None: + raise ValueError("No valid params found") + + nprox, ndist = countEvokedInputs(self.params) + ltprox, ltdist = [], [] + for i in range(nprox): + input_mu = self.params['t_evprox_' + str(i+1)] + input_sigma = self.params['sigma_t_evprox_' + str(i+1)] + ltprox.append((input_mu, input_sigma)) + for i in range(ndist): + input_mu = self.params['t_evdist_' + str(i+1)] + input_sigma = self.params['sigma_t_evdist_' + str(i+1)] + ltdist.append((input_mu, input_sigma)) + return ltprox, ltdist + + def drawEVInputTimes(self, ax, yl, h=0.1, hw=15, hl=15): + # draw the evoked input times using arrows + ltprox, ltdist = self.getEVInputTimes() + yrange = abs(yl[1] - yl[0]) + + for tt in ltprox: + ax.arrow(tt[0], yl[0], 0, h * yrange, fc='r', ec='r', + head_width=hw, head_length=hl) + for tt in ltdist: + ax.arrow(tt[0], yl[1], 0, -h * yrange, fc='g', ec='g', + head_width=hw, head_length=hl) + + def getnextcolor(self): + # get next color for external data (colors selected in order) + self.clridx += 5 + if self.clridx > 100: + self.clridx = 5 + return self.clridx + + def _has_simdata(self): + """check if any simulation data available""" + avg_dpl = self.sim_data._sim_data[self.paramfn]['data']['avg_dpl'] + if avg_dpl is not None: + return True + + return False + + def plotextdat(self, recalcErr=True): + global fontsize + + if self.sim_data._exp_data is None or \ + len(self.sim_data._exp_data) == 0: + return + + initial_err = None + # plot 'external' data (e.g. from experiment/other simulation) + if self._has_simdata(): # has the simulation been run yet? + if recalcErr: + tstop = self.params['tstop'] + # recalculate/save the error? + self.lerr, self.errtot = self.sim_data.calcerr(self.paramfn, + tstop) + + if self.optMode: + initial_err = self.sim_data._opt_data['initial_error'] + + if self.axdipole is None: + self.axdipole = self.figure.add_subplot(self.G[0:-1, 0]) + xl = (0.0, 1.0) + yl = (-0.001, 0.001) + else: + xl = self.axdipole.get_xlim() + yl = self.axdipole.get_ylim() + + cmap = plt.get_cmap('nipy_spectral') + csm = plt.cm.ScalarMappable(cmap=cmap) + csm.set_clim((0, 100)) + + self.clearlextdatobj() # clear annotation objects + + ddx = 0 + for fn, dat in self.sim_data._exp_data.items(): + shp = dat.shape + clr = csm.to_rgba(self.getnextcolor()) + c = min(shp[1], 1) + self.lextdatobj.append(self.axdipole.plot(dat[:, 0], dat[:, c], + color=clr, linewidth=self.linewidth + 1)) + xl = ((min(xl[0], min(dat[:, 0]))), (max(xl[1], max(dat[:, 0])))) + yl = ((min(yl[0], min(dat[:, c]))), (max(yl[1], max(dat[:, c])))) + fx = int(shp[0] * float(c) / shp[1]) + if self.lerr: + tx, ty = dat[fx, 0], dat[fx, c] + txt = 'RMSE: %.2f' % round(self.lerr[ddx], 2) + if not self.optMode: + self.axdipole.annotate(txt, xy=(dat[0, 0], dat[0, c]), + xytext=(tx, ty), color=clr, + fontweight='bold') + label = fn.split(os.path.sep)[-1].split('.txt')[0] + self.lpatch.append(mpatches.Patch(color=clr, label=label)) + ddx += 1 + + self.axdipole.set_xlim(xl) + self.axdipole.set_ylim(yl) + + if self.lpatch: + self.axdipole.legend(handles=self.lpatch, loc=2) + + if self.errtot: + tx, ty = 0, 0 + if self.optMode and initial_err: + clr = 'black' + txt = 'RMSE: %.2f' % round(initial_err, 2) + textcoords = 'axes fraction' + self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), + xytext=(0.005, 0.005), + textcoords=textcoords, + color=clr, + fontweight='bold') + clr = 'gray' + txt = 'RMSE: %.2f' % round(self.errtot, 2) + self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), + xytext=(0.86, 0.005), + textcoords=textcoords, + color=clr, + fontweight='bold') + else: + clr = 'black' + txt = 'Avg. RMSE: %.2f' % round(self.errtot, 2) + self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), + xytext=(0.005, 0.005), + textcoords=textcoords, + color=clr, + fontweight='bold') + + if not self._has_simdata(): # need axis labels + self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) + self.axdipole.set_ylabel('Dipole (nAm)', fontsize=fontsize) + myxl = self.axdipole.get_xlim() + if myxl[0] < 0.0: + self.axdipole.set_xlim((0.0, myxl[1] + myxl[0])) + + def clearlextdatobj(self): + # clear list of external data objects + for o in self.lextdatobj: + if isinstance(o, list): + # this is the plot. clear the line + o[0].set_visible(False) + del self.lextdatobj + self.lextdatobj = [] # reset list of external data objects + self.lpatch = [] # reset legend + self.clridx = 5 # reset index for next color for drawing ext data if self.optMode: - initial_err = initial_ddat['errtot'] - except KeyError: - pass - - - if self.axdipole is None: - self.axdipole = self.figure.add_subplot(self.G[0:-1,0]) # dipole - xl = (0.0,1.0) - yl = (-0.001,0.001) - else: - xl = self.axdipole.get_xlim() - yl = self.axdipole.get_ylim() - - cmap=plt.get_cmap('nipy_spectral') - csm = plt.cm.ScalarMappable(cmap=cmap) - csm.set_clim((0,100)) - - self.clearlextdatobj() # clear annotation objects - - ddx = 0 - for fn,dat in ddat['dextdata'].items(): - shp = dat.shape - clr = csm.to_rgba(self.getnextcolor()) - c = min(shp[1],1) - self.lextdatobj.append(self.axdipole.plot(dat[:,0],dat[:,c],color=clr,linewidth=self.gui.linewidth+1)) - xl = ((min(xl[0],min(dat[:,0]))),(max(xl[1],max(dat[:,0])))) - yl = ((min(yl[0],min(dat[:,c]))),(max(yl[1],max(dat[:,c])))) - fx = int(shp[0] * float(c) / shp[1]) - if lerr: - tx,ty=dat[fx,0],dat[fx,c] - txt='RMSE: %.2f' % round(lerr[ddx],2) + self.lpatch.append(mpatches.Patch(color='grey', + label='Optimization')) + self.lpatch.append(mpatches.Patch(color='black', label='Initial')) + elif self._has_simdata(): + self.lpatch.append(mpatches.Patch(color='black', + label='Simulation')) + if hasattr(self, 'annot_avg'): + self.annot_avg.set_visible(False) + del self.annot_avg + + def plotsimdat(self): + """plot the simulation data""" + + global drawindivdpl, drawavgdpl, fontsize + + self.gRow = 0 + bottom = 0.0 + + failed_loading = False + only_create_axes = False + + if self.params is None: + only_create_axes = True + DrawSpec = False + xl = (0.0, 1.0) + else: + # setup the figure axis for drawing the dipole signal + dinty = get_inputs(self.params) + + self.sim_data.update_sim_data(self.paramfn, self.params) + + single_sim_data = self.sim_data._sim_data[self.paramfn]['data'] + if single_sim_data['avg_dpl'] is None: + failed_loading = True + + xl = (0.0, self.params['tstop']) + if dinty['Ongoing'] or dinty['Evoked'] or dinty['Poisson']: + xo = self.plotinputhist(xl, dinty) + if xo: + self.gRow = xo[1] + + # whether to draw the specgram - should draw if user saved it or + # have ongoing, poisson, or tonic inputs + DrawSpec = (not failed_loading) and \ + single_sim_data['spec'] is not None and \ + (self.params['save_spec_data'] or dinty['Ongoing'] + or dinty['Poisson'] or dinty['Tonic']) + + if DrawSpec: # dipole axis takes fewer rows if also drawing specgram + self.axdipole = self.figure.add_subplot(self.G[self.gRow:5, 0]) + bottom = 0.08 + else: + self.axdipole = self.figure.add_subplot(self.G[self.gRow:-1, 0]) + + yl = (-0.001, 0.001) + self.axdipole.set_ylim(yl) + self.axdipole.set_xlim(xl) + + left = 0.08 + w, _ = getscreengeom() + if w < 2800: + left = 0.1 + # reduce padding + self.figure.subplots_adjust(left=left, right=0.99, bottom=bottom, + top=0.99, hspace=0.1, wspace=0.1) + + if failed_loading or only_create_axes: + return + + ds = None + tstop = self.params['tstop'] + dt = self.params['dt'] + xl = (0, tstop) + + # get spectrogram if it exists, then adjust axis limits but only + # if drawing spectrogram + if DrawSpec: + single_sim_data = self.sim_data._sim_data[self.paramfn]['data'] + if single_sim_data['spec'] is not None: + ds = single_sim_data['spec'] # spectrogram + xl = (ds['time'][0], ds['time'][-1]) # use spectogram limits + else: + DrawSpec = False + + sampr = 1e3 / dt # dipole sampling rate + # use these indices to find dipole min,max + sidx, eidx = int(sampr*xl[0] / 1e3), int(sampr*xl[1] / 1e3) + + yl = [0, 0] + dpl = self.sim_data._sim_data[self.paramfn]['data']['avg_dpl'] + yl[0] = min(yl[0], np.amin(dpl[sidx:eidx, 1])) + yl[1] = max(yl[1], np.amax(dpl[sidx:eidx, 1])) + if not self.optMode: - self.lextdatobj.append(self.axdipole.annotate(txt,xy=(dat[0,0],dat[0,c]),xytext=(tx,ty),color=clr,fontweight='bold')) - self.lpatch.append(mpatches.Patch(color=clr, label=fn.split(os.path.sep)[-1].split('.txt')[0])) - ddx+=1 - - self.axdipole.set_xlim(xl) - self.axdipole.set_ylim(yl) - - if self.lextdatobj and self.lpatch: - self.lextdatobj.append(self.axdipole.legend(handles=self.lpatch, loc=2)) - - if errtot: - tx,ty=0,0 - if self.optMode and initial_err: - clr = 'black' - txt='RMSE: %.2f' % round(initial_err,2) - self.annot_avg = self.axdipole.annotate(txt,xy=(0,0),xytext=(0.005,0.005),textcoords='axes fraction',color=clr,fontweight='bold') - clr = 'gray' - txt='RMSE: %.2f' % round(errtot,2) - self.annot_avg = self.axdipole.annotate(txt,xy=(0,0),xytext=(0.86,0.005),textcoords='axes fraction',color=clr,fontweight='bold') - else: - clr = 'black' - txt='Avg. RMSE: %.2f' % round(errtot,2) - self.annot_avg = self.axdipole.annotate(txt,xy=(0,0),xytext=(0.005,0.005),textcoords='axes fraction',color=clr,fontweight='bold') - - if not hassimdata: # need axis labels - self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) - self.axdipole.set_ylabel('Dipole (nAm)', fontsize=fontsize) - myxl = self.axdipole.get_xlim() - if myxl[0] < 0.0: - self.axdipole.set_xlim((0.0, myxl[1] + myxl[0])) - - def hassimdata (self): - # check if any simulation data available in ddat dictionary - return 'dpl' in ddat - - def clearlextdatobj (self): - # clear list of external data objects - for o in self.lextdatobj: - # try: - o.set_visible(False) - # except: - # o[0].set_visible(False) - del self.lextdatobj - self.lextdatobj = [] # reset list of external data objects - self.lpatch = [] # reset legend - self.clridx = 5 # reset index for next color for drawing external data - - if self.optMode: - self.lpatch.append(mpatches.Patch(color='grey', label='Optimization')) - self.lpatch.append(mpatches.Patch(color='black', label='Initial')) - elif self.hassimdata(): - self.lpatch.append(mpatches.Patch(color='black', label='Simulation')) - if hasattr(self,'annot_avg'): - self.annot_avg.set_visible(False) - del self.annot_avg - - def plotsimdat (self): - """plot the simulation data""" - - global drawindivdpl, drawavgdpl, fontsize - - self.gRow = 0 - bottom = 0.0 - - failed_loading = False - only_create_axes = False - if self.params is None: - only_create_axes = True - DrawSpec = False - xl = (0.0, 1.0) - else: - # setup the figure axis for drawing the dipole signal - dinty = self.getInputs() - - updatedat(self.params) - if 'dpl' not in ddat: - failed_loading = True - - xl = (0.0, self.params['tstop']) - if dinty['Ongoing'] or dinty['Evoked'] or dinty['Poisson']: - xo = self.plotinputhist(xl, dinty) - if xo: - self.gRow = xo[1] - - # whether to draw the specgram - should draw if user saved it or have ongoing, poisson, or tonic inputs - DrawSpec = (not failed_loading) and \ - 'spec' in ddat and \ - (self.params['save_spec_data'] or dinty['Ongoing'] or dinty['Poisson'] or dinty['Tonic']) - - if DrawSpec: # dipole axis takes fewer rows if also drawing specgram - self.axdipole = self.figure.add_subplot(self.G[self.gRow:5,0]) # dipole - bottom = 0.08 - else: - self.axdipole = self.figure.add_subplot(self.G[self.gRow:-1,0]) # dipole - - yl = (-0.001,0.001) - self.axdipole.set_ylim(yl) - self.axdipole.set_xlim(xl) - - left = 0.08 - w, _ = getscreengeom() - if w < 2800: left = 0.1 - self.figure.subplots_adjust(left=left,right=0.99,bottom=bottom,top=0.99,hspace=0.1,wspace=0.1) # reduce padding - - if failed_loading or only_create_axes: - return - - ds = None - xl = (0,ddat['dpl'][-1,0]) - dt = ddat['dpl'][1,0] - ddat['dpl'][0,0] - - # get spectrogram if it exists, then adjust axis limits but only if drawing spectrogram - if DrawSpec: - if ddat['spec'] is not None: - ds = ddat['spec'] # spectrogram - xl = (ds['time'][0],ds['time'][-1]) # use specgram time limits - else: - DrawSpec = False - - sampr = 1e3/dt # dipole sampling rate - sidx, eidx = int(sampr*xl[0]/1e3), int(sampr*xl[1]/1e3) # use these indices to find dipole min,max - - yl = [0,0] - yl[0] = min(yl[0],np.amin(ddat['dpl'][sidx:eidx,1])) - yl[1] = max(yl[1],np.amax(ddat['dpl'][sidx:eidx,1])) - - if not self.optMode: - # skip for optimization - for lsim in lsimdat: # plot average dipoles from prior simulations - olddpl = lsim['dpl'] - self.axdipole.plot(olddpl[:,0],olddpl[:,1],'--',color='black',linewidth=self.gui.linewidth) - - if self.params['N_trials']>1 and drawindivdpl and len(ddat['dpltrials']) > 0: # plot dipoles from individual trials - for dpltrial in ddat['dpltrials']: - self.axdipole.plot(dpltrial[:,0],dpltrial[:,1],color='gray',linewidth=self.gui.linewidth) - yl[0] = min(yl[0],dpltrial[sidx:eidx,1].min()) - yl[1] = max(yl[1],dpltrial[sidx:eidx,1].max()) - - if drawavgdpl or self.params['N_trials'] <= 1: - # this is the average dipole (across trials) - # it's also the ONLY dipole when running a single trial - self.axdipole.plot(ddat['dpl'][:,0],ddat['dpl'][:,1],'k',linewidth=self.gui.linewidth+1) - yl[0] = min(yl[0],ddat['dpl'][sidx:eidx,1].min()) - yl[1] = max(yl[1],ddat['dpl'][sidx:eidx,1].max()) - else: - if 'dpl' in optdat: - # show optimized dipole as gray line - optdpl = optdat['dpl'] - self.axdipole.plot(optdpl[:,0],optdpl[:,1],'k',color='gray',linewidth=self.gui.linewidth+1) - yl[0] = min(yl[0],optdpl[sidx:eidx,1].min()) - yl[1] = max(yl[1],optdpl[sidx:eidx,1].max()) - - if 'dpl' in initial_ddat: - # show initial dipole in dotted black line - self.axdipole.plot(initial_ddat['dpl'][:,0],initial_ddat['dpl'][:,1],'--',color='black',linewidth=self.gui.linewidth) - yl[0] = min(yl[0],initial_ddat['dpl'][sidx:eidx,1].min()) - yl[1] = max(yl[1],initial_ddat['dpl'][sidx:eidx,1].max()) - - scalefctr = float(self.params['dipole_scalefctr']) - - # get the number of pyramidal neurons used in the simulation - try: - x = self.params['N_pyr_x'] - y = self.params['N_pyr_y'] - num_pyr = int(x * y * 2) - except KeyError: - num_pyr = 0 - - NEstPyr = int(num_pyr * scalefctr) - - if NEstPyr > 0: - self.axdipole.set_ylabel(r'Dipole (nAm $\times$ ' + \ - str(scalefctr) + \ - ')\nFrom Estimated ' + \ - str(NEstPyr) + ' Cells', - fontsize=fontsize) - else: - self.axdipole.set_ylabel(r'Dipole (nAm $\times$ ' + \ - str(scalefctr) + \ - ')\n', fontsize=fontsize) - self.axdipole.set_xlim(xl); self.axdipole.set_ylim(yl) - - if DrawSpec: - gRow = 6 - self.axspec = self.figure.add_subplot(self.G[gRow:10,0]) - cax = self.axspec.imshow(ds['TFR'], extent=(ds['time'][0], - ds['time'][-1], - ds['freq'][-1], - ds['freq'][0]), - aspect='auto', origin='upper', - cmap=plt.get_cmap(self.params['spec_cmap'])) - self.axspec.set_ylabel('Frequency (Hz)', fontsize=fontsize) - self.axspec.set_xlabel('Time (ms)', fontsize=fontsize) - self.axspec.set_xlim(xl) - self.axspec.set_ylim(ds['freq'][-1], ds['freq'][0]) - cbaxes = self.figure.add_axes([0.6, 0.49, 0.3, 0.005]) - plt.colorbar(cax, cax = cbaxes, orientation='horizontal') # horizontal to save space - else: - self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) - - def plotarrows (self): - # run after scales have been updated - xl = self.axdipole.get_xlim() - yl = self.axdipole.get_ylim() - - dinty = self.getInputs() - if dinty['Evoked']: - self.drawEVInputTimes(self.axdipole,yl,0.1,(xl[1]-xl[0])*.02,(yl[1]-yl[0])*.02)#15.0) - - def plot (self, recalcErr=True): - self.clearaxes() - plt.close(self.figure) - self.figure.clf() - self.axdipole = None - - self.plotsimdat() # creates self.axdipole - self.plotextdat(recalcErr) - self.plotarrows() - - self.draw() + # skip for optimization + # plot average dipoles from prior simulations + for paramfn in self.sim_data._sim_data.keys(): + old_data = self.sim_data._sim_data[paramfn]['data']['avg_dpl'] + if old_data is None: + continue + times = old_data[:, 0] + old_dpl = old_data[:, 1] + self.axdipole.plot(times, old_dpl, '--', color='black', + linewidth=self.linewidth) + + sim_data = self.sim_data._sim_data[self.paramfn]['data'] + # plot dipoles from individual trials + if self.params['N_trials'] > 1 and drawindivdpl and \ + len(sim_data['dpl_trials']) > 0: + for dpltrial in sim_data['dpl_trials']: + self.axdipole.plot(dpltrial[:, 0], dpltrial[:, 1], + color='gray', + linewidth=self.linewidth) + yl[0] = min(yl[0], dpltrial[sidx:eidx, 1].min()) + yl[1] = max(yl[1], dpltrial[sidx:eidx, 1].max()) + + if drawavgdpl or self.params['N_trials'] <= 1: + # this is the average dipole (across trials) + # it's also the ONLY dipole when running a single trial + self.axdipole.plot(dpl[:, 0], dpl[:, 1], 'k', + linewidth=self.linewidth + 1) + yl[0] = min(yl[0], dpl[sidx:eidx, 1].min()) + yl[1] = max(yl[1], dpl[sidx:eidx, 1].max()) + else: + if self.sim_data._opt_data['avg_dpl'] is not None: + # show optimized dipole as gray line + optdpl = self.sim_data._opt_data['avg_dpl'] + self.axdipole.plot(optdpl[:, 0], optdpl[:, 1], 'k', + color='gray', + linewidth=self.linewidth + 1) + yl[0] = min(yl[0], optdpl[sidx:eidx, 1].min()) + yl[1] = max(yl[1], optdpl[sidx:eidx, 1].max()) + + if self.sim_data._opt_data['initial_dpl'] is not None: + # show initial dipole in dotted black line + plot_data = self.sim_data._opt_data['initial_dpl'] + times = plot_data[:, 0] + plot_dpl = plot_data[:, 0] + self.axdipole.plot(times, plot_dpl, '--', color='black', + linewidth=self.linewidth) + dpl = self.sim_data._opt_data['initial_dpl'][sidx:eidx, 1] + yl[0] = min(yl[0], dpl.min()) + yl[1] = max(yl[1], dpl.max()) + + scalefctr = float(self.params['dipole_scalefctr']) + + # get the number of pyramidal neurons used in the simulation + try: + x = self.params['N_pyr_x'] + y = self.params['N_pyr_y'] + num_pyr = int(x * y * 2) + except KeyError: + num_pyr = 0 + + NEstPyr = int(num_pyr * scalefctr) + + if NEstPyr > 0: + self.axdipole.set_ylabel(r'Dipole (nAm $\times$ ' + + str(scalefctr) + + ')\nFrom Estimated ' + + str(NEstPyr) + ' Cells', + fontsize=fontsize) + else: + self.axdipole.set_ylabel(r'Dipole (nAm $\times$ ' + + str(scalefctr) + + ')\n', fontsize=fontsize) + self.axdipole.set_xlim(xl) + self.axdipole.set_ylim(yl) + + if DrawSpec: + gRow = 6 + self.axspec = self.figure.add_subplot(self.G[gRow:10, 0]) + cax = self.axspec.imshow(ds['TFR'], extent=(ds['time'][0], + ds['time'][-1], + ds['freq'][-1], + ds['freq'][0]), + aspect='auto', origin='upper', + cmap=plt.get_cmap( + self.params['spec_cmap'])) + self.axspec.set_ylabel('Frequency (Hz)', fontsize=fontsize) + self.axspec.set_xlabel('Time (ms)', fontsize=fontsize) + self.axspec.set_xlim(xl) + self.axspec.set_ylim(ds['freq'][-1], ds['freq'][0]) + cbaxes = self.figure.add_axes([0.6, 0.49, 0.3, 0.005]) + # plot colorbar horizontally to save space + plt.colorbar(cax, cax=cbaxes, orientation='horizontal') + else: + self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) + + def plotarrows(self): + # run after scales have been updated + xl = self.axdipole.get_xlim() + yl = self.axdipole.get_ylim() + + dinty = get_inputs(self.params) + if dinty['Evoked']: + self.drawEVInputTimes(self.axdipole, yl, 0.1, + (xl[1] - xl[0]) * .02, + (yl[1] - yl[0]) * .02) + + def plot(self, recalcErr=True): + self.clearaxes() + plt.close(self.figure) + self.figure.clf() + self.axdipole = None + + self.plotsimdat() # creates self.axdipole + self.plotextdat(recalcErr) + self.plotarrows() + + self.draw() diff --git a/hnn/visrast.py b/hnn/visrast.py index fca67e42b..5d87e654e 100644 --- a/hnn/visrast.py +++ b/hnn/visrast.py @@ -288,7 +288,6 @@ def plot (self): class SpikeGUI (QMainWindow): def __init__ (self): - global dfile, ddat, paramf super().__init__() self.initUI() diff --git a/hnn/visspec.py b/hnn/visspec.py index 6f6dcd4f8..eb493ca0c 100644 --- a/hnn/visspec.py +++ b/hnn/visspec.py @@ -26,7 +26,6 @@ # assumes column 0 is time, rest of columns are time-series def extractspec (dat, fmax=40.0): - global ntrial #print('extractspec',dat.shape) lspec = [] tvec = dat[:,0] diff --git a/hnn/visvolt.py b/hnn/visvolt.py index 4ac07c26d..2d8edd1b2 100644 --- a/hnn/visvolt.py +++ b/hnn/visvolt.py @@ -106,8 +106,6 @@ def drawvolt (self, dvolt, fig, G, sz=8, ltextra=''): return lax def plot (self): - global params - if self.index == 0: if ntrial == 1: dvolt = pickle.load(open(voltpath,'rb')) @@ -122,7 +120,7 @@ def plot (self): class VoltGUI (QMainWindow): def __init__ (self): - global dfile, ddat, paramf, fontsize + global fontsize super().__init__() self.fontsize = fontsize self.linewidth = plt.rcParams['lines.linewidth'] = 1 From 03edfb524924a8a5b0d02f61c320f526964f7173 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 28 Nov 2020 06:10:38 -0500 Subject: [PATCH 056/107] MAINT: placeholders for tooltips --- hnn/hnn_qt5.py | 2 ++ hnn/qt_evoked.py | 8 ++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 6bff6e3d7..a860e0dcf 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -329,6 +329,8 @@ def __init__(self, parent, distal, evwin, rhythwin): self.evwin = evwin self.rhythwin = rhythwin self.initUI() + # TODO: add back tooltips + # self.addtips() def initUI(self): self.layout = QVBoxLayout(self) diff --git a/hnn/qt_evoked.py b/hnn/qt_evoked.py index 9da2f08fe..c1fae8424 100644 --- a/hnn/qt_evoked.py +++ b/hnn/qt_evoked.py @@ -77,6 +77,8 @@ def __init__(self): self.dqline = {} # for translating model variable name to more human-readable form self.dtransvar = {} + # TODO: add back tooltips + # self.addtips() def transvar(self, k): if k in self.dtransvar: @@ -198,12 +200,6 @@ def __init__ (self, parent, din): self.initUI() self.setfromdin(din) - # TODO: add back tooltips - # def addtips (self): - # for ktip in dconf.keys(): - # if ktip in self.dqline: - # self.dqline[ktip].setToolTip(dconf[ktip]) - def transvar (self,k): if k in self.dtransvar: return self.dtransvar[k] return k From ab7ba1cda8538cd50934c884c01192e79f7b2e06 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 29 Nov 2020 16:11:59 -0500 Subject: [PATCH 057/107] MAINT: reimplement saving figures Large change that refactors plotting functions in simdat.py to reuse code when plotting in the GUI and when creating a plot to save to a .png. Only two types of figures are saved: dpl and spec. Other figures can be added in the future. Another limitation is displaying poisson inputs along with evprox and evdist; it's not supported right now. The spectrograms are configured to cut of the first 50ms to avoid edge effects. This produces not so visually pleasing figures when saving. In the GUI, the dipole also loses the first 50ms! A better way of choosing the time range for the spectograms needs to be devices. --- hnn/hnn_qt5.py | 17 +- hnn/paramrw.py | 244 +++++--------------- hnn/qt_evoked.py | 210 +++++++++++++++-- hnn/run.py | 74 +----- hnn/simdat.py | 583 ++++++++++++++++++++++++++++++----------------- hnn/spikefn.py | 19 +- hnn/visdipole.py | 4 +- hnn/visrast.py | 2 +- 8 files changed, 671 insertions(+), 482 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index a860e0dcf..e9f620646 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -1501,9 +1501,6 @@ def distribsubwin (self): if cury >= sh: cury = cury = 0 def updateDatCanv(self, params): - # update the simulation data and canvas - # self.sim_data.update_sim_data(self.paramfn, params) - # now update the GUI components to reflect the param file selected self.baseparamwin.updateDispParam(params) self.initSimCanvas() # recreate canvas @@ -2023,6 +2020,11 @@ def startsim (self, ncore): self.statusBar().showMessage("Running simulation. . .") + # check that valid number of trials was given + if 'N_trials' not in params or params['N_trials'] == 0: + print("Warning: invalid configured number of trials. Setting to 1.") + params['N_trials'] = 1 + self.runthread = RunSimThread(ncore, params, self.param_signal, self.done_signal, self.waitsimwin, self.baseparamwin, @@ -2069,6 +2071,15 @@ def done (self, optMode, except_msg): if failed: QMessageBox.critical(self, "Failed!", msg + "using " + self.baseparamwin.paramfn + '. Check simulation log or console for error messages') else: + self.sim_data.update_sim_data(self.baseparamwin.paramfn, + self.baseparamwin.params) + + if self.baseparamwin.params['save_figs']: + self.sim_data.save_dipole_with_hist(self.baseparamwin.paramfn, + self.baseparamwin.params) + self.sim_data.save_spec_with_hist(self.baseparamwin.paramfn, + self.baseparamwin.params) + data_dir = os.path.join(get_output_dir(), 'data') sim_dir = os.path.join(data_dir, self.baseparamwin.params['sim_prefix']) QMessageBox.information(self, "Done!", msg + "using " + self.baseparamwin.paramfn + '. Saved data/figures in: ' + sim_dir) diff --git a/hnn/paramrw.py b/hnn/paramrw.py index 3c15b1c3a..d46af40ed 100644 --- a/hnn/paramrw.py +++ b/hnn/paramrw.py @@ -9,8 +9,6 @@ import numpy as np import itertools as it -from hnn_core import read_params - def get_output_dir(): """Return the base directory for storing output files""" @@ -21,6 +19,69 @@ def get_output_dir(): return os.path.join(base_dir, 'hnn_out') + +def get_fname(sim_dir, key, trial=0, ntrial=1): + """Build the file names using the old HNN scheme + + Parameters + ---------- + sim_dir : str + The base data directory where simulation result files are stored + key : str + A string describing the type of file (HNN specific) + trial : int | None + Trial number for which to generate files (separate files per trial). + If None is given, then trial number 0 is assumed. + ntrial : int | None + The total number of trials that are part of this simulation. If None + is given, then a total of 1 trial is assumed. + + Returns + ---------- + fname : str + A string with the correct filename + """ + + datatypes = {'rawspk': ('spk', '.txt'), + 'rawdpl': ('rawdpl', '.txt'), + 'normdpl': ('dpl', '.txt'), + 'rawcurrent': ('i', '.txt'), + 'rawspec': ('rawspec', '.npz'), + 'rawspeccurrent': ('speci', '.npz'), + 'avgdpl': ('dplavg', '.txt'), + 'avgspec': ('specavg', '.npz'), + 'figavgdpl': ('dplavg', '.png'), + 'figavgspec': ('specavg', '.png'), + 'figdpl': ('dpl', '.png'), + 'figspec': ('spec', '.png'), + 'figspk': ('spk', '.png'), + 'param': ('param', '.txt'), + 'vsoma': ('vsoma', '.pkl')} + + if ntrial == 1 or key == 'param': + # param file currently identical for all trials + return os.path.join(sim_dir, datatypes[key][0] + datatypes[key][1]) + else: + return os.path.join(sim_dir, datatypes[key][0] + '_' + str(trial) + + datatypes[key][1]) + + +def get_inputs(params): + """ get a dictionary of input types used in simulation + with distal/proximal specificity for evoked,ongoing inputs + """ + + dinty = {'evoked': usingEvokedInputs(params), + 'ongoing': usingOngoingInputs(params), + 'tonic': usingTonicInputs(params), + 'pois': usingPoissonInputs(params), + 'evdist': usingEvokedInputs(params, lsuffty=['_evdist_']), + 'evprox': usingEvokedInputs(params, lsuffty=['_evprox_']), + 'dist': usingOngoingInputs(params, lty=['_dist']), + 'prox': usingOngoingInputs(params, lty=['_prox'])} + + return dinty + # Cleans input files def clean_lines (file): with open(file) as f_in: @@ -187,182 +248,3 @@ def write_gids_param(fparam, gid_list): else: f.write('[]') f.write('\n') - -def consolidate_chunks(input_dict): - # MOVE to hnn-core - # get a list of sorted chunks - sorted_inputs = sorted(input_dict.items(), key=lambda x: x[1]['user_start']) - - consolidated_chunks = [] - for one_input in sorted_inputs: - if not 'opt_start' in one_input[1]: - continue - - # extract info from sorted list - input_dict = {'inputs': [one_input[0]], - 'chunk_start': one_input[1]['user_start'], - 'chunk_end': one_input[1]['user_end'], - 'opt_start': one_input[1]['opt_start'], - 'opt_end': one_input[1]['opt_end'], - 'weights': one_input[1]['weights'], - } - - if (len(consolidated_chunks) > 0) and \ - (input_dict['chunk_start'] <= consolidated_chunks[-1]['chunk_end']): - # update previous chunk - consolidated_chunks[-1]['inputs'].extend(input_dict['inputs']) - consolidated_chunks[-1]['chunk_end'] = input_dict['chunk_end'] - consolidated_chunks[-1]['opt_end'] = max(consolidated_chunks[-1]['opt_end'], input_dict['opt_end']) - # average the weights - consolidated_chunks[-1]['weights'] = (consolidated_chunks[-1]['weights'] + one_input[1]['weights'])/2 - else: - # new chunk - consolidated_chunks.append(input_dict) - - return consolidated_chunks - -def combine_chunks(input_chunks): - # MOVE to hnn-core - # Used for creating the opt params of the last step with all inputs - - final_chunk = {'inputs': [], - 'opt_start': 0.0, - 'opt_end': 0.0, - 'chunk_start': 0.0, - 'chunk_end': 0.0} - - for evinput in input_chunks: - final_chunk['inputs'].extend(evinput['inputs']) - if evinput['opt_end'] > final_chunk['opt_end']: - final_chunk['opt_end'] = evinput['opt_end'] - if evinput['chunk_end'] > final_chunk['chunk_end']: - final_chunk['chunk_end'] = evinput['chunk_end'] - - # wRMSE with weights of 1's is the same as regular RMSE. - final_chunk['weights'] = np.ones(len(input_chunks[-1]['weights'])) - return final_chunk - -def chunk_evinputs(opt_params, sim_tstop, sim_dt): - # MOVE to hnn-core - """ - Take dictionary (opt_params) sorted by input and - return a sorted list of dictionaries describing - chunks with inputs consolidated as determined the - range between 'user_start' and 'user_end'. - - The keys of the chunks in chunk_list dictionary - returned are: - 'weights' - 'chunk_start' - 'chunk_end' - 'opt_start' - 'opt_end' - """ - - import re - import scipy.stats as stats - from math import ceil, floor - - num_step = ceil(sim_tstop / sim_dt) + 1 - times = np.linspace(0, sim_tstop, num_step) - - # input_dict will be passed to consolidate_chunks, so it has - # keys 'user_start' and 'user_end' instead of chunk_start and - # 'chunk_start' that will be returned in the dicts returned - # in chunk_list - input_dict = {} - cdfs = {} - - - for input_name in opt_params.keys(): - if opt_params[input_name]['user_start'] > sim_tstop or \ - opt_params[input_name]['user_end'] < 0: - # can't optimize over this input - continue - - # calculate cdf using start time (minival of optimization range) - cdf = stats.norm.cdf(times, opt_params[input_name]['user_start'], - opt_params[input_name]['sigma']) - cdfs[input_name] = cdf.copy() - - for input_name in opt_params.keys(): - if opt_params[input_name]['user_start'] > sim_tstop or \ - opt_params[input_name]['user_end'] < 0: - # can't optimize over this input - continue - input_dict[input_name] = {'weights': cdfs[input_name].copy(), - 'user_start': opt_params[input_name]['user_start'], - 'user_end': opt_params[input_name]['user_end']} - - for other_input in opt_params: - if opt_params[other_input]['user_start'] > sim_tstop or \ - opt_params[other_input]['user_end'] < 0: - # not optimizing over that input - continue - if input_name == other_input: - # don't subtract our own cdf(s) - continue - if opt_params[other_input]['mean'] < \ - opt_params[input_name]['mean']: - # check ordering to only use inputs after us - continue - else: - decay_factor = opt_params[input_name]['decay_multiplier']*(opt_params[other_input]['mean'] - \ - opt_params[input_name]['mean']) / \ - sim_tstop - input_dict[input_name]['weights'] -= cdfs[other_input] * decay_factor - - # weights should not drop below 0 - input_dict[input_name]['weights'] = np.clip(input_dict[input_name]['weights'], a_min=0, a_max=None) - - # start and stop optimization where the weights are insignificant - good_indices = np.where( input_dict[input_name]['weights'] > 0.01) - if len(good_indices[0]) > 0: - input_dict[input_name]['opt_start'] = min(opt_params[input_name]['user_start'], times[good_indices][0]) - input_dict[input_name]['opt_end'] = max(opt_params[input_name]['user_end'], times[good_indices][-1]) - else: - input_dict[input_name]['opt_start'] = opt_params[other_input]['user_start'] - input_dict[input_name]['opt_end'] = opt_params[other_input]['user_end'] - - # convert to multiples of dt - input_dict[input_name]['opt_start'] = floor(input_dict[input_name]['opt_start']/sim_dt)*sim_dt - input_dict[input_name]['opt_end'] = ceil(input_dict[input_name]['opt_end']/sim_dt)*sim_dt - - # combined chunks that have overlapping ranges - # opt_params is a dict, turn into a list - chunk_list = consolidate_chunks(input_dict) - - # add one last chunk to the end - if len(chunk_list) > 1: - chunk_list.append(combine_chunks(chunk_list)) - - return chunk_list - -def get_inputs (params): - # MOVE - import re - input_list = [] - - # first pass through all params to get mu and sigma for each - for k in params.keys(): - input_mu = re.match('^t_ev(prox|dist)_([0-9]+)', k) - if input_mu: - id_str = 'ev' + input_mu.group(1) + '_' + input_mu.group(2) - input_list.append(id_str) - - return input_list - -def trans_input (input_var): - # MOVE - import re - - input_str = input_var - input_match = re.match('^ev(prox|dist)_([0-9]+)', input_var) - if input_match: - if input_match.group(1) == "prox": - input_str = 'Proximal ' + input_match.group(2) - if input_match.group(1) == "dist": - input_str = 'Distal ' + input_match.group(2) - - return input_str - diff --git a/hnn/qt_evoked.py b/hnn/qt_evoked.py index c1fae8424..f7c78d8de 100644 --- a/hnn/qt_evoked.py +++ b/hnn/qt_evoked.py @@ -4,6 +4,7 @@ # Blake Caldwell import os +import numpy as np from math import isclose from copy import deepcopy @@ -16,12 +17,187 @@ from .qt_lib import QRangeSlider, MyLineEdit, ClickLabel, setscalegeom from .qt_lib import lookupresource -from .paramrw import chunk_evinputs, get_inputs, trans_input, countEvokedInputs +from .paramrw import countEvokedInputs decay_multiplier = 1.6 +def _consolidate_chunks(input_dict): + # MOVE to hnn-core + # get a list of sorted chunks + sorted_inputs = sorted(input_dict.items(), key=lambda x: x[1]['user_start']) -def format_range_str(value): + consolidated_chunks = [] + for one_input in sorted_inputs: + if not 'opt_start' in one_input[1]: + continue + + # extract info from sorted list + input_dict = {'inputs': [one_input[0]], + 'chunk_start': one_input[1]['user_start'], + 'chunk_end': one_input[1]['user_end'], + 'opt_start': one_input[1]['opt_start'], + 'opt_end': one_input[1]['opt_end'], + 'weights': one_input[1]['weights'], + } + + if (len(consolidated_chunks) > 0) and \ + (input_dict['chunk_start'] <= consolidated_chunks[-1]['chunk_end']): + # update previous chunk + consolidated_chunks[-1]['inputs'].extend(input_dict['inputs']) + consolidated_chunks[-1]['chunk_end'] = input_dict['chunk_end'] + consolidated_chunks[-1]['opt_end'] = max(consolidated_chunks[-1]['opt_end'], input_dict['opt_end']) + # average the weights + consolidated_chunks[-1]['weights'] = (consolidated_chunks[-1]['weights'] + one_input[1]['weights'])/2 + else: + # new chunk + consolidated_chunks.append(input_dict) + + return consolidated_chunks + +def _combine_chunks(input_chunks): + # MOVE to hnn-core + # Used for creating the opt params of the last step with all inputs + + final_chunk = {'inputs': [], + 'opt_start': 0.0, + 'opt_end': 0.0, + 'chunk_start': 0.0, + 'chunk_end': 0.0} + + for evinput in input_chunks: + final_chunk['inputs'].extend(evinput['inputs']) + if evinput['opt_end'] > final_chunk['opt_end']: + final_chunk['opt_end'] = evinput['opt_end'] + if evinput['chunk_end'] > final_chunk['chunk_end']: + final_chunk['chunk_end'] = evinput['chunk_end'] + + # wRMSE with weights of 1's is the same as regular RMSE. + final_chunk['weights'] = np.ones(len(input_chunks[-1]['weights'])) + return final_chunk + +def _chunk_evinputs(opt_params, sim_tstop, sim_dt): + # MOVE to hnn-core + """ + Take dictionary (opt_params) sorted by input and + return a sorted list of dictionaries describing + chunks with inputs consolidated as determined the + range between 'user_start' and 'user_end'. + + The keys of the chunks in chunk_list dictionary + returned are: + 'weights' + 'chunk_start' + 'chunk_end' + 'opt_start' + 'opt_end' + """ + + import re + import scipy.stats as stats + from math import ceil, floor + + num_step = ceil(sim_tstop / sim_dt) + 1 + times = np.linspace(0, sim_tstop, num_step) + + # input_dict will be passed to consolidate_chunks, so it has + # keys 'user_start' and 'user_end' instead of chunk_start and + # 'chunk_start' that will be returned in the dicts returned + # in chunk_list + input_dict = {} + cdfs = {} + + + for input_name in opt_params.keys(): + if opt_params[input_name]['user_start'] > sim_tstop or \ + opt_params[input_name]['user_end'] < 0: + # can't optimize over this input + continue + + # calculate cdf using start time (minival of optimization range) + cdf = stats.norm.cdf(times, opt_params[input_name]['user_start'], + opt_params[input_name]['sigma']) + cdfs[input_name] = cdf.copy() + + for input_name in opt_params.keys(): + if opt_params[input_name]['user_start'] > sim_tstop or \ + opt_params[input_name]['user_end'] < 0: + # can't optimize over this input + continue + input_dict[input_name] = {'weights': cdfs[input_name].copy(), + 'user_start': opt_params[input_name]['user_start'], + 'user_end': opt_params[input_name]['user_end']} + + for other_input in opt_params: + if opt_params[other_input]['user_start'] > sim_tstop or \ + opt_params[other_input]['user_end'] < 0: + # not optimizing over that input + continue + if input_name == other_input: + # don't subtract our own cdf(s) + continue + if opt_params[other_input]['mean'] < \ + opt_params[input_name]['mean']: + # check ordering to only use inputs after us + continue + else: + decay_factor = opt_params[input_name]['decay_multiplier']*(opt_params[other_input]['mean'] - \ + opt_params[input_name]['mean']) / \ + sim_tstop + input_dict[input_name]['weights'] -= cdfs[other_input] * decay_factor + + # weights should not drop below 0 + input_dict[input_name]['weights'] = np.clip(input_dict[input_name]['weights'], a_min=0, a_max=None) + + # start and stop optimization where the weights are insignificant + good_indices = np.where( input_dict[input_name]['weights'] > 0.01) + if len(good_indices[0]) > 0: + input_dict[input_name]['opt_start'] = min(opt_params[input_name]['user_start'], times[good_indices][0]) + input_dict[input_name]['opt_end'] = max(opt_params[input_name]['user_end'], times[good_indices][-1]) + else: + input_dict[input_name]['opt_start'] = opt_params[other_input]['user_start'] + input_dict[input_name]['opt_end'] = opt_params[other_input]['user_end'] + + # convert to multiples of dt + input_dict[input_name]['opt_start'] = floor(input_dict[input_name]['opt_start']/sim_dt)*sim_dt + input_dict[input_name]['opt_end'] = ceil(input_dict[input_name]['opt_end']/sim_dt)*sim_dt + + # combined chunks that have overlapping ranges + # opt_params is a dict, turn into a list + chunk_list = _consolidate_chunks(input_dict) + + # add one last chunk to the end + if len(chunk_list) > 1: + chunk_list.append(_combine_chunks(chunk_list)) + + return chunk_list + +def _get_param_inputs(params): + import re + input_list = [] + + # first pass through all params to get mu and sigma for each + for k in params.keys(): + input_mu = re.match('^t_ev(prox|dist)_([0-9]+)', k) + if input_mu: + id_str = 'ev' + input_mu.group(1) + '_' + input_mu.group(2) + input_list.append(id_str) + + return input_list + +def _trans_input(input_var): + import re + + input_str = input_var + input_match = re.match('^ev(prox|dist)_([0-9]+)', input_var) + if input_match: + if input_match.group(1) == "prox": + input_str = 'Proximal ' + input_match.group(2) + if input_match.group(1) == "dist": + input_str = 'Distal ' + input_match.group(2) + + return input_str + +def _format_range_str(value): if value == 0: value_str = "0.000" elif value < 0.1: @@ -32,7 +208,7 @@ def format_range_str(value): return value_str -def get_prox_dict(nprox): +def _get_prox_dict(nprox): # evprox feed strength dprox = { @@ -51,7 +227,7 @@ def get_prox_dict(nprox): return dprox -def get_dist_dict(ndist): +def _get_dist_dict(ndist): # evdist feed strength ddist = { @@ -399,7 +575,7 @@ def makePixLabel (self,fn): def addProx (self): self.nprox += 1 # starts at 1 - dprox = get_prox_dict(self.nprox) + dprox = _get_prox_dict(self.nprox) self.ld.append(dprox) self.addtransvarfromdict(dprox) self.addFormToTab(dprox, self.addTab('Proximal ' + str(self.nprox))) @@ -411,7 +587,7 @@ def addProx (self): def addDist (self): self.ndist += 1 - ddist = get_dist_dict(self.ndist) + ddist = _get_dist_dict(self.ndist) self.ld.append(ddist) self.addtransvarfromdict(ddist) self.addFormToTab(ddist,self.addTab('Distal ' + str(self.ndist))) @@ -535,7 +711,7 @@ def addTab (self,id_str): tab = QWidget() self.ltabs.append(tab) - name_str = trans_input(id_str) + name_str = _trans_input(id_str) self.tabs.addTab(tab, name_str) tab_index = len(self.ltabs)-1 @@ -686,7 +862,7 @@ def addGridToTab (self, d, tab): def addProx (self): self.nprox += 1 - dprox = get_prox_dict(self.nprox) + dprox = _get_prox_dict(self.nprox) self.ld.append(dprox) self.addtransvarfromdict(dprox) tab = self.addTab('evprox_' + str(self.nprox)) @@ -694,7 +870,7 @@ def addProx (self): def addDist (self): self.ndist += 1 - ddist = get_dist_dict(self.ndist) + ddist = _get_dist_dict(self.ndist) self.ld.append(ddist) self.addtransvarfromdict(ddist) tab = self.addTab('evdist_' + str(self.ndist)) @@ -802,7 +978,7 @@ def updateRange(self, label, save_slider=True): self.dqrange_slider[label].setRange(range_min, range_max) if range_min == range_max: - self.dqrange_label[label].setText(format_range_str(range_min)) # use the exact value + self.dqrange_label[label].setText(_format_range_str(range_min)) # use the exact value self.dqrange_label[label].setEnabled(False) # uncheck because invalid range self.dqchkbox[label].setChecked(False) @@ -810,9 +986,9 @@ def updateRange(self, label, save_slider=True): self.dqrange_slider[label].setEnabled(False) self.changeParamEnabledStatus(label, False) else: - self.dqrange_label[label].setText(format_range_str(range_min) + + self.dqrange_label[label].setText(_format_range_str(range_min) + " - " + - format_range_str(range_max)) + _format_range_str(range_max)) if self.dqrange_label[label].sizeHint().width() > max_width: max_width = self.dqrange_label[label].sizeHint().width() + 15 @@ -941,7 +1117,7 @@ def clean_opt_grid(self): def rebuildOptStepInfo(self): # split chunks from paramter file - self.chunk_list = chunk_evinputs(self.opt_params, self.simlength, self.sim_dt) + self.chunk_list = _chunk_evinputs(self.opt_params, self.simlength, self.sim_dt) if len(self.chunk_list) == 0: self.clean_opt_grid() @@ -971,7 +1147,7 @@ def rebuildOptStepInfo(self): inputs = [] for input_name in chunk['inputs']: all_inputs.append(input_name) - inputs.append(trans_input(input_name)) + inputs.append(_trans_input(input_name)) if chunk_index >= self.old_num_steps: qlabel = QLabel("Optimization step %d:"%(chunk_index+1)) @@ -1193,8 +1369,8 @@ def updateRangeFromSlider(self, label, range_min, range_max): self.dqrange_min[label] = range_min self.dqrange_max[label] = range_max - self.dqrange_label[label].setText(format_range_str(range_min) + " - " + - format_range_str(range_max)) + self.dqrange_label[label].setText(_format_range_str(range_min) + " - " + + _format_range_str(range_max)) self.opt_params[tab_name]['ranges'][label]['minval'] = range_min self.opt_params[tab_name]['ranges'][label]['maxval'] = range_max @@ -1222,7 +1398,7 @@ def setfromdin (self,din): self.dtab_idx = {} self.dtab_names = {} - for evinput in get_inputs(din): + for evinput in _get_param_inputs(din): if 'evprox_' in evinput: self.addProx() elif 'evdist_' in evinput: diff --git a/hnn/run.py b/hnn/run.py index 2eb2ca968..d95b66640 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -19,57 +19,11 @@ import nlopt from psutil import wait_procs, process_iter, NoSuchProcess -from .paramrw import usingOngoingInputs, write_gids_param +from .paramrw import usingOngoingInputs, write_gids_param, get_fname from .specfn import analysis_simp from .paramrw import write_legacy_paramf, get_output_dir -def get_fname(sim_dir, key, trial=0, ntrial=1): - """Build the file names using the old HNN scheme - - Parameters - ---------- - sim_dir : str - The base data directory where simulation result files are stored - key : str - A string describing the type of file (HNN specific) - trial : int | None - Trial number for which to generate files (separate files per trial). - If None is given, then trial number 0 is assumed. - ntrial : int | None - The total number of trials that are part of this simulation. If None - is given, then a total of 1 trial is assumed. - - Returns - ---------- - fname : str - A string with the correct filename - """ - - datatypes = {'rawspk': ('spk', '.txt'), - 'rawdpl': ('rawdpl', '.txt'), - 'normdpl': ('dpl', '.txt'), - 'rawcurrent': ('i', '.txt'), - 'rawspec': ('rawspec', '.npz'), - 'rawspeccurrent': ('speci', '.npz'), - 'avgdpl': ('dplavg', '.txt'), - 'avgspec': ('specavg', '.npz'), - 'figavgdpl': ('dplavg', '.png'), - 'figavgspec': ('specavg', '.png'), - 'figdpl': ('dpl', '.png'), - 'figspec': ('spec', '.png'), - 'figspk': ('spk', '.png'), - 'param': ('param', '.txt'), - 'vsoma': ('vsoma', '.pkl')} - - if ntrial == 1 or key == 'param': - # param file currently identical for all trials - return op.join(sim_dir, datatypes[key][0] + datatypes[key][1]) - else: - return op.join(sim_dir, datatypes[key][0] + '_' + str(trial) + - datatypes[key][1]) - - class TextSignal (QObject): """for passing text""" tsig = pyqtSignal(str) @@ -177,21 +131,21 @@ def simulate(params, n_procs=None): # TODO: Can below be removed if spk.txt is new hnn-core format with 3 # columns (including spike type)? - write_gids_param(get_fname(sim_dir, 'param'), net.gid_dict) + write_gids_param(get_fname(sim_dir, 'param'), net.gid_ranges) # save spikes by trial glob = op.join(sim_dir, 'spk_%d.txt') - net.spikes.write(glob) + net.cell_response.write(glob) spike_fn = get_fname(sim_dir, 'rawspk') # save spikes from the individual trials in a single file with open(spike_fn, 'w') as fspkout: - for trial_idx in range(len(net.spikes.times)): - for spike_idx in range(len(net.spikes.times[trial_idx])): + for trial_idx in range(len(net.cell_response.spike_times)): + for spike_idx in range(len(net.cell_response.spike_times[trial_idx])): fspkout.write('{:.3f}\t{}\t{}\n'.format( - net.spikes.times[trial_idx][spike_idx], - int(net.spikes.gids[trial_idx][spike_idx]), - net.spikes.types[trial_idx][spike_idx])) + net.cell_response.spike_times[trial_idx][spike_idx], + int(net.cell_response.spike_gids[trial_idx][spike_idx]), + net.cell_response.spike_types[trial_idx][spike_idx])) # save dipole for each trial and perform spectral analysis for trial_idx, dpl in enumerate(dpls): @@ -210,21 +164,15 @@ def simulate(params, n_procs=None): get_fname(sim_dir, 'rawspec', trial_idx, params['N_trials'])) - # TODO: the savefigs functionality is quite complicated and rewriting - # from scratch in hnn-core is probably a better option that will allow - # deprecating the large amount of legacy code - - # if params['save_figs']: - # savefigs(params) # save output figures - if params['save_spec_data'] or usingOngoingInputs(params): # save average spectrogram from individual trials in a single file dspecin = {} dout = {} lf = [] - for i in range(ntrial): - lf.append(op.join(sim_dir, 'rawspec_' + str(i) + '.npz')) + for _ in range(ntrial): + f_spec = get_fname(sim_dir, 'rawspec', trial_idx, ntrial) + lf.append(f_spec) for f in lf: dspecin[f] = np.load(f) diff --git a/hnn/simdat.py b/hnn/simdat.py index 31bc532b7..136a5adef 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -2,6 +2,7 @@ from PyQt5.QtWidgets import QSizePolicy from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure +import matplotlib as mpl import matplotlib.pyplot as plt import matplotlib.patches as mpatches import matplotlib.gridspec as gridspec @@ -11,10 +12,11 @@ from scipy import signal from hnn_core import read_spikes +from hnn_core.dipole import Dipole from .spikefn import ExtInputs -from .paramrw import usingOngoingInputs, usingEvokedInputs, usingPoissonInputs -from .paramrw import usingTonicInputs, countEvokedInputs, get_output_dir +from .paramrw import get_output_dir, get_fname, get_inputs +from .paramrw import countEvokedInputs from .qt_lib import getscreengeom drawindivdpl = 1 @@ -32,34 +34,102 @@ def read_dpltrials(sim_dir): try: dpl_trial = np.loadtxt(dipole_fn) except OSError: - print('Warning: could not read file:', dipole_fn) + if os.path.exists(sim_dir): + print('Warning: could not read file:', dipole_fn) except ValueError: - print('Warning: could not read file:', dipole_fn) + if os.path.exists(sim_dir): + print('Warning: could not read file:', dipole_fn) ldpl.append(dpl_trial) return ldpl -def get_inputs(params): - """ get a dictionary of input types used in simulation - with distal/proximal specificity for evoked,ongoing inputs - """ - - dinty = {'Evoked': False, 'Ongoing': False, 'Poisson': False, - 'Tonic': False, 'EvokedDist': False, 'EvokedProx': False, - 'OngoingDist': False, 'OngoingProx': False} - - dinty['Evoked'] = usingEvokedInputs(params) - dinty['EvokedDist'] = usingEvokedInputs(params, lsuffty=['_evdist_']) - dinty['EvokedProx'] = usingEvokedInputs(params, lsuffty=['_evprox_']) - dinty['Ongoing'] = usingOngoingInputs(params) - dinty['OngoingDist'] = usingOngoingInputs(params, lty=['_dist']) - dinty['OngoingProx'] = usingOngoingInputs(params, lty=['_prox']) - dinty['Poisson'] = usingPoissonInputs(params) - dinty['Tonic'] = usingTonicInputs(params) - - return dinty +def check_feeds_to_plot(feeds_from_spikes, params): + # ensures synaptic weight > 0 + using_feeds = get_inputs(params) + + feed_counts = {'pois': len(feeds_from_spikes['pois']) > 0, + 'evdist': len(feeds_from_spikes['evdist']) > 0, + 'evprox': len(feeds_from_spikes['evprox']) > 0, + 'dist': len(feeds_from_spikes['dist']) > 0, + 'prox': len(feeds_from_spikes['prox']) > 0} + feed_counts['evoked'] = feed_counts['evdist'] or feed_counts['evprox'] + feed_counts['ongoing'] = feed_counts['dist'] or feed_counts['prox'] + + feeds_to_plot = {} + for key in feed_counts.keys(): + feeds_to_plot[key] = feed_counts[key] and using_feeds[key] + + return feeds_to_plot + + +def plot_hists_on_gridspec(figure, gridspec, feeds_to_plot, extinputs, times, + xlim, linewidth): + + axdist = axpois = axprox = None + axes = [] + n_hists = 0 + + # check poisson inputs, create subplot + if feeds_to_plot['pois']: + axpois = figure.add_subplot(gridspec[n_hists, :]) + n_hists += 1 + + # check distal inputs, create subplot + if feeds_to_plot['evdist'] or feeds_to_plot['dist']: + axdist = figure.add_subplot(gridspec[n_hists, :]) + n_hists += 1 + + # check proximal inputs, create subplot + if feeds_to_plot['evprox'] or feeds_to_plot['prox']: + axprox = figure.add_subplot(gridspec[n_hists, :]) + n_hists += 1 + + # check input types provided in simulation + if feeds_to_plot['pois']: + extinputs.plot_hist(axpois, 'pois', times, 'auto', xlim, + color='k', hty='step', + lw=linewidth+1) + axes.append(axpois) + if feeds_to_plot['dist']: + extinputs.plot_hist(axdist, 'dist', times, 'auto', xlim, + color='g', lw=linewidth+1) + axes.append(axdist) + if feeds_to_plot['prox']: + extinputs.plot_hist(axprox, 'prox', times, 'auto', xlim, + color='r', lw=linewidth+1) + axes.append(axprox) + if feeds_to_plot['evdist']: + extinputs.plot_hist(axdist, 'evdist', times, 'auto', xlim, + color='g', hty='step', + lw=linewidth+1) + axes.append(axdist) + if feeds_to_plot['evprox']: + extinputs.plot_hist(axprox, 'evprox', times, 'auto', xlim, + color='r', hty='step', + lw=linewidth+1) + axes.append(axprox) + + # get the ymax for the two histograms + ymax = 0 + for ax in [axpois, axdist, axprox]: + if ax is not None: + if ax.get_ylim()[1] > ymax: + ymax = ax.get_ylim()[1] + + # set ymax for both to be the same + for ax in [axpois, axdist, axprox]: + if ax is not None: + ax.set_ylim(0, ymax) + ax.set_xlim(xlim) + ax.legend(loc=1) # legend in upper right + + # invert the distal input axes + if axdist is not None: + axdist.invert_yaxis() + + return axes class SimData(object): @@ -69,6 +139,7 @@ def __init__(self): self._sim_data = {} self._opt_data = {} self._exp_data = {} + self._data_dir = os.path.join(get_output_dir(), 'data') def remove_sim_by_fn(self, paramfn): """Deletes sim from SimData @@ -132,8 +203,7 @@ def update_sim_data(self, paramfn, params): Dictionary containing parameters """ - data_dir = os.path.join(get_output_dir(), 'data') - sim_dir = os.path.join(data_dir, params['sim_prefix']) + sim_dir = os.path.join(self._data_dir, params['sim_prefix']) warn_nofile = False if os.path.exists(sim_dir): warn_nofile = True @@ -155,7 +225,8 @@ def update_sim_data(self, paramfn, params): spikes_array = None try: raw_spikes = read_spikes(spike_fn) - spikes_array = np.r_[raw_spikes.times, raw_spikes.gids].T + spikes_array = np.r_[raw_spikes.spike_times, + raw_spikes.spike_gids].T if len(spikes_array) == 0 and warn_nofile: print(warning_message, spike_fn) except ValueError: @@ -172,9 +243,9 @@ def update_sim_data(self, paramfn, params): spike_fn) warn_nospec = False - dinty = get_inputs(params) - if params['save_spec_data'] or dinty['Ongoing'] or \ - dinty['Poisson'] or dinty['Tonic']: + using_feeds = get_inputs(params) + if params['save_spec_data'] or using_feeds['ongoing'] or \ + using_feeds['pois'] or using_feeds['tonic']: warn_nospec = True spec_fn = os.path.join(sim_dir, 'rawspec.npz') @@ -287,6 +358,165 @@ def update_opt_data(self, paramfn, params, avg_dpl): self._opt_data = {'paramfn': paramfn, 'params': params, 'data': {'avg_dpl': avg_dpl}} + def _read_dpl(self, paramfn, trial_idx, ntrial): + if ntrial == 1: + dpltrial = self._sim_data[paramfn]['data']['avg_dpl'] + else: + trial_data = self._sim_data[paramfn]['data']['dpl_trials'] + if trial_idx > len(trial_data): + print("Warning: data not available for trials above index", + (len(trial_data) - 1)) + return None + + dpltrial = self._sim_data[paramfn]['data']['dpl_trials'][trial_idx] + + dpl_data = np.c_[dpltrial[:, 1], + dpltrial[:, 2], + dpltrial[:, 3]] + + return Dipole(dpltrial[:, 0], dpl_data) + + def _plot_spec(self, ax, paramfn, params, xlim, fontsize): + single_spec = self._sim_data[paramfn]['data']['spec'] + + # Plot TFR data and add colorbar + plot = ax.imshow(single_spec['TFR'], + extent=(single_spec['time'][0], + single_spec['time'][-1], + single_spec['freq'][-1], + single_spec['freq'][0]), + aspect='auto', origin='upper', + cmap=plt.get_cmap(params['spec_cmap'])) + ax.set_ylabel('Frequency (Hz)', fontsize=fontsize) + ax.set_xlabel('Time (ms)', fontsize=fontsize) + ax.set_xlim(xlim) + ax.set_ylim(single_spec['freq'][-1], single_spec['freq'][0]) + + return plot + + def save_spec_with_hist(self, paramfn, params): + sim_dir = os.path.join(self._data_dir, params['sim_prefix']) + ntrial = params['N_trials'] + xmin = 0. + xmax = params['tstop'] + xlim = (xmin, xmax) + linewidth = 1 + + num_step = ceil(xmax / params['dt']) + 1 + times = np.linspace(xmin, xmax, num_step) + + for trial_idx in range(ntrial): + spec_fn = get_fname(sim_dir, 'rawspec', trial_idx, ntrial) + spike_fn = get_fname(sim_dir, 'rawspk', trial_idx, ntrial) + out_paramfn = get_fname(sim_dir, 'param', trial_idx, ntrial) + + # Generate file prefix + fprefix = os.path.splitext(os.path.split(spec_fn)[-1])[0] + + # Create the fig name + fig_name = os.path.join(sim_dir, fprefix+'.png') + + f = plt.figure(figsize=(8, 8)) + font_prop = {'size': 8} + mpl.rc('font', **font_prop) + + # get inputs from spike file + extinputs = ExtInputs(spike_fn, out_paramfn, params) + extinputs.add_delay_times() + feeds_from_spikes = extinputs.inputs + feeds_to_plot = check_feeds_to_plot(feeds_from_spikes, params) + + if feeds_to_plot['ongoing'] or feeds_to_plot['evoked'] or \ + feeds_to_plot['pois']: + # hist gridspec + gs2 = gridspec.GridSpec(2, 1, hspace=0.14, bottom=0.75, + top=0.95, left=0.1, right=0.82) + + plot_hists_on_gridspec(f, gs2, feeds_to_plot, extinputs, + times, xlim, linewidth) + + # the right margin is a hack and NOT guaranteed! + # it's making space for the stupid colorbar that creates a new + # grid to replace gs1 when called, and it doesn't update the + # params of gs1 + gs0 = gridspec.GridSpec(1, 4, wspace=0.05, hspace=0., bottom=0.05, + top=0.45, left=0.1, right=1.) + gs1 = gridspec.GridSpec(2, 1, height_ratios=[1, 3], bottom=0.50, + top=0.70, left=0.1, right=0.82) + + axspec = f.add_subplot(gs0[:, :]) + axdipole = f.add_subplot(gs1[:, :]) + + cax = self._plot_spec(axspec, paramfn, params, xlim, fontsize) + f.colorbar(cax, ax=axspec) + + # set xlim based on TFR plot + # xlim_new = axspec.get_xlim() + + # dipole + dpl = self._read_dpl(paramfn, trial_idx, ntrial) + if dpl is None: + break + dpl.plot(axdipole, 'agg', show=False) + axdipole.set_xlim(xlim) + + f.savefig(fig_name, dpi=300) + plt.close(f) + + def save_dipole_with_hist(self, paramfn, params): + sim_dir = os.path.join(self._data_dir, params['sim_prefix']) + ntrial = params['N_trials'] + xmin = 0. + xmax = params['tstop'] + xlim = (xmin, xmax) + linewidth = 1 + + num_step = ceil(xmax / params['dt']) + 1 + times = np.linspace(xmin, xmax, num_step) + + for trial_idx in range(ntrial): + dipole_fn = get_fname(sim_dir, 'normdpl', trial_idx, ntrial) + spike_fn = get_fname(sim_dir, 'rawspk', trial_idx, ntrial) + out_paramfn = get_fname(sim_dir, 'param', trial_idx, ntrial) + + # split to find file prefix + file_prefix = dipole_fn.split('/')[-1].split('.')[0] + # Create the fig name + fig_name = os.path.join(sim_dir, file_prefix+'.png') + f = plt.figure(figsize=(12, 6)) + font_prop = {'size': 8} + mpl.rc('font', **font_prop) + + extinputs = ExtInputs(spike_fn, out_paramfn, params) + extinputs.add_delay_times() + feeds_from_spikes = extinputs.inputs + feeds_to_plot = check_feeds_to_plot(feeds_from_spikes, params) + + if feeds_to_plot['ongoing'] or feeds_to_plot['evoked'] or \ + feeds_to_plot['pois']: + # hist gridspec + gs1 = gridspec.GridSpec(2, 1, hspace=0.14, bottom=0.60, + top=0.95, left=0.1, right=0.90) + + plot_hists_on_gridspec(f, gs1, feeds_to_plot, extinputs, + times, xlim, linewidth) + + # dipole gridpec + gs0 = gridspec.GridSpec(1, 1, wspace=0.05, hspace=0, bottom=0.10, + top=0.55, left=0.1, right=0.90) + axdipole = f.add_subplot(gs0[:, :]) + + # dipole + dpl = self._read_dpl(paramfn, trial_idx, ntrial) + if dpl is None: + break + dpl.plot(axdipole, 'agg', show=False) + axdipole.set_xlim(xlim) + + fig_name = os.path.join(sim_dir, file_prefix+'.png') + f.savefig(fig_name, dpi=300) + plt.close(f) + class SIMCanvas (FigureCanvasQTAgg): # matplotlib/pyqt-compatible canvas for drawing simulation & external data @@ -313,6 +543,7 @@ def __init__(self, paramfn, params, parent=None, width=5, height=4, self.paramfn = paramfn self.initaxes() self.G = gridspec.GridSpec(10, 1) + self._data_dir = os.path.join(get_output_dir(), 'data') self.optMode = optMode if not optMode: @@ -321,136 +552,83 @@ def __init__(self, paramfn, params, parent=None, width=5, height=4, def initaxes(self): # initialize the axes - self.axdist = self.axprox = self.axdipole = self.axspec = None - self.axpois = None - - def plotinputhist(self, xl, dinty): - """ plot input histograms - xl = x axis limits - dinty = dict of input types used, - determines how many/which axes created/displayed - """ + self.axdipole = self.axspec = None - extinputs = None - plot_distribs = False + def plotinputhist(self, extinputs=None, feeds_to_plot=None, + plot_distribs=False): + """ plot input histograms""" - if self.params is None: - raise ValueError("No valid params found") + xmin = 0. + xmax = self.params['tstop'] + xlim = (xmin, xmax) + axes = [] - sim_tstop = self.params['tstop'] - sim_dt = self.params['tstop'] - num_step = ceil(sim_tstop / sim_dt) + 1 - times = np.linspace(0, sim_tstop, num_step) + sim_dt = self.params['dt'] + num_step = ceil(xmax / sim_dt) + 1 + times = np.linspace(xmin, xmax, num_step) - data_dir = os.path.join(get_output_dir(), 'data') - sim_dir = os.path.join(data_dir, self.params['sim_prefix']) + sim_dir = os.path.join(self._data_dir, self.params['sim_prefix']) spike_fn = os.path.join(sim_dir, 'spk.txt') out_paramfn = os.path.join(sim_dir, 'param.txt') - try: - extinputs = ExtInputs(spike_fn, out_paramfn, self.params) - extinputs.add_delay_times() - dinput = extinputs.inputs - except ValueError: + + if not plot_distribs: + if feeds_to_plot is None: + extinputs = ExtInputs(spike_fn, out_paramfn, self.params) + extinputs.add_delay_times() + dinput = extinputs.inputs + feeds_to_plot = check_feeds_to_plot(dinput, self.params) + + if feeds_to_plot['ongoing'] or feeds_to_plot['evoked'] or \ + feeds_to_plot['pois']: + # hist gridspec + axes = plot_hists_on_gridspec(self.figure, self.G, + feeds_to_plot, extinputs, times, + xlim, self.linewidth) + else: + plot_distribs = True + + if plot_distribs: dinput = self.getInputDistrib() - plot_distribs = True - - if len(dinput['dist']) <= 0 and len(dinput['prox']) <= 0 and \ - len(dinput['evdist']) <= 0 and len(dinput['evprox']) <= 0 and \ - len(dinput['pois']) <= 0: - return False - - self.hist = {'feed_dist': None, - 'feed_prox': None, - 'feed_evdist': None, - 'feed_evprox': None, - 'feed_pois': None} - - # dinty ensures synaptic weight > 0 - hasPois = len(dinput['pois']) > 0 and dinty['Poisson'] - gRow = 0 - self.axdist = self.axprox = self.axpois = None # axis objects - - # check poisson inputs, create subplot - if hasPois: - self.axpois = self.figure.add_subplot(self.G[gRow, 0]) - gRow += 1 - - # check distal inputs, create subplot - if (len(dinput['dist']) > 0 and dinty['OngoingDist']) or \ - (len(dinput['evdist']) > 0 and dinty['EvokedDist']): - self.axdist = self.figure.add_subplot(self.G[gRow, 0]) - gRow += 1 - - # check proximal inputs, create subplot - if (len(dinput['prox']) > 0 and dinty['OngoingProx']) or \ - (len(dinput['evprox']) > 0 and dinty['EvokedProx']): - self.axprox = self.figure.add_subplot(self.G[gRow, 0]) - gRow += 1 - - # check input types provided in simulation - if extinputs is not None and self._has_simdata(): - if hasPois: - extinputs.plot_hist(self.axpois, 'pois', times, 'auto', xl, - color='k', hty='step', - lw=self.linewidth+1) - - # dinty condition ensures synaptic weight > 0 - if len(dinput['dist']) > 0 and dinty['OngoingDist']: - extinputs.plot_hist(self.axdist, 'dist', times, 'auto', xl, - color='g', lw=self.linewidth+1) - - if len(dinput['prox']) > 0 and dinty['OngoingProx']: - extinputs.plot_hist(self.axprox, 'prox', times, 'auto', xl, - color='r', lw=self.linewidth+1) - - if len(dinput['evdist']) > 0 and dinty['EvokedDist']: - extinputs.plot_hist(self.axdist, 'evdist', times, 'auto', xl, - color='g', hty='step', - lw=self.linewidth+1) - - if len(dinput['evprox']) > 0 and dinty['EvokedProx']: - extinputs.plot_hist(self.axprox, 'evprox', times, 'auto', xl, - color='r', hty='step', - lw=self.linewidth+1) - elif plot_distribs: - if len(dinput['evprox']) > 0 and dinty['EvokedProx']: - prox_tot = np.zeros(len(dinput['evprox'][0][0])) - for prox in dinput['evprox']: - prox_tot += prox[1] - self.axprox.plot(dinput['evprox'][0][0], prox_tot, color='r', - lw=self.linewidth, - label='evprox distribution') - self.axprox.set_xlim(dinput['evprox'][0][0][0], - dinput['evprox'][0][0][-1]) - if len(dinput['evdist']) > 0 and dinty['EvokedDist']: + feeds_to_plot = check_feeds_to_plot(dinput, self.params) + if not (feeds_to_plot['ongoing'] or feeds_to_plot['evoked'] or + feeds_to_plot['pois']): + # no plots to create + return axes + + n_hists = 0 + + if feeds_to_plot['evdist']: dist_tot = np.zeros(len(dinput['evdist'][0][0])) for dist in dinput['evdist']: dist_tot += dist[1] - self.axdist.plot(dinput['evdist'][0][0], dist_tot, color='g', - lw=self.linewidth, - label='evdist distribution') - self.axprox.set_xlim(dinput['evdist'][0][0][0], - dinput['evdist'][0][0][-1]) - - ymax = 0 - for ax in [self.axpois, self.axdist, self.axprox]: - if ax is not None: - if ax.get_ylim()[1] > ymax: - ymax = ax.get_ylim()[1] - - if ymax == 0: - return False - else: - for ax in [self.axpois, self.axdist, self.axprox]: - if ax is not None: - ax.set_ylim(0, ymax) - if self.axdist: - self.axdist.invert_yaxis() - for ax in [self.axpois, self.axdist, self.axprox]: - if ax: - ax.set_xlim(xl) - ax.legend(loc=1) # legend in upper right - return True, gRow + + axdist = self.figure.add_subplot(self.G[n_hists, :]) + n_hists += 1 + + axdist.plot(dinput['evdist'][0][0], dist_tot, color='g', + lw=self.linewidth, + label='evdist distribution') + axdist.set_xlim(dinput['evdist'][0][0][0], + dinput['evdist'][0][0][-1]) + axdist.invert_yaxis() # invert the distal input axes + axes.append(axdist) + + if feeds_to_plot['evprox']: + prox_tot = np.zeros(len(dinput['evprox'][0][0])) + for prox in dinput['evprox']: + prox_tot += prox[1] + + axprox = self.figure.add_subplot(self.G[n_hists, :]) + n_hists += 1 + + axprox.plot(dinput['evprox'][0][0], prox_tot, color='r', + lw=self.linewidth, + label='evprox distribution') + axprox.set_xlim(dinput['evprox'][0][0][0], + dinput['evprox'][0][0][-1]) + axes.append(axprox) + + return axes def clearaxes(self): # clear the figures axes @@ -655,29 +833,41 @@ def plotsimdat(self): if self.params is None: only_create_axes = True DrawSpec = False - xl = (0.0, 1.0) + xlim = (0.0, 1.0) else: - # setup the figure axis for drawing the dipole signal - dinty = get_inputs(self.params) - - self.sim_data.update_sim_data(self.paramfn, self.params) - - single_sim_data = self.sim_data._sim_data[self.paramfn]['data'] - if single_sim_data['avg_dpl'] is None: + tstop = self.params['tstop'] + xlim = (0.0, tstop) + sim_dir = os.path.join(self._data_dir, self.params['sim_prefix']) + spike_fn = os.path.join(sim_dir, 'spk.txt') + out_paramfn = os.path.join(sim_dir, 'param.txt') + try: + extinputs = ExtInputs(spike_fn, out_paramfn, self.params) + extinputs.add_delay_times() + feeds_from_spikes = extinputs.inputs + feeds_to_plot = check_feeds_to_plot(feeds_from_spikes, + self.params) + axes = self.plotinputhist(extinputs, feeds_to_plot, + plot_distribs=False) + self.gRow = len(axes) + except FileNotFoundError: + axes = self.plotinputhist(plot_distribs=True) + self.gRow = len(axes) + + # for trying to plot a simulation read from disk (e.g. default) + if self.paramfn not in self.sim_data._sim_data: + self.sim_data.update_sim_data(self.paramfn, self.params) + + # check that dipole data is present + single_sim = self.sim_data._sim_data[self.paramfn]['data'] + if single_sim['avg_dpl'] is None: failed_loading = True - xl = (0.0, self.params['tstop']) - if dinty['Ongoing'] or dinty['Evoked'] or dinty['Poisson']: - xo = self.plotinputhist(xl, dinty) - if xo: - self.gRow = xo[1] - # whether to draw the specgram - should draw if user saved it or # have ongoing, poisson, or tonic inputs DrawSpec = (not failed_loading) and \ - single_sim_data['spec'] is not None and \ - (self.params['save_spec_data'] or dinty['Ongoing'] - or dinty['Poisson'] or dinty['Tonic']) + single_sim['spec'] is not None and \ + (self.params['save_spec_data'] or feeds_to_plot['ongoing'] + or feeds_to_plot['pois'] or feeds_to_plot['tonic']) if DrawSpec: # dipole axis takes fewer rows if also drawing specgram self.axdipole = self.figure.add_subplot(self.G[self.gRow:5, 0]) @@ -685,9 +875,9 @@ def plotsimdat(self): else: self.axdipole = self.figure.add_subplot(self.G[self.gRow:-1, 0]) - yl = (-0.001, 0.001) - self.axdipole.set_ylim(yl) - self.axdipole.set_xlim(xl) + ylim = (-0.001, 0.001) + self.axdipole.set_ylim(ylim) + self.axdipole.set_xlim(xlim) left = 0.08 w, _ = getscreengeom() @@ -700,29 +890,21 @@ def plotsimdat(self): if failed_loading or only_create_axes: return - ds = None - tstop = self.params['tstop'] - dt = self.params['dt'] - xl = (0, tstop) - # get spectrogram if it exists, then adjust axis limits but only # if drawing spectrogram if DrawSpec: - single_sim_data = self.sim_data._sim_data[self.paramfn]['data'] - if single_sim_data['spec'] is not None: - ds = single_sim_data['spec'] # spectrogram - xl = (ds['time'][0], ds['time'][-1]) # use spectogram limits + single_sim = self.sim_data._sim_data[self.paramfn]['data'] + if single_sim['spec'] is not None: + # use spectogram limits (missing first 50 ms b/c edge effects) + xl = (single_sim['spec']['time'][0], + single_sim['spec']['time'][-1]) else: DrawSpec = False - sampr = 1e3 / dt # dipole sampling rate - # use these indices to find dipole min,max - sidx, eidx = int(sampr*xl[0] / 1e3), int(sampr*xl[1] / 1e3) - yl = [0, 0] dpl = self.sim_data._sim_data[self.paramfn]['data']['avg_dpl'] - yl[0] = min(yl[0], np.amin(dpl[sidx:eidx, 1])) - yl[1] = max(yl[1], np.amax(dpl[sidx:eidx, 1])) + yl[0] = min(yl[0], np.amin(dpl[:, 1])) + yl[1] = max(yl[1], np.amax(dpl[:, 1])) if not self.optMode: # skip for optimization @@ -744,16 +926,16 @@ def plotsimdat(self): self.axdipole.plot(dpltrial[:, 0], dpltrial[:, 1], color='gray', linewidth=self.linewidth) - yl[0] = min(yl[0], dpltrial[sidx:eidx, 1].min()) - yl[1] = max(yl[1], dpltrial[sidx:eidx, 1].max()) + yl[0] = min(yl[0], dpltrial[:, 1].min()) + yl[1] = max(yl[1], dpltrial[:, 1].max()) if drawavgdpl or self.params['N_trials'] <= 1: # this is the average dipole (across trials) # it's also the ONLY dipole when running a single trial self.axdipole.plot(dpl[:, 0], dpl[:, 1], 'k', linewidth=self.linewidth + 1) - yl[0] = min(yl[0], dpl[sidx:eidx, 1].min()) - yl[1] = max(yl[1], dpl[sidx:eidx, 1].max()) + yl[0] = min(yl[0], dpl[:, 1].min()) + yl[1] = max(yl[1], dpl[:, 1].max()) else: if self.sim_data._opt_data['avg_dpl'] is not None: # show optimized dipole as gray line @@ -761,8 +943,8 @@ def plotsimdat(self): self.axdipole.plot(optdpl[:, 0], optdpl[:, 1], 'k', color='gray', linewidth=self.linewidth + 1) - yl[0] = min(yl[0], optdpl[sidx:eidx, 1].min()) - yl[1] = max(yl[1], optdpl[sidx:eidx, 1].max()) + yl[0] = min(yl[0], optdpl[:, 1].min()) + yl[1] = max(yl[1], optdpl[:, 1].max()) if self.sim_data._opt_data['initial_dpl'] is not None: # show initial dipole in dotted black line @@ -771,7 +953,7 @@ def plotsimdat(self): plot_dpl = plot_data[:, 0] self.axdipole.plot(times, plot_dpl, '--', color='black', linewidth=self.linewidth) - dpl = self.sim_data._opt_data['initial_dpl'][sidx:eidx, 1] + dpl = self.sim_data._opt_data['initial_dpl'][:, 1] yl[0] = min(yl[0], dpl.min()) yl[1] = max(yl[1], dpl.max()) @@ -797,23 +979,14 @@ def plotsimdat(self): self.axdipole.set_ylabel(r'Dipole (nAm $\times$ ' + str(scalefctr) + ')\n', fontsize=fontsize) - self.axdipole.set_xlim(xl) + self.axdipole.set_xlim(xlim) self.axdipole.set_ylim(yl) if DrawSpec: gRow = 6 self.axspec = self.figure.add_subplot(self.G[gRow:10, 0]) - cax = self.axspec.imshow(ds['TFR'], extent=(ds['time'][0], - ds['time'][-1], - ds['freq'][-1], - ds['freq'][0]), - aspect='auto', origin='upper', - cmap=plt.get_cmap( - self.params['spec_cmap'])) - self.axspec.set_ylabel('Frequency (Hz)', fontsize=fontsize) - self.axspec.set_xlabel('Time (ms)', fontsize=fontsize) - self.axspec.set_xlim(xl) - self.axspec.set_ylim(ds['freq'][-1], ds['freq'][0]) + cax = self.sim_data._plot_spec(self.axspec, self.paramfn, + self.params, xl, fontsize) cbaxes = self.figure.add_axes([0.6, 0.49, 0.3, 0.005]) # plot colorbar horizontally to save space plt.colorbar(cax, cax=cbaxes, orientation='horizontal') @@ -825,8 +998,8 @@ def plotarrows(self): xl = self.axdipole.get_xlim() yl = self.axdipole.get_ylim() - dinty = get_inputs(self.params) - if dinty['Evoked']: + using_feeds = get_inputs(self.params) + if using_feeds['evoked']: self.drawEVInputTimes(self.axdipole, yl, 0.1, (xl[1] - xl[0]) * .02, (yl[1] - yl[0]) * .02) diff --git a/hnn/spikefn.py b/hnn/spikefn.py index a76086609..f8872e26e 100644 --- a/hnn/spikefn.py +++ b/hnn/spikefn.py @@ -5,14 +5,15 @@ # last major: (SL: toward python3) import numpy as np -import scipy.signal as sps import matplotlib.pyplot as plt import itertools as it import os -from .paramrw import read_gids_param +import scipy.signal as sps from hnn_core import read_spikes +from .paramrw import read_gids_param + # meant as a class for ONE cell type class Spikes(): def __init__ (self, s_all, ranges): @@ -64,15 +65,14 @@ class ExtInputs (Spikes): def __init__ (self, fspk, fgids, params, evoked=False): self.p_dict = params - try: - self.gid_dict = read_gids_param(fgids) - except FileNotFoundError: - raise ValueError + self.gid_dict = read_gids_param(fgids) if 'common' in self.gid_dict: extinput_key = 'common' - else: + elif 'extinput' in self.gid_dict: extinput_key = 'extinput' + else: + raise ValueError("Bad 'param.txt' file for reading input gids") self.evoked = evoked @@ -164,7 +164,7 @@ def __get_extinput_times (self, fspk): s_all = [] try: spikes = read_spikes(fspk) - s_all = np.r_[spikes.times, spikes.gids].T + s_all = np.r_[spikes.spike_times, spikes.spike_gids].T except ValueError: s_all = np.loadtxt(open(fspk, 'rb')) except IndexError: @@ -175,7 +175,7 @@ def __get_extinput_times (self, fspk): if len(s_all) == 0: # couldn't read spike times - raise ValueError + raise ValueError("No spikes in file: %s" % fspk) inputs = {k:np.array([]) for k in ['prox','dist','evprox','evdist','pois']} if self.gid_prox is not None: inputs['prox'] = self.get_times(self.gid_prox,s_all) @@ -252,7 +252,6 @@ def bin_count(bins_per_second, tinterval): return bins_per_second * tinterval / # splits ext random feeds (of type exttype) by supplied cell type def split_extrand(s, gid_dict, celltype, exttype): - gid_cell = gid_dict[celltype] gid_exttype_start = gid_dict[exttype][0] gid_exttype_cell = [gid + gid_exttype_start for gid in gid_dict[celltype]] return Spikes(s, gid_exttype_cell) diff --git a/hnn/visdipole.py b/hnn/visdipole.py index 5d9f57b0f..25fb8c077 100644 --- a/hnn/visdipole.py +++ b/hnn/visdipole.py @@ -17,7 +17,7 @@ from DataViewGUI import DataViewGUI from paramrw import get_output_dir import spikefn -from simdat import readdpltrials +from simdat import read_dpltrials from hnn_core import read_params @@ -37,7 +37,7 @@ ddat = {} basedir = os.path.join(get_output_dir(), 'data', params['sim_prefix']) -ddat['dpltrials'] = readdpltrials(basedir) +ddat['dpltrials'] = read_dpltrials(basedir) ddat['dpl'] = np.loadtxt(os.path.join(basedir,'dpl.txt')) diff --git a/hnn/visrast.py b/hnn/visrast.py index 5d87e654e..6c6dcad97 100644 --- a/hnn/visrast.py +++ b/hnn/visrast.py @@ -94,7 +94,7 @@ def getdspk (fn): ddat = {} try: spikes = read_spikes(fn) - ddat['spk'] = np.r_[spikes.times, spikes.gids].T + ddat['spk'] = np.r_[spikes.spike_times, spikes.spike_gids].T except ValueError: ddat['spk'] = np.loadtxt(fn) From 9beba7f81237ed2e3ec7724faf663b48746fdc3f Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 29 Nov 2020 16:15:56 -0500 Subject: [PATCH 058/107] MAINT: remove unused code in specfn.py --- hnn/specfn.py | 454 ++++++-------------------------------------------- 1 file changed, 52 insertions(+), 402 deletions(-) diff --git a/hnn/specfn.py b/hnn/specfn.py index f6f734db6..9c59913b8 100644 --- a/hnn/specfn.py +++ b/hnn/specfn.py @@ -1,18 +1,20 @@ -# specfn.py - Average time-frequency energy representation using Morlet wavelet method +# specfn.py - Average time-frequency energy representation using Morlet +# wavelet method # # v 1.10.2-py35 # rev 2017-02-21 (SL: fixed an issue with indexing) # last major: (SL: more comments on the units of Morlet Spec) +# 11-29-2020: BC removed code that no longer uses in preparation for +# hnn-core integration -import os -import sys import numpy as np import scipy.signal as sps -import matplotlib.pyplot as plt + # MorletSpec class based on a time vec tvec and a time series vec tsvec class MorletSpec(): - def __init__(self, tvec, tsvec, f_max=None, p_dict=None, tmin = 50.0, f_min = 1.): + def __init__(self, tvec, tsvec, f_max=None, p_dict=None, tmin=50.0, + f_min=1.): # Save variable portion of fdata_spec as identifying attribute # self.name = fdata_spec @@ -53,37 +55,9 @@ def __init__(self, tvec, tsvec, f_max=None, p_dict=None, tmin = 50.0, f_min = 1. # Generate Spec data self.TFR = self.__traces2TFR() - - # Add time vector as first row of TFR data - # self.TFR = np.vstack([self.timevec, self.TFR]) - else: - print("tstop not greater than %4.2f ms. Skipping wavelet analysis." % self.tmin) - - # externally callable save function - def save(self, fdata_spec): - raise DeprecationWarning - write(fdata_spec, self.timevec, self.freqvec, self.TFR) - - # plots spec to axis - def plot_to_ax(self, ax_spec, dt): - # pc = ax.imshow(self.TFR, extent=[xmin, xmax, self.freqvec[-1], self.freqvec[0]], aspect='auto', origin='upper') - pc = ax_spec.imshow(self.TFR, aspect='auto', origin='upper', cmap=plt.get_cmap(self.params['spec_cmap'])) - - return pc - - # get time and freq of max spectral power - def max(self): - print("Warning: you are using max() in MorletSpec(). It should be changed from == to np.isclose()") - max_spec = self.TFR.max() - - t_mask = (self.TFR==max_spec).sum(axis=0) - t_at_max = self.tvec[t_mask == 1] - - f_mask = (self.TFR==max_spec).sum(axis=1) - f_at_max = self.f[f_mask == 1] - - return np.array((max_spec, t_at_max, f_at_max)) + print("tstop not greater than %4.2f ms. " % self.tmin + + "Skipping wavelet analysis.") # also creates self.timevec def __traces2TFR(self): @@ -93,7 +67,8 @@ def __traces2TFR(self): # range should probably be 0 to len(self.S_trans) # shift tvec to reflect change # this is in ms - self.t = 1000. * np.arange(1, len(self.S_trans)+1) / self.fs + self.tmin - self.params['dt'] + self.t = 1000. * np.arange(1, len(self.S_trans)+1) / self.fs + \ + self.tmin - self.params['dt'] # preallocation B = np.zeros((len(self.f), len(self.S_trans))) @@ -102,9 +77,9 @@ def __traces2TFR(self): for j in range(0, len(self.f)): s = sps.detrend(self.S_trans[:]) - # += is used here because these were zeros and now it's adding the solution + # += is used here because these were zeros and now it's adding + # the solution B[j, :] += self.__energyvec(self.f[j], s) - # B[j,:] = B[j,:] + self.__energyvec(self.freqvec[j], self.__lnr50(s)) return B @@ -112,9 +87,8 @@ def __traces2TFR(self): else: for i in range(0, self.S_trans.shape[0]): for j in range(0, len(self.f)): - s = sps.detrend(self.S_trans[i,:]) - B[j,:] += self.__energyvec(self.f[j], s) - # B[j,:] = B[j,:] + self.__energyvec(self.freqvec[j], self.__lnr50(s)) + s = sps.detrend(self.S_trans[i, :]) + B[j, :] += self.__energyvec(self.f[j], s) # calculate the morlet wavelet for central frequency f def __morlet(self, f, t): @@ -133,49 +107,16 @@ def __morlet(self, f, t): A = 1. / (st * np.sqrt(2.*np.pi)) # units: 1/s * (exp (s**2 / s**2)) * exp( 1/ s * s) - y = A * np.exp(-t**2. / (2. * st**2.)) * np.exp(1.j * 2. * np.pi * f * t) + y = A * np.exp(-t**2. / (2. * st**2.)) * np.exp(1.j * 2. * np.pi * f * + t) return y - # notch filter for UK - def __lnr50(self, s): - """ - presently unused - Line noise reduction (50 Hz) the amplitude and phase of the line notch is estimate. - A sinusoid with these characterisitics is then subtracted from the signal. - s: signal - """ - raise DeprecationWarning - fNoise = 50. - tv = np.arange(0,len(s)) / self.fs - - if np.ndim(s) == 1: - Sc = np.zeros(s.shape) - Sft = self.__ft(s[:], fNoise) - Sc[:] = s[:] - abs(Sft) * np.cos(2. * np.pi * fNoise * tv - np.angle(Sft)) - - return Sc - - else: - s = s.transpose() - Sc = np.zeros(s.shape) - - for k in range(0, len(s)): - Sft = ft(s[k,:], fNoise) - Sc[k,:] = s[k,:] - abs(Sft) * np.cos(2. * np.pi * fNoise * tv - np.angle(Sft)) - - return Sc.tranpose() - - def __ft(self, s, f): - tv = np.arange(0,len(s)) / self.fs - tmp = np.exp(1.j*2. * np.pi * f * tv) - S = 2 * sum(s * tmp) / len(s) - - return S - # Return an array containing the energy as function of time for freq f def __energyvec(self, f, s): - """ Final units of y: signal units squared. For instance, a signal of Am would have Am^2 + """ Final units of y: signal units squared. + + For instance, a signal of Am would have Am^2 The energy is calculated using Morlet's wavelets f: frequency s: signal @@ -201,260 +142,6 @@ def __energyvec(self, f, s): return y -# functions on the aggregate spec data -class Spec(): - def __init__(self, fspec, spec_cmap='jet', dtype='dpl'): - raise DeprecationWarning - - # save dtype - self.dtype = dtype - - # save details of file - # may be better ways of doing this... - self.fspec = fspec - print('Spec: fspec:',fspec) - # try: - self.expmt = fspec.split('/')[6].split('.')[0] - # except: - # self.expmt = '' - self.fname = 'spec.npz' # fspec.split('/')[-1].split('-spec')[0] - - self.spec_cmap = spec_cmap - - # parse data - self.__parse_f(fspec) - - # parses the specific data file - def __parse_f(self, fspec): - data_spec = np.load(fspec, allow_pickle=True) - - if self.dtype == 'dpl': - self.spec = {} - - # Try to load aggregate spec data - try: - self.spec['agg'] = { - 't': data_spec['t_agg'], - 'f': data_spec['f_agg'], - 'TFR': data_spec['TFR_agg'], - } - - except KeyError: - # Try loading aggregate spec data using old keys - try: - self.spec['agg'] = { - 't': data_spec['time'], - 'f': data_spec['freq'], - 'TFR': data_spec['TFR'], - } - except KeyError: - print("No aggregate spec data found. Don't use fns that require it...") - - # Try loading Layer specific data - try: - self.spec['L2'] = { - 't': data_spec['t_L2'], - 'f': data_spec['f_L2'], - 'TFR': data_spec['TFR_L2'], - } - - self.spec['L5'] = { - 't': data_spec['t_L5'], - 'f': data_spec['f_L5'], - 'TFR': data_spec['TFR_L5'], - } - - except KeyError: - print("All or some layer data is missing. Don't use fns that require it...") - - # Try loading periodigram data - try: - self.spec['pgram'] = { - 'p': data_spec['p_pgram'], - 'f': data_spec['f_pgram'], - } - - except KeyError: - try: - self.spec['pgram'] = { - 'p': data_spec['pgram_p'], - 'f': data_spec['pgram_f'], - } - except KeyError: - print("No periodigram data found. Don't use fns that require it...") - - # Try loading aggregate max spectral data - try: - self.spec['max_agg'] = { - 'p': data_spec['max_agg'][0], - 't': data_spec['max_agg'][1], - 'f': data_spec['max_agg'][2], - } - - except KeyError: - print("No aggregate max spectral data found. Don't use fns that require it...") - - elif self.dtype == 'current': - self.spec = { - 'L2': { - 't': data_spec['t_L2'], - 'f': data_spec['f_L2'], - 'TFR': data_spec['TFR_L2'], - }, - - 'L5': { - 't': data_spec['t_L5'], - 'f': data_spec['f_L5'], - 'TFR': data_spec['TFR_L5'], - } - } - - # Truncate t, f, and TFR for a specific layer over specified t and f intervals - # Be warned: MODIFIES THE CLASS INTERNALLY - def truncate(self, layer, t_interval, f_interval): - self.spec[layer] = self.truncate_ext(layer, t_interval, f_interval) - - # Truncate t, f, and TFR for a specific layer over specified t and f intervals - # Only returns truncated values. DOES NOT MODIFY THE CLASS INTERNALLY - def truncate_ext(self, layer, t_interval, f_interval): - # set f_max and f_min - if f_interval is None: - f_min = self.spec[layer]['f'][0] - f_max = self.spec[layer]['f'][-1] - - else: - f_min, f_max = f_interval - - # create an f_mask for the bounds of f, inclusive - f_mask = (self.spec[layer]['f']>=f_min) & (self.spec[layer]['f']<=f_max) - - # do the same for t - if t_interval is None: - t_min = self.spec[layer]['t'][0] - t_max = self.spec[layer]['t'][-1] - - else: - t_min, t_max = t_interval - - t_mask = (self.spec[layer]['t']>=t_min) & (self.spec[layer]['t']<=t_max) - - # use the masks truncate these appropriately - TFR_fcut = self.spec[layer]['TFR'][f_mask, :] - TFR_tfcut = TFR_fcut[:, t_mask] - - f_fcut = self.spec[layer]['f'][f_mask] - t_tcut = self.spec[layer]['t'][t_mask] - - return { - 't': t_tcut, - 'f': f_fcut, - 'TFR': TFR_tfcut, - } - - # find the max spectral power over specified time and frequency intervals - def max(self, layer, t_interval=None, f_interval=None, f_sort=None): - # If f_sort not provided, sort over all frequencies - if not f_sort: - f_sort = (self.spec['agg']['f'][0], self.spec['agg']['f'][-1]) - - # If f_sort is -1, assume upper abound is highest frequency - elif f_sort[1] < 0: - f_sort[1] = self.spec['agg']['f'][-1] - - # Only continue if absolute max of spectral power occurs at f in range of f_sorted - # Add +1 to f_sort[0] so range is inclusive - if self.spec['max_agg']['f'] not in np.arange(f_sort[0], f_sort[1]+1): - print("%s's absolute max spectral pwr does not occur between %i-%i Hz." %(self.fname, f_sort[0], f_sort[1])) - - else: - print("Warning: you are using max() in Spec(). It should be changed from == to np.isclose()") - # truncate data based on specified intervals - dcut = self.truncate_ext(layer, t_interval, f_interval) - - # find the max power over this new range - pwr_max = dcut['TFR'].max() - max_mask = (dcut['TFR'] == pwr_max) - - # find the t and f at max - # these are slightly crude and do not allow for the possibility of multiple maxes (rare?) - t_at_max = dcut['t'][max_mask.sum(axis=0) == 1][0] - f_at_max = dcut['f'][max_mask.sum(axis=1) == 1][0] - - # if f_interval provided and lower bound is not zero, set pd_at_max with lower bound: - # otherwise set it based on f_at_max - if f_interval and f_interval[0] > 0: - pd_at_max = 1000./f_interval[0] - else: - pd_at_max = 1000./f_at_max - - t_start = t_at_max - pd_at_max - t_end = t_at_max + pd_at_max - - # output structure - data_max = { - 'fname': self.fname, - 'pwr': pwr_max, - 't_int': [t_start, t_end], - 't_at_max': t_at_max, - 'f_at_max': f_at_max, - } - - return data_max - - # Averages spectral power over specified time interval for specified frequencies - def stationary_avg(self, layer='agg', t_interval=None, f_interval=None): - print("Warning: you are using stationary_avg() in Spec(). It should be changed from == to np.isclose()") - - # truncate data based on specified intervals - dcut = self.truncate_ext(layer, t_interval, f_interval) - - # avg TFR pwr over time - # axis = 1 sums over columns - pwr_avg = dcut['TFR'].sum(axis=1) / len(dcut['t']) - - # Get max pwr and freq at which max pwr occurs - pwr_max = pwr_avg.max() - f_at_max = dcut['f'][pwr_avg == pwr_max] - - return { - 'p_avg': pwr_avg, - 'p_max': pwr_max, - 'f_max': f_at_max, - 'freq': dcut['f'], - 'expmt': self.expmt, - } - - def plot_TFR(self, ax, layer='agg', xlim=None, ylim=None): - # truncate data based on specifed xlim and ylim - # xlim is a time interval - # ylim is a frequency interval - dcut = self.truncate_ext(layer, xlim, ylim) - - # Update xlim to have values guaranteed to exist - xlim_new = (dcut['t'][0], dcut['t'][-1]) - xmin, xmax = xlim_new - - # Update ylim to have values guaranteed to exist - ylim_new = (dcut['f'][0], dcut['f'][-1]) - ymin, ymax = ylim_new - - # set extent of plot - # order is ymax, ymin so y-axis is inverted - extent_xy = [xmin, xmax, ymax, ymin] - - # plot - im = ax.imshow(dcut['TFR'], extent=extent_xy, aspect='auto', origin='upper', cmap=plt.get_cmap(self.spec_cmap)) - - return im - - def plot_pgram(self, ax, f_max=None): - # If f_max is not supplied, set it to highest freq of aggregate analysis - if f_max is None: - f_max = self.spec['agg']['f'][-1] - - # plot - ax.plot(self.spec['pgram']['f'], self.spec['pgram']['p']) - ax.set_xlim((0., f_max)) # core class for frequency analysis assuming stationary time series class Welch(): @@ -470,64 +157,19 @@ def __init__(self, t_vec, ts_vec, dt): self.N = len(ts_vec) else: - # raise an exception for real sometime in the future, for now just say something - print("in specfn.Welch(), your lengths don't match! Something will fail!") - - # in fact, this will fail (see above) - # self.N_fft = self.__nextpow2(self.N) + # raise an exception for real sometime in the future, for now + # just say something + print("in specfn.Welch(), your lengths don't match! Something" + " will fail!") # grab the dt (in ms) and calc sampling frequency self.fs = 1000. / self.dt # calculate the actual Welch - self.f, self.P = sps.welch(self.ts_vec, self.fs, window='hanning', nperseg=self.N, noverlap=0, nfft=self.N, return_onesided=True, scaling='spectrum') - - # simple plot to an axis - def plot_to_ax(self, ax, f_max=80.): - ax.plot(self.f, self.P) - ax.set_xlim((0., f_max)) - - def scale(self, scalefactor): - self.P *= scalefactor - self.units += ' x%3.4e' % scalefactor - - # return the next power of 2 generally for a given L - def __nextpow2(self, L): - n = 2 - # j = 1 - while n < L: - # j += 1 - n *= 2 - - return n - # return n, j - -# general spec write function -def write(fdata_spec, t_vec, f_vec, TFR): - np.savez_compressed(fdata_spec, time=t_vec, freq=f_vec, TFR=TFR) - -# general spec read function -def read(fdata_spec, type='dpl'): - if type == 'dpl': - data_spec = np.load(fdata_spec) - return data_spec - - elif type == 'current': - # split this up into 2 spec types - data_spec = np.load(fdata_spec) - spec_L2 = { - 't': data_spec['t_L2'], - 'f': data_spec['f_L2'], - 'TFR': data_spec['TFR_L2'], - } - - spec_L5 = { - 't': data_spec['t_L5'], - 'f': data_spec['f_L5'], - 'TFR': data_spec['TFR_L5'], - } - - return spec_L2, spec_L5 + self.f, self.P = sps.welch(self.ts_vec, self.fs, window='hanning', + nperseg=self.N, noverlap=0, nfft=self.N, + return_onesided=True, scaling='spectrum') + # Kernel for spec analysis of dipole data # necessary for parallelization @@ -537,27 +179,35 @@ def spec_dpl_kernel(params, dpl, fspec, opts): # dpl.convert_fAm_to_nAm() # Generate various spec results - spec_agg = MorletSpec(dpl.times, dpl.data['agg'], opts['f_max'], p_dict=params) - spec_L2 = MorletSpec(dpl.times, dpl.data['L2'], opts['f_max'], p_dict=params) - spec_L5 = MorletSpec(dpl.times, dpl.data['L5'], opts['f_max'], p_dict=params) + spec_agg = MorletSpec(dpl.times, dpl.data['agg'], opts['f_max'], + p_dict=params) + spec_L2 = MorletSpec(dpl.times, dpl.data['L2'], opts['f_max'], + p_dict=params) + spec_L5 = MorletSpec(dpl.times, dpl.data['L5'], opts['f_max'], + p_dict=params) # Get max spectral power data - # for now, only doing this for agg - max_agg = spec_agg.max() + # BC (11/29/2020): no longer calculating this + max_agg = [] # Generate periodogram resutls pgram = Welch(dpl.times, dpl.data['agg'], params['dt']) # Save spec results - np.savez_compressed(fspec, time=spec_agg.t, freq=spec_agg.f, TFR=spec_agg.TFR, max_agg=max_agg, t_L2=spec_L2.t, f_L2=spec_L2.f, TFR_L2=spec_L2.TFR, t_L5=spec_L5.t, f_L5=spec_L5.f, TFR_L5=spec_L5.TFR, pgram_p=pgram.P, pgram_f=pgram.f) - -def analysis_simp (opts, params, fdpl, fspec): - opts_run = {'type': 'dpl_laminar', - 'f_max': 100., - 'save_data': 0, - 'runtype': 'parallel', - } - if opts: + np.savez_compressed(fspec, time=spec_agg.t, freq=spec_agg.f, + TFR=spec_agg.TFR, max_agg=max_agg, + t_L2=spec_L2.t, f_L2=spec_L2.f, TFR_L2=spec_L2.TFR, + t_L5=spec_L5.t, f_L5=spec_L5.f, TFR_L5=spec_L5.TFR, + pgram_p=pgram.P, pgram_f=pgram.f) + + +def analysis_simp(opts, params, fdpl, fspec): + opts_run = {'type': 'dpl_laminar', + 'f_max': 100., + 'save_data': 0, + 'runtype': 'parallel', + } for key, val in opts.items(): - if key in opts_run.keys(): opts_run[key] = val - spec_dpl_kernel(params, fdpl, fspec, opts_run) + if key in opts_run.keys(): + opts_run[key] = val + spec_dpl_kernel(params, fdpl, fspec, opts_run) From 76f52ef92b8bd4756c6bdec88dce96f659ffcfd1 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Mon, 30 Nov 2020 21:04:06 -0500 Subject: [PATCH 059/107] MAINT: pass results from sim in memory Send a signal with the results of a simulation back to the main thread for storing in the SimData structure in memory. Avoid write/read cycles with simulation data. Fixed a bug in specfn.py that wrote the spec files multiple times unnecessarily! Flake8 changes to hnn_qt5.py --- hnn/hnn_qt5.py | 1829 ++++++++++++++++++++++++++---------------------- hnn/run.py | 141 +--- hnn/simdat.py | 86 ++- hnn/specfn.py | 9 +- hnn/spikefn.py | 4 +- 5 files changed, 1080 insertions(+), 989 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index e9f620646..406bed7c4 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -8,11 +8,11 @@ import sys import os import multiprocessing -from subprocess import Popen -from collections import namedtuple, OrderedDict import numpy as np import traceback -from psutil import cpu_count +from subprocess import Popen +from collections import namedtuple, OrderedDict +from copy import deepcopy # External libraries from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication @@ -25,10 +25,14 @@ from PyQt5.QtCore import pyqtSignal, QObject, Qt from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT import matplotlib.pyplot as plt -from hnn_core import read_params +from psutil import cpu_count +from hnn_core import read_params, read_spikes +from hnn_core.dipole import average_dipoles # HNN modules from .paramrw import usingOngoingInputs, usingEvokedInputs, get_output_dir +from .paramrw import write_gids_param, get_fname +from .specfn import analysis_simp from .simdat import SIMCanvas, SimData from .run import RunSimThread, ParamSignal from .qt_lib import setscalegeom, setscalegeomcenter, getmplDPI, getscreengeom @@ -1189,681 +1193,327 @@ def stopsim (self): class HNNGUI (QMainWindow): - # main HNN GUI class - def __init__ (self): - # initialize the main HNN GUI - - super().__init__() - sys.excepthook = self.excepthook - - global fontsize - - hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) - - self.runningsim = False - self.runthread = None - self.fontsize = fontsize - self.linewidth = plt.rcParams['lines.linewidth'] = 1 - self.markersize = plt.rcParams['lines.markersize'] = 5 - self.schemwin = SchematicDialog(self) - self.sim_canvas = self.toolbar = None - paramfn = os.path.join(hnn_root_dir, 'param', 'default.param') - self.baseparamwin = BaseParamDialog(self, paramfn, self.startoptmodel) - self.optMode = False - self.sim_data = SimData() - self.initUI() - self.helpwin = HelpDialog(self) - self.erselectdistal = EvokedOrRhythmicDialog(self, True, self.baseparamwin.evparamwin, self.baseparamwin.distparamwin) - self.erselectprox = EvokedOrRhythmicDialog(self, False, self.baseparamwin.evparamwin, self.baseparamwin.proxparamwin) - self.waitsimwin = WaitSimDialog(self) - - default_param = os.path.join(get_output_dir(), 'data', 'default') - first_load = not (os.path.exists(default_param)) - - if first_load: - QMessageBox.information(self, "HNN", "Welcome to HNN! Default parameter file loaded. " - "Press 'Run Simulation' to display simulation output") - else: - self.statusBar().showMessage("Loaded %s"%default_param) - # successful initialization, catch all further exceptions - - def excepthook(self, exc_type, exc_value, exc_tb): - enriched_tb = _add_missing_frames(exc_tb) if exc_tb else exc_tb - # Note: sys.__excepthook__(...) would not work here. - # We need to use print_exception(...): - traceback.print_exception(exc_type, exc_value, enriched_tb) - msgBox = QMessageBox(self) - msgBox.information(self, "Exception", "WARNING: an exception occurred! " - "Details can be found in the console output. Please " - "include this output when opening an issue on GitHub: " - "" - "https://github.com/jonescompneurolab/hnn/issues") - - - def redraw (self): - # redraw simulation & external data - self.sim_canvas.plot() - self.sim_canvas.draw() - - def changeFontSize (self): - # bring up window to change font sizes - global fontsize - - i, ok = QInputDialog.getInt(self, "Set Font Size","Font Size:", plt.rcParams['font.size'], 1, 100, 1) - if ok: - self.fontsize = plt.rcParams['font.size'] = fontsize = i - self.redraw() - - def changeLineWidth (self): - # bring up window to change line width(s) - i, ok = QInputDialog.getInt(self, "Set Line Width","Line Width:", plt.rcParams['lines.linewidth'], 1, 20, 1) - if ok: - self.linewidth = plt.rcParams['lines.linewidth'] = i - self.redraw() - - def changeMarkerSize (self): - # bring up window to change marker size - i, ok = QInputDialog.getInt(self, "Set Marker Size","Font Size:", self.markersize, 1, 100, 1) - if ok: - self.markersize = plt.rcParams['lines.markersize'] = i - self.redraw() - - def selParamFileDialog (self): - # bring up window to select simulation parameter file - - hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) - - qfd = QFileDialog() - qfd.setHistory([os.path.join(get_output_dir(), 'param'), - os.path.join(hnn_root_dir, 'param')]) - fn = qfd.getOpenFileName(self, 'Open param file', - os.path.join(hnn_root_dir,'param'), - "Param files (*.param)") - if len(fn) > 0 and fn[0] == '': - # no file selected in dialog - return - - tmpfn = os.path.abspath(fn[0]) + # main HNN GUI class + def __init__ (self): + # initialize the main HNN GUI - try: - params = read_params(tmpfn) - except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not" - "retrieve parameters from %s" % - tmpfn) - return + super().__init__() + sys.excepthook = self.excepthook - # Now update GUI components - self.baseparamwin.paramfn = tmpfn + global fontsize - # now update the GUI components to reflect the param file selected - self.baseparamwin.updateDispParam(params) - self.initSimCanvas() # recreate canvas - # self.sim_canvas.plot() # replot data - self.setWindowTitle(self.baseparamwin.paramfn) + hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) - self.populateSimCB() # populate the combobox + self.runningsim = False + self.runthread = None + self.fontsize = fontsize + self.linewidth = plt.rcParams['lines.linewidth'] = 1 + self.markersize = plt.rcParams['lines.markersize'] = 5 + self.schemwin = SchematicDialog(self) + self.sim_canvas = self.toolbar = None + paramfn = os.path.join(hnn_root_dir, 'param', 'default.param') + self.baseparamwin = BaseParamDialog(self, paramfn, self.startoptmodel) + self.optMode = False + self.sim_data = SimData() + self.initUI() + self.helpwin = HelpDialog(self) + self.erselectdistal = EvokedOrRhythmicDialog(self, True, self.baseparamwin.evparamwin, self.baseparamwin.distparamwin) + self.erselectprox = EvokedOrRhythmicDialog(self, False, self.baseparamwin.evparamwin, self.baseparamwin.proxparamwin) + self.waitsimwin = WaitSimDialog(self) + + default_param = os.path.join(get_output_dir(), 'data', 'default') + first_load = not (os.path.exists(default_param)) + + if first_load: + QMessageBox.information(self, "HNN", "Welcome to HNN! Default parameter file loaded. " + "Press 'Run Simulation' to display simulation output") + else: + self.statusBar().showMessage("Loaded %s"%default_param) + # successful initialization, catch all further exceptions + + def excepthook(self, exc_type, exc_value, exc_tb): + enriched_tb = _add_missing_frames(exc_tb) if exc_tb else exc_tb + # Note: sys.__excepthook__(...) would not work here. + # We need to use print_exception(...): + traceback.print_exception(exc_type, exc_value, enriched_tb) + msgBox = QMessageBox(self) + msgBox.information(self, "Exception", "WARNING: an exception occurred! " + "Details can be found in the console output. Please " + "include this output when opening an issue on GitHub: " + "" + "https://github.com/jonescompneurolab/hnn/issues") + + + def redraw(self): + # redraw simulation & external data + self.sim_canvas.plot() + self.sim_canvas.draw() - if self.sim_data.get_exp_data_size() > 0: - self.toggleEnableOptimization(True) + def changeFontSize(self): + # bring up window to change font sizes + global fontsize + + i, ok = QInputDialog.getInt(self, "Set Font Size","Font Size:", plt.rcParams['font.size'], 1, 100, 1) + if ok: + self.fontsize = plt.rcParams['font.size'] = fontsize = i + self.redraw() + + def changeLineWidth(self): + # bring up window to change line width(s) + i, ok = QInputDialog.getInt(self, "Set Line Width","Line Width:", plt.rcParams['lines.linewidth'], 1, 20, 1) + if ok: + self.linewidth = plt.rcParams['lines.linewidth'] = i + self.redraw() + + def changeMarkerSize(self): + # bring up window to change marker size + i, ok = QInputDialog.getInt(self, "Set Marker Size","Font Size:", self.markersize, 1, 100, 1) + if ok: + self.markersize = plt.rcParams['lines.markersize'] = i + self.redraw() + + def selParamFileDialog(self): + # bring up window to select simulation parameter file + + hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) + + qfd = QFileDialog() + qfd.setHistory([os.path.join(get_output_dir(), 'param'), + os.path.join(hnn_root_dir, 'param')]) + fn = qfd.getOpenFileName(self, 'Open param file', + os.path.join(hnn_root_dir,'param'), + "Param files (*.param)") + if len(fn) > 0 and fn[0] == '': + # no file selected in dialog + return - def loadDataFile (self, fn): - # load a dipole data file + tmpfn = os.path.abspath(fn[0]) - extdata = None - try: - extdata = np.loadtxt(fn) - except ValueError: - # possible that data file is comma delimted instead of whitespace delimted try: - extdata = np.loadtxt(fn, delimiter=',') + params = read_params(tmpfn) except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not load data file %s" % fn) - return False - except IsADirectoryError: - QMessageBox.information(self, "HNN", "WARNING: could not load data file %s" % fn) - return False - - self.sim_data.update_exp_data(fn, extdata) - print('Loaded data in ', fn) - - self.sim_canvas.plot() - self.sim_canvas.draw() # make sure new lines show up in plot - - if self.baseparamwin.paramfn: - self.toggleEnableOptimization(True) - return True - - def loadDataFileDialog (self): - # bring up window to select/load external dipole data file - hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) - - qfd = QFileDialog() - qfd.setHistory([os.path.join(get_output_dir(), 'data'), - os.path.join(hnn_root_dir, 'data')]) - fn = qfd.getOpenFileName(self, 'Open data file', - os.path.join(hnn_root_dir,'data'), - "Data files (*.txt)") - if len(fn) > 0 and fn[0] == '': - # no file selected in dialog - return - - self.loadDataFile(os.path.abspath(fn[0])) # use abspath to make sure have right path separators - - def clearDataFile (self): - # clear external dipole data - self.sim_canvas.clearlextdatobj() - self.sim_data.clear_exp_data() - self.toggleEnableOptimization(False) - self.sim_canvas.plot() # recreate canvas - self.sim_canvas.draw() - - def setparams (self): - # show set parameters dialog window - if self.baseparamwin: - for win in self.baseparamwin.lsubwin: bringwintobot(win) - bringwintotop(self.baseparamwin) - - def showAboutDialog (self): - # show HNN's about dialog box - from hnn import __version__ - msgBox = QMessageBox(self) - msgBox.setTextFormat(Qt.RichText) - msgBox.setWindowTitle('About') - msgBox.setText("Human Neocortical Neurosolver (HNN) v" + __version__ + "
"+\ - "https://hnn.brown.edu
"+\ - "HNN On Github
"+\ - "© 2017-2019 Brown University, Providence, RI
"+\ - "Software License") - msgBox.setStandardButtons(QMessageBox.Ok) - msgBox.exec_() - - def showOptWarnDialog (self): - # TODO : not implemented yet - msgBox = QMessageBox(self) - msgBox.setTextFormat(Qt.RichText) - msgBox.setWindowTitle('Warning') - msgBox.setText("") - msgBox.setStandardButtons(QMessageBox.Ok) - msgBox.exec_() - - def showHelpDialog (self): - # show the help dialog box - bringwintotop(self.helpwin) - - def showSomaVPlot (self): - # start the somatic voltage visualization process (separate window) - if not float(self.baseparamwin.runparamwin.getval('save_vsoma')): - smsg='In order to view somatic voltages you must first rerun the simulation with saving somatic voltages. To do so from the main GUI, click on Set Parameters -> Run -> Analysis -> Save Somatic Voltages, enter a 1 and then rerun the simulation.' - msg = QMessageBox() - msg.setIcon(QMessageBox.Information) - msg.setText(smsg) - msg.setWindowTitle('Rerun simulation') - msg.setStandardButtons(QMessageBox.Ok) - msg.exec_() - else: - outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) - outparamf = os.path.join(outdir, - self.baseparamwin.params['sim_prefix'] + - '.param') - lcmd = [getPyComm(), 'visvolt.py',outparamf] - Popen(lcmd) # nonblocking - - def showPSDPlot (self): - # start the PSD visualization process (separate window) - outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) - outparamf = os.path.join(outdir, - self.baseparamwin.params['sim_prefix'] + - '.param') - lcmd = [getPyComm(), 'vispsd.py',outparamf] - Popen(lcmd) # nonblocking - - def showSpecPlot (self): - # start the spectrogram visualization process (separate window) - outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) - outparamf = os.path.join(outdir, - self.baseparamwin.params['sim_prefix'] + - '.param') - lcmd = [getPyComm(), 'visspec.py',outparamf] - Popen(lcmd) # nonblocking - - def showRasterPlot (self): - # start the raster plot visualization process (separate window) - global drawindivrast - - outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) - spikefile = os.path.join(outdir,'spk.txt') - if os.path.isfile(spikefile): - outparamf = os.path.join(outdir, - self.baseparamwin.params['sim_prefix'] + - '.param') - lcmd = [getPyComm(), 'visrast.py',outparamf,spikefile] - else: - QMessageBox.information(self, "HNN", "WARNING: no spiking data at %s" % spikefile) - return - - if drawindivrast: - lcmd.append('indiv') - Popen(lcmd) # nonblocking - - def showDipolePlot (self): - # start the dipole visualization process (separate window) - - outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) - dipole_file = os.path.join(outdir,'dpl.txt') - if os.path.isfile(dipole_file): - outparamf = os.path.join(outdir, - self.baseparamwin.params['sim_prefix'] + - '.param') - lcmd = [getPyComm(), 'visdipole.py',outparamf,dipole_file] - else: - QMessageBox.information(self, "HNN", "WARNING: no dipole data at %s" % dipole_file) - return + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + tmpfn) + return - Popen(lcmd) # nonblocking + # Now update GUI components + self.baseparamwin.paramfn = tmpfn - def showwaitsimwin (self): - # show the wait sim window (has simulation log) - bringwintotop(self.waitsimwin) + # now update the GUI components to reflect the param file selected + self.baseparamwin.updateDispParam(params) + self.initSimCanvas() # recreate canvas + # self.sim_canvas.plot() # replot data + self.setWindowTitle(self.baseparamwin.paramfn) - def togAvgDpl (self): - # toggle drawing of the average (across trials) dipole - global drawavgdpl + self.populateSimCB() # populate the combobox - drawavgdpl = not drawavgdpl - self.sim_canvas.plot() - self.sim_canvas.draw() - - def hidesubwin (self): - # hide GUI's sub windows - self.baseparamwin.hide() - self.schemwin.hide() - self.baseparamwin.syngainparamwin.hide() - for win in self.baseparamwin.lsubwin: win.hide() - self.activateWindow() - - def distribsubwin (self): - # distribute GUI's sub-windows on screen - sw,sh = getscreengeom() - lwin = [win for win in self.baseparamwin.lsubwin if win.isVisible()] - if self.baseparamwin.isVisible(): lwin.insert(0,self.baseparamwin) - if self.schemwin.isVisible(): lwin.insert(0,self.schemwin) - if self.baseparamwin.syngainparamwin.isVisible(): lwin.append(self.baseparamwin.syngainparamwin) - curx,cury,maxh=0,0,0 - for win in lwin: - win.move(curx, cury) - curx += win.width() - maxh = max(maxh,win.height()) - if curx >= sw: - curx = 0 - cury += maxh - maxh = win.height() - if cury >= sh: cury = cury = 0 - - def updateDatCanv(self, params): - # now update the GUI components to reflect the param file selected - self.baseparamwin.updateDispParam(params) - self.initSimCanvas() # recreate canvas - self.setWindowTitle(self.baseparamwin.paramfn) - - def updateSelectedSim(self, sim_idx): - """Update the sim shown in the ComboBox""" - - paramfn = self.cbsim.itemText(sim_idx) - try: - params = read_params(paramfn) - except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not" - "retrieve parameters from %s" % - paramfn) - return - self.baseparamwin.paramfn = paramfn - - # update GUI - self.updateDatCanv(params) - self.cbsim.setCurrentIndex(sim_idx) - - def removeSim(self): - """Remove the currently selected simulation""" - - sim_idx = self.cbsim.currentIndex() - paramfn = self.cbsim.itemText(sim_idx) - if not paramfn == '': - self.sim_data.remove_sim_by_fn(paramfn) - - self.cbsim.removeItem(sim_idx) - - # go to last entry - new_simidx = self.cbsim.count() - 1 - if new_simidx < 0: - self.clearSimulations() - else: - self.updateSelectedSim(new_simidx) - - def prevSim(self): - """Go to previous simulation""" - - new_simidx = self.cbsim.currentIndex() - 1 - if new_simidx < 0: - print("There is no previous simulation") - return - else: - self.updateSelectedSim(new_simidx) - - def nextSim (self): - # go to next simulation - - if self.cbsim.currentIndex() + 2 > self.cbsim.count(): - print("There is no next simulation") - return - else: - new_simidx = self.cbsim.currentIndex() + 1 - self.updateSelectedSim(new_simidx) - - def clearSimulationData (self): - - # clear the simulation data - self.baseparamwin.params = None - self.baseparamwin.paramfn = None - - self.sim_data.clear_sim_data() - self.cbsim.clear() # un-populate the combobox - self.toggleEnableOptimization(False) - - - def clearSimulations (self): - # clear all simulation data and erase simulations from canvas (does not clear external data) - self.clearSimulationData() - self.initSimCanvas() # recreate canvas - self.sim_canvas.draw() - self.setWindowTitle('') - - def clearCanvas (self): - # clear all simulation & external data and erase everything from the canvas - self.sim_canvas.clearlextdatobj() # clear the external data - self.clearSimulationData() - self.sim_data.clear_exp_data() - self.initSimCanvas() # recreate canvas - self.sim_canvas.draw() - self.setWindowTitle('') - - def initMenu (self): - # initialize the GUI's menu - exitAction = QAction(QIcon.fromTheme('exit'), 'Exit', self) - exitAction.setShortcut('Ctrl+Q') - exitAction.setStatusTip('Exit HNN application') - exitAction.triggered.connect(qApp.quit) - - selParamFile = QAction(QIcon.fromTheme('open'), 'Load parameter file', self) - selParamFile.setShortcut('Ctrl+P') - selParamFile.setStatusTip('Load simulation parameter (.param) file') - selParamFile.triggered.connect(self.selParamFileDialog) - - clearCanv = QAction('Clear canvas', self) - clearCanv.setShortcut('Ctrl+X') - clearCanv.setStatusTip('Clear canvas (simulation+data)') - clearCanv.triggered.connect(self.clearCanvas) - - clearSims = QAction('Clear simulation(s)', self) - #clearSims.setShortcut('Ctrl+X') - clearSims.setStatusTip('Clear simulation(s)') - clearSims.triggered.connect(self.clearSimulations) - - loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data file', self) - loadDataFile.setShortcut('Ctrl+D') - loadDataFile.setStatusTip('Load (dipole) data file') - loadDataFile.triggered.connect(self.loadDataFileDialog) - - clearDataFileAct = QAction(QIcon.fromTheme('close'), 'Clear data file(s)', self) - clearDataFileAct.setShortcut('Ctrl+C') - clearDataFileAct.setStatusTip('Clear (dipole) data file(s)') - clearDataFileAct.triggered.connect(self.clearDataFile) - - runSimAct = QAction('Run simulation', self) - runSimAct.setShortcut('Ctrl+S') - runSimAct.setStatusTip('Run simulation') - runSimAct.triggered.connect(self.controlsim) - - self.menubar = self.menuBar() - fileMenu = self.menubar.addMenu('&File') - self.menubar.setNativeMenuBar(False) - fileMenu.addAction(selParamFile) - fileMenu.addSeparator() - fileMenu.addAction(loadDataFile) - fileMenu.addAction(clearDataFileAct) - fileMenu.addSeparator() - fileMenu.addAction(exitAction) - - # part of edit menu for changing drawing properties (line thickness, font size, toggle avg dipole drawing) - editMenu = self.menubar.addMenu('&Edit') - viewAvgDplAction = QAction('Toggle Average Dipole Drawing',self) - viewAvgDplAction.setStatusTip('Toggle Average Dipole Drawing') - viewAvgDplAction.triggered.connect(self.togAvgDpl) - editMenu.addAction(viewAvgDplAction) - changeFontSizeAction = QAction('Change Font Size',self) - changeFontSizeAction.setStatusTip('Change Font Size.') - changeFontSizeAction.triggered.connect(self.changeFontSize) - editMenu.addAction(changeFontSizeAction) - changeLineWidthAction = QAction('Change Line Width',self) - changeLineWidthAction.setStatusTip('Change Line Width.') - changeLineWidthAction.triggered.connect(self.changeLineWidth) - editMenu.addAction(changeLineWidthAction) - changeMarkerSizeAction = QAction('Change Marker Size',self) - changeMarkerSizeAction.setStatusTip('Change Marker Size.') - changeMarkerSizeAction.triggered.connect(self.changeMarkerSize) - editMenu.addAction(changeMarkerSizeAction) - editMenu.addSeparator() - editMenu.addAction(clearSims) - clearDataFileAct2 = QAction(QIcon.fromTheme('close'), 'Clear data file(s)', self) # need new act to avoid DBus warning - clearDataFileAct2.setStatusTip('Clear (dipole) data file(s)') - clearDataFileAct2.triggered.connect(self.clearDataFile) - editMenu.addAction(clearDataFileAct2) - editMenu.addAction(clearCanv) - - # view menu - to view drawing/visualizations - viewMenu = self.menubar.addMenu('&View') - self.viewDipoleAction = QAction('View Simulation Dipoles',self) - self.viewDipoleAction.setStatusTip('View Simulation Dipoles') - self.viewDipoleAction.triggered.connect(self.showDipolePlot) - viewMenu.addAction(self.viewDipoleAction) - self.viewRasterAction = QAction('View Simulation Spiking Activity',self) - self.viewRasterAction.setStatusTip('View Simulation Raster Plot') - self.viewRasterAction.triggered.connect(self.showRasterPlot) - viewMenu.addAction(self.viewRasterAction) - self.viewPSDAction = QAction('View PSD',self) - self.viewPSDAction.setStatusTip('View PSD') - self.viewPSDAction.triggered.connect(self.showPSDPlot) - viewMenu.addAction(self.viewPSDAction) - - self.viewSomaVAction = QAction('View Somatic Voltage',self) - self.viewSomaVAction.setStatusTip('View Somatic Voltage') - self.viewSomaVAction.triggered.connect(self.showSomaVPlot) - viewMenu.addAction(self.viewSomaVAction) - - self.viewSpecAction = QAction('View Spectrograms',self) - self.viewSpecAction.setStatusTip('View Spectrograms/Dipoles from Experimental Data') - self.viewSpecAction.triggered.connect(self.showSpecPlot) - viewMenu.addAction(self.viewSpecAction) - - viewMenu.addSeparator() - viewSchemAction = QAction('View Model Schematics',self) - viewSchemAction.setStatusTip('View Model Schematics') - viewSchemAction.triggered.connect(self.showschematics) - viewMenu.addAction(viewSchemAction) - viewSimLogAction = QAction('View Simulation Log',self) - viewSimLogAction.setStatusTip('View Detailed Simulation Log') - viewSimLogAction.triggered.connect(self.showwaitsimwin) - viewMenu.addAction(viewSimLogAction) - viewMenu.addSeparator() - distributeWindowsAction = QAction('Distribute Windows',self) - distributeWindowsAction.setStatusTip('Distribute Parameter Windows Across Screen.') - distributeWindowsAction.triggered.connect(self.distribsubwin) - viewMenu.addAction(distributeWindowsAction) - hideWindowsAction = QAction('Hide Windows',self) - hideWindowsAction.setStatusTip('Hide Parameter Windows.') - hideWindowsAction.triggered.connect(self.hidesubwin) - hideWindowsAction.setShortcut('Ctrl+H') - viewMenu.addAction(hideWindowsAction) - - simMenu = self.menubar.addMenu('&Simulation') - setParmAct = QAction('Set Parameters',self) - setParmAct.setStatusTip('Set Simulation Parameters') - setParmAct.triggered.connect(self.setparams) - simMenu.addAction(setParmAct) - simMenu.addAction(runSimAct) - setOptParamAct = QAction('Configure Optimization', self) - setOptParamAct.setShortcut('Ctrl+O') - setOptParamAct.setStatusTip('Set parameters for evoked input optimization') - setOptParamAct.triggered.connect(self.showoptparamwin) - simMenu.addAction(setOptParamAct) - self.toggleEnableOptimization(False) - prevSimAct = QAction('Go to Previous Simulation',self) - prevSimAct.setShortcut('Ctrl+Z') - prevSimAct.setStatusTip('Go Back to Previous Simulation') - prevSimAct.triggered.connect(self.prevSim) - simMenu.addAction(prevSimAct) - nextSimAct = QAction('Go to Next Simulation',self) - nextSimAct.setShortcut('Ctrl+Y') - nextSimAct.setStatusTip('Go Forward to Next Simulation') - nextSimAct.triggered.connect(self.nextSim) - simMenu.addAction(nextSimAct) - clearSims2 = QAction('Clear simulation(s)', self) # need another QAction to avoid DBus warning - clearSims2.setStatusTip('Clear simulation(s)') - clearSims2.triggered.connect(self.clearSimulations) - simMenu.addAction(clearSims2) - - aboutMenu = self.menubar.addMenu('&About') - aboutAction = QAction('About HNN',self) - aboutAction.setStatusTip('About HNN') - aboutAction.triggered.connect(self.showAboutDialog) - aboutMenu.addAction(aboutAction) - helpAction = QAction('Help',self) - helpAction.setStatusTip('Help on how to use HNN (parameters).') - helpAction.triggered.connect(self.showHelpDialog) - #aboutMenu.addAction(helpAction) - - def toggleEnableOptimization (self, toEnable): - for menu in self.menubar.findChildren(QMenu): - if menu.title() == '&Simulation': - for item in menu.actions(): - if item.text() == 'Configure Optimization': - item.setEnabled(toEnable) - break - break - - def addButtons (self, gRow): - self.pbtn = pbtn = QPushButton('Set Parameters', self) - pbtn.setToolTip('Set Parameters') - pbtn.resize(pbtn.sizeHint()) - pbtn.clicked.connect(self.setparams) - self.grid.addWidget(self.pbtn, gRow, 0, 1, 3) - - self.pfbtn = pfbtn = QPushButton('Set Parameters From File', self) - pfbtn.setToolTip('Set Parameters From File') - pfbtn.resize(pfbtn.sizeHint()) - pfbtn.clicked.connect(self.selParamFileDialog) - self.grid.addWidget(self.pfbtn, gRow, 3, 1, 3) - - self.btnsim = btn = QPushButton('Run Simulation', self) - btn.setToolTip('Run Simulation') - btn.resize(btn.sizeHint()) - btn.clicked.connect(self.controlsim) - self.grid.addWidget(self.btnsim, gRow, 6, 1, 3) - - self.qbtn = qbtn = QPushButton('Quit', self) - qbtn.clicked.connect(QApplication.exit) - qbtn.resize(qbtn.sizeHint()) - self.grid.addWidget(self.qbtn, gRow, 9, 1, 3) - - def shownetparamwin (self): bringwintotop(self.baseparamwin.netparamwin) - def showoptparamwin (self): bringwintotop(self.baseparamwin.optparamwin) - def showdistparamwin (self): bringwintotop(self.erselectdistal) - def showproxparamwin (self): bringwintotop(self.erselectprox) - def showschematics (self): bringwintotop(self.schemwin) - - def addParamImageButtons (self,gRow): - # add parameter image buttons to the GUI + if self.sim_data.get_exp_data_size() > 0: + self.toggleEnableOptimization(True) - self.locbtn = QPushButton('Local Network'+os.linesep+'Connections',self) - self.locbtn.setIcon(QIcon(lookupresource('connfig'))) - self.locbtn.clicked.connect(self.shownetparamwin) - self.grid.addWidget(self.locbtn,gRow,0,1,4) + def loadDataFile(self, fn): + # load a dipole data file - self.proxbtn = QPushButton('Proximal Drive'+os.linesep+'Thalamus',self) - self.proxbtn.setIcon(QIcon(lookupresource('proxfig'))) - self.proxbtn.clicked.connect(self.showproxparamwin) - self.grid.addWidget(self.proxbtn,gRow,4,1,4) - - self.distbtn = QPushButton('Distal Drive Non3Lemniscal'+os.linesep+'Thal./Cortical Feedback',self) - self.distbtn.setIcon(QIcon(lookupresource('distfig'))) - self.distbtn.clicked.connect(self.showdistparamwin) - self.grid.addWidget(self.distbtn,gRow,8,1,4) - - gRow += 1 + extdata = None + try: + extdata = np.loadtxt(fn) + except ValueError: + # possible that data file is comma delimted instead of whitespace delimted + try: + extdata = np.loadtxt(fn, delimiter=',') + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not load data file %s" % fn) + return False + except IsADirectoryError: + QMessageBox.information(self, "HNN", "WARNING: could not load data file %s" % fn) + return False - def initUI (self): - # initialize the user interface (UI) + self.sim_data.update_exp_data(fn, extdata) + print('Loaded data in ', fn) - self.initMenu() - self.statusBar() + self.sim_canvas.plot() + self.sim_canvas.draw() # make sure new lines show up in plot - setscalegeomcenter(self, 1500, 1300) # start GUI in center of screenm, scale based on screen w x h + if self.baseparamwin.paramfn: + self.toggleEnableOptimization(True) + return True - # move param windows to be offset from main GUI - new_x = max(0, self.x() - 300) - new_y = max(0, self.y() + 100) - self.baseparamwin.move(new_x, new_y) - self.baseparamwin.evparamwin.move(new_x+50, new_y+50) - self.baseparamwin.optparamwin.move(new_x+100, new_y+100) - self.setWindowTitle(self.baseparamwin.paramfn) - QToolTip.setFont(QFont('SansSerif', 10)) + def loadDataFileDialog(self): + # bring up window to select/load external dipole data file + hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) - self.grid = grid = QGridLayout() - #grid.setSpacing(10) + qfd = QFileDialog() + qfd.setHistory([os.path.join(get_output_dir(), 'data'), + os.path.join(hnn_root_dir, 'data')]) + fn = qfd.getOpenFileName(self, 'Open data file', + os.path.join(hnn_root_dir,'data'), + "Data files (*.txt)") + if len(fn) > 0 and fn[0] == '': + # no file selected in dialog + return - gRow = 0 + self.loadDataFile(os.path.abspath(fn[0])) # use abspath to make sure have right path separators - self.addButtons(gRow) + def clearDataFile(self): + # clear external dipole data + self.sim_canvas.clearlextdatobj() + self.sim_data.clear_exp_data() + self.toggleEnableOptimization(False) + self.sim_canvas.plot() # recreate canvas + self.sim_canvas.draw() - gRow += 1 + def setparams(self): + # show set parameters dialog window + if self.baseparamwin: + for win in self.baseparamwin.lsubwin: bringwintobot(win) + bringwintotop(self.baseparamwin) + + def showAboutDialog(self): + # show HNN's about dialog box + from hnn import __version__ + msgBox = QMessageBox(self) + msgBox.setTextFormat(Qt.RichText) + msgBox.setWindowTitle('About') + msgBox.setText("Human Neocortical Neurosolver (HNN) v" + __version__ + "
"+\ + "https://hnn.brown.edu
"+\ + "HNN On Github
"+\ + "© 2017-2019 Brown University, Providence, RI
"+\ + "Software License") + msgBox.setStandardButtons(QMessageBox.Ok) + msgBox.exec_() + + def showOptWarnDialog(self): + # TODO : not implemented yet + msgBox = QMessageBox(self) + msgBox.setTextFormat(Qt.RichText) + msgBox.setWindowTitle('Warning') + msgBox.setText("") + msgBox.setStandardButtons(QMessageBox.Ok) + msgBox.exec_() + + def showHelpDialog(self): + # show the help dialog box + bringwintotop(self.helpwin) + + def showSomaVPlot(self): + # start the somatic voltage visualization process (separate window) + if not float(self.baseparamwin.runparamwin.getval('save_vsoma')): + smsg='In order to view somatic voltages you must first rerun the simulation with saving somatic voltages. To do so from the main GUI, click on Set Parameters -> Run -> Analysis -> Save Somatic Voltages, enter a 1 and then rerun the simulation.' + msg = QMessageBox() + msg.setIcon(QMessageBox.Information) + msg.setText(smsg) + msg.setWindowTitle('Rerun simulation') + msg.setStandardButtons(QMessageBox.Ok) + msg.exec_() + else: + outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) + outparamf = os.path.join(outdir, + self.baseparamwin.params['sim_prefix'] + + '.param') + lcmd = [getPyComm(), 'visvolt.py',outparamf] + Popen(lcmd) # nonblocking + + def showPSDPlot(self): + # start the PSD visualization process (separate window) + outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) + outparamf = os.path.join(outdir, + self.baseparamwin.params['sim_prefix'] + + '.param') + lcmd = [getPyComm(), 'vispsd.py',outparamf] + Popen(lcmd) # nonblocking - self.initSimCanvas(gRow=gRow, reInit=False) - gRow += 2 + def showSpecPlot(self): + # start the spectrogram visualization process (separate window) + outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) + outparamf = os.path.join(outdir, + self.baseparamwin.params['sim_prefix'] + + '.param') + lcmd = [getPyComm(), 'visspec.py',outparamf] + Popen(lcmd) # nonblocking - self.cbsim = QComboBox(self) - self.populateSimCB() # populate the combobox - self.cbsim.activated[str].connect(self.onActivateSimCB) - self.grid.addWidget(self.cbsim, gRow, 0, 1, 8)#, 1, 3) - self.btnrmsim = QPushButton('Remove Simulation',self) - self.btnrmsim.resize(self.btnrmsim.sizeHint()) - self.btnrmsim.clicked.connect(self.removeSim) - self.btnrmsim.setToolTip('Remove Currently Selected Simulation') - self.grid.addWidget(self.btnrmsim, gRow, 8, 1, 4) + def showRasterPlot(self): + # start the raster plot visualization process (separate window) + global drawindivrast - gRow += 1 - self.addParamImageButtons(gRow) + outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) + spikefile = os.path.join(outdir,'spk.txt') + if os.path.isfile(spikefile): + outparamf = os.path.join(outdir, + self.baseparamwin.params['sim_prefix'] + + '.param') + lcmd = [getPyComm(), 'visrast.py',outparamf,spikefile] + else: + QMessageBox.information(self, "HNN", "WARNING: no spiking data at %s" % spikefile) + return - # need a separate widget to put grid on - widget = QWidget(self) - widget.setLayout(grid) - self.setCentralWidget(widget) + if drawindivrast: + lcmd.append('indiv') + Popen(lcmd) # nonblocking - self.param_signal = ParamSignal() - self.param_signal.psig.connect(self.baseparamwin.updateDispParam) + def showDipolePlot(self): + # start the dipole visualization process (separate window) - self.done_signal = DoneSignal() - self.done_signal.finishSim.connect(self.done) + outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) + dipole_file = os.path.join(outdir,'dpl.txt') + if os.path.isfile(dipole_file): + outparamf = os.path.join(outdir, + self.baseparamwin.params['sim_prefix'] + + '.param') + lcmd = [getPyComm(), 'visdipole.py',outparamf,dipole_file] + else: + QMessageBox.information(self, "HNN", "WARNING: no dipole data at %s" % dipole_file) + return - self.setWindowIcon(QIcon(os.path.join('res', 'icon.png'))) + Popen(lcmd) # nonblocking - self.schemwin.show() # so it's underneath main window + def showwaitsimwin(self): + # show the wait sim window (has simulation log) + bringwintotop(self.waitsimwin) - self.show() + def togAvgDpl(self): + # toggle drawing of the average (across trials) dipole + global drawavgdpl - def onActivateSimCB(self, paramfn): - # load simulation when activating simulation combobox + drawavgdpl = not drawavgdpl + self.sim_canvas.plot() + self.sim_canvas.draw() - if paramfn != self.baseparamwin.paramfn: + def hidesubwin(self): + # hide GUI's sub windows + self.baseparamwin.hide() + self.schemwin.hide() + self.baseparamwin.syngainparamwin.hide() + for win in self.baseparamwin.lsubwin: win.hide() + self.activateWindow() + + def distribsubwin(self): + # distribute GUI's sub-windows on screen + sw,sh = getscreengeom() + lwin = [win for win in self.baseparamwin.lsubwin if win.isVisible()] + if self.baseparamwin.isVisible(): lwin.insert(0,self.baseparamwin) + if self.schemwin.isVisible(): lwin.insert(0,self.schemwin) + if self.baseparamwin.syngainparamwin.isVisible(): lwin.append(self.baseparamwin.syngainparamwin) + curx,cury,maxh=0,0,0 + for win in lwin: + win.move(curx, cury) + curx += win.width() + maxh = max(maxh,win.height()) + if curx >= sw: + curx = 0 + cury += maxh + maxh = win.height() + if cury >= sh: cury = cury = 0 + + def updateDatCanv(self, params): + # now update the GUI components to reflect the param file selected + self.baseparamwin.updateDispParam(params) + self.initSimCanvas() # recreate canvas + self.setWindowTitle(self.baseparamwin.paramfn) + + def updateSelectedSim(self, sim_idx): + """Update the sim shown in the ComboBox""" + + paramfn = self.cbsim.itemText(sim_idx) try: params = read_params(paramfn) except ValueError: @@ -1873,225 +1523,702 @@ def onActivateSimCB(self, paramfn): return self.baseparamwin.paramfn = paramfn + # update GUI self.updateDatCanv(params) + self.cbsim.setCurrentIndex(sim_idx) - def populateSimCB(self, index=None): - # populate the simulation combobox - - self.cbsim.clear() - for paramfn in self.sim_data._sim_data.keys(): - self.cbsim.addItem(paramfn) + def removeSim(self): + """Remove the currently selected simulation""" - if self.cbsim.count() == 0: - raise ValueError("No simulations to add to combo box") + sim_idx = self.cbsim.currentIndex() + paramfn = self.cbsim.itemText(sim_idx) + if not paramfn == '': + self.sim_data.remove_sim_by_fn(paramfn) - if index is None: - # set to last entry - self.cbsim.setCurrentIndex(self.cbsim.count() - 1) - else: - self.cbsim.setCurrentIndex(index) + self.cbsim.removeItem(sim_idx) - def initSimCanvas(self, recalcErr=True, optMode=False, gRow=1, reInit=True): - # initialize the simulation canvas, loading any required data - gCol = 0 + # go to last entry + new_simidx = self.cbsim.count() - 1 + if new_simidx < 0: + self.clearSimulations() + else: + self.updateSelectedSim(new_simidx) - if reInit == True: - self.grid.itemAtPosition(gRow, gCol).widget().deleteLater() - self.grid.itemAtPosition(gRow + 1, gCol).widget().deleteLater() + def prevSim(self): + """Go to previous simulation""" - # if just initialized or after clearSimulationData - if self.baseparamwin.paramfn and self.baseparamwin.params is None: - try: - self.baseparamwin.params = read_params(self.baseparamwin.paramfn) - except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not" - "retrieve parameters from %s" % - self.baseparamwin.paramfn) + new_simidx = self.cbsim.currentIndex() - 1 + if new_simidx < 0: + print("There is no previous simulation") return + else: + self.updateSelectedSim(new_simidx) + + def nextSim (self): + # go to next simulation + + if self.cbsim.currentIndex() + 2 > self.cbsim.count(): + print("There is no next simulation") + return + else: + new_simidx = self.cbsim.currentIndex() + 1 + self.updateSelectedSim(new_simidx) + + def clearSimulationData (self): + + # clear the simulation data + self.baseparamwin.params = None + self.baseparamwin.paramfn = None + + self.sim_data.clear_sim_data() + self.cbsim.clear() # un-populate the combobox + self.toggleEnableOptimization(False) - self.sim_canvas = SIMCanvas(self.baseparamwin.paramfn, self.baseparamwin.params, - parent=self, width=10, height=1, dpi=getmplDPI(), - optMode=optMode) - # this is the Navigation widget - # it takes the Canvas widget and a parent - self.toolbar = NavigationToolbar2QT(self.sim_canvas, self) - gWidth = 12 - self.grid.addWidget(self.toolbar, gRow, gCol, 1, gWidth) - self.grid.addWidget(self.sim_canvas, gRow + 1, gCol, 1, gWidth) - if self.sim_data.get_exp_data_size() > 0: - self.sim_canvas.plot(recalcErr) + + def clearSimulations (self): + # clear all simulation data and erase simulations from canvas (does not clear external data) + self.clearSimulationData() + self.initSimCanvas() # recreate canvas + self.sim_canvas.draw() + self.setWindowTitle('') + + def clearCanvas (self): + # clear all simulation & external data and erase everything from the canvas + self.sim_canvas.clearlextdatobj() # clear the external data + self.clearSimulationData() + self.sim_data.clear_exp_data() + self.initSimCanvas() # recreate canvas self.sim_canvas.draw() + self.setWindowTitle('') + + def initMenu (self): + # initialize the GUI's menu + exitAction = QAction(QIcon.fromTheme('exit'), 'Exit', self) + exitAction.setShortcut('Ctrl+Q') + exitAction.setStatusTip('Exit HNN application') + exitAction.triggered.connect(qApp.quit) + + selParamFile = QAction(QIcon.fromTheme('open'), 'Load parameter file', self) + selParamFile.setShortcut('Ctrl+P') + selParamFile.setStatusTip('Load simulation parameter (.param) file') + selParamFile.triggered.connect(self.selParamFileDialog) + + clearCanv = QAction('Clear canvas', self) + clearCanv.setShortcut('Ctrl+X') + clearCanv.setStatusTip('Clear canvas (simulation+data)') + clearCanv.triggered.connect(self.clearCanvas) + + clearSims = QAction('Clear simulation(s)', self) + #clearSims.setShortcut('Ctrl+X') + clearSims.setStatusTip('Clear simulation(s)') + clearSims.triggered.connect(self.clearSimulations) + + loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data file', self) + loadDataFile.setShortcut('Ctrl+D') + loadDataFile.setStatusTip('Load (dipole) data file') + loadDataFile.triggered.connect(self.loadDataFileDialog) + + clearDataFileAct = QAction(QIcon.fromTheme('close'), 'Clear data file(s)', self) + clearDataFileAct.setShortcut('Ctrl+C') + clearDataFileAct.setStatusTip('Clear (dipole) data file(s)') + clearDataFileAct.triggered.connect(self.clearDataFile) + + runSimAct = QAction('Run simulation', self) + runSimAct.setShortcut('Ctrl+S') + runSimAct.setStatusTip('Run simulation') + runSimAct.triggered.connect(self.controlsim) + + self.menubar = self.menuBar() + fileMenu = self.menubar.addMenu('&File') + self.menubar.setNativeMenuBar(False) + fileMenu.addAction(selParamFile) + fileMenu.addSeparator() + fileMenu.addAction(loadDataFile) + fileMenu.addAction(clearDataFileAct) + fileMenu.addSeparator() + fileMenu.addAction(exitAction) + + # part of edit menu for changing drawing properties (line thickness, font size, toggle avg dipole drawing) + editMenu = self.menubar.addMenu('&Edit') + viewAvgDplAction = QAction('Toggle Average Dipole Drawing',self) + viewAvgDplAction.setStatusTip('Toggle Average Dipole Drawing') + viewAvgDplAction.triggered.connect(self.togAvgDpl) + editMenu.addAction(viewAvgDplAction) + changeFontSizeAction = QAction('Change Font Size',self) + changeFontSizeAction.setStatusTip('Change Font Size.') + changeFontSizeAction.triggered.connect(self.changeFontSize) + editMenu.addAction(changeFontSizeAction) + changeLineWidthAction = QAction('Change Line Width',self) + changeLineWidthAction.setStatusTip('Change Line Width.') + changeLineWidthAction.triggered.connect(self.changeLineWidth) + editMenu.addAction(changeLineWidthAction) + changeMarkerSizeAction = QAction('Change Marker Size',self) + changeMarkerSizeAction.setStatusTip('Change Marker Size.') + changeMarkerSizeAction.triggered.connect(self.changeMarkerSize) + editMenu.addAction(changeMarkerSizeAction) + editMenu.addSeparator() + editMenu.addAction(clearSims) + clearDataFileAct2 = QAction(QIcon.fromTheme('close'), 'Clear data file(s)', self) # need new act to avoid DBus warning + clearDataFileAct2.setStatusTip('Clear (dipole) data file(s)') + clearDataFileAct2.triggered.connect(self.clearDataFile) + editMenu.addAction(clearDataFileAct2) + editMenu.addAction(clearCanv) + + # view menu - to view drawing/visualizations + viewMenu = self.menubar.addMenu('&View') + self.viewDipoleAction = QAction('View Simulation Dipoles',self) + self.viewDipoleAction.setStatusTip('View Simulation Dipoles') + self.viewDipoleAction.triggered.connect(self.showDipolePlot) + viewMenu.addAction(self.viewDipoleAction) + self.viewRasterAction = QAction('View Simulation Spiking Activity',self) + self.viewRasterAction.setStatusTip('View Simulation Raster Plot') + self.viewRasterAction.triggered.connect(self.showRasterPlot) + viewMenu.addAction(self.viewRasterAction) + self.viewPSDAction = QAction('View PSD',self) + self.viewPSDAction.setStatusTip('View PSD') + self.viewPSDAction.triggered.connect(self.showPSDPlot) + viewMenu.addAction(self.viewPSDAction) + + self.viewSomaVAction = QAction('View Somatic Voltage',self) + self.viewSomaVAction.setStatusTip('View Somatic Voltage') + self.viewSomaVAction.triggered.connect(self.showSomaVPlot) + viewMenu.addAction(self.viewSomaVAction) + + self.viewSpecAction = QAction('View Spectrograms',self) + self.viewSpecAction.setStatusTip('View Spectrograms/Dipoles from Experimental Data') + self.viewSpecAction.triggered.connect(self.showSpecPlot) + viewMenu.addAction(self.viewSpecAction) + + viewMenu.addSeparator() + viewSchemAction = QAction('View Model Schematics',self) + viewSchemAction.setStatusTip('View Model Schematics') + viewSchemAction.triggered.connect(self.showschematics) + viewMenu.addAction(viewSchemAction) + viewSimLogAction = QAction('View Simulation Log',self) + viewSimLogAction.setStatusTip('View Detailed Simulation Log') + viewSimLogAction.triggered.connect(self.showwaitsimwin) + viewMenu.addAction(viewSimLogAction) + viewMenu.addSeparator() + distributeWindowsAction = QAction('Distribute Windows',self) + distributeWindowsAction.setStatusTip('Distribute Parameter Windows Across Screen.') + distributeWindowsAction.triggered.connect(self.distribsubwin) + viewMenu.addAction(distributeWindowsAction) + hideWindowsAction = QAction('Hide Windows',self) + hideWindowsAction.setStatusTip('Hide Parameter Windows.') + hideWindowsAction.triggered.connect(self.hidesubwin) + hideWindowsAction.setShortcut('Ctrl+H') + viewMenu.addAction(hideWindowsAction) + + simMenu = self.menubar.addMenu('&Simulation') + setParmAct = QAction('Set Parameters',self) + setParmAct.setStatusTip('Set Simulation Parameters') + setParmAct.triggered.connect(self.setparams) + simMenu.addAction(setParmAct) + simMenu.addAction(runSimAct) + setOptParamAct = QAction('Configure Optimization', self) + setOptParamAct.setShortcut('Ctrl+O') + setOptParamAct.setStatusTip('Set parameters for evoked input optimization') + setOptParamAct.triggered.connect(self.showoptparamwin) + simMenu.addAction(setOptParamAct) + self.toggleEnableOptimization(False) + prevSimAct = QAction('Go to Previous Simulation',self) + prevSimAct.setShortcut('Ctrl+Z') + prevSimAct.setStatusTip('Go Back to Previous Simulation') + prevSimAct.triggered.connect(self.prevSim) + simMenu.addAction(prevSimAct) + nextSimAct = QAction('Go to Next Simulation',self) + nextSimAct.setShortcut('Ctrl+Y') + nextSimAct.setStatusTip('Go Forward to Next Simulation') + nextSimAct.triggered.connect(self.nextSim) + simMenu.addAction(nextSimAct) + clearSims2 = QAction('Clear simulation(s)', self) # need another QAction to avoid DBus warning + clearSims2.setStatusTip('Clear simulation(s)') + clearSims2.triggered.connect(self.clearSimulations) + simMenu.addAction(clearSims2) + + aboutMenu = self.menubar.addMenu('&About') + aboutAction = QAction('About HNN',self) + aboutAction.setStatusTip('About HNN') + aboutAction.triggered.connect(self.showAboutDialog) + aboutMenu.addAction(aboutAction) + helpAction = QAction('Help',self) + helpAction.setStatusTip('Help on how to use HNN (parameters).') + helpAction.triggered.connect(self.showHelpDialog) + #aboutMenu.addAction(helpAction) + + def toggleEnableOptimization (self, toEnable): + for menu in self.menubar.findChildren(QMenu): + if menu.title() == '&Simulation': + for item in menu.actions(): + if item.text() == 'Configure Optimization': + item.setEnabled(toEnable) + break + break + + def addButtons (self, gRow): + self.pbtn = pbtn = QPushButton('Set Parameters', self) + pbtn.setToolTip('Set Parameters') + pbtn.resize(pbtn.sizeHint()) + pbtn.clicked.connect(self.setparams) + self.grid.addWidget(self.pbtn, gRow, 0, 1, 3) + + self.pfbtn = pfbtn = QPushButton('Set Parameters From File', self) + pfbtn.setToolTip('Set Parameters From File') + pfbtn.resize(pfbtn.sizeHint()) + pfbtn.clicked.connect(self.selParamFileDialog) + self.grid.addWidget(self.pfbtn, gRow, 3, 1, 3) + + self.btnsim = btn = QPushButton('Run Simulation', self) + btn.setToolTip('Run Simulation') + btn.resize(btn.sizeHint()) + btn.clicked.connect(self.controlsim) + self.grid.addWidget(self.btnsim, gRow, 6, 1, 3) + + self.qbtn = qbtn = QPushButton('Quit', self) + qbtn.clicked.connect(QApplication.exit) + qbtn.resize(qbtn.sizeHint()) + self.grid.addWidget(self.qbtn, gRow, 9, 1, 3) + + def shownetparamwin (self): bringwintotop(self.baseparamwin.netparamwin) + def showoptparamwin (self): bringwintotop(self.baseparamwin.optparamwin) + def showdistparamwin (self): bringwintotop(self.erselectdistal) + def showproxparamwin (self): bringwintotop(self.erselectprox) + def showschematics (self): bringwintotop(self.schemwin) + + def addParamImageButtons (self,gRow): + # add parameter image buttons to the GUI + + self.locbtn = QPushButton('Local Network'+os.linesep+'Connections',self) + self.locbtn.setIcon(QIcon(lookupresource('connfig'))) + self.locbtn.clicked.connect(self.shownetparamwin) + self.grid.addWidget(self.locbtn,gRow,0,1,4) + + self.proxbtn = QPushButton('Proximal Drive'+os.linesep+'Thalamus',self) + self.proxbtn.setIcon(QIcon(lookupresource('proxfig'))) + self.proxbtn.clicked.connect(self.showproxparamwin) + self.grid.addWidget(self.proxbtn,gRow,4,1,4) + + self.distbtn = QPushButton('Distal Drive Non3Lemniscal'+os.linesep+'Thal./Cortical Feedback',self) + self.distbtn.setIcon(QIcon(lookupresource('distfig'))) + self.distbtn.clicked.connect(self.showdistparamwin) + self.grid.addWidget(self.distbtn,gRow,8,1,4) + + gRow += 1 + + def initUI (self): + # initialize the user interface (UI) + + self.initMenu() + self.statusBar() + + # start GUI in center of screenm, scale based on screen w x h + setscalegeomcenter(self, 1500, 1300) + + # move param windows to be offset from main GUI + new_x = max(0, self.x() - 300) + new_y = max(0, self.y() + 100) + self.baseparamwin.move(new_x, new_y) + self.baseparamwin.evparamwin.move(new_x+50, new_y+50) + self.baseparamwin.optparamwin.move(new_x+100, new_y+100) + self.setWindowTitle(self.baseparamwin.paramfn) + QToolTip.setFont(QFont('SansSerif', 10)) + + self.grid = grid = QGridLayout() + #grid.setSpacing(10) + + gRow = 0 + + self.addButtons(gRow) + + gRow += 1 + + self.initSimCanvas(gRow=gRow, reInit=False) + gRow += 2 + + self.cbsim = QComboBox(self) + self.populateSimCB() # populate the combobox + self.cbsim.activated[str].connect(self.onActivateSimCB) + self.grid.addWidget(self.cbsim, gRow, 0, 1, 8)#, 1, 3) + self.btnrmsim = QPushButton('Remove Simulation',self) + self.btnrmsim.resize(self.btnrmsim.sizeHint()) + self.btnrmsim.clicked.connect(self.removeSim) + self.btnrmsim.setToolTip('Remove Currently Selected Simulation') + self.grid.addWidget(self.btnrmsim, gRow, 8, 1, 4) + + gRow += 1 + self.addParamImageButtons(gRow) + + # need a separate widget to put grid on + widget = QWidget(self) + widget.setLayout(grid) + self.setCentralWidget(widget) - def setcursors(self, cursor): - # set cursors of self and children - self.setCursor(cursor) - self.update() - kids = self.children() - kids.append(self.sim_canvas) # matplotlib simcanvas - for k in kids: - if type(k) == QLayout or type(k) == QAction: - # These types don't have setCursor() - continue - k.setCursor(cursor) - k.update() - - def startoptmodel (self): - # start model optimization - if self.runningsim: - self.stopsim() # stop sim works but leaves subproc as zombie until this main GUI thread exits - else: - self.optMode = True - try: - self.optmodel(self.baseparamwin.runparamwin.getncore()) - except RuntimeError: - print("ERR: Optimization aborted") - - def controlsim (self): - # control the simulation - if self.runningsim: - self.stopsim() # stop sim works but leaves subproc as zombie until this main GUI thread exits - else: - self.optMode = False - self.startsim(self.baseparamwin.runparamwin.getncore()) + self.param_signal = ParamSignal() + self.param_signal.psig.connect(self.baseparamwin.updateDispParam) + + self.done_signal = DoneSignal() + self.done_signal.finishSim.connect(self.done) - def stopsim (self): - # stop the simulation - if self.runningsim: - self.waitsimwin.hide() - print('Terminating simulation. . .') - self.statusBar().showMessage('Terminating sim. . .') - self.runningsim = False - self.runthread.stop() # killed = True # terminate() - self.btnsim.setText("Run Simulation") - self.qbtn.setEnabled(True) - self.statusBar().showMessage('') - self.setcursors(Qt.ArrowCursor) - - def optmodel (self, ncore): - # make sure params saved and ok to run - if not self.baseparamwin.saveparams(): - return - - self.baseparamwin.optparamwin.btnreset.setEnabled(False) - self.baseparamwin.optparamwin.btnrunop.setText('Stop Optimization') - self.baseparamwin.optparamwin.btnrunop.clicked.disconnect() - self.baseparamwin.optparamwin.btnrunop.clicked.connect(self.stopsim) - - # optimize the model - self.setcursors(Qt.WaitCursor) - print('Starting model optimization. . .') - - self.runningsim = True - - self.statusBar().showMessage("Optimizing model. . .") - - self.runthread = RunSimThread(ncore, self.baseparamwin.params, - self.param_signal, self.done_signal, - self.waitsimwin, self.baseparamwin, - mainwin=self, opt=True) - - # We have all the events we need connected we can start the thread - self.runthread.start() - # At this point we want to allow user to stop/terminate the thread - # so we enable that button - self.btnsim.setText("Stop Optimization") - self.qbtn.setEnabled(False) - bringwintotop(self.waitsimwin) - - def startsim (self, ncore): - # start the simulation - if not self.baseparamwin.saveparams(self.baseparamwin.paramfn): - return # make sure params saved and ok to run - - # reread the params to get anything new - try: - params = read_params(self.baseparamwin.paramfn) - except ValueError: - txt = "WARNING: could not retrieve parameters from %s" % \ - self.baseparamwin.paramfn - QMessageBox.information(self, "HNN", txt) - print(txt) - return - - self.setcursors(Qt.WaitCursor) - - print('Starting simulation (%d cores). . .'%ncore) - self.runningsim = True - - self.statusBar().showMessage("Running simulation. . .") - - # check that valid number of trials was given - if 'N_trials' not in params or params['N_trials'] == 0: - print("Warning: invalid configured number of trials. Setting to 1.") - params['N_trials'] = 1 - - self.runthread = RunSimThread(ncore, params, - self.param_signal, self.done_signal, - self.waitsimwin, self.baseparamwin, - mainwin=self, opt=False) - - # We have all the events we need connected we can start the thread - self.runthread.start() - # At this point we want to allow user to stop/terminate the thread - # so we enable that button - self.btnsim.setText("Stop Simulation") # setEnabled(False) - # We don't want to enable user to start another thread while this one is - # running so we disable the start button. - # self.btn_start.setEnabled(False) - self.qbtn.setEnabled(False) - - bringwintotop(self.waitsimwin) - - def done (self, optMode, except_msg): - # called when the simulation completes running - self.runningsim = False - self.waitsimwin.hide() - self.statusBar().showMessage("") - self.btnsim.setText("Run Simulation") - self.qbtn.setEnabled(True) - self.initSimCanvas(optMode=optMode) # recreate canvas (plots too) to avoid incorrect axes - # self.sim_canvas.plot() - self.setcursors(Qt.ArrowCursor) - - failed=False - if len(except_msg) > 0: - failed = True - msg = "%s: Failed " % except_msg - else: - msg = "Finished " - - if optMode: - msg += "running optimization " - self.baseparamwin.optparamwin.btnrunop.setText('Prepare for Another Optimization') - self.baseparamwin.optparamwin.btnrunop.clicked.disconnect() - self.baseparamwin.optparamwin.btnrunop.clicked.connect(self.baseparamwin.optparamwin.prepareOptimization) - else: - msg += "running sim " - - if failed: - QMessageBox.critical(self, "Failed!", msg + "using " + self.baseparamwin.paramfn + '. Check simulation log or console for error messages') - else: - self.sim_data.update_sim_data(self.baseparamwin.paramfn, - self.baseparamwin.params) - - if self.baseparamwin.params['save_figs']: - self.sim_data.save_dipole_with_hist(self.baseparamwin.paramfn, - self.baseparamwin.params) - self.sim_data.save_spec_with_hist(self.baseparamwin.paramfn, - self.baseparamwin.params) + self.setWindowIcon(QIcon(os.path.join('res', 'icon.png'))) - data_dir = os.path.join(get_output_dir(), 'data') - sim_dir = os.path.join(data_dir, self.baseparamwin.params['sim_prefix']) - QMessageBox.information(self, "Done!", msg + "using " + self.baseparamwin.paramfn + '. Saved data/figures in: ' + sim_dir) - self.setWindowTitle(self.baseparamwin.paramfn) + self.schemwin.show() # so it's underneath main window + + self.show() + + def onActivateSimCB(self, paramfn): + # load simulation when activating simulation combobox + + if paramfn != self.baseparamwin.paramfn: + try: + params = read_params(paramfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + paramfn) + return + self.baseparamwin.paramfn = paramfn + + self.updateDatCanv(params) + + def populateSimCB(self, index=None): + # populate the simulation combobox + + self.cbsim.clear() + for paramfn in self.sim_data._sim_data.keys(): + self.cbsim.addItem(paramfn) + + if self.cbsim.count() == 0: + raise ValueError("No simulations to add to combo box") + + if index is None: + # set to last entry + self.cbsim.setCurrentIndex(self.cbsim.count() - 1) + else: + self.cbsim.setCurrentIndex(index) + + def initSimCanvas(self, recalcErr=True, optMode=False, gRow=1, reInit=True): + # initialize the simulation canvas, loading any required data + gCol = 0 + + if reInit == True: + self.grid.itemAtPosition(gRow, gCol).widget().deleteLater() + self.grid.itemAtPosition(gRow + 1, gCol).widget().deleteLater() + + # if just initialized or after clearSimulationData + if self.baseparamwin.paramfn and self.baseparamwin.params is None: + try: + self.baseparamwin.params = read_params(self.baseparamwin.paramfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + self.baseparamwin.paramfn) + return + + self.sim_canvas = SIMCanvas(self.baseparamwin.paramfn, self.baseparamwin.params, + parent=self, width=10, height=1, dpi=getmplDPI(), + optMode=optMode) + # this is the Navigation widget + # it takes the Canvas widget and a parent + self.toolbar = NavigationToolbar2QT(self.sim_canvas, self) + gWidth = 12 + self.grid.addWidget(self.toolbar, gRow, gCol, 1, gWidth) + self.grid.addWidget(self.sim_canvas, gRow + 1, gCol, 1, gWidth) + if self.sim_data.get_exp_data_size() > 0: + self.sim_canvas.plot(recalcErr) + self.sim_canvas.draw() + + def setcursors(self, cursor): + # set cursors of self and children + self.setCursor(cursor) + self.update() + kids = self.children() + kids.append(self.sim_canvas) # matplotlib simcanvas + for k in kids: + if type(k) == QLayout or type(k) == QAction: + # These types don't have setCursor() + continue + k.setCursor(cursor) + k.update() + + def startoptmodel(self): + # start model optimization + if self.runningsim: + self.stopsim() # stop sim works but leaves subproc as zombie until this main GUI thread exits + else: + self.optMode = True + try: + self.optmodel(self.baseparamwin.runparamwin.getncore()) + except RuntimeError: + print("ERR: Optimization aborted") + + def controlsim(self): + # control the simulation + if self.runningsim: + # stop sim works but leaves subproc as zombie until this main GUI + # thread exits + self.stopsim() + else: + self.optMode = False + self.startsim(self.baseparamwin.runparamwin.getncore()) + + def stopsim(self): + # stop the simulation + if self.runningsim: + self.waitsimwin.hide() + print('Terminating simulation. . .') + self.statusBar().showMessage('Terminating sim. . .') + self.runningsim = False + self.runthread.stop() # killed = True # terminate() + self.btnsim.setText("Run Simulation") + self.qbtn.setEnabled(True) + self.statusBar().showMessage('') + self.setcursors(Qt.ArrowCursor) + + def optmodel(self, ncore): + # make sure params saved and ok to run + if not self.baseparamwin.saveparams(): + return + + self.baseparamwin.optparamwin.btnreset.setEnabled(False) + self.baseparamwin.optparamwin.btnrunop.setText('Stop Optimization') + self.baseparamwin.optparamwin.btnrunop.clicked.disconnect() + self.baseparamwin.optparamwin.btnrunop.clicked.connect(self.stopsim) + + # optimize the model + self.setcursors(Qt.WaitCursor) + print('Starting model optimization. . .') + + self.runningsim = True + + self.statusBar().showMessage("Optimizing model. . .") + + self.runthread = RunSimThread(ncore, self.baseparamwin.params, + self.param_signal, self.done_signal, + self.waitsimwin, self.baseparamwin, + self.result_callback, + mainwin=self, opt=True) + + # We have all the events we need connected we can start the thread + self.runthread.start() + # At this point we want to allow user to stop/terminate the thread + # so we enable that button + self.btnsim.setText("Stop Optimization") + self.qbtn.setEnabled(False) + bringwintotop(self.waitsimwin) + + def startsim(self, ncore): + # start the simulation + if not self.baseparamwin.saveparams(self.baseparamwin.paramfn): + return # make sure params saved and ok to run + + # reread the params to get anything new + try: + params = read_params(self.baseparamwin.paramfn) + except ValueError: + txt = "WARNING: could not retrieve parameters from %s" % \ + self.baseparamwin.paramfn + QMessageBox.information(self, "HNN", txt) + print(txt) + return + + self.setcursors(Qt.WaitCursor) + + print('Starting simulation (%d cores). . .' % ncore) + self.runningsim = True + + self.statusBar().showMessage("Running simulation. . .") + + # check that valid number of trials was given + if 'N_trials' not in params or params['N_trials'] == 0: + print("Warning: invalid configured number of trials. Setting to 1.") + params['N_trials'] = 1 + + self.runthread = RunSimThread(ncore, params, + self.param_signal, self.done_signal, + self.waitsimwin, self.baseparamwin, + self.result_callback, + mainwin=self, opt=False) + + # We have all the events we need connected we can start the thread + self.runthread.start() + # At this point we want to allow user to stop/terminate the thread + # so we enable that button + self.btnsim.setText("Stop Simulation") # setEnabled(False) + # We don't want to enable user to start another thread while this one is + # running so we disable the start button. + # self.btn_start.setEnabled(False) + self.qbtn.setEnabled(False) + + bringwintotop(self.waitsimwin) + + def result_callback(self, result): + sim_data = result.data + sim_data['spec'] = [] + params = result.params + + sim_data['dpls'] = deepcopy(sim_data['raw_dpls']) + ntrial = len(sim_data['raw_dpls']) + for trial_idx in range(ntrial): + N_pyr_x = params['N_pyr_x'] + N_pyr_y = params['N_pyr_y'] + winsz = params['dipole_smooth_win'] / params['dt'] + fctr = params['dipole_scalefctr'] + sim_data['dpls'][trial_idx].post_proc(N_pyr_x, N_pyr_y, winsz, fctr) + + # save average dipole from individual trials in a single file + if ntrial > 1: + sim_data['avg_dpl'] = average_dipoles(sim_data['dpls']) + elif ntrial == 1: + sim_data['avg_dpl'] = sim_data['dpls'][0] + else: + raise ValueError("No dipole(s) returned from simulation") + + # make sure the directory for saving data has been created + data_dir = os.path.join(get_output_dir(), 'data') + sim_dir = os.path.join(data_dir, params['sim_prefix']) + try: + os.mkdir(sim_dir) + except FileExistsError: + pass + + # now write the files + sim_data['avg_dpl'].write(os.path.join(sim_dir, 'dpl.txt')) + + # TODO: Can below be removed if spk.txt is new hnn-core format with 3 + # columns (including spike type)? + # Follow https://github.com/jonescompneurolab/hnn-core/issues/219 + write_gids_param(get_fname(sim_dir, 'param'), sim_data['gid_ranges']) + + # save spikes by trial + glob = os.path.join(sim_dir, 'spk_%d.txt') + sim_data['spikes'].write(glob) + + spike_fn = get_fname(sim_dir, 'rawspk') + # save spikes from the individual trials in a single file + with open(spike_fn, 'w') as fspkout: + for trial_idx in range(len(sim_data['spikes'].spike_times)): + spike_times = sim_data['spikes'].spike_times[trial_idx] + spike_gids = sim_data['spikes'].spike_gids[trial_idx] + spike_types = sim_data['spikes'].spike_types[trial_idx] + for spike_idx in range(len(spike_times)): + fspkout.write('{:.3f}\t{}\t{}\n'.format( + spike_times[spike_idx], + int(spike_gids[spike_idx]), + spike_types[spike_idx])) + + spec_fns = [] + # save dipole for each trial and perform spectral analysis + for trial_idx, dpl in enumerate(sim_data['dpls']): + dipole_fn = get_fname(sim_dir, 'normdpl', trial_idx, + ntrial) + dpl.write(dipole_fn) + + if params['save_dpl']: + raw_dipole_fn = get_fname(sim_dir, 'rawdpl', trial_idx, + ntrial) + sim_data['raw_dpls'][trial_idx].write(raw_dipole_fn) + + if params['save_spec_data'] or \ + usingOngoingInputs(params): + spec_opts = {'type': 'dpl_laminar', + 'f_max': params['f_max_spec'], + 'save_data': 1, + 'runtype': 'parallel'} + spec_fn = get_fname(sim_dir, 'rawspec', trial_idx, ntrial) + spec_fns.append(spec_fn) + + # run the spectral analysis + spec_results = analysis_simp(spec_opts, params, dpl, spec_fn) + sim_data['spec'].append(spec_results) + + # save average spectrogram from individual trials in a single file + if params['save_spec_data'] or \ + usingOngoingInputs(params): + + spec_out = {} + for key in ['TFR', 'TFR_L5', 'TFR_L2']: + spec_list = [sim_data['spec'][i][key] for i in range(ntrial)] + spec_out[key] = np.mean(np.array(spec_list), axis=0) + with open(os.path.join(sim_dir, 'rawspec.npz'), 'wb') as spec_fn: + np.savez_compressed(spec_fn, t_L5=sim_data['spec'][0]['t_L5'], + f_L5=sim_data['spec'][0]['f_L5'], + t_L2=sim_data['spec'][0]['t_L2'], + f_L2=sim_data['spec'][0]['f_L2'], + time=sim_data['spec'][0]['time'], + freq=sim_data['spec'][0]['freq'], + TFR=spec_out['TFR'], + TFR_L5=spec_out['TFR_L5'], + TFR_L2=spec_out['TFR_L2']) + + paramfn = os.path.join(get_output_dir(), 'param', + params['sim_prefix'] + '.param') + + self.sim_data.update_sim_data(paramfn, params, sim_data['dpls'], + sim_data['avg_dpl'], sim_data['spikes'], + sim_data['gid_ranges'], + sim_data['raw_dpls'], sim_data['spec']) + + def done(self, optMode, except_msg): + # called when the simulation completes running + self.runningsim = False + self.waitsimwin.hide() + self.statusBar().showMessage("") + self.btnsim.setText("Run Simulation") + self.qbtn.setEnabled(True) + # recreate canvas (plots too) to avoid incorrect axes + self.initSimCanvas(optMode=optMode) + # self.sim_canvas.plot() + self.setcursors(Qt.ArrowCursor) + + failed=False + if len(except_msg) > 0: + failed = True + msg = "%s: Failed " % except_msg + else: + msg = "Finished " + + if optMode: + msg += "running optimization " + self.baseparamwin.optparamwin.btnrunop.setText( + 'Prepare for Another Optimization') + self.baseparamwin.optparamwin.btnrunop.clicked.disconnect() + self.baseparamwin.optparamwin.btnrunop.clicked.connect( + self.baseparamwin.optparamwin.prepareOptimization) + else: + msg += "running sim " + + if failed: + QMessageBox.critical(self, "Failed!", msg + "using " + + self.baseparamwin.paramfn + + '. Check simulation log or console for error ' + 'messages') + else: + self.sim_data.update_sim_data_from_disk(self.baseparamwin.paramfn, + self.baseparamwin.params) + + if self.baseparamwin.params['save_figs']: + self.sim_data.save_dipole_with_hist(self.baseparamwin.paramfn, + self.baseparamwin.params) + self.sim_data.save_spec_with_hist(self.baseparamwin.paramfn, + self.baseparamwin.params) + + data_dir = os.path.join(get_output_dir(), 'data') + sim_dir = os.path.join(data_dir, + self.baseparamwin.params['sim_prefix']) + QMessageBox.information(self, "Done!", msg + "using " + + self.baseparamwin.paramfn + + '. Saved data/figures in: ' + sim_dir) + self.setWindowTitle(self.baseparamwin.paramfn) + + self.populateSimCB() # populate the combobox + cb_index = self.cbsim.findText(self.baseparamwin.paramfn) + if cb_index < 0: + raise ValueError("Couldn't find simulation in combobox: %s" % + self.baseparamwin.paramfn) + self.cbsim.setCurrentIndex(cb_index) - self.populateSimCB() # populate the combobox - cb_index = self.cbsim.findText(self.baseparamwin.paramfn) - if cb_index < 0: - raise ValueError("Couldn't find simulation in combobox: %s" % self.baseparamwin.paramfn) - self.cbsim.setCurrentIndex(cb_index) if __name__ == '__main__': - app = QApplication(sys.argv) - ex = HNNGUI() - sys.exit(app.exec_()) + app = QApplication(sys.argv) + ex = HNNGUI() + sys.exit(app.exec_()) diff --git a/hnn/run.py b/hnn/run.py index d95b66640..3764d5a28 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -13,35 +13,38 @@ from math import ceil, isclose from contextlib import redirect_stdout -from PyQt5.QtCore import QThread, pyqtSignal, QObject +from PyQt5 import QtCore from hnn_core import simulate_dipole, Network, MPIBackend -from hnn_core.dipole import average_dipoles import nlopt from psutil import wait_procs, process_iter, NoSuchProcess -from .paramrw import usingOngoingInputs, write_gids_param, get_fname -from .specfn import analysis_simp from .paramrw import write_legacy_paramf, get_output_dir -class TextSignal (QObject): +class TextSignal(QtCore.QObject): """for passing text""" - tsig = pyqtSignal(str) + tsig = QtCore.pyqtSignal(str) -class DataSignal (QObject): +class DataSignal(QtCore.QObject): """for signalling data read""" - dsig = pyqtSignal(str, dict) + dsig = QtCore.pyqtSignal(str, dict) -class ParamSignal (QObject): +class ParamSignal(QtCore.QObject): """for updating GUI & param file during optimization""" - psig = pyqtSignal(dict) + psig = QtCore.pyqtSignal(dict) -class CanvSignal (QObject): +class CanvSignal(QtCore.QObject): """for updating main GUI canvas""" - csig = pyqtSignal(bool, bool) + csig = QtCore.pyqtSignal(bool, bool) + + +class ResultObj(QtCore.QObject): + def __init__(self, data, params): + self.data = data + self.params = params def _kill_list_of_procs(procs): @@ -103,94 +106,18 @@ def simulate(params, n_procs=None): # been created yet net = Network(params) + sim_data = {} # run the simulation with MPIBackend for faster completion time with MPIBackend(n_procs=n_procs, mpi_cmd='mpiexec'): - dpls = simulate_dipole(net, params['N_trials']) - - ntrial = len(dpls) - # save average dipole from individual trials in a single file - if ntrial > 1: - avg_dpl = average_dipoles(dpls) - elif ntrial == 1: - avg_dpl = dpls[0] - else: - raise RuntimeError("No dipole(s) rerturned from simulation") - - # make sure the directory for saving data has been created - data_dir = op.join(get_output_dir(), 'data') - sim_dir = op.join(data_dir, params['sim_prefix']) - try: - os.mkdir(sim_dir) - except FileExistsError: - pass - - # now write the files - # TODO: rawdpl in hnn-core - - avg_dpl.write(op.join(sim_dir, 'dpl.txt')) - - # TODO: Can below be removed if spk.txt is new hnn-core format with 3 - # columns (including spike type)? - write_gids_param(get_fname(sim_dir, 'param'), net.gid_ranges) - - # save spikes by trial - glob = op.join(sim_dir, 'spk_%d.txt') - net.cell_response.write(glob) - - spike_fn = get_fname(sim_dir, 'rawspk') - # save spikes from the individual trials in a single file - with open(spike_fn, 'w') as fspkout: - for trial_idx in range(len(net.cell_response.spike_times)): - for spike_idx in range(len(net.cell_response.spike_times[trial_idx])): - fspkout.write('{:.3f}\t{}\t{}\n'.format( - net.cell_response.spike_times[trial_idx][spike_idx], - int(net.cell_response.spike_gids[trial_idx][spike_idx]), - net.cell_response.spike_types[trial_idx][spike_idx])) - - # save dipole for each trial and perform spectral analysis - for trial_idx, dpl in enumerate(dpls): - dipole_fn = get_fname(sim_dir, 'normdpl', trial_idx, - params['N_trials']) - dpl.write(dipole_fn) - - if params['save_spec_data'] or usingOngoingInputs(params): - spec_opts = {'type': 'dpl_laminar', - 'f_max': params['f_max_spec'], - 'save_data': 1, - 'runtype': 'parallel'} - - # run the spectral analysis - analysis_simp(spec_opts, params, dpl, - get_fname(sim_dir, 'rawspec', trial_idx, - params['N_trials'])) - - if params['save_spec_data'] or usingOngoingInputs(params): - # save average spectrogram from individual trials in a single file - - dspecin = {} - dout = {} - lf = [] - for _ in range(ntrial): - f_spec = get_fname(sim_dir, 'rawspec', trial_idx, ntrial) - lf.append(f_spec) - - for f in lf: - dspecin[f] = np.load(f) - for k in ['t_L5', 'f_L5', 't_L2', 'f_L2', 'time', 'freq']: - dout[k] = dspecin[lf[0]][k] - for k in ['TFR', 'TFR_L5', 'TFR_L2']: - dout[k] = np.mean(np.array([dspecin[f][k] for f in lf]), axis=0) - - with open(op.join(sim_dir, 'rawspec.npz'), 'wb') as spec_fn: - np.savez_compressed(spec_fn, t_L5=dout['t_L5'], f_L5=dout['f_L5'], - t_L2=dout['t_L2'], f_L2=dout['f_L2'], - time=dout['time'], freq=dout['freq'], - TFR=dout['TFR'], TFR_L5=dout['TFR_L5'], - TFR_L2=dout['TFR_L2']) + sim_data['raw_dpls'] = simulate_dipole(net, params['N_trials'], postproc=False) + + sim_data['gid_ranges'] = net.gid_ranges + sim_data['spikes'] = net.cell_response + return sim_data # based on https://nikolak.com/pyqt-threading-tutorial/ -class RunSimThread(QThread): +class RunSimThread(QtCore.QThread): """The RunSimThread class. Parameters @@ -237,9 +164,11 @@ class RunSimThread(QThread): Whether this simulation was forcefully terminated """ + result_signal = QtCore.pyqtSignal(object) + def __init__(self, ncore, params, param_signal, done_signal, waitsimwin, - baseparamwin, mainwin, opt=False): - QThread.__init__(self) + baseparamwin, result_callback, mainwin, opt=False): + QtCore.QThread.__init__(self) self.ncore = ncore self.params = params self.param_signal = param_signal @@ -247,6 +176,7 @@ def __init__(self, ncore, params, param_signal, done_signal, waitsimwin, self.waitsimwin = waitsimwin self.baseparamwin = baseparamwin self.mainwin = mainwin + self.result_signal.connect(result_callback) self.opt = opt self.killed = False @@ -254,7 +184,7 @@ def __init__(self, ncore, params, param_signal, done_signal, waitsimwin, self.params['sim_prefix'] + '.param') self.dataComm = DataSignal() - self.dataComm.dsig.connect(self.mainwin.sim_data.update_sim_data) + self.dataComm.dsig.connect(self.mainwin.sim_data.update_sim_data_from_disk) self.txtComm = TextSignal() self.txtComm.tsig.connect(self.waitsimwin.updatetxt) @@ -327,6 +257,10 @@ def run(self): else: try: self._runsim() # run simulation + + # send signal to read data from files and save + self._read_sim_data(self.paramfn, self.params) + # update params in all windows (optimization) self._updatedispparam() except RuntimeError as e: @@ -345,7 +279,7 @@ def _runsim(self, is_opt=False, banner=True, simlength=None): try: sim_log = self._log_sim_status(parent=self) with redirect_stdout(sim_log): - simulate(self.params, self.ncore) + sim_data = simulate(self.params, self.ncore) break except RuntimeError as e: if self.ncore == 1: @@ -366,9 +300,8 @@ def _runsim(self, is_opt=False, banner=True, simlength=None): print(txt) self._updatewaitsimwin(txt) - if not is_opt: - # send signal to read data from files and save - self._read_sim_data(self.paramfn, self.params) + # put sim_data into the val attribute of a ResultObj + self.result_signal.emit(ResultObj(sim_data, self.params)) def optmodel(self): need_initial_ddat = False @@ -455,10 +388,10 @@ def optmodel(self): self._runsim(is_opt=True, banner=False) # update lsimdat and its current sim index - update_sim_data(self.paramfn, self.params, ddat['dpl']) + update_sim_data_from_disk(self.paramfn, self.params, ddat['dpl']) # update optdat with the final best - update_opt_data(self.paramfn, self.params, ddat['dpl']) + update_opt_data_from_disk(self.paramfn, self.params, ddat['dpl']) # re-enable all the range sliders self.baseparamwin.optparamwin.toggleEnableUserFields(step, diff --git a/hnn/simdat.py b/hnn/simdat.py index 136a5adef..dc68c22fe 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -16,7 +16,7 @@ from .spikefn import ExtInputs from .paramrw import get_output_dir, get_fname, get_inputs -from .paramrw import countEvokedInputs +from .paramrw import countEvokedInputs, read_gids_param from .qt_lib import getscreengeom drawindivdpl = 1 @@ -151,12 +151,15 @@ def remove_sim_by_fn(self, paramfn): """ del self._sim_data[paramfn] - def _update_sim_list(self, paramfn, params, avg_dpl, dpl_trials, - spikes=None, spec=None): + def update_sim_data(self, paramfn, params, dpls, avg_dpl, + spikes, gid_ranges, raw_dpls=None, + spec=None): self._sim_data[paramfn] = {'params': params, - 'data': {'avg_dpl': avg_dpl, - 'dpl_trials': dpl_trials, - 'spk': spikes, + 'data': {'dpls': dpls, + 'avg_dpl': avg_dpl, + 'spikes': spikes, + 'gid_ranges': gid_ranges, + 'raw_dpls': raw_dpls, 'spec': spec}} def clear_exp_data(self): @@ -192,7 +195,7 @@ def get_exp_data_size(self): return len(self._exp_data) - def update_sim_data(self, paramfn, params): + def update_sim_data_from_disk(self, paramfn, params): """Adds simulation data to SimData Parameters @@ -226,7 +229,8 @@ def update_sim_data(self, paramfn, params): try: raw_spikes = read_spikes(spike_fn) spikes_array = np.r_[raw_spikes.spike_times, - raw_spikes.spike_gids].T + raw_spikes.spike_gids, + raw_spikes.spike_types].T if len(spikes_array) == 0 and warn_nofile: print(warning_message, spike_fn) except ValueError: @@ -242,27 +246,45 @@ def update_sim_data(self, paramfn, params): print('Warning: incorrect dimensions for spike file: %s' % spike_fn) - warn_nospec = False + paramtxt_fn = get_fname(sim_dir, 'param') + gid_ranges = read_gids_param(paramtxt_fn) + + warn_no_spec = False using_feeds = get_inputs(params) if params['save_spec_data'] or using_feeds['ongoing'] or \ using_feeds['pois'] or using_feeds['tonic']: - warn_nospec = True + warn_no_spec = True spec_fn = os.path.join(sim_dir, 'rawspec.npz') spec = None try: spec = np.load(spec_fn) except OSError: - if warn_nospec: + if warn_no_spec and warn_nofile: print(warning_message, spec_fn) except ValueError: - if warn_nospec: + if warn_no_spec and warn_nofile: print(warning_message, spec_fn) - dpl_trials = read_dpltrials(sim_dir) + warn_no_raw_dpl = False + if params['save_dpl']: + warn_no_raw_dpl = True + + raw_dipole_fn = os.path.join(sim_dir, 'rawdpl.txt') + raw_dpl = None + try: + raw_dpl = np.loadtxt(raw_dipole_fn) + except OSError: + if warn_no_raw_dpl and warn_nofile: + print(warning_message, raw_dipole_fn) + except ValueError: + if warn_no_raw_dpl and warn_nofile: + print(warning_message, raw_dipole_fn) + + dpls = read_dpltrials(sim_dir) - self._update_sim_list(paramfn, params, avg_dpl, dpl_trials, - spikes_array, spec) + self.update_sim_data(paramfn, params, dpls, avg_dpl, spikes_array, + gid_ranges, raw_dpl, spec) def calcerr(self, paramfn, tstop, tstart=0.0, weights=None): """Calculate root mean squared error using SimData @@ -362,13 +384,13 @@ def _read_dpl(self, paramfn, trial_idx, ntrial): if ntrial == 1: dpltrial = self._sim_data[paramfn]['data']['avg_dpl'] else: - trial_data = self._sim_data[paramfn]['data']['dpl_trials'] + trial_data = self._sim_data[paramfn]['data']['dpls'] if trial_idx > len(trial_data): print("Warning: data not available for trials above index", (len(trial_data) - 1)) return None - dpltrial = self._sim_data[paramfn]['data']['dpl_trials'][trial_idx] + dpltrial = self._sim_data[paramfn]['data']['dpls'][trial_idx] dpl_data = np.c_[dpltrial[:, 1], dpltrial[:, 2], @@ -408,7 +430,6 @@ def save_spec_with_hist(self, paramfn, params): for trial_idx in range(ntrial): spec_fn = get_fname(sim_dir, 'rawspec', trial_idx, ntrial) spike_fn = get_fname(sim_dir, 'rawspk', trial_idx, ntrial) - out_paramfn = get_fname(sim_dir, 'param', trial_idx, ntrial) # Generate file prefix fprefix = os.path.splitext(os.path.split(spec_fn)[-1])[0] @@ -421,7 +442,8 @@ def save_spec_with_hist(self, paramfn, params): mpl.rc('font', **font_prop) # get inputs from spike file - extinputs = ExtInputs(spike_fn, out_paramfn, params) + gid_ranges = self._sim_data[paramfn]['data']['gid_ranges'] + extinputs = ExtInputs(spike_fn, gid_ranges, params) extinputs.add_delay_times() feeds_from_spikes = extinputs.inputs feeds_to_plot = check_feeds_to_plot(feeds_from_spikes, params) @@ -477,7 +499,6 @@ def save_dipole_with_hist(self, paramfn, params): for trial_idx in range(ntrial): dipole_fn = get_fname(sim_dir, 'normdpl', trial_idx, ntrial) spike_fn = get_fname(sim_dir, 'rawspk', trial_idx, ntrial) - out_paramfn = get_fname(sim_dir, 'param', trial_idx, ntrial) # split to find file prefix file_prefix = dipole_fn.split('/')[-1].split('.')[0] @@ -487,7 +508,8 @@ def save_dipole_with_hist(self, paramfn, params): font_prop = {'size': 8} mpl.rc('font', **font_prop) - extinputs = ExtInputs(spike_fn, out_paramfn, params) + gid_ranges = self._sim_data[paramfn]['data']['gid_ranges'] + extinputs = ExtInputs(spike_fn, gid_ranges, params) extinputs.add_delay_times() feeds_from_spikes = extinputs.inputs feeds_to_plot = check_feeds_to_plot(feeds_from_spikes, params) @@ -569,11 +591,11 @@ def plotinputhist(self, extinputs=None, feeds_to_plot=None, sim_dir = os.path.join(self._data_dir, self.params['sim_prefix']) spike_fn = os.path.join(sim_dir, 'spk.txt') - out_paramfn = os.path.join(sim_dir, 'param.txt') if not plot_distribs: if feeds_to_plot is None: - extinputs = ExtInputs(spike_fn, out_paramfn, self.params) + sim_data = self.sim_data._sim_data[self.paramfn]['data'] + extinputs = ExtInputs(spike_fn, sim_data['gid_ranges'], self.params) extinputs.add_delay_times() dinput = extinputs.inputs feeds_to_plot = check_feeds_to_plot(dinput, self.params) @@ -835,13 +857,19 @@ def plotsimdat(self): DrawSpec = False xlim = (0.0, 1.0) else: + # for trying to plot a simulation read from disk (e.g. default) + if self.paramfn not in self.sim_data._sim_data: + self.sim_data.update_sim_data_from_disk(self.paramfn, + self.params) + tstop = self.params['tstop'] xlim = (0.0, tstop) sim_dir = os.path.join(self._data_dir, self.params['sim_prefix']) spike_fn = os.path.join(sim_dir, 'spk.txt') - out_paramfn = os.path.join(sim_dir, 'param.txt') try: - extinputs = ExtInputs(spike_fn, out_paramfn, self.params) + sim_data = self.sim_data._sim_data[self.paramfn]['data'] + extinputs = ExtInputs(spike_fn, sim_data['gid_ranges'], + self.params) extinputs.add_delay_times() feeds_from_spikes = extinputs.inputs feeds_to_plot = check_feeds_to_plot(feeds_from_spikes, @@ -853,10 +881,6 @@ def plotsimdat(self): axes = self.plotinputhist(plot_distribs=True) self.gRow = len(axes) - # for trying to plot a simulation read from disk (e.g. default) - if self.paramfn not in self.sim_data._sim_data: - self.sim_data.update_sim_data(self.paramfn, self.params) - # check that dipole data is present single_sim = self.sim_data._sim_data[self.paramfn]['data'] if single_sim['avg_dpl'] is None: @@ -921,8 +945,8 @@ def plotsimdat(self): sim_data = self.sim_data._sim_data[self.paramfn]['data'] # plot dipoles from individual trials if self.params['N_trials'] > 1 and drawindivdpl and \ - len(sim_data['dpl_trials']) > 0: - for dpltrial in sim_data['dpl_trials']: + len(sim_data['dpls']) > 0: + for dpltrial in sim_data['dpls']: self.axdipole.plot(dpltrial[:, 0], dpltrial[:, 1], color='gray', linewidth=self.linewidth) diff --git a/hnn/specfn.py b/hnn/specfn.py index 9c59913b8..d237d3c58 100644 --- a/hnn/specfn.py +++ b/hnn/specfn.py @@ -193,6 +193,11 @@ def spec_dpl_kernel(params, dpl, fspec, opts): # Generate periodogram resutls pgram = Welch(dpl.times, dpl.data['agg'], params['dt']) + spec_results = {'time': spec_agg.t, 'freq': spec_agg.f, + 'TFR': spec_agg.TFR, 'max_agg': max_agg, + 't_L2': spec_L2.t, 'f_L2': spec_L2.f, 'TFR_L2': spec_L2.TFR, + 't_L5': spec_L5.t, 'f_L5': spec_L5.f, 'TFR_L5': spec_L5.TFR, + 'pgram_p': pgram.P, 'pgram_f': pgram.f} # Save spec results np.savez_compressed(fspec, time=spec_agg.t, freq=spec_agg.f, TFR=spec_agg.TFR, max_agg=max_agg, @@ -200,6 +205,7 @@ def spec_dpl_kernel(params, dpl, fspec, opts): t_L5=spec_L5.t, f_L5=spec_L5.f, TFR_L5=spec_L5.TFR, pgram_p=pgram.P, pgram_f=pgram.f) + return spec_results def analysis_simp(opts, params, fdpl, fspec): opts_run = {'type': 'dpl_laminar', @@ -210,4 +216,5 @@ def analysis_simp(opts, params, fdpl, fspec): for key, val in opts.items(): if key in opts_run.keys(): opts_run[key] = val - spec_dpl_kernel(params, fdpl, fspec, opts_run) + + return spec_dpl_kernel(params, fdpl, fspec, opts_run) diff --git a/hnn/spikefn.py b/hnn/spikefn.py index f8872e26e..a39ee7aa6 100644 --- a/hnn/spikefn.py +++ b/hnn/spikefn.py @@ -62,10 +62,10 @@ def ppsth (self, a): # Class to handle extinput event times class ExtInputs (Spikes): # class for external inputs - extracts gids and times - def __init__ (self, fspk, fgids, params, evoked=False): + def __init__ (self, fspk, gid_ranges, params, evoked=False): self.p_dict = params - self.gid_dict = read_gids_param(fgids) + self.gid_dict = gid_ranges if 'common' in self.gid_dict: extinput_key = 'common' From 69676659646ba70868c81b323ed562402a9e5ab2 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 1 Dec 2020 09:42:14 -0500 Subject: [PATCH 060/107] MAINT: use hnn-core data structures Instead of reading files the old way with np.loadtxt(), use read_* functions from hnn_core to load data into objects. Reduce the number of file reads by storing objects in memory until they are used for plotting. DEP: no longer write spk.txt and rawspec.txt --- hnn/hnn_qt5.py | 44 +--- hnn/paramrw.py | 9 +- hnn/run.py | 10 - hnn/simdat.py | 392 ++++++++++++++++---------------- hnn/specfn.py | 14 +- hnn/spikefn.py | 592 +++++++++++++++++++------------------------------ hnn/visrast.py | 3 - 7 files changed, 435 insertions(+), 629 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 406bed7c4..0f7dcd908 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -26,7 +26,7 @@ from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT import matplotlib.pyplot as plt from psutil import cpu_count -from hnn_core import read_params, read_spikes +from hnn_core import read_params, CellResponse from hnn_core.dipole import average_dipoles # HNN modules @@ -2081,9 +2081,6 @@ def result_callback(self, result): except FileExistsError: pass - # now write the files - sim_data['avg_dpl'].write(os.path.join(sim_dir, 'dpl.txt')) - # TODO: Can below be removed if spk.txt is new hnn-core format with 3 # columns (including spike type)? # Follow https://github.com/jonescompneurolab/hnn-core/issues/219 @@ -2093,19 +2090,6 @@ def result_callback(self, result): glob = os.path.join(sim_dir, 'spk_%d.txt') sim_data['spikes'].write(glob) - spike_fn = get_fname(sim_dir, 'rawspk') - # save spikes from the individual trials in a single file - with open(spike_fn, 'w') as fspkout: - for trial_idx in range(len(sim_data['spikes'].spike_times)): - spike_times = sim_data['spikes'].spike_times[trial_idx] - spike_gids = sim_data['spikes'].spike_gids[trial_idx] - spike_types = sim_data['spikes'].spike_types[trial_idx] - for spike_idx in range(len(spike_times)): - fspkout.write('{:.3f}\t{}\t{}\n'.format( - spike_times[spike_idx], - int(spike_gids[spike_idx]), - spike_types[spike_idx])) - spec_fns = [] # save dipole for each trial and perform spectral analysis for trial_idx, dpl in enumerate(sim_data['dpls']): @@ -2127,36 +2111,17 @@ def result_callback(self, result): spec_fn = get_fname(sim_dir, 'rawspec', trial_idx, ntrial) spec_fns.append(spec_fn) - # run the spectral analysis + # run the spectral analysis and save to spec_fn spec_results = analysis_simp(spec_opts, params, dpl, spec_fn) sim_data['spec'].append(spec_results) - # save average spectrogram from individual trials in a single file - if params['save_spec_data'] or \ - usingOngoingInputs(params): - - spec_out = {} - for key in ['TFR', 'TFR_L5', 'TFR_L2']: - spec_list = [sim_data['spec'][i][key] for i in range(ntrial)] - spec_out[key] = np.mean(np.array(spec_list), axis=0) - with open(os.path.join(sim_dir, 'rawspec.npz'), 'wb') as spec_fn: - np.savez_compressed(spec_fn, t_L5=sim_data['spec'][0]['t_L5'], - f_L5=sim_data['spec'][0]['f_L5'], - t_L2=sim_data['spec'][0]['t_L2'], - f_L2=sim_data['spec'][0]['f_L2'], - time=sim_data['spec'][0]['time'], - freq=sim_data['spec'][0]['freq'], - TFR=spec_out['TFR'], - TFR_L5=spec_out['TFR_L5'], - TFR_L2=spec_out['TFR_L2']) - paramfn = os.path.join(get_output_dir(), 'param', params['sim_prefix'] + '.param') self.sim_data.update_sim_data(paramfn, params, sim_data['dpls'], sim_data['avg_dpl'], sim_data['spikes'], sim_data['gid_ranges'], - sim_data['raw_dpls'], sim_data['spec']) + sim_data['spec']) def done(self, optMode, except_msg): # called when the simulation completes running @@ -2193,9 +2158,6 @@ def done(self, optMode, except_msg): '. Check simulation log or console for error ' 'messages') else: - self.sim_data.update_sim_data_from_disk(self.baseparamwin.paramfn, - self.baseparamwin.params) - if self.baseparamwin.params['save_figs']: self.sim_data.save_dipole_with_hist(self.baseparamwin.paramfn, self.baseparamwin.params) diff --git a/hnn/paramrw.py b/hnn/paramrw.py index d46af40ed..95858ace5 100644 --- a/hnn/paramrw.py +++ b/hnn/paramrw.py @@ -20,7 +20,7 @@ def get_output_dir(): return os.path.join(base_dir, 'hnn_out') -def get_fname(sim_dir, key, trial=0, ntrial=1): +def get_fname(sim_dir, key, trial=0, ntrial=0): """Build the file names using the old HNN scheme Parameters @@ -34,7 +34,8 @@ def get_fname(sim_dir, key, trial=0, ntrial=1): If None is given, then trial number 0 is assumed. ntrial : int | None The total number of trials that are part of this simulation. If None - is given, then a total of 1 trial is assumed. + is given, then ntrial will be 0, which creates filenames without + the trial number suffix. Returns ---------- @@ -53,12 +54,12 @@ def get_fname(sim_dir, key, trial=0, ntrial=1): 'figavgdpl': ('dplavg', '.png'), 'figavgspec': ('specavg', '.png'), 'figdpl': ('dpl', '.png'), - 'figspec': ('spec', '.png'), + 'figspec': ('rawspec', '.png'), 'figspk': ('spk', '.png'), 'param': ('param', '.txt'), 'vsoma': ('vsoma', '.pkl')} - if ntrial == 1 or key == 'param': + if ntrial == 0 or key == 'param': # param file currently identical for all trials return os.path.join(sim_dir, datatypes[key][0] + datatypes[key][1]) else: diff --git a/hnn/run.py b/hnn/run.py index 3764d5a28..b4c1b5d74 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -183,9 +183,6 @@ def __init__(self, ncore, params, param_signal, done_signal, waitsimwin, self.paramfn = os.path.join(get_output_dir(), 'param', self.params['sim_prefix'] + '.param') - self.dataComm = DataSignal() - self.dataComm.dsig.connect(self.mainwin.sim_data.update_sim_data_from_disk) - self.txtComm = TextSignal() self.txtComm.tsig.connect(self.waitsimwin.updatetxt) @@ -214,10 +211,6 @@ def write(self, message): def flush(self): self.out.flush() - def _read_sim_data(self, paramfn, params): - """Signals main window to read sim data from files""" - self.dataComm.dsig.emit(paramfn, params) - def _updatebaseparamwin(self, d): """Signals baseparamwin to update its parameter from passed dict""" self.prmComm.psig.emit(d) @@ -258,9 +251,6 @@ def run(self): try: self._runsim() # run simulation - # send signal to read data from files and save - self._read_sim_data(self.paramfn, self.params) - # update params in all windows (optimization) self._updatedispparam() except RuntimeError as e: diff --git a/hnn/simdat.py b/hnn/simdat.py index dc68c22fe..c5cea061a 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -1,18 +1,19 @@ import os -from PyQt5.QtWidgets import QSizePolicy +import numpy as np +from math import ceil +from glob import glob +from copy import deepcopy + +from scipy import signal +from PyQt5 import QtWidgets from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure import matplotlib as mpl import matplotlib.pyplot as plt import matplotlib.patches as mpatches import matplotlib.gridspec as gridspec -import numpy as np -from math import ceil -from glob import glob -from scipy import signal - from hnn_core import read_spikes -from hnn_core.dipole import Dipole +from hnn_core.dipole import read_dipole, average_dipoles from .spikefn import ExtInputs from .paramrw import get_output_dir, get_fname, get_inputs @@ -29,10 +30,15 @@ def read_dpltrials(sim_dir): ldpl = [] dpl_fname_pattern = os.path.join(sim_dir, 'dpl_*.txt') - for dipole_fn in sorted(glob(str(dpl_fname_pattern))): + glob_list = sorted(glob(str(dpl_fname_pattern))) + if len(glob_list) == 0: + # get the old style filename + glob_list = [get_fname(sim_dir, 'normdpl')] + + for dipole_fn in glob_list: dpl_trial = None try: - dpl_trial = np.loadtxt(dipole_fn) + dpl_trial = read_dipole(dipole_fn) except OSError: if os.path.exists(sim_dir): print('Warning: could not read file:', dipole_fn) @@ -45,6 +51,34 @@ def read_dpltrials(sim_dir): return ldpl +def read_spectrials(sim_dir): + """read spectrogram data files for individual trials""" + spec_list = [] + + spec_fname_pattern = os.path.join(sim_dir, 'rawspec_*.npz') + glob_list = sorted(glob(str(spec_fname_pattern))) + if len(glob_list) == 0: + # get the old style filename + glob_list = [get_fname(sim_dir, 'rawspec')] + + for spec_fn in glob_list: + spec_trial = None + try: + with np.load(spec_fn, allow_pickle=True) as spec_data: + # need to make a copy of data so we can close NpzFile + spec_trial = dict(spec_data) + except OSError: + if os.path.exists(sim_dir): + print('Warning: could not read file:', spec_fn) + except ValueError: + if os.path.exists(sim_dir): + print('Warning: could not read file:', spec_fn) + + spec_list.append(spec_trial) + + return spec_list + + def check_feeds_to_plot(feeds_from_spikes, params): # ensures synaptic weight > 0 using_feeds = get_inputs(params) @@ -86,29 +120,30 @@ def plot_hists_on_gridspec(figure, gridspec, feeds_to_plot, extinputs, times, axprox = figure.add_subplot(gridspec[n_hists, :]) n_hists += 1 + plot_linewidth = linewidth + 1 # check input types provided in simulation if feeds_to_plot['pois']: extinputs.plot_hist(axpois, 'pois', times, 'auto', xlim, color='k', hty='step', - lw=linewidth+1) + lw=plot_linewidth) axes.append(axpois) if feeds_to_plot['dist']: extinputs.plot_hist(axdist, 'dist', times, 'auto', xlim, - color='g', lw=linewidth+1) + color='g', lw=plot_linewidth) axes.append(axdist) if feeds_to_plot['prox']: extinputs.plot_hist(axprox, 'prox', times, 'auto', xlim, - color='r', lw=linewidth+1) + color='r', lw=plot_linewidth) axes.append(axprox) if feeds_to_plot['evdist']: extinputs.plot_hist(axdist, 'evdist', times, 'auto', xlim, color='g', hty='step', - lw=linewidth+1) + lw=plot_linewidth) axes.append(axdist) if feeds_to_plot['evprox']: extinputs.plot_hist(axprox, 'evprox', times, 'auto', xlim, color='r', hty='step', - lw=linewidth+1) + lw=plot_linewidth) axes.append(axprox) # get the ymax for the two histograms @@ -151,15 +186,13 @@ def remove_sim_by_fn(self, paramfn): """ del self._sim_data[paramfn] - def update_sim_data(self, paramfn, params, dpls, avg_dpl, - spikes, gid_ranges, raw_dpls=None, - spec=None): + def update_sim_data(self, paramfn, params, dpls, avg_dpl, spikes, + gid_ranges, spec=None): self._sim_data[paramfn] = {'params': params, 'data': {'dpls': dpls, 'avg_dpl': avg_dpl, 'spikes': spikes, 'gid_ranges': gid_ranges, - 'raw_dpls': raw_dpls, 'spec': spec}} def clear_exp_data(self): @@ -204,87 +237,60 @@ def update_sim_data_from_disk(self, paramfn, params): Simulation paramter filename params : dict Dictionary containing parameters + + Returns + ---------- + found: bool + Whether simulation data directory exists or not """ sim_dir = os.path.join(self._data_dir, params['sim_prefix']) - warn_nofile = False - if os.path.exists(sim_dir): - warn_nofile = True + if not os.path.exists(sim_dir): + self.update_sim_data(paramfn, params, None, None, None, None) + return False warning_message = 'Warning: could not read file:' - dipole_fn = os.path.join(sim_dir, 'dpl.txt') - avg_dpl = None - try: - avg_dpl = np.loadtxt(dipole_fn) - except OSError: - if warn_nofile: - print(warning_message, dipole_fn) - except ValueError: - if warn_nofile: - print(warning_message, dipole_fn) + # dipoles + dpls = read_dpltrials(sim_dir) + if len(dpls) == 0: + print("Warning: no dipole(s) read from %s" % sim_dir) + self.update_sim_data(paramfn, params, None, None, None, None) + return False + elif len(dpls) == 1: + avg_dpl = dpls[0] + else: + avg_dpl = average_dipoles(dpls) - spike_fn = os.path.join(sim_dir, 'spk.txt') - spikes_array = None + # gid_ranges + paramtxt_fn = get_fname(sim_dir, 'param') try: - raw_spikes = read_spikes(spike_fn) - spikes_array = np.r_[raw_spikes.spike_times, - raw_spikes.spike_gids, - raw_spikes.spike_types].T - if len(spikes_array) == 0 and warn_nofile: - print(warning_message, spike_fn) - except ValueError: - try: - spikes_array = np.loadtxt(spike_fn) - except OSError: - if warn_nofile: - print(warning_message, spike_fn) - except ValueError: - if warn_nofile: - print(warning_message, spike_fn) - except IndexError: - print('Warning: incorrect dimensions for spike file: %s' % - spike_fn) + gid_ranges = read_gids_param(paramtxt_fn) + except FileNotFoundError: + print(warning_message, paramtxt_fn) - paramtxt_fn = get_fname(sim_dir, 'param') - gid_ranges = read_gids_param(paramtxt_fn) + # spikes + spk_fname_pattern = os.path.join(sim_dir, 'spk_*.txt') + if len(glob(str(spk_fname_pattern))) == 0: + # if legacy HNN only ran one trial, then no spk_0.txt gets written + spk_fname_pattern = get_fname(sim_dir, 'rawspk') - warn_no_spec = False + try: + spikes = read_spikes(spk_fname_pattern, gid_ranges) + except FileNotFoundError: + print(warning_message, spk_fname_pattern) + + # spec data + spec = None using_feeds = get_inputs(params) if params['save_spec_data'] or using_feeds['ongoing'] or \ using_feeds['pois'] or using_feeds['tonic']: - warn_no_spec = True - - spec_fn = os.path.join(sim_dir, 'rawspec.npz') - spec = None - try: - spec = np.load(spec_fn) - except OSError: - if warn_no_spec and warn_nofile: - print(warning_message, spec_fn) - except ValueError: - if warn_no_spec and warn_nofile: - print(warning_message, spec_fn) + spec = read_spectrials(sim_dir) - warn_no_raw_dpl = False - if params['save_dpl']: - warn_no_raw_dpl = True + self.update_sim_data(paramfn, params, dpls, avg_dpl, spikes, + gid_ranges, spec) - raw_dipole_fn = os.path.join(sim_dir, 'rawdpl.txt') - raw_dpl = None - try: - raw_dpl = np.loadtxt(raw_dipole_fn) - except OSError: - if warn_no_raw_dpl and warn_nofile: - print(warning_message, raw_dipole_fn) - except ValueError: - if warn_no_raw_dpl and warn_nofile: - print(warning_message, raw_dipole_fn) - - dpls = read_dpltrials(sim_dir) - - self.update_sim_data(paramfn, params, dpls, avg_dpl, spikes_array, - gid_ranges, raw_dpl, spec) + return True def calcerr(self, paramfn, tstop, tstart=0.0, weights=None): """Calculate root mean squared error using SimData @@ -320,7 +326,7 @@ def calcerr(self, paramfn, tstop, tstart=0.0, weights=None): shp = dat.shape exp_times = dat[:, 0] - sim_times = self._sim_data[paramfn]['data']['avg_dpl'][:, 0] + sim_times = self._sim_data[paramfn]['data']['avg_dpl'].times # do tstart and tstop fall within both datasets? # if not, use the closest data point as the new tstop/tstart @@ -383,6 +389,7 @@ def update_opt_data(self, paramfn, params, avg_dpl): def _read_dpl(self, paramfn, trial_idx, ntrial): if ntrial == 1: dpltrial = self._sim_data[paramfn]['data']['avg_dpl'] + else: trial_data = self._sim_data[paramfn]['data']['dpls'] if trial_idx > len(trial_data): @@ -392,27 +399,31 @@ def _read_dpl(self, paramfn, trial_idx, ntrial): dpltrial = self._sim_data[paramfn]['data']['dpls'][trial_idx] - dpl_data = np.c_[dpltrial[:, 1], - dpltrial[:, 2], - dpltrial[:, 3]] + return dpltrial - return Dipole(dpltrial[:, 0], dpl_data) + def _plot_spec(self, ax, paramfn, ntrial, spec_cmap, xlim, fontsize): + """Use SimData to plot spectrogram""" - def _plot_spec(self, ax, paramfn, params, xlim, fontsize): - single_spec = self._sim_data[paramfn]['data']['spec'] + # calculate TFR from spec trial data + # start with data from the first trial + spec_TFR = deepcopy(self._sim_data[paramfn]['data']['spec'][0]) + for key in ['TFR', 'TFR_L5', 'TFR_L2']: + spec_list = [self._sim_data[paramfn]['data']['spec'][i][key] + for i in range(ntrial)] + spec_TFR[key] = np.mean(np.array(spec_list), axis=0) # Plot TFR data and add colorbar - plot = ax.imshow(single_spec['TFR'], - extent=(single_spec['time'][0], - single_spec['time'][-1], - single_spec['freq'][-1], - single_spec['freq'][0]), + plot = ax.imshow(spec_TFR['TFR'], + extent=(spec_TFR['time'][0], + spec_TFR['time'][-1], + spec_TFR['freq'][-1], + spec_TFR['freq'][0]), aspect='auto', origin='upper', - cmap=plt.get_cmap(params['spec_cmap'])) + cmap=plt.get_cmap(spec_cmap)) ax.set_ylabel('Frequency (Hz)', fontsize=fontsize) ax.set_xlabel('Time (ms)', fontsize=fontsize) ax.set_xlim(xlim) - ax.set_ylim(single_spec['freq'][-1], single_spec['freq'][0]) + ax.set_ylim(spec_TFR['freq'][-1], spec_TFR['freq'][0]) return plot @@ -428,25 +439,15 @@ def save_spec_with_hist(self, paramfn, params): times = np.linspace(xmin, xmax, num_step) for trial_idx in range(ntrial): - spec_fn = get_fname(sim_dir, 'rawspec', trial_idx, ntrial) - spike_fn = get_fname(sim_dir, 'rawspk', trial_idx, ntrial) - - # Generate file prefix - fprefix = os.path.splitext(os.path.split(spec_fn)[-1])[0] - - # Create the fig name - fig_name = os.path.join(sim_dir, fprefix+'.png') - f = plt.figure(figsize=(8, 8)) font_prop = {'size': 8} mpl.rc('font', **font_prop) # get inputs from spike file gid_ranges = self._sim_data[paramfn]['data']['gid_ranges'] - extinputs = ExtInputs(spike_fn, gid_ranges, params) - extinputs.add_delay_times() - feeds_from_spikes = extinputs.inputs - feeds_to_plot = check_feeds_to_plot(feeds_from_spikes, params) + spikes = self._sim_data[paramfn]['data']['spikes'][trial_idx] + extinputs = ExtInputs(spikes, gid_ranges, [trial_idx], params) + feeds_to_plot = check_feeds_to_plot(extinputs.inputs, params) if feeds_to_plot['ongoing'] or feeds_to_plot['evoked'] or \ feeds_to_plot['pois']: @@ -469,7 +470,8 @@ def save_spec_with_hist(self, paramfn, params): axspec = f.add_subplot(gs0[:, :]) axdipole = f.add_subplot(gs1[:, :]) - cax = self._plot_spec(axspec, paramfn, params, xlim, fontsize) + cax = self._plot_spec(axspec, paramfn, ntrial, + params['spec_cmap'], xlim, fontsize) f.colorbar(cax, ax=axspec) # set xlim based on TFR plot @@ -482,7 +484,8 @@ def save_spec_with_hist(self, paramfn, params): dpl.plot(axdipole, 'agg', show=False) axdipole.set_xlim(xlim) - f.savefig(fig_name, dpi=300) + spec_fig_fn = get_fname(sim_dir, 'figspec', trial_idx, ntrial) + f.savefig(spec_fig_fn, dpi=300) plt.close(f) def save_dipole_with_hist(self, paramfn, params): @@ -497,22 +500,14 @@ def save_dipole_with_hist(self, paramfn, params): times = np.linspace(xmin, xmax, num_step) for trial_idx in range(ntrial): - dipole_fn = get_fname(sim_dir, 'normdpl', trial_idx, ntrial) - spike_fn = get_fname(sim_dir, 'rawspk', trial_idx, ntrial) - - # split to find file prefix - file_prefix = dipole_fn.split('/')[-1].split('.')[0] - # Create the fig name - fig_name = os.path.join(sim_dir, file_prefix+'.png') f = plt.figure(figsize=(12, 6)) font_prop = {'size': 8} mpl.rc('font', **font_prop) gid_ranges = self._sim_data[paramfn]['data']['gid_ranges'] - extinputs = ExtInputs(spike_fn, gid_ranges, params) - extinputs.add_delay_times() - feeds_from_spikes = extinputs.inputs - feeds_to_plot = check_feeds_to_plot(feeds_from_spikes, params) + spikes = self._sim_data[paramfn]['data']['spikes'][trial_idx] + extinputs = ExtInputs(spikes, gid_ranges, [trial_idx], params) + feeds_to_plot = check_feeds_to_plot(extinputs.inputs, params) if feeds_to_plot['ongoing'] or feeds_to_plot['evoked'] or \ feeds_to_plot['pois']: @@ -535,8 +530,8 @@ def save_dipole_with_hist(self, paramfn, params): dpl.plot(axdipole, 'agg', show=False) axdipole.set_xlim(xlim) - fig_name = os.path.join(sim_dir, file_prefix+'.png') - f.savefig(fig_name, dpi=300) + dipole_fig_fn = get_fname(sim_dir, 'figdpl', trial_idx, ntrial) + f.savefig(dipole_fig_fn, dpi=300) plt.close(f) @@ -558,8 +553,8 @@ def __init__(self, paramfn, params, parent=None, width=5, height=4, self.lpatch = [mpatches.Patch(color='black', label='Sim.')] self.setParent(parent) self.linewidth = parent.linewidth - FigureCanvasQTAgg.setSizePolicy(self, QSizePolicy.Expanding, - QSizePolicy.Expanding) + FigureCanvasQTAgg.setSizePolicy(self, QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Expanding) FigureCanvasQTAgg.updateGeometry(self) self.params = params self.paramfn = paramfn @@ -576,8 +571,7 @@ def initaxes(self): # initialize the axes self.axdipole = self.axspec = None - def plotinputhist(self, extinputs=None, feeds_to_plot=None, - plot_distribs=False): + def plotinputhist(self, extinputs=None, feeds_to_plot=None): """ plot input histograms""" xmin = 0. @@ -589,16 +583,11 @@ def plotinputhist(self, extinputs=None, feeds_to_plot=None, num_step = ceil(xmax / sim_dt) + 1 times = np.linspace(xmin, xmax, num_step) - sim_dir = os.path.join(self._data_dir, self.params['sim_prefix']) - spike_fn = os.path.join(sim_dir, 'spk.txt') - - if not plot_distribs: + plot_distribs = True + if extinputs is not None and feeds_to_plot is not None: if feeds_to_plot is None: - sim_data = self.sim_data._sim_data[self.paramfn]['data'] - extinputs = ExtInputs(spike_fn, sim_data['gid_ranges'], self.params) - extinputs.add_delay_times() - dinput = extinputs.inputs - feeds_to_plot = check_feeds_to_plot(dinput, self.params) + feeds_to_plot = check_feeds_to_plot(extinputs.inputs, + self.params) if feeds_to_plot['ongoing'] or feeds_to_plot['evoked'] or \ feeds_to_plot['pois']: @@ -606,8 +595,8 @@ def plotinputhist(self, extinputs=None, feeds_to_plot=None, axes = plot_hists_on_gridspec(self.figure, self.G, feeds_to_plot, extinputs, times, xlim, self.linewidth) - else: - plot_distribs = True + + plot_distribs = False if plot_distribs: dinput = self.getInputDistrib() @@ -689,12 +678,12 @@ def getEVInputTimes(self): nprox, ndist = countEvokedInputs(self.params) ltprox, ltdist = [], [] for i in range(nprox): - input_mu = self.params['t_evprox_' + str(i+1)] - input_sigma = self.params['sigma_t_evprox_' + str(i+1)] + input_mu = self.params['t_evprox_' + str(i + 1)] + input_sigma = self.params['sigma_t_evprox_' + str(i + 1)] ltprox.append((input_mu, input_sigma)) for i in range(ndist): - input_mu = self.params['t_evdist_' + str(i+1)] - input_sigma = self.params['sigma_t_evdist_' + str(i+1)] + input_mu = self.params['t_evdist_' + str(i + 1)] + input_sigma = self.params['sigma_t_evdist_' + str(i + 1)] ltdist.append((input_mu, input_sigma)) return ltprox, ltdist @@ -849,49 +838,54 @@ def plotsimdat(self): self.gRow = 0 bottom = 0.0 - failed_loading = False + failed_loading_dpl = False + failed_loading_spec = False only_create_axes = False - if self.params is None: - only_create_axes = True - DrawSpec = False - xlim = (0.0, 1.0) - else: + DrawSpec = False + xlim = (0.0, 1.0) + only_create_axes = False + + if self.params is not None: + ntrial = self.params['N_trials'] # for trying to plot a simulation read from disk (e.g. default) if self.paramfn not in self.sim_data._sim_data: - self.sim_data.update_sim_data_from_disk(self.paramfn, - self.params) - + found = self.sim_data.update_sim_data_from_disk(self.paramfn, + self.params) + if not found: + # best we can do is plot the distributions of the inputs + axes = self.plotinputhist() + self.gRow = len(axes) + only_create_axes = True + + if not only_create_axes: tstop = self.params['tstop'] xlim = (0.0, tstop) - sim_dir = os.path.join(self._data_dir, self.params['sim_prefix']) - spike_fn = os.path.join(sim_dir, 'spk.txt') - try: - sim_data = self.sim_data._sim_data[self.paramfn]['data'] - extinputs = ExtInputs(spike_fn, sim_data['gid_ranges'], - self.params) - extinputs.add_delay_times() - feeds_from_spikes = extinputs.inputs - feeds_to_plot = check_feeds_to_plot(feeds_from_spikes, - self.params) - axes = self.plotinputhist(extinputs, feeds_to_plot, - plot_distribs=False) - self.gRow = len(axes) - except FileNotFoundError: - axes = self.plotinputhist(plot_distribs=True) - self.gRow = len(axes) + + sim_data = self.sim_data._sim_data[self.paramfn]['data'] + trials = [trial_idx for trial_idx in range(ntrial)] + extinputs = ExtInputs(sim_data['spikes'], sim_data['gid_ranges'], + trials, self.params) + + feeds_to_plot = check_feeds_to_plot(extinputs.inputs, + self.params) + axes = self.plotinputhist(extinputs, feeds_to_plot) + self.gRow = len(axes) # check that dipole data is present single_sim = self.sim_data._sim_data[self.paramfn]['data'] if single_sim['avg_dpl'] is None: - failed_loading = True + failed_loading_dpl = True + if single_sim['spec'] is None or len(single_sim['spec']) == 0: + failed_loading_spec = True # whether to draw the specgram - should draw if user saved it or # have ongoing, poisson, or tonic inputs - DrawSpec = (not failed_loading) and \ - single_sim['spec'] is not None and \ - (self.params['save_spec_data'] or feeds_to_plot['ongoing'] - or feeds_to_plot['pois'] or feeds_to_plot['tonic']) + if (not failed_loading_spec) and single_sim['spec'] is not None \ + and (self.params['save_spec_data'] or + feeds_to_plot['ongoing'] or + feeds_to_plot['pois'] or feeds_to_plot['tonic']): + DrawSpec = True if DrawSpec: # dipole axis takes fewer rows if also drawing specgram self.axdipole = self.figure.add_subplot(self.G[self.gRow:5, 0]) @@ -911,7 +905,7 @@ def plotsimdat(self): self.figure.subplots_adjust(left=left, right=0.99, bottom=bottom, top=0.99, hspace=0.1, wspace=0.1) - if failed_loading or only_create_axes: + if failed_loading_dpl or only_create_axes: return # get spectrogram if it exists, then adjust axis limits but only @@ -919,16 +913,17 @@ def plotsimdat(self): if DrawSpec: single_sim = self.sim_data._sim_data[self.paramfn]['data'] if single_sim['spec'] is not None: + first_spec_trial = single_sim['spec'][0] # use spectogram limits (missing first 50 ms b/c edge effects) - xl = (single_sim['spec']['time'][0], - single_sim['spec']['time'][-1]) + xlim = (first_spec_trial['time'][0], + first_spec_trial['time'][-1]) else: DrawSpec = False yl = [0, 0] dpl = self.sim_data._sim_data[self.paramfn]['data']['avg_dpl'] - yl[0] = min(yl[0], np.amin(dpl[:, 1])) - yl[1] = max(yl[1], np.amax(dpl[:, 1])) + yl[0] = min(yl[0], np.amin(dpl.data['agg'])) + yl[1] = max(yl[1], np.amax(dpl.data['agg'])) if not self.optMode: # skip for optimization @@ -937,47 +932,47 @@ def plotsimdat(self): old_data = self.sim_data._sim_data[paramfn]['data']['avg_dpl'] if old_data is None: continue - times = old_data[:, 0] - old_dpl = old_data[:, 1] + times = old_data.times + old_dpl = old_data.data['agg'] self.axdipole.plot(times, old_dpl, '--', color='black', linewidth=self.linewidth) sim_data = self.sim_data._sim_data[self.paramfn]['data'] # plot dipoles from individual trials - if self.params['N_trials'] > 1 and drawindivdpl and \ + if ntrial > 1 and drawindivdpl and \ len(sim_data['dpls']) > 0: for dpltrial in sim_data['dpls']: - self.axdipole.plot(dpltrial[:, 0], dpltrial[:, 1], + self.axdipole.plot(dpltrial.times, dpltrial.data['agg'], color='gray', linewidth=self.linewidth) - yl[0] = min(yl[0], dpltrial[:, 1].min()) - yl[1] = max(yl[1], dpltrial[:, 1].max()) + yl[0] = min(yl[0], dpltrial.data['agg'].min()) + yl[1] = max(yl[1], dpltrial.data['agg'].max()) - if drawavgdpl or self.params['N_trials'] <= 1: + if drawavgdpl or ntrial <= 1: # this is the average dipole (across trials) # it's also the ONLY dipole when running a single trial - self.axdipole.plot(dpl[:, 0], dpl[:, 1], 'k', + self.axdipole.plot(dpl.times, dpl.data['agg'], 'k', linewidth=self.linewidth + 1) - yl[0] = min(yl[0], dpl[:, 1].min()) - yl[1] = max(yl[1], dpl[:, 1].max()) + yl[0] = min(yl[0], dpl.data['agg'].min()) + yl[1] = max(yl[1], dpl.data['agg'].max()) else: if self.sim_data._opt_data['avg_dpl'] is not None: # show optimized dipole as gray line optdpl = self.sim_data._opt_data['avg_dpl'] - self.axdipole.plot(optdpl[:, 0], optdpl[:, 1], 'k', + self.axdipole.plot(optdpl.times, optdpl.data['agg'], 'k', color='gray', linewidth=self.linewidth + 1) - yl[0] = min(yl[0], optdpl[:, 1].min()) - yl[1] = max(yl[1], optdpl[:, 1].max()) + yl[0] = min(yl[0], optdpl.data['agg'].min()) + yl[1] = max(yl[1], optdpl.data['agg'].max()) if self.sim_data._opt_data['initial_dpl'] is not None: # show initial dipole in dotted black line plot_data = self.sim_data._opt_data['initial_dpl'] - times = plot_data[:, 0] - plot_dpl = plot_data[:, 0] + times = plot_data.times + plot_dpl = plot_data.data['agg'] self.axdipole.plot(times, plot_dpl, '--', color='black', linewidth=self.linewidth) - dpl = self.sim_data._opt_data['initial_dpl'][:, 1] + dpl = self.sim_data._opt_data['initial_dpl'].data['agg'] yl[0] = min(yl[0], dpl.min()) yl[1] = max(yl[1], dpl.max()) @@ -1009,8 +1004,9 @@ def plotsimdat(self): if DrawSpec: gRow = 6 self.axspec = self.figure.add_subplot(self.G[gRow:10, 0]) - cax = self.sim_data._plot_spec(self.axspec, self.paramfn, - self.params, xl, fontsize) + cax = self.sim_data._plot_spec(self.axspec, self.paramfn, ntrial, + self.params['spec_cmap'], xlim, + fontsize) cbaxes = self.figure.add_axes([0.6, 0.49, 0.3, 0.005]) # plot colorbar horizontally to save space plt.colorbar(cax, cax=cbaxes, orientation='horizontal') diff --git a/hnn/specfn.py b/hnn/specfn.py index d237d3c58..cadef9a13 100644 --- a/hnn/specfn.py +++ b/hnn/specfn.py @@ -67,7 +67,7 @@ def __traces2TFR(self): # range should probably be 0 to len(self.S_trans) # shift tvec to reflect change # this is in ms - self.t = 1000. * np.arange(1, len(self.S_trans)+1) / self.fs + \ + self.t = 1000. * np.arange(1, len(self.S_trans) + 1) / self.fs + \ self.tmin - self.params['dt'] # preallocation @@ -104,7 +104,7 @@ def __morlet(self, f, t): st = 1. / (2. * np.pi * sf) # A in 1 / s - A = 1. / (st * np.sqrt(2.*np.pi)) + A = 1. / (st * np.sqrt(2. * np.pi)) # units: 1/s * (exp (s**2 / s**2)) * exp( 1/ s * s) y = A * np.exp(-t**2. / (2. * st**2.)) * np.exp(1.j * 2. * np.pi * f * @@ -125,7 +125,7 @@ def __energyvec(self, f, s): sf = f / self.width st = 1. / (2. * np.pi * sf) - t = np.arange(-3.5*st, 3.5*st, dt) + t = np.arange(-3.5 * st, 3.5 * st, dt) # calculate the morlet wavelet for this frequency # units of m are 1/s @@ -137,7 +137,7 @@ def __energyvec(self, f, s): # take the power ... y = (2. * abs(y) / self.fs)**2. i_lower = int(np.ceil(len(m) / 2.)) - i_upper = int(len(y) - np.floor(len(m) / 2.)+1) + i_upper = int(len(y) - np.floor(len(m) / 2.) + 1) y = y[i_lower:i_upper] return y @@ -195,8 +195,9 @@ def spec_dpl_kernel(params, dpl, fspec, opts): spec_results = {'time': spec_agg.t, 'freq': spec_agg.f, 'TFR': spec_agg.TFR, 'max_agg': max_agg, - 't_L2': spec_L2.t, 'f_L2': spec_L2.f, 'TFR_L2': spec_L2.TFR, - 't_L5': spec_L5.t, 'f_L5': spec_L5.f, 'TFR_L5': spec_L5.TFR, + 't_L2': spec_L2.t, 'f_L2': spec_L2.f, + 'TFR_L2': spec_L2.TFR, 't_L5': spec_L5.t, + 'f_L5': spec_L5.f, 'TFR_L5': spec_L5.TFR, 'pgram_p': pgram.P, 'pgram_f': pgram.f} # Save spec results np.savez_compressed(fspec, time=spec_agg.t, freq=spec_agg.f, @@ -207,6 +208,7 @@ def spec_dpl_kernel(params, dpl, fspec, opts): return spec_results + def analysis_simp(opts, params, fdpl, fspec): opts_run = {'type': 'dpl_laminar', 'f_max': 100., diff --git a/hnn/spikefn.py b/hnn/spikefn.py index a39ee7aa6..62bbe22f8 100644 --- a/hnn/spikefn.py +++ b/hnn/spikefn.py @@ -3,374 +3,232 @@ # v 1.10.0-py35 # rev 2016-05-01 (SL: minor) # last major: (SL: toward python3) +# 2020-12-1 BC: use hnn-core and remove old code import numpy as np -import matplotlib.pyplot as plt -import itertools as it -import os - -import scipy.signal as sps -from hnn_core import read_spikes - -from .paramrw import read_gids_param - -# meant as a class for ONE cell type -class Spikes(): - def __init__ (self, s_all, ranges): - self.r = ranges - self.spike_list = self.filter(s_all) - self.N_cells = len(self.r) - self.N_spikingcells = len(self.spike_list) - # this is set externally - self.tick_marks = [] - - # returns spike_list, a list of lists of spikes. - # Each list corresponds to a cell, counted by range - def filter (self, s_all): - spike_list = [] - if len(s_all) > 0: - for ri in self.r: - srange = s_all[s_all[:, 1] == ri][:, 0] - srange[srange.argsort()] - spike_list.append(srange) - - return spike_list - - # simple return of all spikes *or* each spike indexed i in every list - def collapse_all (self, i=None): - if i == 'None': - spk_all = [] - for spk_list in self.spike_list: - spk_all.extend(spk_list) - else: - spk_all = [spk_list[i] for spk_list in self.spike_list if spk_list] - return spk_all - - # uses self.collapse_all() and returns unique spike times - def unique_all (self, i=None): - spk_all = self.collapse_all(i) - return np.unique(spk_all) - - # plot psth - def ppsth (self, a): - # flatten list of spikes - s_agg = np.array(list(it.chain.from_iterable(self.spike_list))) - # plot histogram to axis 'a' - bins = hist_bin_opt(s_agg, 1) - a.hist(s_agg, bins, normed=True, facecolor='g', alpha=0.75) - -# Class to handle extinput event times -class ExtInputs (Spikes): - # class for external inputs - extracts gids and times - def __init__ (self, fspk, gid_ranges, params, evoked=False): - - self.p_dict = params - self.gid_dict = gid_ranges - - if 'common' in self.gid_dict: - extinput_key = 'common' - elif 'extinput' in self.gid_dict: - extinput_key = 'extinput' - else: - raise ValueError("Bad 'param.txt' file for reading input gids") - - self.evoked = evoked - - # parse evoked prox and dist input gids from gid_dict - self.gid_evprox, self.gid_evdist = self.__get_evokedinput_gids() - - # parse ongoing prox and dist input gids from gid_dict - self.gid_prox, self.gid_dist = self.__get_extinput_gids(extinput_key) - # poisson input gids - #print('getting pois input gids') - self.gid_pois = self.__get_poisinput_gids() - # self.inputs is dict of input times with keys 'prox' and 'dist' - self.inputs = self.__get_extinput_times(fspk) - - def __get_extinput_gids (self, extinput_key): - # Determine if both feeds exist in this sim - # If they do, self.gid_dict['extinput'] has length 2 - # If so, first gid is guaraneteed to be prox feed, second to be dist feed - if len(self.gid_dict[extinput_key]) == 2: - return self.gid_dict[extinput_key] - # Otherwise, only one feed exists in this sim - # Must use param file to figure out which one... - elif len(self.gid_dict[extinput_key]) > 0: - if self.p_dict['t0_input_prox'] < self.p_dict['tstop']: - return self.gid_dict[extinput_key][0], None - elif self.p_dict['t0_input_dist'] < self.p_dict['tstop']: - return None, self.gid_dict[extinput_key][0] - else: - return None, None - - def __get_poisinput_gids (self): - # get Poisson input gids - gids = [] - if len(self.gid_dict['extpois']) > 0: - if self.p_dict['t0_pois'] < self.p_dict['tstop']: - gids = np.array(self.gid_dict['extpois']) - self.pois_gid_range = (min(gids),max(gids)) - return gids - - def countevinputs (self, ty): - # count number of evoked inputs - n = 0 - for k in self.gid_dict.keys(): - if k.startswith(ty) and len(self.gid_dict[k]) > 0: n += 1 - return n - - def countevprox (self): return self.countevinputs('evprox') - def countevdist (self): return self.countevinputs('evdist') - - def __get_evokedinput_gids (self): - gid_prox,gid_dist=None,None - nprox,ndist = self.countevprox(), self.countevdist() - #print('__get_evokedinput_gids keys:',self.gid_dict.keys(),'nprox:',nprox,'ndist:',ndist) - if nprox > 0: - gid_prox = [] - for i in range(nprox): - if len(self.gid_dict['evprox'+str(i+1)]) > 0: - l = list(self.gid_dict['evprox'+str(i+1)]) - for x in l: gid_prox.append(x) - gid_prox = np.array(gid_prox) - self.evprox_gid_range = (min(gid_prox),max(gid_prox)) - if ndist > 0: - gid_dist = [] - for i in range(ndist): - if len(self.gid_dict['evdist'+str(i+1)]) > 0: - l = list(self.gid_dict['evdist'+str(i+1)]) - for x in l: gid_dist.append(x) - gid_dist = np.array(gid_dist) - self.evdist_gid_range = (min(gid_dist),max(gid_dist)) - return gid_prox, gid_dist - - def unique_times (self,s_all,lidx): - self.r = [x for x in lidx] - lfilttime = self.filter(s_all); ltime = [] - for arr in lfilttime: - for time in arr: - ltime.append(time) - return np.array(list(set(ltime))) - - def get_times (self, gid, s_all): - # self.filter() inherited from Spikes() - # self.r weirdness is necessary to use self.filter() - # i.e. self.r must exist and be a list to execute self.filter() - self.r = [gid] - return self.filter(s_all)[0] - - def __get_extinput_times (self, fspk): - # load all spike times from file - s_all = [] - try: - spikes = read_spikes(fspk) - s_all = np.r_[spikes.spike_times, spikes.spike_gids].T - except ValueError: - s_all = np.loadtxt(open(fspk, 'rb')) - except IndexError: - # incorrect dimensions (bad spike file) - print('Warning: bad data in spike file:', fspk) - except OSError: - print('Warning: could not read spike file:', fspk) - - if len(s_all) == 0: - # couldn't read spike times - raise ValueError("No spikes in file: %s" % fspk) - - inputs = {k:np.array([]) for k in ['prox','dist','evprox','evdist','pois']} - if self.gid_prox is not None: inputs['prox'] = self.get_times(self.gid_prox,s_all) - if self.gid_dist is not None: inputs['dist'] = self.get_times(self.gid_dist,s_all) - if self.gid_evprox is not None: inputs['evprox'] = self.unique_times(s_all, self.gid_evprox) - if self.gid_evdist is not None: inputs['evdist'] = self.unique_times(s_all, self.gid_evdist) - if self.gid_pois is not None: inputs['pois'] = self.unique_times(s_all, self.gid_pois) - return inputs - - # gid associated with evoked input - def is_evoked_gid (self,gid): - if len(self.inputs['evprox']) > 0: - if self.evprox_gid_range[0] <= gid <= self.evprox_gid_range[1]: - return True - if len(self.inputs['evdist']) > 0: - if self.evdist_gid_range[0] <= gid <= self.evdist_gid_range[1]: - return True - return False - - # check if gid is associated with a proximal input - def is_prox_gid (self, gid): - if gid == self.gid_prox: return True - if len(self.inputs['evprox']) > 0: - return self.evprox_gid_range[0] <= gid <= self.evprox_gid_range[1] - return False - - # check if gid is associated with a distal input - def is_dist_gid (self, gid): - if gid == self.gid_dist: return True - if len(self.inputs['evdist']) > 0: - return self.evdist_gid_range[0] <= gid <= self.evdist_gid_range[1] - return False - - # check if gid is associated with a Poisson input - def is_pois_gid (self, gid): - if len(self.inputs['pois']) > 0: - return self.pois_gid_range[0] <= gid <= self.pois_gid_range[1] - - return False - - def truncate_ext (self, dtype, t_int): - if dtype == 'prox' or dtype == 'dist': - tmask = (self.inputs[dtype] >= t_int[0]) & (self.inputs[dtype] <= t_int[1]) - return self.inputs[dtype][tmask] - if dtype == 'env': - tmask = (self.inputs['t'] >= t_int[0]) & (self.inputs['t'] <= t_int[1]) - return [self.inputs[dtype][tmask], self.inputs['t'][tmask]] - - def add_delay_times (self): - # if prox delay to both layers is the same, add it to the prox input times - if self.p_dict['input_prox_A_delay_L2'] == self.p_dict['input_prox_A_delay_L5']: - self.inputs['prox'] += self.p_dict['input_prox_A_delay_L2'] - # if dist delay to both layers is the same, add it to the dist input times - if self.p_dict['input_dist_A_delay_L2'] == self.p_dict['input_dist_A_delay_L5']: - self.inputs['dist'] += self.p_dict['input_dist_A_delay_L2'] - - # extinput is either 'dist' or 'prox' - def plot_hist (self, ax, extinput, tvec, bins='auto', xlim=None, color='green', hty='bar',lw=4): - if bins == 'auto': - bins = hist_bin_opt(self.inputs[extinput], 1) - if not xlim: - xlim = (0., self.p_dict['tstop']) - if len(self.inputs[extinput]): - #print("plot_hist bins:",bins,type(bins)) - hist = ax.hist(self.inputs[extinput], bins, range=xlim, color=color, label=extinput, histtype=hty,linewidth=lw) - ax.set_xticklabels([]) - ax.tick_params(bottom=False, left=False) - else: - hist = None - return hist - -# weird bin counting function -def bin_count(bins_per_second, tinterval): return bins_per_second * tinterval / 1000. - -# splits ext random feeds (of type exttype) by supplied cell type -def split_extrand(s, gid_dict, celltype, exttype): - gid_exttype_start = gid_dict[exttype][0] - gid_exttype_cell = [gid + gid_exttype_start for gid in gid_dict[celltype]] - return Spikes(s, gid_exttype_cell) + # histogram bin optimization -def hist_bin_opt(x, N_trials): - """ Shimazaki and Shinomoto, Neural Comput, 2007 - """ - bin_checks = np.arange(80, 300, 10) - # bin_checks = np.linspace(150, 300, 16) - costs = np.zeros(len(bin_checks)) - i = 0 - # this might be vectorizable in np - for n_bins in bin_checks: - # use np.histogram to do the numerical minimization - pdf, bin_edges = np.histogram(x, n_bins) - # calculate bin width - # some discrepancy here but should be fine - w_bin = np.unique(np.diff(bin_edges)) - if len(w_bin) > 1: w_bin = w_bin[0] - # calc mean and var - kbar = np.mean(pdf) - kvar = np.var(pdf) - # calc cost - costs[i] = (2.*kbar - kvar) / (N_trials * w_bin)**2. - i += 1 - # find the bin size corresponding to a minimization of the costs - bin_opt_list = bin_checks[costs.min() == costs] - bin_opt = bin_opt_list[0] - return bin_opt - -# from the supplied key name, return a marker style -def get_markerstyle(key): - markerstyle = '' - # ext now same color, not ideal yet - # if 'L2' in key: - # markerstyle += 'k' - # elif 'L5' in key: - # markerstyle += 'b' - # short circuit this by putting extgauss first ... cheap. - if 'extgauss' in key: - markerstyle += 'k.' - elif 'extpois' in key: - markerstyle += 'k.' - elif 'pyramidal' in key: - markerstyle += 'k.' - elif 'basket' in key: - markerstyle += 'r|' - return markerstyle - -# Add synaptic delays to alpha input times if applicable: -def add_delay_times(s_dict, p_dict): - # Only add delays if delay is same for L2 and L5 - # Proximal feed - # if L5 delay is -1, has same delays as L2 - # if p_dict['input_prox_A_delay_L5'] == -1: - # s_dict['alpha_feed_prox'].spike_list = [num+p_dict['input_prox_A_delay_L2'] for num in s_dict['alpha_feed_prox'].spike_list] - # else, check to see if delays are the same anyway - # else: - if s_dict['alpha_feed_prox'].spike_list and p_dict['input_prox_A_delay_L2'] == p_dict['input_prox_A_delay_L5']: - s_dict['alpha_feed_prox'].spike_list = [num+p_dict['input_prox_A_delay_L2'] for num in s_dict['alpha_feed_prox'].spike_list] - # Distal - # if L5 delay is -1, has same delays as L2 - # if p_dict['input_dist_A_delay_L5'] == -1: - # s_dict['alpha_feed_dist'].spike_list = [num+p_dict['input_dist_A_delay_L2'] for num in s_dict['alpha_feed_dist'].spike_list] - # else, check to see if delays are the same anyway - # else: - if s_dict['alpha_feed_dist'].spike_list and p_dict['input_dist_A_delay_L2'] == p_dict['input_dist_A_delay_L5']: - s_dict['alpha_feed_dist'].spike_list = [num+p_dict['input_dist_A_delay_L2'] for num in s_dict['alpha_feed_dist'].spike_list] - return s_dict - -# Checks for existance of alpha feed keys in s_dict. -def alpha_feed_verify(s_dict, p_dict): - """ If they do not exist, then simulation used one or no feeds. Creates keys accordingly - """ - # check for existance of keys. If exist, do nothing - if 'alpha_feed_prox' and 'alpha_feed_dist' in s_dict.keys(): - pass - # if they do not exist, create them and add proper data - else: - # if proximal feed's t0 < tstop, it exists and data is stored in s_dict['extinputs']. - # distal feed does not exist and gets empty list - if p_dict['t0_input_prox'] < p_dict['tstop']: - s_dict['alpha_feed_prox'] = s_dict['extinput'] - # make object on the fly with attribute 'spike_list' - # A little hack-y - s_dict['alpha_feed_dist'] = type('emptyspike', (object,), {'spike_list': np.array([])}) - # if distal feed's t0 < tstop, it exists and data is stored in s_dict['extinputs']. - # Proximal feed does not exist and gets empty list - elif p_dict['t0_input_dist'] < p_dict['tstop']: - s_dict['alpha_feed_prox'] = type('emptyspike', (object,), {'spike_list': np.array([])}) - s_dict['alpha_feed_dist'] = s_dict['extinput'] - # if neither had t0 < tstop, neither exists and both get empty list - else: - s_dict['alpha_feed_prox'] = type('emptyspike', (object,), {'spike_list': np.array([])}) - s_dict['alpha_feed_dist'] = type('emptyspike', (object,), {'spike_list': np.array([])}) - return s_dict - -# input histogram on 2 axes -def pinput_hist(a0, a1, s_list0, s_list1, n_bins, xlim): - hists = { - 'prox': a0.hist(s_list0, n_bins, color='red', label='Proximal input', alpha=0.75), - 'dist': a1.hist(s_list1, n_bins, color='green', label='Distal input', alpha=0.75), - } - # assumes these axes are inverted and figure it out - ylim_max = 2*np.max([a0.get_ylim()[1], a1.get_ylim()[1]]) + 1 - # set the ylims here - a0.set_ylim((0, ylim_max)) - a1.set_ylim((0, ylim_max)) - a0.set_xlim(xlim) - a1.set_xlim(xlim) - a1.invert_yaxis() - return hists - -def pinput_hist_onesided(a0, s_list, n_bins): - hists = { - 'prox': a0.hist(s_list, n_bins, color='k', label='Proximal input', alpha=0.75), - } - return hists - -if __name__ == '__main__': - pass +def _hist_bin_opt(x, N_trials): + """ Shimazaki and Shinomoto, Neural Comput, 2007 """ + + bin_checks = np.arange(80, 300, 10) + # bin_checks = np.linspace(150, 300, 16) + costs = np.zeros(len(bin_checks)) + i = 0 + # this might be vectorizable in np + for n_bins in bin_checks: + # use np.histogram to do the numerical minimization + pdf, bin_edges = np.histogram(x, n_bins) + # calculate bin width + # some discrepancy here but should be fine + w_bin = np.unique(np.diff(bin_edges)) + if len(w_bin) > 1: + w_bin = w_bin[0] + # calc mean and var + kbar = np.mean(pdf) + kvar = np.var(pdf) + # calc cost + costs[i] = (2. * kbar - kvar) / (N_trials * w_bin)**2. + i += 1 + # find the bin size corresponding to a minimization of the costs + bin_opt_list = bin_checks[costs.min() == costs] + bin_opt = bin_opt_list[0] + return bin_opt + + +class ExtInputs(object): + """Class for extracting gids and times from external inputs""" + + def __init__(self, spikes, gid_ranges, trials, params): + self.p_dict = params + self.gid_ranges = gid_ranges + + if 'common' in self.gid_ranges: + # hnn-core + extinput_key = 'common' + elif 'extinput' in self.gid_ranges: + # hnn legacy + extinput_key = 'extinput' + else: + print(self.gid_ranges) + raise ValueError("Unable to find key for external inputs") + + # parse evoked prox and dist input gids from gid_ranges + self.gid_evprox, self.gid_evdist = self._get_evokedinput_gids() + + # parse ongoing prox and dist input gids from gid_ranges + self.gid_prox, self.gid_dist = self._get_extinput_gids(extinput_key) + + # poisson input gids + self.gid_pois = self._get_poisinput_gids() + + # self.inputs is dict of input times with keys 'prox' and 'dist' + self.inputs = self._get_extinput_times(spikes, trials) + + self._add_delay_times() + + def _get_extinput_gids(self, extinput_key): + """Determine if both feeds exist in this sim + + If they do, self.gid_ranges[extinput_key] has length 2 + If so, first gid is guaraneteed to be prox feed, second to be dist + feed + """ + + if len(self.gid_ranges[extinput_key]) == 2: + return self.gid_ranges[extinput_key] + elif len(self.gid_ranges[extinput_key]) > 0: + # Otherwise, only one feed exists in this sim + # Must use param file to figure out which one... + if self.p_dict['t0_input_prox'] < self.p_dict['tstop']: + return self.gid_ranges[extinput_key][0], None + elif self.p_dict['t0_input_dist'] < self.p_dict['tstop']: + return None, self.gid_ranges[extinput_key][0] + else: + return None, None + + def _get_poisinput_gids(self): + """get Poisson input gids""" + + gids = [] + if len(self.gid_ranges['extpois']) > 0: + if self.p_dict['t0_pois'] < self.p_dict['tstop']: + gids = np.array(self.gid_ranges['extpois']) + self.pois_gid_range = (min(gids), max(gids)) + return gids + + def countevinputs(self, ty): + # count number of evoked inputs + num_inputs = 0 + for key in self.gid_ranges.keys(): + if key.startswith(ty) and len(self.gid_ranges[key]) > 0: + num_inputs += 1 + return num_inputs + + def countevprox(self): + return self.countevinputs('evprox') + + def countevdist(self): + return self.countevinputs('evdist') + + def _get_evokedinput_gids(self): + gid_prox, gid_dist = None, None + nprox, ndist = self.countevprox(), self.countevdist() + + if nprox > 0: + gid_prox = [] + for i in range(nprox): + if len(self.gid_ranges['evprox' + str(i + 1)]) > 0: + gid_prox += list(self.gid_ranges['evprox' + str(i + 1)]) + gid_prox = np.array(gid_prox) + self.evprox_gid_range = (min(gid_prox), max(gid_prox)) + if ndist > 0: + gid_dist = [] + for i in range(ndist): + if len(self.gid_ranges['evdist' + str(i + 1)]) > 0: + gid_dist += list(self.gid_ranges['evdist' + str(i + 1)]) + gid_dist = np.array(gid_dist) + self.evdist_gid_range = (min(gid_dist), max(gid_dist)) + + return gid_prox, gid_dist + + def _filter(self, spikes, trials, filter_range): + """returns spike_list, a list of lists of spikes. + + Each list corresponds to a cell, counted by range + """ + + filtered_spike_times = [] + for trial_idx in trials: + indices = np.where(np.in1d(spikes.spike_gids[trial_idx], + filter_range))[0] + matches = np.array(spikes.spike_times[trial_idx])[indices] + filtered_spike_times += list(matches) + + return np.array(filtered_spike_times) + + def _get_times(self, spikes, trials, filter_range): + return self._filter(spikes, trials, filter_range) + + def _unique_times(self, spikes, trials, filter_range): + filtered_spike_times = self._get_times(spikes, trials, filter_range) + + return np.unique(filtered_spike_times) + + def _get_extinput_times(self, spikes, trials): + """load all spike times from file""" + + inputs = {k: np.array([]) for k in ['prox', 'dist', 'evprox', 'evdist', + 'pois']} + if self.gid_prox is not None: + inputs['prox'] = self._get_times(spikes, trials, [self.gid_prox]) + if self.gid_dist is not None: + inputs['dist'] = self._get_times(spikes, trials, [self.gid_dist]) + if self.gid_evprox is not None: + inputs['evprox'] = self._unique_times(spikes, trials, + self.gid_evprox) + if self.gid_evdist is not None: + inputs['evdist'] = self._unique_times(spikes, trials, + self.gid_evdist) + if self.gid_pois is not None: + inputs['pois'] = self._unique_times(spikes, trials, self.gid_pois) + + return inputs + + def is_prox_gid(self, gid): + """check if gid is associated with a proximal input""" + + if gid == self.gid_prox: + return True + if len(self.inputs['evprox']) > 0: + return self.evprox_gid_range[0] <= gid <= self.evprox_gid_range[1] + + return False + + def is_dist_gid(self, gid): + """check if gid is associated with a distal input""" + + if gid == self.gid_dist: + return True + if len(self.inputs['evdist']) > 0: + return self.evdist_gid_range[0] <= gid <= self.evdist_gid_range[1] + + return False + + def is_pois_gid(self, gid): + """check if gid is associated with a Poisson input""" + if len(self.inputs['pois']) > 0: + return self.pois_gid_range[0] <= gid <= self.pois_gid_range[1] + + return False + + def _add_delay_times(self): + # if same prox delay to both layers, add it to the prox input times + if self.p_dict['input_prox_A_delay_L2'] == \ + self.p_dict['input_prox_A_delay_L5']: + self.inputs['prox'] += self.p_dict['input_prox_A_delay_L2'] + + # if same dist delay to both layers, add it to the dist input times + if self.p_dict['input_dist_A_delay_L2'] == \ + self.p_dict['input_dist_A_delay_L5']: + self.inputs['dist'] += self.p_dict['input_dist_A_delay_L2'] + + def plot_hist(self, ax, extinput, tvec, bins='auto', xlim=None, + color='green', hty='bar', lw=4): + # extinput is either 'dist' or 'prox' + + if bins == 'auto': + bins = _hist_bin_opt(self.inputs[extinput], 1) + if not xlim: + xlim = (0., self.p_dict['tstop']) + if len(self.inputs[extinput]): + hist = ax.hist(self.inputs[extinput], bins, range=xlim, + color=color, label=extinput, histtype=hty, + linewidth=lw) + ax.set_xticklabels([]) + ax.tick_params(bottom=False, left=False) + else: + hist = None + + return hist diff --git a/hnn/visrast.py b/hnn/visrast.py index 6c6dcad97..23b0dd148 100644 --- a/hnn/visrast.py +++ b/hnn/visrast.py @@ -52,7 +52,6 @@ params['sim_prefix'], 'param.txt') extinputs = spikefn.ExtInputs(spkpath, outparamf, params) -extinputs.add_delay_times() alldat = {} @@ -235,7 +234,6 @@ def loadspk (self,idx): except ValueError: print("Error: could not load spike timings from %s" % spkpath) return - extinputs.add_delay_times() dspk,haveinputs,dhist = getdspk(spkpath) alldat[idx]['dspk'] = dspk alldat[idx]['haveinputs'] = haveinputs @@ -251,7 +249,6 @@ def loadspk (self,idx): except ValueError: print("Error: could not load spike timings from %s" % spkpath) return - extinputs.add_delay_times() alldat[idx]['dspk'] = dspktrial alldat[idx]['haveinputs'] = haveinputs alldat[idx]['dhist'] = dhisttrial From 79a7388b8a2467670f997c14fc08e5e042800795 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 1 Dec 2020 17:52:08 -0500 Subject: [PATCH 061/107] TST: enable flake8 checking --- hnn/__init__.py | 2 +- hnn/qt_lib.py | 10 +++++----- hnn/tests/test_compare_hnn.py | 1 + scripts/run-pytest.sh | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/hnn/__init__.py b/hnn/__init__.py index f22111efa..1b32f85ca 100644 --- a/hnn/__init__.py +++ b/hnn/__init__.py @@ -1,3 +1,3 @@ __version__ = "1.4.0" -from .hnn_qt5 import HNNGUI \ No newline at end of file +from .hnn_qt5 import HNNGUI diff --git a/hnn/qt_lib.py b/hnn/qt_lib.py index 86e9503e3..aa8ee2261 100644 --- a/hnn/qt_lib.py +++ b/hnn/qt_lib.py @@ -116,7 +116,7 @@ def setscalegeomcenter(dlg, origw, origh): def scale(val, src, dst): numerator = val - src[0] - denominator = float(src[1]-src[0]) * (dst[1]-dst[0]) + dst[0] + denominator = float(src[1] - src[0]) * (dst[1] - dst[0]) + dst[0] if denominator == 0: return 0 @@ -365,11 +365,11 @@ def setRange(self, start, end): def keyPressEvent(self, event): key = event.key() if key == Qt.Key_Left: - s = self.start()-1 - e = self.end()-1 + s = self.start() - 1 + e = self.end() - 1 elif key == Qt.Key_Right: - s = self.start()+1 - e = self.end()+1 + s = self.start() + 1 + e = self.end() + 1 else: event.ignore() return diff --git a/hnn/tests/test_compare_hnn.py b/hnn/tests/test_compare_hnn.py index 288bef6f4..99eb07d5c 100644 --- a/hnn/tests/test_compare_hnn.py +++ b/hnn/tests/test_compare_hnn.py @@ -35,6 +35,7 @@ def run_hnn(qtbot, monkeypatch): qtbot.mouseClick(main.qbtn, QtCore.Qt.LeftButton) assert exit_calls == [1] + @pytest.mark.skipif(sys.platform == 'win32', reason="does not run on windows") def test_hnn(qtbot, monkeypatch): diff --git a/scripts/run-pytest.sh b/scripts/run-pytest.sh index cd7d0fdb6..67e23acf3 100755 --- a/scripts/run-pytest.sh +++ b/scripts/run-pytest.sh @@ -9,8 +9,8 @@ fi # first check code style with flake8 (ignored currently) echo "Checking code style compliance with flake8..." -flake8 --exit-zero --quiet --count \ - --exclude visdipole.py,visrast.py,visvolt.py,visspec.py,vispsd.py,spikefn.py,specfn.py,DataViewGUI.py \ +flake8 --quiet --count \ + --exclude hnn_qt5.py,qt_evoked.py,run.py,paramrw.py,visdipole.py,visrast.py,visvolt.py,visspec.py,vispsd.py,DataViewGUI.py \ hnn echo "Running unit tests with pytest..." From 9757c1ab46ebad73a556634711754c3fc7852389 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 1 Dec 2020 18:56:45 -0500 Subject: [PATCH 062/107] ENH: handle SIMCanvas exceptions gracefully --- hnn/hnn_qt5.py | 8 ++++++-- hnn/simdat.py | 48 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 0f7dcd908..0d68e5c2b 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -1302,10 +1302,10 @@ def selParamFileDialog(self): # now update the GUI components to reflect the param file selected self.baseparamwin.updateDispParam(params) - self.initSimCanvas() # recreate canvas - # self.sim_canvas.plot() # replot data self.setWindowTitle(self.baseparamwin.paramfn) + self.initSimCanvas() # recreate canvas + self.populateSimCB() # populate the combobox if self.sim_data.get_exp_data_size() > 0: @@ -1917,6 +1917,7 @@ def initSimCanvas(self, recalcErr=True, optMode=False, gRow=1, reInit=True): self.sim_canvas = SIMCanvas(self.baseparamwin.paramfn, self.baseparamwin.params, parent=self, width=10, height=1, dpi=getmplDPI(), optMode=optMode) + # this is the Navigation widget # it takes the Canvas widget and a parent self.toolbar = NavigationToolbar2QT(self.sim_canvas, self) @@ -1927,6 +1928,9 @@ def initSimCanvas(self, recalcErr=True, optMode=False, gRow=1, reInit=True): self.sim_canvas.plot(recalcErr) self.sim_canvas.draw() + if self.sim_canvas.saved_exception is not None: + raise self.sim_canvas.saved_exception + def setcursors(self, cursor): # set cursors of self and children self.setCursor(cursor) diff --git a/hnn/simdat.py b/hnn/simdat.py index c5cea061a..7f4bf07fa 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -79,6 +79,19 @@ def read_spectrials(sim_dir): return spec_list +def read_spktrials(sim_dir, gid_ranges): + spk_fname_pattern = os.path.join(sim_dir, 'spk_*.txt') + if len(glob(str(spk_fname_pattern))) == 0: + # if legacy HNN only ran one trial, then no spk_0.txt gets written + spk_fname_pattern = get_fname(sim_dir, 'rawspk') + + try: + spikes = read_spikes(spk_fname_pattern, gid_ranges) + except FileNotFoundError: + print('Warning: could not read file:', spk_fname_pattern) + + return spikes + def check_feeds_to_plot(feeds_from_spikes, params): # ensures synaptic weight > 0 using_feeds = get_inputs(params) @@ -262,6 +275,10 @@ def update_sim_data_from_disk(self, paramfn, params): else: avg_dpl = average_dipoles(dpls) + if len(dpls) < params['N_trials']: + print("Warning: only read %d of %d dipole files in %s" % + (len(dpls), params['N_trials'], sim_dir)) + # gid_ranges paramtxt_fn = get_fname(sim_dir, 'param') try: @@ -270,15 +287,12 @@ def update_sim_data_from_disk(self, paramfn, params): print(warning_message, paramtxt_fn) # spikes - spk_fname_pattern = os.path.join(sim_dir, 'spk_*.txt') - if len(glob(str(spk_fname_pattern))) == 0: - # if legacy HNN only ran one trial, then no spk_0.txt gets written - spk_fname_pattern = get_fname(sim_dir, 'rawspk') - - try: - spikes = read_spikes(spk_fname_pattern, gid_ranges) - except FileNotFoundError: - print(warning_message, spk_fname_pattern) + spikes = read_spktrials(sim_dir, gid_ranges) + if len(spikes.spike_times) == 0: + print("Warning: no spikes read from %s" % sim_dir) + elif len(spikes.spike_times) < params['N_trials']: + print("Warning: only read %d of %d spike files in %s" % + (len(spikes.spike_times), params['N_trials'], sim_dir)) # spec data spec = None @@ -286,6 +300,11 @@ def update_sim_data_from_disk(self, paramfn, params): if params['save_spec_data'] or using_feeds['ongoing'] or \ using_feeds['pois'] or using_feeds['tonic']: spec = read_spectrials(sim_dir) + if len(spec) == 0: + print("Warning: no spec data read from %s" % sim_dir) + elif len(spec) < params['N_trials']: + print("Warning: only read %d of %d spec files in %s" % + (len(spec), params['N_trials'], sim_dir)) self.update_sim_data(paramfn, params, dpls, avg_dpl, spikes, gid_ranges, spec) @@ -558,18 +577,19 @@ def __init__(self, paramfn, params, parent=None, width=5, height=4, FigureCanvasQTAgg.updateGeometry(self) self.params = params self.paramfn = paramfn - self.initaxes() + self.axdipole = self.axspec = None self.G = gridspec.GridSpec(10, 1) self._data_dir = os.path.join(get_output_dir(), 'data') self.optMode = optMode if not optMode: self.sim_data.clear_opt_data() - self.plot() - def initaxes(self): - # initialize the axes - self.axdipole = self.axspec = None + self.saved_exception = None + try: + self.plot() + except Exception as err: + self.saved_exception = err def plotinputhist(self, extinputs=None, feeds_to_plot=None): """ plot input histograms""" From 9fe8222263eff2d342bcf0edd25a25e8ef734f30 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 2 Mar 2021 10:38:25 -0500 Subject: [PATCH 063/107] MAINT: working dipole and spec viewers Update visdpole.py and visspec.py to displays windows within the main Qt application instead of starting a separate process. Fixes pep8 errors in modified files (except hnn_qt5.py and paramrw.py) TODO items: - Read dipole and spec data from memory instead of from disk - Revisit the case when canvas is initialized multiple times. Make sure there isn't a memory leak. --- hnn/DataViewGUI.py | 295 +++++++++++++++------------- hnn/hnn_qt5.py | 37 +--- hnn/paramrw.py | 16 +- hnn/simdat.py | 52 +++-- hnn/visdipole.py | 245 +++++++++++------------ hnn/visspec.py | 480 ++++++++++++++++++++------------------------- 6 files changed, 550 insertions(+), 575 deletions(-) diff --git a/hnn/DataViewGUI.py b/hnn/DataViewGUI.py index 15fa194ad..6fb11c39e 100644 --- a/hnn/DataViewGUI.py +++ b/hnn/DataViewGUI.py @@ -1,140 +1,163 @@ -import sys, os -from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout -from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QDialog, QGridLayout, QLineEdit, QLabel -from PyQt5.QtWidgets import QCheckBox, QInputDialog -from PyQt5.QtGui import QIcon, QFont, QPixmap -from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, pyqtSlot -from PyQt5 import QtCore -from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar -from qt_lib import getmplDPI +""" GUI for viewing data from individual/all trials""" + +# Authors: Sam Neymotin +# Blake Caldwell + +import os + +from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QWidget, QComboBox +from PyQt5.QtWidgets import QGridLayout, QInputDialog +from PyQt5.QtGui import QIcon +from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT import matplotlib.pyplot as plt -from paramrw import get_output_dir + +from .qt_lib import getmplDPI +from .paramrw import get_output_dir fontsize = plt.rcParams['font.size'] = 10 -# GUI for viewing data from individual/all trials -class DataViewGUI (QMainWindow): - def __init__ (self, CanvasType, params, title): - super().__init__() - - global fontsize - - self.fontsize = fontsize - self.linewidth = plt.rcParams['lines.linewidth'] = 1 - self.markersize = plt.rcParams['lines.markersize'] = 5 - self.CanvasType = CanvasType - self.ntrial = params['N_trials'] - self.params = params - self.title = title - self.initUI() - - def initMenu (self): - exitAction = QAction(QIcon.fromTheme('exit'), 'Exit', self) - exitAction.setShortcut('Ctrl+Q') - exitAction.setStatusTip('Exit ' + self.title + '.') - exitAction.triggered.connect(qApp.quit) - - menubar = self.menuBar() - self.fileMenu = menubar.addMenu('&File') - menubar.setNativeMenuBar(False) - self.fileMenu.addAction(exitAction) - - viewMenu = menubar.addMenu('&View') - changeFontSizeAction = QAction('Change Font Size',self) - changeFontSizeAction.setStatusTip('Change Font Size.') - changeFontSizeAction.triggered.connect(self.changeFontSize) - viewMenu.addAction(changeFontSizeAction) - changeLineWidthAction = QAction('Change Line Width',self) - changeLineWidthAction.setStatusTip('Change Line Width.') - changeLineWidthAction.triggered.connect(self.changeLineWidth) - viewMenu.addAction(changeLineWidthAction) - changeMarkerSizeAction = QAction('Change Marker Size',self) - changeMarkerSizeAction.setStatusTip('Change Marker Size.') - changeMarkerSizeAction.triggered.connect(self.changeMarkerSize) - viewMenu.addAction(changeMarkerSizeAction) - - def changeFontSize (self): - global fontsize - - i, okPressed = QInputDialog.getInt(self, "Set Font Size","Font Size:", plt.rcParams['font.size'], 1, 100, 1) - if okPressed: - self.fontsize = plt.rcParams['font.size'] = fontsize = i - self.initCanvas() - self.m.plot() - - def changeLineWidth (self): - i, okPressed = QInputDialog.getInt(self, "Set Line Width","Line Width:", plt.rcParams['lines.linewidth'], 1, 20, 1) - if okPressed: - self.linewidth = plt.rcParams['lines.linewidth'] = i - self.initCanvas() - self.m.plot() - - def changeMarkerSize (self): - i, okPressed = QInputDialog.getInt(self, "Set Marker Size","Font Size:", self.markersize, 1, 100, 1) - if okPressed: - self.markersize = plt.rcParams['lines.markersize'] = i - self.initCanvas() - self.m.plot() - - def printStat (self,s): - print(s) - self.statusBar().showMessage(s) - - def initCanvas (self): - self.grid.removeWidget(self.m) - self.grid.removeWidget(self.toolbar) - self.m.setParent(None) - self.toolbar.setParent(None) - self.m = self.toolbar = None - - self.m = self.CanvasType(self.params, self.index, parent = self, width=12, height=10, dpi=getmplDPI()) - # this is the Navigation widget - # it takes the Canvas widget and a parent - self.toolbar = NavigationToolbar(self.m, self) - self.grid.addWidget(self.toolbar, 0, 0, 1, 4) - self.grid.addWidget(self.m, 1, 0, 1, 4) - - def updateCB (self): - self.cb.clear() - if self.ntrial > 1: - self.cb.addItem('Show All Trials') - for i in range(self.ntrial): - self.cb.addItem('Show Trial ' + str(i+1)) - else: - self.cb.addItem('All Trials') - self.cb.activated[int].connect(self.onActivated) - - def initUI (self): - self.initMenu() - self.statusBar() - self.setGeometry(300, 300, 1300, 1100) - self.setWindowTitle(self.title + ' - ' + os.path.join(get_output_dir(), 'data', self.params['sim_prefix'] + '.param')) - self.grid = grid = QGridLayout() - self.index = 0 - self.initCanvas() - self.cb = QComboBox(self) - self.grid.addWidget(self.cb,2,0,1,4) - - self.updateCB() - - # need a separate widget to put grid on - widget = QWidget(self) - widget.setLayout(grid) - self.setCentralWidget(widget) - - self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) - - self.show() - - def onActivated(self, idx): - if idx != self.index: - self.index = idx - if self.index == 0: - self.statusBar().showMessage('Loading data from all trials.') - else: - self.statusBar().showMessage('Loading data from trial ' + str(self.index) + '.') - self.m.index = self.index - self.initCanvas() - self.m.plot() - self.statusBar().showMessage('') + +class DataViewGUI(QMainWindow): + def __init__(self, CanvasType, params, title): + super().__init__() + + global fontsize + + self.fontsize = fontsize + self.linewidth = plt.rcParams['lines.linewidth'] = 1 + self.markersize = plt.rcParams['lines.markersize'] = 5 + self.CanvasType = CanvasType + self.ntrial = params['N_trials'] + self.params = params + self.title = title + self.initUI() + + def initMenu(self): + exitAction = QAction(QIcon.fromTheme('exit'), 'Exit', self) + exitAction.setShortcut('Ctrl+Q') + exitAction.setStatusTip('Exit ' + self.title + '.') + exitAction.triggered.connect(qApp.quit) + + menubar = self.menuBar() + self.fileMenu = menubar.addMenu('&File') + menubar.setNativeMenuBar(False) + self.fileMenu.addAction(exitAction) + + viewMenu = menubar.addMenu('&View') + changeFontSizeAction = QAction('Change Font Size', self) + changeFontSizeAction.setStatusTip('Change Font Size.') + changeFontSizeAction.triggered.connect(self.changeFontSize) + viewMenu.addAction(changeFontSizeAction) + changeLineWidthAction = QAction('Change Line Width', self) + changeLineWidthAction.setStatusTip('Change Line Width.') + changeLineWidthAction.triggered.connect(self.changeLineWidth) + viewMenu.addAction(changeLineWidthAction) + changeMarkerSizeAction = QAction('Change Marker Size', self) + changeMarkerSizeAction.setStatusTip('Change Marker Size.') + changeMarkerSizeAction.triggered.connect(self.changeMarkerSize) + viewMenu.addAction(changeMarkerSizeAction) + + def changeFontSize(self): + global fontsize + + i, okPressed = QInputDialog.getInt(self, "Set Font Size", + "Font Size:", + plt.rcParams['font.size'], 1, 100, + 1) + if okPressed: + self.fontsize = plt.rcParams['font.size'] = fontsize = i + self.initCanvas() + self.m.plot() + + def changeLineWidth(self): + i, okPressed = QInputDialog.getInt(self, "Set Line Width", + "Line Width:", + plt.rcParams['lines.linewidth'], 1, + 20, 1) + if okPressed: + self.linewidth = plt.rcParams['lines.linewidth'] = i + self.initCanvas() + self.m.plot() + + def changeMarkerSize(self): + i, okPressed = QInputDialog.getInt(self, "Set Marker Size", + "Font Size:", self.markersize, 1, + 100, 1) + if okPressed: + self.markersize = plt.rcParams['lines.markersize'] = i + self.initCanvas() + self.m.plot() + + def printStat(self, s): + print(s) + self.statusBar().showMessage(s) + + def initCanvas(self): + """Initialize canvas + + This function will add widgets, which may create a memory leak if it + is called repeatedly (according to a comment in the previous code). + The previous code addressed it with the following: + + self.grid.removeWidget(self.m) + self.grid.removeWidget(self.toolbar) + self.m.setParent(None) + self.toolbar.setParent(None) + self.m = self.toolbar = None + """ + + self.m = self.CanvasType(self.params, self.index, parent=self, + width=12, height=10, dpi=getmplDPI()) + # this is the Navigation widget + # it takes the Canvas widget and a parent + self.toolbar = NavigationToolbar2QT(self.m, self) + self.grid.addWidget(self.toolbar, 0, 0, 1, 4) + self.grid.addWidget(self.m, 1, 0, 1, 4) + + def updateCB(self): + self.cb.clear() + if self.ntrial > 1: + self.cb.addItem('Show All Trials') + for i in range(self.ntrial): + self.cb.addItem('Show Trial ' + str(i + 1)) + else: + self.cb.addItem('All Trials') + self.cb.activated[int].connect(self.onActivated) + + def initUI(self): + self.initMenu() + self.statusBar() + self.setGeometry(300, 300, 1300, 1100) + self.setWindowTitle(self.title + ' - ' + + os.path.join(get_output_dir(), 'data', + self.params['sim_prefix'] + + '.param')) + self.grid = grid = QGridLayout() + self.index = 0 + self.initCanvas() + self.cb = QComboBox(self) + self.grid.addWidget(self.cb, 2, 0, 1, 4) + + self.updateCB() + + # need a separate widget to put grid on + widget = QWidget(self) + widget.setLayout(grid) + self.setCentralWidget(widget) + + self.setWindowIcon(QIcon(os.path.join('res', 'icon.png'))) + + self.show() + + def onActivated(self, idx): + if idx != self.index: + self.index = idx + if self.index == 0: + self.statusBar().showMessage('Loading data from all trials.') + else: + self.statusBar().showMessage('Loading data from trial ' + + str(self.index) + '.') + self.m.index = self.index + self.initCanvas() + self.m.plot() + self.statusBar().showMessage('') diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 0d68e5c2b..9e843ae7b 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -13,6 +13,7 @@ from subprocess import Popen from collections import namedtuple, OrderedDict from copy import deepcopy +from glob import glob # External libraries from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication @@ -38,6 +39,9 @@ from .qt_lib import setscalegeom, setscalegeomcenter, getmplDPI, getscreengeom from .qt_lib import lookupresource, ClickLabel from .qt_evoked import EvokedInputParamDialog, OptEvokedInputParamDialog +from .DataViewGUI import DataViewGUI +from .visdipole import DipoleCanvas +from .visspec import SpecViewGUI, SpecCanvas # TODO: These globals should be made configurable via the GUI drawindivrast = 0 @@ -908,7 +912,7 @@ def initUI (self): self.layout.addStretch(1) setscalegeom(self, 100, 100, 300, 100) - self.setWindowTitle('Help') + self.setWindowTitle('Help') class SchematicDialog (QDialog): # class for holding model schematics (and parameter shortcuts) @@ -1423,13 +1427,7 @@ def showPSDPlot(self): Popen(lcmd) # nonblocking def showSpecPlot(self): - # start the spectrogram visualization process (separate window) - outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) - outparamf = os.path.join(outdir, - self.baseparamwin.params['sim_prefix'] + - '.param') - lcmd = [getPyComm(), 'visspec.py',outparamf] - Popen(lcmd) # nonblocking + SpecViewGUI(SpecCanvas, self.baseparamwin.params,'Spectrogram Viewer') def showRasterPlot(self): # start the raster plot visualization process (separate window) @@ -1451,20 +1449,7 @@ def showRasterPlot(self): Popen(lcmd) # nonblocking def showDipolePlot(self): - # start the dipole visualization process (separate window) - - outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) - dipole_file = os.path.join(outdir,'dpl.txt') - if os.path.isfile(dipole_file): - outparamf = os.path.join(outdir, - self.baseparamwin.params['sim_prefix'] + - '.param') - lcmd = [getPyComm(), 'visdipole.py',outparamf,dipole_file] - else: - QMessageBox.information(self, "HNN", "WARNING: no dipole data at %s" % dipole_file) - return - - Popen(lcmd) # nonblocking + DataViewGUI(DipoleCanvas, self.baseparamwin.params, 'Dipole Viewer') def showwaitsimwin(self): # show the wait sim window (has simulation log) @@ -2097,13 +2082,11 @@ def result_callback(self, result): spec_fns = [] # save dipole for each trial and perform spectral analysis for trial_idx, dpl in enumerate(sim_data['dpls']): - dipole_fn = get_fname(sim_dir, 'normdpl', trial_idx, - ntrial) + dipole_fn = get_fname(sim_dir, 'normdpl', trial_idx) dpl.write(dipole_fn) if params['save_dpl']: - raw_dipole_fn = get_fname(sim_dir, 'rawdpl', trial_idx, - ntrial) + raw_dipole_fn = get_fname(sim_dir, 'rawdpl', trial_idx) sim_data['raw_dpls'][trial_idx].write(raw_dipole_fn) if params['save_spec_data'] or \ @@ -2112,7 +2095,7 @@ def result_callback(self, result): 'f_max': params['f_max_spec'], 'save_data': 1, 'runtype': 'parallel'} - spec_fn = get_fname(sim_dir, 'rawspec', trial_idx, ntrial) + spec_fn = get_fname(sim_dir, 'rawspec', trial_idx) spec_fns.append(spec_fn) # run the spectral analysis and save to spec_fn diff --git a/hnn/paramrw.py b/hnn/paramrw.py index 95858ace5..e68f509ba 100644 --- a/hnn/paramrw.py +++ b/hnn/paramrw.py @@ -20,7 +20,7 @@ def get_output_dir(): return os.path.join(base_dir, 'hnn_out') -def get_fname(sim_dir, key, trial=0, ntrial=0): +def get_fname(sim_dir, key, trial=None): """Build the file names using the old HNN scheme Parameters @@ -31,11 +31,7 @@ def get_fname(sim_dir, key, trial=0, ntrial=0): A string describing the type of file (HNN specific) trial : int | None Trial number for which to generate files (separate files per trial). - If None is given, then trial number 0 is assumed. - ntrial : int | None - The total number of trials that are part of this simulation. If None - is given, then ntrial will be 0, which creates filenames without - the trial number suffix. + If None is given, then will use filename with trial suffix Returns ---------- @@ -59,13 +55,15 @@ def get_fname(sim_dir, key, trial=0, ntrial=0): 'param': ('param', '.txt'), 'vsoma': ('vsoma', '.pkl')} - if ntrial == 0 or key == 'param': + if trial == None or key == 'param': # param file currently identical for all trials - return os.path.join(sim_dir, datatypes[key][0] + datatypes[key][1]) + fname = os.path.join(sim_dir, datatypes[key][0] + datatypes[key][1]) else: - return os.path.join(sim_dir, datatypes[key][0] + '_' + str(trial) + + fname = os.path.join(sim_dir, datatypes[key][0] + '_' + str(trial) + datatypes[key][1]) + return fname + def get_inputs(params): """ get a dictionary of input types used in simulation diff --git a/hnn/simdat.py b/hnn/simdat.py index 7f4bf07fa..2d896c160 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -25,10 +25,23 @@ fontsize = plt.rcParams['font.size'] = 10 -def read_dpltrials(sim_dir): - """read dipole data files for individual trials""" - ldpl = [] - +def get_dipoles_from_disk(sim_dir, ntrials): + """Read dipole trial data from disk + + Parameters + ---------- + sim_dir : str + Path of simulation data directory + ntrials : int + Number of trials expected to be read from disk + + Returns + ---------- + dpls: list of Dipole objects + List containing Dipoles of each trial + """ + + dpls = [] dpl_fname_pattern = os.path.join(sim_dir, 'dpl_*.txt') glob_list = sorted(glob(str(dpl_fname_pattern))) if len(glob_list) == 0: @@ -46,9 +59,16 @@ def read_dpltrials(sim_dir): if os.path.exists(sim_dir): print('Warning: could not read file:', dipole_fn) - ldpl.append(dpl_trial) + dpls.append(dpl_trial) + + if len(dpls) == 0: + print("Warning: no dipole(s) read from %s" % sim_dir) - return ldpl + if len(dpls) < ntrials: + print("Warning: only read %d of %d dipole files in %s" % + (len(dpls), ntrials, sim_dir)) + + return dpls def read_spectrials(sim_dir): @@ -92,6 +112,7 @@ def read_spktrials(sim_dir, gid_ranges): return spikes + def check_feeds_to_plot(feeds_from_spikes, params): # ensures synaptic weight > 0 using_feeds = get_inputs(params) @@ -247,7 +268,7 @@ def update_sim_data_from_disk(self, paramfn, params): Parameters ---------- paramfn : str - Simulation paramter filename + Simulation parameter filename params : dict Dictionary containing parameters @@ -262,12 +283,8 @@ def update_sim_data_from_disk(self, paramfn, params): self.update_sim_data(paramfn, params, None, None, None, None) return False - warning_message = 'Warning: could not read file:' - - # dipoles - dpls = read_dpltrials(sim_dir) + dpls = get_dipoles_from_disk(sim_dir, params['N_trials']) if len(dpls) == 0: - print("Warning: no dipole(s) read from %s" % sim_dir) self.update_sim_data(paramfn, params, None, None, None, None) return False elif len(dpls) == 1: @@ -275,10 +292,7 @@ def update_sim_data_from_disk(self, paramfn, params): else: avg_dpl = average_dipoles(dpls) - if len(dpls) < params['N_trials']: - print("Warning: only read %d of %d dipole files in %s" % - (len(dpls), params['N_trials'], sim_dir)) - + warning_message = 'Warning: could not read file:' # gid_ranges paramtxt_fn = get_fname(sim_dir, 'param') try: @@ -304,7 +318,7 @@ def update_sim_data_from_disk(self, paramfn, params): print("Warning: no spec data read from %s" % sim_dir) elif len(spec) < params['N_trials']: print("Warning: only read %d of %d spec files in %s" % - (len(spec), params['N_trials'], sim_dir)) + (len(spec), params['N_trials'], sim_dir)) self.update_sim_data(paramfn, params, dpls, avg_dpl, spikes, gid_ranges, spec) @@ -503,7 +517,7 @@ def save_spec_with_hist(self, paramfn, params): dpl.plot(axdipole, 'agg', show=False) axdipole.set_xlim(xlim) - spec_fig_fn = get_fname(sim_dir, 'figspec', trial_idx, ntrial) + spec_fig_fn = get_fname(sim_dir, 'figspec', trial_idx) f.savefig(spec_fig_fn, dpi=300) plt.close(f) @@ -549,7 +563,7 @@ def save_dipole_with_hist(self, paramfn, params): dpl.plot(axdipole, 'agg', show=False) axdipole.set_xlim(xlim) - dipole_fig_fn = get_fname(sim_dir, 'figdpl', trial_idx, ntrial) + dipole_fig_fn = get_fname(sim_dir, 'figdpl', trial_idx) f.savefig(dipole_fig_fn, dpi=300) plt.close(f) diff --git a/hnn/visdipole.py b/hnn/visdipole.py index 25fb8c077..15899382d 100644 --- a/hnn/visdipole.py +++ b/hnn/visdipole.py @@ -1,131 +1,132 @@ -import sys, os -from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout -from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QDialog, QGridLayout, QLineEdit, QLabel -from PyQt5.QtWidgets import QCheckBox -from PyQt5.QtGui import QIcon, QFont, QPixmap -from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, pyqtSlot -from PyQt5 import QtCore +"""Class for Dipole viewing window""" + +# Authors: Sam Neymotin +# Blake Caldwell + +import os import numpy as np + +from PyQt5.QtWidgets import QSizePolicy import matplotlib.pyplot as plt import matplotlib.patches as mpatches -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure -import pylab as plt -import matplotlib.gridspec as gridspec -from DataViewGUI import DataViewGUI -from paramrw import get_output_dir -import spikefn -from simdat import read_dpltrials +from hnn_core.dipole import average_dipoles -from hnn_core import read_params +from .paramrw import get_output_dir +from .simdat import get_dipoles_from_disk fontsize = plt.rcParams['font.size'] = 10 -tstop = -1; ntrial = 1; scalefctr = 30e3; dplpath = ''; paramf = '' -for i in range(len(sys.argv)): - if sys.argv[i].endswith('.txt'): - dplpath = sys.argv[i] - elif sys.argv[i].endswith('.param'): - paramf = sys.argv[i] - params = read_params(paramf) - scalefctr = params['dipole_scalefctr'] - if type(scalefctr)!=float and type(scalefctr)!=int: scalefctr=30e3 - tstop = params['tstop'] - ntrial = params['N_trials'] - -ddat = {} -basedir = os.path.join(get_output_dir(), 'data', params['sim_prefix']) -ddat['dpltrials'] = read_dpltrials(basedir) -ddat['dpl'] = np.loadtxt(os.path.join(basedir,'dpl.txt')) - - -class DipoleCanvas (FigureCanvas): - - def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, title='Dipole Viewer'): - FigureCanvas.__init__(self, Figure(figsize=(width, height), dpi=dpi)) - self.title = title - self.setParent(parent) - self.gui = parent - self.index = index - FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding) - FigureCanvas.updateGeometry(self) - self.paramf = paramf - self.plot() - - def clearaxes(self): - for ax in self.lax: - ax.set_yticks([]) - ax.cla() - - def drawdipole (self, fig): - - gdx = 311 - - ltitle = ['Layer 2/3', 'Layer 5', 'Aggregate'] - - white_patch = mpatches.Patch(color='white', label='Average') - gray_patch = mpatches.Patch(color='gray', label='Individual') - lpatch = [] - - if len(ddat['dpltrials']) > 0: lpatch = [white_patch,gray_patch] - - yl = [1e9,-1e9] - - for i in [2,3,1]: - yl[0] = min(yl[0],ddat['dpl'][:,i].min()) - yl[1] = max(yl[1],ddat['dpl'][:,i].max()) - if len(ddat['dpltrials']) > 0: # plot dipoles from individual trials - for dpltrial in ddat['dpltrials']: - yl[0] = min(yl[0],dpltrial[:,i].min()) - yl[1] = max(yl[1],dpltrial[:,i].max()) - - yl = tuple(yl) - - self.lax = [] - - for i,title in zip([2, 3, 1],ltitle): - ax = fig.add_subplot(gdx) - self.lax.append(ax) - - if i == 1: ax.set_xlabel('Time (ms)'); - - lw = self.gui.linewidth - if self.index != 0: lw = self.gui.linewidth + 2 - - if len(ddat['dpltrials']) > 0: # plot dipoles from individual trials - for ddx,dpltrial in enumerate(ddat['dpltrials']): - if self.index == 0 or (self.index > 0 and ddx == self.index-1): - ax.plot(dpltrial[:,0],dpltrial[:,i],color='gray',linewidth=lw) - - # average dipole (across trials) - if self.index == 0: ax.plot(ddat['dpl'][:,0],ddat['dpl'][:,i],'w',linewidth=self.gui.linewidth+2) - - ax.set_ylabel(r'(nAm $\times$ '+str(scalefctr)+')') - if tstop != -1: ax.set_xlim((0,tstop)) - ax.set_ylim(yl) - - if i == 2 and len(ddat['dpltrials']) > 0: ax.legend(handles=lpatch) - - ax.set_facecolor('k') - ax.grid(True) - ax.set_title(title) - - gdx += 1 - - self.figure.subplots_adjust(bottom=0.06, left=0.06, right=1.0, top=0.97, wspace=0.1, hspace=0.09) - - def plot (self): - self.drawdipole(self.figure) - self.draw() - if "TRAVIS_TESTING" in os.environ and os.environ["TRAVIS_TESTING"] == "1": - print("Exiting gracefully with TRAVIS_TESTING=1") - qApp.quit() - exit(0) - -if __name__ == '__main__': - app = QApplication(sys.argv) - ex = DataViewGUI(DipoleCanvas,params,'Dipole Viewer') - sys.exit(app.exec_()) +class DipoleCanvas(FigureCanvasQTAgg): + def __init__(self, params, index, parent=None, width=12, height=10, + dpi=120, title='Dipole Viewer'): + FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), + dpi=dpi)) + self.title = title + self.setParent(parent) + self.gui = parent + self.index = index + FigureCanvasQTAgg.setSizePolicy(self, QSizePolicy.Expanding, + QSizePolicy.Expanding) + FigureCanvasQTAgg.updateGeometry(self) + + self.params = params + self.scalefctr = self.params['dipole_scalefctr'] + if type(self.scalefctr) != float and type(self.scalefctr) != int: + self.scalefctr = 30e3 + self.tstop = self.params['tstop'] + self.ntrial = self.params['N_trials'] + + sim_dir = os.path.join(get_output_dir(), 'data', + self.params['sim_prefix']) + + self.dpls = get_dipoles_from_disk(sim_dir, params['N_trials']) + if len(self.dpls) == 1: + self.avg_dpl = self.dpls[0] + else: + self.avg_dpl = average_dipoles(self.dpls) + + if len(self.dpls) < self.params['N_trials']: + print("Warning: only read %d of %d dipole files in %s" % + (len(self.dpls), self.params['N_trials'], sim_dir)) + + self.plot() + + def clearaxes(self): + for ax in self.lax: + ax.set_yticks([]) + ax.cla() + + def drawdipole(self, fig): + gdx = 311 + + ltitle = ['Layer 2/3', 'Layer 5', 'Aggregate'] + dipole_keys = ['L2', 'L5', 'agg'] + + white_patch = mpatches.Patch(color='white', label='Average') + gray_patch = mpatches.Patch(color='gray', label='Individual') + lpatch = [] + + if len(self.dpls) > 0: + lpatch = [white_patch, gray_patch] + + yl = [1e9, -1e9] + for key in dipole_keys: + yl[0] = min(yl[0], np.amin(self.avg_dpl.data[key])) + yl[1] = max(yl[1], np.amax(self.avg_dpl.data[key])) + + # plot dipoles from individual trials + if len(self.dpls) > 0: + for dpltrial in self.dpls: + yl[0] = min(yl[0], np.amin(dpltrial.data[key])) + yl[1] = max(yl[1], np.amax(dpltrial.data[key])) + yl = tuple(yl) + + self.lax = [] + + for key, title in zip(dipole_keys, ltitle): + ax = fig.add_subplot(gdx) + self.lax.append(ax) + + if key == 'agg': + ax.set_xlabel('Time (ms)') + + lw = self.gui.linewidth + if self.index != 0: + lw = self.gui.linewidth + 2 + + # plot dipoles from individual trials + if len(self.dpls) > 0: + for ddx, dpltrial in enumerate(self.dpls): + if self.index == 0 or (self.index > 0 and + ddx == (self.index - 1)): + ax.plot(dpltrial.times, dpltrial.data[key], + color='gray', linewidth=lw) + + # average dipole (across trials) + if self.index == 0: + ax.plot(self.avg_dpl.times, self.avg_dpl.data[key], 'w', + linewidth=self.gui.linewidth + 2) + + ax.set_ylabel(r'(nAm $\times$ ' + str(self.scalefctr) + ')') + if self.tstop != -1: + ax.set_xlim((0, self.tstop)) + ax.set_ylim(yl) + + if key == 'L2' and len(self.dpls) > 0: + ax.legend(handles=lpatch) + + ax.set_facecolor('k') + ax.grid(True) + ax.set_title(title) + + gdx += 1 + + self.figure.subplots_adjust(bottom=0.06, left=0.06, right=1.0, + top=0.97, wspace=0.1, hspace=0.09) + + def plot(self): + self.drawdipole(self.figure) + self.draw() diff --git a/hnn/visspec.py b/hnn/visspec.py index eb493ca0c..f77bf6be5 100644 --- a/hnn/visspec.py +++ b/hnn/visspec.py @@ -1,270 +1,226 @@ -import sys, os -from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout -from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QDialog, QGridLayout, QLineEdit, QLabel -from PyQt5.QtWidgets import QCheckBox -from PyQt5.QtGui import QIcon, QFont, QPixmap -from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, pyqtSlot -from PyQt5 import QtCore +"""Create the Spectrogram viewing window""" + +# Authors: Sam Neymotin +# Blake Caldwell + +import os import numpy as np + +from PyQt5.QtWidgets import QSizePolicy, QAction +from PyQt5.QtGui import QIcon import matplotlib.pyplot as plt -import matplotlib.patches as mpatches -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar -from matplotlib.figure import Figure -import pylab as plt import matplotlib.gridspec as gridspec -from DataViewGUI import DataViewGUI -from specfn import MorletSpec -import simdat -from simdat import readdpltrials -from paramrw import get_output_dir +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg +from matplotlib.figure import Figure +from hnn_core.dipole import average_dipoles -from hnn_core import read_params +from .DataViewGUI import DataViewGUI +from .specfn import MorletSpec +from .simdat import get_dipoles_from_disk +from .paramrw import get_output_dir fontsize = plt.rcParams['font.size'] = 10 -# assumes column 0 is time, rest of columns are time-series -def extractspec (dat, fmax=40.0): - #print('extractspec',dat.shape) - lspec = [] - tvec = dat[:,0] - dt = tvec[1] - tvec[0] - tstop = tvec[-1] - - prm = {'f_max_spec':fmax,'dt':dt,'tstop':tstop} - - if dat.shape[1] > 2: - for col in range(1,dat.shape[1],1): - ms = MorletSpec(tvec,dat[:,col],None,p_dict=prm) - lspec.append(ms) - else: - ms = MorletSpec(tvec,dat[:,1],None,p_dict=prm) - lspec.append(ms) - - ntrial = len(lspec) - - if ntrial > 1: - avgdipole = np.mean(dat[:,1:-1],axis=1) - else: - avgdipole = dat[:,1] - - avgspec = MorletSpec(tvec,avgdipole,None,p_dict=prm) # !!should fix to average of individual spectrograms!! - - ltfr = [ms.TFR for ms in lspec] - npspec = np.array(ltfr) - avgspec.TFR = np.mean(npspec,axis=0)#,axis=0) - - return ms.f, lspec, avgdipole, avgspec - -def loaddat_txt (fname): - dat = np.loadtxt(fname) - print('Loaded data in ' + fname + '. Extracting Spectrograms.') - return dat - -def loaddat (sim_prefix, ntrial): - basedir = os.path.join(get_output_dir(), 'data', sim_prefix) - if ntrial > 1: - ddat = readdpltrials(basedir) - #print('read dpl trials',ddat[0].shape) - dout = np.zeros((ddat[0].shape[0],1+ntrial)) - #print('set dout shape',dout.shape) - dout[:,0] = ddat[0][:,0] - for i in range(ntrial): - dout[:,i+1] = ddat[i][:,1] - return dout - else: - ddat = np.loadtxt(os.path.join(basedir,'dpl.txt')) - #print('ddat.shape:',ddat.shape) - dout = np.zeros((ddat.shape[0],2)) - #print('dout.shape:',dout.shape) - dout[:,0] = ddat[:,0] - dout[:,1] = ddat[:,1] - return dout - # except: - # print('Could not load data in ' + fname) - # return None - return None - -class SpecCanvas (FigureCanvas): - def __init__ (self, parama, index, parent=None, width=12, height=10, dpi=120, title='Spectrogram Viewer'): - - FigureCanvas.__init__(self, Figure(figsize=(width, height), dpi=dpi)) - self.title = title - self.setParent(parent) - self.gui = parent - self.index = index - FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding) - FigureCanvas.updateGeometry(self) - self.params = params - self.invertedhistax = False - self.G = gridspec.GridSpec(10,1) - self.dat = [] - self.lextspec = [] - self.lax = [] - self.avgdipole = [] - self.avgspec = [] - - if 'spec_cmap' in self.params: - self.spec_cmap = self.params['spec_cmap'] - else: - # default to jet, but allow user to change in param file - self.spec_cmap = 'jet' - - self.plot() - - def clearaxes (self): - # try: - for ax in self.lax: - ax.set_yticks([]) - ax.cla() - # except: - # pass - - def clearlextdatobj (self): - if hasattr(self,'lextdatobj'): - for o in self.lextdatobj: - # try: - o.set_visible(False) - # except: - # o[0].set_visible(False) - del self.lextdatobj - - def drawspec (self, dat, lspec, sdx, avgdipole, avgspec, fig, G, ltextra=''): - if len(lspec) == 0: return - - plt.ion() - - gdx = 211 - - ax = fig.add_subplot(gdx) - lax = [ax] - tvec = dat[:,0] - # dt = tvec[1] - tvec[0] - # tstop = tvec[-1] - - if sdx == 0: - for i in range(1,dat.shape[1],1): - #print('sdx is 0',dat.shape,i) - ax.plot(tvec, dat[:,i],linewidth=self.gui.linewidth,color='gray') - ax.plot(tvec,avgdipole,linewidth=self.gui.linewidth+1,color='black') - else: - ax.plot(dat[:,0], dat[:,sdx],linewidth=self.gui.linewidth+1,color='gray') - - ax.set_xlim(tvec[0],tvec[-1]) - ax.set_ylabel('Dipole (nAm)') - - gdx = 212 - - ax = fig.add_subplot(gdx) - - #print('sdx:',sdx,avgspec.TFR.shape) - - if sdx==0: ms = avgspec - else: ms = lspec[sdx-1] - #print('ms.TFR.shape:',ms.TFR.shape) - - ax.imshow(ms.TFR, extent=[tvec[0], tvec[-1], ms.f[-1], ms.f[0]], aspect='auto', origin='upper',cmap=plt.get_cmap(self.spec_cmap)) - - ax.set_xlim(tvec[0],tvec[-1]) - ax.set_xlabel('Time (ms)') - ax.set_ylabel('Frequency (Hz)') - - lax.append(ax) - - return lax - - def plot (self): - ltextra = 'Trial '+str(self.index) - if self.index == 0: ltextra = 'All Trials' - self.lax = self.drawspec(self.dat, self.lextspec,self.index, self.avgdipole, self.avgspec, self.figure, self.G, ltextra=ltextra) - self.figure.subplots_adjust(bottom=0.06, left=0.06, right=0.98, top=0.97, wspace=0.1, hspace=0.09) - self.draw() - -class SpecViewGUI (DataViewGUI): - def __init__ (self,CanvasType,params,title): - self.lF = [] # frequencies associated with external data spec - self.lextspec = [] # external data spec - # self.lextfiles = [] # external data files - self.dat = None - self.avgdipole = [] - self.avgspec = [] - self.params = params - super(SpecViewGUI,self).__init__(CanvasType,self.params,title) - self.addLoadDataActions() - self.loadDisplayData(params) - - if "TRAVIS_TESTING" in os.environ and os.environ["TRAVIS_TESTING"] == "1": - print("Exiting gracefully with TRAVIS_TESTING=1") - qApp.quit() - exit(0) - - def initCanvas (self): - super(SpecViewGUI,self).initCanvas() - self.m.lextspec = self.lextspec - self.m.dat = self.dat - self.m.avgdipole = self.avgdipole - self.m.avgspec = self.avgspec - - def addLoadDataActions (self): - loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data.', self) - loadDataFile.setShortcut('Ctrl+D') - loadDataFile.setStatusTip('Load experimental (.txt) / simulation (.param) data.') - loadDataFile.triggered.connect(self.loadDisplayData) - - clearDataFileAct = QAction(QIcon.fromTheme('close'), 'Clear data.', self) - clearDataFileAct.setShortcut('Ctrl+C') - clearDataFileAct.setStatusTip('Clear data.') - clearDataFileAct.triggered.connect(self.clearDataFile) - - self.fileMenu.addAction(loadDataFile) - self.fileMenu.addAction(clearDataFileAct) - - def loadDisplayData (self, params=None): - if params is None or params is False: - fname = QFileDialog.getOpenFileName(self, 'Open .param or .txt file', 'data') - fname = os.path.abspath(fname[0]) - dat = loaddat_txt(fname) + +def extract_spec(params): + """Read dipoles and extract Mortlet spectrograms + + Parameters + ---------- + params : dict + Dictionary containing parameters + + Returns + ---------- + specs: list of MortletSpec objects + List containing spectrograms of each trial + avg_spec: MortletSpec objects + spectrogram averaged over all trials + dpls: list of Dipole objects + List containing Dipoles of each trial + avg_dpl: Dipole object + Dipole object containing the average of individual trial Dipoles + """ + + sim_dir = os.path.join(get_output_dir(), 'data', + params['sim_prefix']) + + dpls = get_dipoles_from_disk(sim_dir, params['N_trials']) + if len(dpls) == 1: + avg_dpl = dpls[0] else: - dat = loaddat(self.params['sim_prefix'], self.params['N_trials']) - self.dat = dat - - fmax = self.params['f_max_spec'] - - f, lspec, avgdipole, avgspec = extractspec(dat,fmax=fmax) - self.ntrial = len(lspec) - self.updateCB() - self.printStat('Extracted ' + str(len(lspec)) + ' spectrograms for ' + self.params['sim_prefix']) - self.lextspec = lspec - # self.lextfiles.append(fname) - self.avgdipole = avgdipole - self.avgspec = avgspec - self.lF.append(f) - - if len(self.lextspec) > 0: - self.printStat('Plotting Spectrograms.') - self.m.lextspec = self.lextspec - self.m.dat = self.dat - self.m.avgspec = self.avgspec - self.m.avgdipole = self.avgdipole - self.m.plot() - self.m.draw() # make sure new lines show up in plot - self.printStat('') - - def clearDataFile (self): - self.m.clearlextdatobj() - self.lextspec = [] - self.lF = [] - self.m.draw() - - -if __name__ == '__main__': - for i in range(len(sys.argv)): - if sys.argv[i].endswith('.param'): - paramf = sys.argv[i] - params = read_params(paramf) - - app = QApplication(sys.argv) - ex = SpecViewGUI(SpecCanvas,params,'Spectrogram Viewer') - sys.exit(app.exec_()) - + avg_dpl = average_dipoles(dpls) + + specs = [] + for dpltrial in dpls: + ms = MorletSpec(dpltrial.times, dpltrial.data['agg'], None, + p_dict=params) + specs.append(ms) + + # !!should fix to average of individual spectrograms!! + avg_spec = MorletSpec(avg_dpl.times, avg_dpl.data['agg'], None, + p_dict=params) + + ltfr = [ms.TFR for ms in specs] + npspec = np.array(ltfr) + avg_spec.TFR = np.mean(npspec, axis=0) + + return specs, avg_spec, dpls, avg_dpl + + +class SpecCanvas(FigureCanvasQTAgg): + """Class for the Spectrogram viewer + + This is designed to be called from SpecViewGUI class to add functionality + for loading and clearing data + """ + def __init__(self, params, index, parent=None, width=12, height=10, + dpi=120, title='Spectrogram Viewer'): + FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), + dpi=dpi)) + self.title = title + self.setParent(parent) + self.gui = parent + self.index = index + FigureCanvasQTAgg.setSizePolicy(self, QSizePolicy.Expanding, + QSizePolicy.Expanding) + FigureCanvasQTAgg.updateGeometry(self) + self.params = params + self.invertedhistax = False + self.G = gridspec.GridSpec(10, 1) + self.dpls = [] + self.specs = [] + self.lax = [] + self.avg_dpl = [] + self.avg_spec = [] + + if 'spec_cmap' in self.params: + self.spec_cmap = self.params['spec_cmap'] + else: + # default to jet, but allow user to change in param file + self.spec_cmap = 'jet' + + self.plot() + + def clearaxes(self): + for ax in self.lax: + ax.set_yticks([]) + ax.cla() + + def clearlextdatobj(self): + if hasattr(self, 'lextdatobj'): + for o in self.lextdatobj: + o.set_visible(False) + del self.lextdatobj + + def drawspec(self, dpls, lspec, sdx, avgdipole, avgspec, fig, G, + ltextra=''): + if len(lspec) == 0: + return + + plt.ion() + + gdx = 211 + + ax = fig.add_subplot(gdx) + lax = [ax] + tvec = avgdipole.times + + if sdx == 0: + for dpltrial in dpls: + ax.plot(tvec, dpltrial.data['agg'], + linewidth=self.gui.linewidth, color='gray') + ax.plot(tvec, avgdipole.data['agg'], + linewidth=self.gui.linewidth + 1, color='black') + else: + ax.plot(tvec, dpls[sdx].data['agg'], + linewidth=self.gui.linewidth + 1, color='gray') + + ax.set_xlim(tvec[0], tvec[-1]) + ax.set_ylabel('Dipole (nAm)') + + gdx = 212 + + ax = fig.add_subplot(gdx) + + if sdx == 0: + ms = avgspec + else: + ms = lspec[sdx - 1] + + ax.imshow(ms.TFR, extent=[tvec[0], tvec[-1], ms.f[-1], ms.f[0]], + aspect='auto', origin='upper', + cmap=plt.get_cmap(self.spec_cmap)) + + ax.set_xlim(tvec[0], tvec[-1]) + ax.set_xlabel('Time (ms)') + ax.set_ylabel('Frequency (Hz)') + + lax.append(ax) + + return lax + + def plot(self): + ltextra = 'Trial ' + str(self.index) + if self.index == 0: + ltextra = 'All Trials' + self.lax = self.drawspec(self.dpls, self.specs, self.index, + self.avg_dpl, self.avg_spec, self.figure, + self.G, ltextra=ltextra) + self.figure.subplots_adjust(bottom=0.06, left=0.06, right=0.98, + top=0.97, wspace=0.1, hspace=0.09) + self.draw() + + +class SpecViewGUI(DataViewGUI): + def __init__(self, CanvasType, params, title): + self.specs = [] # external data spec + self.dpls = None + self.avg_dpl = [] + self.avg_spec = [] + self.params = params + super(SpecViewGUI, self).__init__(CanvasType, self.params, title) + self.addLoadDataActions() + self.loadDisplayData() + + def initCanvas(self): + super(SpecViewGUI, self).initCanvas() + self.m.specs = self.specs + + def addLoadDataActions(self): + loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data.', self) + loadDataFile.setShortcut('Ctrl+D') + loadDataFile.setStatusTip('Load experimental (.txt) / ' + + 'simulation (.param) data.') + loadDataFile.triggered.connect(self.loadDisplayData) + + clearDataFileAct = QAction(QIcon.fromTheme('close'), 'Clear data.', + self) + clearDataFileAct.setShortcut('Ctrl+C') + clearDataFileAct.setStatusTip('Clear data.') + clearDataFileAct.triggered.connect(self.clearDataFile) + + self.fileMenu.addAction(loadDataFile) + self.fileMenu.addAction(clearDataFileAct) + + def loadDisplayData(self): + specs, avg_spec, dpls, avg_dpl = extract_spec(self.params) + self.m.dpls = dpls + self.m.specs = specs + self.m.avg_dpl = avg_dpl + self.m.avg_spec = avg_spec + + self.updateCB() + self.printStat('Extracted ' + str(len(self.m.specs)) + + ' spectrograms for ' + self.params['sim_prefix']) + + if len(self.m.specs) > 0: + self.printStat('Plotting Spectrograms.') + self.m.plot() + self.m.draw() # make sure new lines show up in plot + self.printStat('') + + def clearDataFile(self): + self.m.clearlextdatobj() + self.specs = [] + self.m.draw() From 90473f7c6b890fdc30957ba3a4418f051d92e850 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 2 Mar 2021 13:57:57 -0500 Subject: [PATCH 064/107] MAINT: pass data to view windows Pass sim_data to the viewer dialog windows instead of reading dpls from disk again. Also prepare visrast.py for running in the main Qt app. --- hnn/DataViewGUI.py | 16 +-- hnn/hnn_qt5.py | 27 ++--- hnn/visdipole.py | 42 ++----- hnn/visrast.py | 293 +++++++++++---------------------------------- hnn/visspec.py | 44 +++---- hnn/visvolt.py | 94 ++------------- 6 files changed, 132 insertions(+), 384 deletions(-) diff --git a/hnn/DataViewGUI.py b/hnn/DataViewGUI.py index 6fb11c39e..9e90f1f89 100644 --- a/hnn/DataViewGUI.py +++ b/hnn/DataViewGUI.py @@ -18,7 +18,7 @@ class DataViewGUI(QMainWindow): - def __init__(self, CanvasType, params, title): + def __init__(self, CanvasType, params, sim_data, title): super().__init__() global fontsize @@ -29,6 +29,7 @@ def __init__(self, CanvasType, params, title): self.CanvasType = CanvasType self.ntrial = params['N_trials'] self.params = params + self.sim_data = sim_data self.title = title self.initUI() @@ -43,19 +44,19 @@ def initMenu(self): menubar.setNativeMenuBar(False) self.fileMenu.addAction(exitAction) - viewMenu = menubar.addMenu('&View') + self.viewMenu = menubar.addMenu('&View') changeFontSizeAction = QAction('Change Font Size', self) changeFontSizeAction.setStatusTip('Change Font Size.') changeFontSizeAction.triggered.connect(self.changeFontSize) - viewMenu.addAction(changeFontSizeAction) + self.viewMenu.addAction(changeFontSizeAction) changeLineWidthAction = QAction('Change Line Width', self) changeLineWidthAction.setStatusTip('Change Line Width.') changeLineWidthAction.triggered.connect(self.changeLineWidth) - viewMenu.addAction(changeLineWidthAction) + self.viewMenu.addAction(changeLineWidthAction) changeMarkerSizeAction = QAction('Change Marker Size', self) changeMarkerSizeAction.setStatusTip('Change Marker Size.') changeMarkerSizeAction.triggered.connect(self.changeMarkerSize) - viewMenu.addAction(changeMarkerSizeAction) + self.viewMenu.addAction(changeMarkerSizeAction) def changeFontSize(self): global fontsize @@ -106,8 +107,9 @@ def initCanvas(self): self.m = self.toolbar = None """ - self.m = self.CanvasType(self.params, self.index, parent=self, - width=12, height=10, dpi=getmplDPI()) + self.m = self.CanvasType(self.params, self.sim_data, self.index, + parent=self, width=12, height=10, + dpi=getmplDPI()) # this is the Navigation widget # it takes the Canvas widget and a parent self.toolbar = NavigationToolbar2QT(self.m, self) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 9e843ae7b..d682438e1 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -42,9 +42,9 @@ from .DataViewGUI import DataViewGUI from .visdipole import DipoleCanvas from .visspec import SpecViewGUI, SpecCanvas +from .visrast import SpikeViewGUI, SpikeCanvas # TODO: These globals should be made configurable via the GUI -drawindivrast = 0 drawavgdpl = 0 fontsize = plt.rcParams['font.size'] = 10 @@ -1427,29 +1427,16 @@ def showPSDPlot(self): Popen(lcmd) # nonblocking def showSpecPlot(self): - SpecViewGUI(SpecCanvas, self.baseparamwin.params,'Spectrogram Viewer') + sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + SpecViewGUI(SpecCanvas, self.baseparamwin.params, sim_data, 'Spectrogram Viewer') def showRasterPlot(self): - # start the raster plot visualization process (separate window) - global drawindivrast - - outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) - spikefile = os.path.join(outdir,'spk.txt') - if os.path.isfile(spikefile): - outparamf = os.path.join(outdir, - self.baseparamwin.params['sim_prefix'] + - '.param') - lcmd = [getPyComm(), 'visrast.py',outparamf,spikefile] - else: - QMessageBox.information(self, "HNN", "WARNING: no spiking data at %s" % spikefile) - return - - if drawindivrast: - lcmd.append('indiv') - Popen(lcmd) # nonblocking + sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + SpikeViewGUI(SpikeCanvas, self.baseparamwin.params, sim_data, 'Spike Viewer') def showDipolePlot(self): - DataViewGUI(DipoleCanvas, self.baseparamwin.params, 'Dipole Viewer') + sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + DataViewGUI(DipoleCanvas, self.baseparamwin.params, sim_data, 'Dipole Viewer') def showwaitsimwin(self): # show the wait sim window (has simulation log) diff --git a/hnn/visdipole.py b/hnn/visdipole.py index 15899382d..166c66fc4 100644 --- a/hnn/visdipole.py +++ b/hnn/visdipole.py @@ -3,7 +3,6 @@ # Authors: Sam Neymotin # Blake Caldwell -import os import numpy as np from PyQt5.QtWidgets import QSizePolicy @@ -11,23 +10,20 @@ import matplotlib.patches as mpatches from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure -from hnn_core.dipole import average_dipoles - -from .paramrw import get_output_dir -from .simdat import get_dipoles_from_disk fontsize = plt.rcParams['font.size'] = 10 class DipoleCanvas(FigureCanvasQTAgg): - def __init__(self, params, index, parent=None, width=12, height=10, - dpi=120, title='Dipole Viewer'): + def __init__(self, params, sim_data, index, parent=None, width=12, + height=10, dpi=120, title='Dipole Viewer'): FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), dpi=dpi)) self.title = title self.setParent(parent) self.gui = parent self.index = index + self.sim_data = sim_data FigureCanvasQTAgg.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) FigureCanvasQTAgg.updateGeometry(self) @@ -39,19 +35,6 @@ def __init__(self, params, index, parent=None, width=12, height=10, self.tstop = self.params['tstop'] self.ntrial = self.params['N_trials'] - sim_dir = os.path.join(get_output_dir(), 'data', - self.params['sim_prefix']) - - self.dpls = get_dipoles_from_disk(sim_dir, params['N_trials']) - if len(self.dpls) == 1: - self.avg_dpl = self.dpls[0] - else: - self.avg_dpl = average_dipoles(self.dpls) - - if len(self.dpls) < self.params['N_trials']: - print("Warning: only read %d of %d dipole files in %s" % - (len(self.dpls), self.params['N_trials'], sim_dir)) - self.plot() def clearaxes(self): @@ -69,17 +52,17 @@ def drawdipole(self, fig): gray_patch = mpatches.Patch(color='gray', label='Individual') lpatch = [] - if len(self.dpls) > 0: + if len(self.sim_data['dpls']) > 0: lpatch = [white_patch, gray_patch] yl = [1e9, -1e9] for key in dipole_keys: - yl[0] = min(yl[0], np.amin(self.avg_dpl.data[key])) - yl[1] = max(yl[1], np.amax(self.avg_dpl.data[key])) + yl[0] = min(yl[0], np.amin(self.sim_data['avg_dpl'].data[key])) + yl[1] = max(yl[1], np.amax(self.sim_data['avg_dpl'].data[key])) # plot dipoles from individual trials - if len(self.dpls) > 0: - for dpltrial in self.dpls: + if len(self.sim_data['dpls']) > 0: + for dpltrial in self.sim_data['dpls']: yl[0] = min(yl[0], np.amin(dpltrial.data[key])) yl[1] = max(yl[1], np.amax(dpltrial.data[key])) yl = tuple(yl) @@ -98,8 +81,8 @@ def drawdipole(self, fig): lw = self.gui.linewidth + 2 # plot dipoles from individual trials - if len(self.dpls) > 0: - for ddx, dpltrial in enumerate(self.dpls): + if len(self.sim_data['dpls']) > 0: + for ddx, dpltrial in enumerate(self.sim_data['dpls']): if self.index == 0 or (self.index > 0 and ddx == (self.index - 1)): ax.plot(dpltrial.times, dpltrial.data[key], @@ -107,7 +90,8 @@ def drawdipole(self, fig): # average dipole (across trials) if self.index == 0: - ax.plot(self.avg_dpl.times, self.avg_dpl.data[key], 'w', + ax.plot(self.sim_data['avg_dpl'].times, + self.sim_data['avg_dpl'].data[key], 'w', linewidth=self.gui.linewidth + 2) ax.set_ylabel(r'(nAm $\times$ ' + str(self.scalefctr) + ')') @@ -115,7 +99,7 @@ def drawdipole(self, fig): ax.set_xlim((0, self.tstop)) ax.set_ylim(yl) - if key == 'L2' and len(self.dpls) > 0: + if key == 'L2' and len(self.sim_data['dpls']) > 0: ax.legend(handles=lpatch) ax.set_facecolor('k') diff --git a/hnn/visrast.py b/hnn/visrast.py index 23b0dd148..7e38c9329 100644 --- a/hnn/visrast.py +++ b/hnn/visrast.py @@ -1,4 +1,9 @@ -import sys, os +import sys +import os +import numpy as np +from numpy import hamming +from math import ceil + from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QDialog, QGridLayout, QLineEdit, QLabel @@ -6,27 +11,27 @@ from PyQt5.QtGui import QIcon, QFont, QPixmap from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, pyqtSlot from PyQt5 import QtCore -import numpy as np import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure -import pylab as plt from pylab import convolve -from numpy import hamming import matplotlib.gridspec as gridspec -import paramrw -import spikefn -from math import ceil -from qt_lib import getmplDPI + +from .DataViewGUI import DataViewGUI +from .paramrw import usingEvokedInputs, usingOngoingInputs, usingPoissonInputs, get_output_dir +from .spikefn import ExtInputs +from .qt_lib import getmplDPI from hnn_core import read_params, read_spikes #plt.rcParams['lines.markersize'] = 15 plt.rcParams['lines.linewidth'] = 1 -rastmarksz = 5 # raster dot size fontsize = plt.rcParams['font.size'] = 10 +rastmarksz = 5 # raster dot size +binsz = 5.0 +smoothsz = 0 # no smoothing # colors for the different cell types dclr = {'L2_pyramidal' : 'g', @@ -34,32 +39,6 @@ 'L2_basket' : 'w', 'L5_basket' : 'b'} -ntrial = 1; tstop = -1; outparamf = spkpath = paramf = ''; EvokedInputs = OngoingInputs = PoissonInputs = False; - -for i in range(len(sys.argv)): - if sys.argv[i].endswith('.txt'): - spkpath = sys.argv[i] - elif sys.argv[i].endswith('.param'): - paramf = sys.argv[i] - print(paramf) - params = read_params(paramf) - tstop = params['tstop'] - ntrial = params['N_trials'] - EvokedInputs = paramrw.usingEvokedInputs(params) - OngoingInputs = paramrw.usingOngoingInputs(params) - PoissonInputs = paramrw.usingPoissonInputs(params) - outparamf = os.path.join(paramrw.get_output_dir(), 'data', - params['sim_prefix'], 'param.txt') - -extinputs = spikefn.ExtInputs(spkpath, outparamf, params) - -alldat = {} - -binsz = 5.0 -smoothsz = 0 # no smoothing - -bDrawHist = True # whether to draw histograms (spike counts per time) - # box filter def boxfilt (x, winsz): win = [1.0/winsz for i in range(int(winsz))] @@ -85,17 +64,13 @@ def adjustinputgid (extinputs, gid): return gid def gid_to_type(extinputs, gid): - for gidtype, gids in extinputs.gid_dict.items(): + for gidtype, gids in extinputs.gid_ranges.items(): if gid in gids: return gidtype -def getdspk (fn): +def getdspk(spikes, extinputs, tstop): ddat = {} - try: - spikes = read_spikes(fn) - ddat['spk'] = np.r_[spikes.spike_times, spikes.spike_gids].T - except ValueError: - ddat['spk'] = np.loadtxt(fn) + ddat['spk'] = np.r_[spikes.spike_times, spikes.spike_gids].T dspk = {'Cell':([],[],[]),'Input':([],[],[])} dhist = {} @@ -127,7 +102,7 @@ def getdspk (fn): dhist[ty] = dhist[ty][0] return dspk,haveinputs,dhist -def drawhist (dhist,fig,G): +def drawhist(dhist, fig, G, ntrial, tstop): ax = fig.add_subplot(G[-4:-1,:]) fctr = 1.0 if ntrial > 0: fctr = 1.0 / ntrial @@ -137,10 +112,8 @@ def drawhist (dhist,fig,G): ax.set_ylabel('Cell Spikes') return ax -invertedax = False -def drawrast (dspk, fig, G, sz=8): - global invertedax +def drawrast(params, dspk, extinputs, haveinputs, fig, G, tstop, bDrawHist, sz=8): lax = [] lk = ['Cell'] row = 0 @@ -151,11 +124,14 @@ def drawrast (dspk, fig, G, sz=8): dinput = extinputs.inputs - for i,k in enumerate(lk): + for _, k in enumerate(lk): if k == 'Input': # input spiking bins = ceil(150. * tstop / 1000.) # bins needs to be an int + EvokedInputs = usingEvokedInputs(params) + OngoingInputs = usingOngoingInputs(params) + PoissonInputs = usingPoissonInputs(params) haveEvokedDist = (EvokedInputs and len(dinput['evdist'])>0) haveOngoingDist = (OngoingInputs and len(dinput['dist'])>0) haveEvokedProx = (EvokedInputs and len(dinput['evprox'])>0) @@ -183,10 +159,10 @@ def drawrast (dspk, fig, G, sz=8): axp.set_ylabel('Poisson Input') else: # local circuit neuron spiking - ncell = len(extinputs.gid_dict['L2_pyramidal']) + \ - len(extinputs.gid_dict['L2_basket']) + \ - len(extinputs.gid_dict['L5_pyramidal']) + \ - len(extinputs.gid_dict['L5_basket']) + ncell = len(extinputs.gid_ranges['L2_pyramidal']) + \ + len(extinputs.gid_ranges['L2_basket']) + \ + len(extinputs.gid_ranges['L5_pyramidal']) + \ + len(extinputs.gid_ranges['L5_basket']) endrow = -1 if bDrawHist: endrow = -4 @@ -207,15 +183,19 @@ def drawrast (dspk, fig, G, sz=8): return lax class SpikeCanvas (FigureCanvas): - def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, title='Spike Viewer'): + def __init__ (self, params, sim_data, index, parent=None, width=12, height=10, dpi=120, title='Spike Viewer'): FigureCanvas.__init__(self, Figure(figsize=(width, height), dpi=dpi)) self.title = title self.setParent(parent) self.index = index FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) - self.paramf = paramf + self.params = params + self.sim_data = sim_data + self.alldat = {} + self.invertedhistax = False + self.bDrawHist = True # whether to draw histograms (spike counts per time) self.G = gridspec.GridSpec(16,1) self.plot() @@ -224,194 +204,67 @@ def clearaxes(self): ax.set_yticks([]) ax.cla() - def loadspk (self,idx): - global haveinputs,extinputs,params - if idx in alldat: return - alldat[idx] = {} - if idx == 0: - try: - extinputs = spikefn.ExtInputs(spkpath, outparamf, params) - except ValueError: - print("Error: could not load spike timings from %s" % spkpath) - return - dspk,haveinputs,dhist = getdspk(spkpath) - alldat[idx]['dspk'] = dspk - alldat[idx]['haveinputs'] = haveinputs - alldat[idx]['dhist'] = dhist - alldat[idx]['extinputs'] = extinputs - else: - spkpathtrial = os.path.join(paramrw.get_output_dir(), 'data', - params['sim_prefix'], - 'spk_' + str(self.index-1) + '.txt') - dspktrial, haveinputs, dhisttrial = getdspk(spkpathtrial) # show spikes from first trial - try: - extinputs = spikefn.ExtInputs(spkpathtrial, outparamf, params) - except ValueError: - print("Error: could not load spike timings from %s" % spkpath) - return - alldat[idx]['dspk'] = dspktrial - alldat[idx]['haveinputs'] = haveinputs - alldat[idx]['dhist'] = dhisttrial - alldat[idx]['extinputs'] = extinputs + def loadspk(self, idx): + if idx in self.alldat: + return + self.alldat[idx] = {} + + trials = [trial_idx for trial_idx in range(self.params['N_trials'])] + self.extinputs = ExtInputs(self.sim_data['spikes'], + self.sim_data['gid_ranges'], + trials, self.params) + + dspk,haveinputs,dhist = getdspk(self.sim_data['spikes'], + self.extinputs, self.params['tstop']) + self.alldat[idx]['dspk'] = dspk + self.alldat[idx]['haveinputs'] = haveinputs + self.alldat[idx]['dhist'] = dhist + self.alldat[idx]['extinputs'] = self.extinputs - def plot (self): - global haveinputs,extinputs + def plot (self): self.loadspk(self.index) idx = self.index - dspk = alldat[idx]['dspk'] - haveinputs = alldat[idx]['haveinputs'] - dhist = alldat[idx]['dhist'] - extinputs = alldat[idx]['extinputs'] - - self.lax = drawrast(dspk,self.figure, self.G, rastmarksz) + dspk = self.alldat[idx]['dspk'] + haveinputs = self.alldat[idx]['haveinputs'] + dhist = self.alldat[idx]['dhist'] + extinputs = self.alldat[idx]['extinputs'] - if bDrawHist: self.lax.append(drawhist(dhist,self.figure,self.G)) + self.lax = drawrast(self.params, dspk, extinputs, haveinputs, self.figure, self.G, rastmarksz, self.params['tstop'], self.bDrawHist) + if self.bDrawHist: self.lax.append(drawhist(dhist,self.figure,self.G,self.params['N_trials'],self.params['tstop'])) for ax in self.lax: ax.set_facecolor('k') ax.grid(True) - if tstop != -1: ax.set_xlim((0,tstop)) + if self.params['tstop'] != -1: + ax.set_xlim((0, self.params['tstop'])) - if idx == 0: self.lax[0].set_title('All Trials') - else: self.lax[0].set_title('Trial '+str(self.index)) + if idx == 0: + self.lax[0].set_title('All Trials') + else: + self.lax[0].set_title('Trial ' + str(self.index)) - self.lax[-1].set_xlabel('Time (ms)'); + self.lax[-1].set_xlabel('Time (ms)') - self.figure.subplots_adjust(bottom=0.0, left=0.06, right=1.0, top=0.97, wspace=0.1, hspace=0.09) + self.figure.subplots_adjust(bottom=0.0, left=0.06, right=1.0, + top=0.97, wspace=0.1, hspace=0.09) self.draw() -class SpikeGUI (QMainWindow): - def __init__ (self): - super().__init__() - self.initUI() - - if "TRAVIS_TESTING" in os.environ and os.environ["TRAVIS_TESTING"] == "1": - print("Exiting gracefully with TRAVIS_TESTING=1") - qApp.quit() - exit(0) - - def initMenu (self): - exitAction = QAction(QIcon.fromTheme('exit'), 'Exit', self) - exitAction.setShortcut('Ctrl+Q') - exitAction.setStatusTip('Exit HNN Spike Viewer.') - exitAction.triggered.connect(qApp.quit) +class SpikeViewGUI(DataViewGUI): + def __init__(self, CanvasType, params, sim_data, title): + super(SpikeViewGUI, self).__init__(CanvasType, params, sim_data, title) + self.addViewHistAction() - menubar = self.menuBar() - fileMenu = menubar.addMenu('&File') - menubar.setNativeMenuBar(False) - fileMenu.addAction(exitAction) - - viewMenu = menubar.addMenu('&View') + def addViewHistAction(self): + """Add 'Toggle Histograms' to view menu""" drawHistAction = QAction('Toggle Histograms',self) drawHistAction.setStatusTip('Toggle Histogram Drawing.') drawHistAction.triggered.connect(self.toggleHist) - viewMenu.addAction(drawHistAction) - changeFontSizeAction = QAction('Change Font Size',self) - changeFontSizeAction.setStatusTip('Change Font Size.') - changeFontSizeAction.triggered.connect(self.changeFontSize) - viewMenu.addAction(changeFontSizeAction) - changeLineWidthAction = QAction('Change Line Width',self) - changeLineWidthAction.setStatusTip('Change Line Width.') - changeLineWidthAction.triggered.connect(self.changeLineWidth) - viewMenu.addAction(changeLineWidthAction) - changeMarkerSizeAction = QAction('Change Marker Size',self) - changeMarkerSizeAction.setStatusTip('Change Marker Size.') - changeMarkerSizeAction.triggered.connect(self.changeMarkerSize) - viewMenu.addAction(changeMarkerSizeAction) - + self.viewMenu.addAction(drawHistAction) def toggleHist (self): - global bDrawHist - bDrawHist = not bDrawHist + self.m.bDrawHist = not self.m.bDrawHist self.initCanvas() self.m.plot() - - def changeFontSize (self): - global fontsize - - i, okPressed = QInputDialog.getInt(self, "Set Font Size","Font Size:", plt.rcParams['font.size'], 1, 100, 1) - if okPressed: - plt.rcParams['font.size'] = fontsize = i - self.initCanvas() - self.m.plot() - - def changeLineWidth (self): - i, okPressed = QInputDialog.getInt(self, "Set Line Width","Line Width:", plt.rcParams['lines.linewidth'], 1, 20, 1) - if okPressed: - plt.rcParams['lines.linewidth'] = i - self.initCanvas() - self.m.plot() - - def changeMarkerSize (self): - global rastmarksz - i, okPressed = QInputDialog.getInt(self, "Set Marker Size","Font Size:", rastmarksz, 1, 100, 1) - if okPressed: - rastmarksz = i - self.initCanvas() - self.m.plot() - - def initCanvas (self): - # to avoid memory leaks remove any pre-existing widgets before adding new ones - self.grid.removeWidget(self.m) - self.grid.removeWidget(self.toolbar) - self.m.setParent(None) - self.toolbar.setParent(None) - self.m = self.toolbar = None - - self.m = SpikeCanvas(paramf, self.index, parent = self, width=12, height=10, dpi=getmplDPI()) - # this is the Navigation widget - # it takes the Canvas widget and a parent - self.toolbar = NavigationToolbar(self.m, self) - self.grid.addWidget(self.toolbar, 0, 0, 1, 4); - self.grid.addWidget(self.m, 1, 0, 1, 4); - - def initUI (self): - self.initMenu() - self.statusBar() - self.setGeometry(300, 300, 1300, 1100) - self.setWindowTitle('Spike Viewer - ' + paramf) - self.grid = grid = QGridLayout() - self.index = 0 - self.initCanvas() - self.cb = QComboBox(self) - self.grid.addWidget(self.cb,2,0,1,4) - - if ntrial > 1: - self.cb.addItem('Show All Trials') - for i in range(ntrial): - self.cb.addItem('Show Trial ' + str(i+1)) - else: - self.cb.addItem('All Trials') - self.cb.activated[int].connect(self.onActivated) - - # need a separate widget to put grid on - widget = QWidget(self) - widget.setLayout(grid) - self.setCentralWidget(widget) - - self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) - - self.show() - - def onActivated(self, idx): - if idx != self.index: - self.index = idx - if self.index == 0: - self.statusBar().showMessage('Loading data from all trials.') - else: - self.statusBar().showMessage('Loading data from trial ' + str(self.index) + '.') - self.m.index = self.index - self.initCanvas() - self.m.plot() - self.statusBar().showMessage('') - -if __name__ == '__main__': - - app = QApplication(sys.argv) - ex = SpikeGUI() - sys.exit(app.exec_()) - - diff --git a/hnn/visspec.py b/hnn/visspec.py index f77bf6be5..45eeda2ee 100644 --- a/hnn/visspec.py +++ b/hnn/visspec.py @@ -3,7 +3,6 @@ # Authors: Sam Neymotin # Blake Caldwell -import os import numpy as np from PyQt5.QtWidgets import QSizePolicy, QAction @@ -12,23 +11,24 @@ import matplotlib.gridspec as gridspec from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure -from hnn_core.dipole import average_dipoles from .DataViewGUI import DataViewGUI from .specfn import MorletSpec -from .simdat import get_dipoles_from_disk -from .paramrw import get_output_dir fontsize = plt.rcParams['font.size'] = 10 -def extract_spec(params): - """Read dipoles and extract Mortlet spectrograms +def extract_spec(params, dpls, avg_dpl): + """Extract Mortlet spectrograms from dipoles Parameters ---------- params : dict Dictionary containing parameters + dpls: list of Dipole objects + List containing Dipoles of each trial + avg_dpl: Dipole object + Dipole object containing the average of individual trial Dipoles Returns ---------- @@ -36,20 +36,8 @@ def extract_spec(params): List containing spectrograms of each trial avg_spec: MortletSpec objects spectrogram averaged over all trials - dpls: list of Dipole objects - List containing Dipoles of each trial - avg_dpl: Dipole object - Dipole object containing the average of individual trial Dipoles - """ - sim_dir = os.path.join(get_output_dir(), 'data', - params['sim_prefix']) - - dpls = get_dipoles_from_disk(sim_dir, params['N_trials']) - if len(dpls) == 1: - avg_dpl = dpls[0] - else: - avg_dpl = average_dipoles(dpls) + """ specs = [] for dpltrial in dpls: @@ -65,7 +53,7 @@ def extract_spec(params): npspec = np.array(ltfr) avg_spec.TFR = np.mean(npspec, axis=0) - return specs, avg_spec, dpls, avg_dpl + return specs, avg_spec class SpecCanvas(FigureCanvasQTAgg): @@ -74,8 +62,8 @@ class SpecCanvas(FigureCanvasQTAgg): This is designed to be called from SpecViewGUI class to add functionality for loading and clearing data """ - def __init__(self, params, index, parent=None, width=12, height=10, - dpi=120, title='Spectrogram Viewer'): + def __init__(self, params, sim_data, index, parent=None, width=12, + height=10, dpi=120, title='Spectrogram Viewer'): FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), dpi=dpi)) self.title = title @@ -173,13 +161,14 @@ def plot(self): class SpecViewGUI(DataViewGUI): - def __init__(self, CanvasType, params, title): + def __init__(self, CanvasType, params, sim_data, title): self.specs = [] # external data spec self.dpls = None self.avg_dpl = [] self.avg_spec = [] self.params = params - super(SpecViewGUI, self).__init__(CanvasType, self.params, title) + super(SpecViewGUI, self).__init__(CanvasType, self.params, sim_data, + title) self.addLoadDataActions() self.loadDisplayData() @@ -204,10 +193,11 @@ def addLoadDataActions(self): self.fileMenu.addAction(clearDataFileAct) def loadDisplayData(self): - specs, avg_spec, dpls, avg_dpl = extract_spec(self.params) - self.m.dpls = dpls + specs, avg_spec = extract_spec(self.params, self.sim_data['dpls'], + self.sim_data['avg_dpl']) + self.m.dpls = self.sim_data['dpls'] self.m.specs = specs - self.m.avg_dpl = avg_dpl + self.m.avg_dpl = self.sim_data['avg_dpl'] self.m.avg_spec = avg_spec self.updateCB() diff --git a/hnn/visvolt.py b/hnn/visvolt.py index 2d8edd1b2..5a2f4ca95 100644 --- a/hnn/visvolt.py +++ b/hnn/visvolt.py @@ -1,4 +1,7 @@ import sys, os +import numpy as np +import pickle + from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QDialog, QGridLayout, QLineEdit, QLabel @@ -6,20 +9,18 @@ from PyQt5.QtGui import QIcon, QFont, QPixmap from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, pyqtSlot from PyQt5 import QtCore -import numpy as np import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure -import pylab as plt import matplotlib.gridspec as gridspec -import pickle -from qt_lib import getmplDPI -from paramrw import get_output_dir - from hnn_core import read_params +from .qt_lib import getmplDPI +from .paramrw import get_output_dir +from .DataViewGUI import DataViewGUI + fontsize = plt.rcParams['font.size'] = 10 # colors for the different cell types @@ -28,20 +29,8 @@ 'L2_basket' : 'w', 'L5_basket' : 'b'} -ntrial = 1; tstop = -1; outparamf = voltpath = paramf = ''; - maxperty = 10 # how many cells of a type to draw -for i in range(len(sys.argv)): - if sys.argv[i].endswith('.param'): - paramf = sys.argv[i] - params = read_params(paramf) - tstop = params['tstop'] - ntrial = params['N_trials'] - outparamf = os.path.join(get_output_dir(), 'data', params['sim_prefix'], 'param.txt') - elif sys.argv[i] == 'maxperty': - maxperty = int(sys.argv[i]) - if ntrial <= 1: voltpath = os.path.join(get_output_dir(), 'data', params['sim_prefix'], 'vsoma.pkl') else: @@ -118,62 +107,12 @@ def plot (self): self.lax=self.drawvolt(dvolttrial,self.figure, self.G, 5, ltextra='Trial '+str(self.index)) self.draw() -class VoltGUI (QMainWindow): - def __init__ (self): - global fontsize - super().__init__() - self.fontsize = fontsize - self.linewidth = plt.rcParams['lines.linewidth'] = 1 - self.markersize = plt.rcParams['lines.markersize'] = 5 - self.initUI() - - def changeFontSize (self): - global fontsize - i, okPressed = QInputDialog.getInt(self, "Set Font Size","Font Size:", plt.rcParams['font.size'], 1, 100, 1) - if okPressed: - self.fontsize = plt.rcParams['font.size'] = fontsize = i - self.initCanvas() - self.m.plot() - - def changeLineWidth (self): - i, okPressed = QInputDialog.getInt(self, "Set Line Width","Line Width:", plt.rcParams['lines.linewidth'], 1, 20, 1) - if okPressed: - self.linewidth = plt.rcParams['lines.linewidth'] = i - self.initCanvas() - self.m.plot() - - def changeMarkerSize (self): - i, okPressed = QInputDialog.getInt(self, "Set Marker Size","Font Size:", self.markersize, 1, 100, 1) - if okPressed: - self.markersize = plt.rcParams['lines.markersize'] = i - self.initCanvas() - self.m.plot() - - def initMenu (self): - exitAction = QAction(QIcon.fromTheme('exit'), 'Exit', self) - exitAction.setShortcut('Ctrl+Q') - exitAction.setStatusTip('Exit HNN Volt Viewer.') - exitAction.triggered.connect(qApp.quit) - - menubar = self.menuBar() - fileMenu = menubar.addMenu('&File') - menubar.setNativeMenuBar(False) - fileMenu.addAction(exitAction) - - viewMenu = menubar.addMenu('&View') - changeFontSizeAction = QAction('Change Font Size',self) - changeFontSizeAction.setStatusTip('Change Font Size.') - changeFontSizeAction.triggered.connect(self.changeFontSize) - viewMenu.addAction(changeFontSizeAction) - changeLineWidthAction = QAction('Change Line Width',self) - changeLineWidthAction.setStatusTip('Change Line Width.') - changeLineWidthAction.triggered.connect(self.changeLineWidth) - viewMenu.addAction(changeLineWidthAction) - changeMarkerSizeAction = QAction('Change Marker Size',self) - changeMarkerSizeAction.setStatusTip('Change Marker Size.') - changeMarkerSizeAction.triggered.connect(self.changeMarkerSize) - viewMenu.addAction(changeMarkerSizeAction) - +class VoltGUI(DataViewGUI): + def __init__(self, CanvasType, params, title): + self.params = params + super(VoltGUI, self).__init__(CanvasType, self.params, title) + self.addLoadDataActions() + self.loadDisplayData() def initCanvas (self): self.invertedax = False @@ -222,10 +161,3 @@ def onActivated(self, idx): self.initCanvas() self.m.plot() self.statusBar().showMessage('') - -if __name__ == '__main__': - - app = QApplication(sys.argv) - ex = VoltGUI() - sys.exit(app.exec_()) - From 6dd4c7b944d4dbf69163329791d8982e91533a1f Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 2 Mar 2021 20:55:47 -0500 Subject: [PATCH 065/107] MAINT: fix spike viewer window Also cleanup code for pep8 --- hnn/visrast.py | 517 ++++++++++++++++++++++++++----------------------- 1 file changed, 277 insertions(+), 240 deletions(-) diff --git a/hnn/visrast.py b/hnn/visrast.py index 7e38c9329..6d549d32b 100644 --- a/hnn/visrast.py +++ b/hnn/visrast.py @@ -1,270 +1,307 @@ -import sys -import os +"""Create the spike raster plot viewing window""" + +# Authors: Sam Neymotin +# Blake Caldwell + import numpy as np from numpy import hamming from math import ceil -from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout -from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QDialog, QGridLayout, QLineEdit, QLabel -from PyQt5.QtWidgets import QCheckBox, QInputDialog -from PyQt5.QtGui import QIcon, QFont, QPixmap -from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, pyqtSlot -from PyQt5 import QtCore +from PyQt5.QtWidgets import QAction, QSizePolicy import matplotlib.pyplot as plt import matplotlib.patches as mpatches -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure from pylab import convolve import matplotlib.gridspec as gridspec from .DataViewGUI import DataViewGUI -from .paramrw import usingEvokedInputs, usingOngoingInputs, usingPoissonInputs, get_output_dir +from .paramrw import usingEvokedInputs, usingOngoingInputs, usingPoissonInputs from .spikefn import ExtInputs -from .qt_lib import getmplDPI - -from hnn_core import read_params, read_spikes -#plt.rcParams['lines.markersize'] = 15 plt.rcParams['lines.linewidth'] = 1 fontsize = plt.rcParams['font.size'] = 10 -rastmarksz = 5 # raster dot size +rastmarksz = 5 # raster dot size binsz = 5.0 -smoothsz = 0 # no smoothing +smoothsz = 0 # no smoothing # colors for the different cell types -dclr = {'L2_pyramidal' : 'g', - 'L5_pyramidal' : 'r', - 'L2_basket' : 'w', - 'L5_basket' : 'b'} +dclr = {'L2_pyramidal': 'g', + 'L5_pyramidal': 'r', + 'L2_basket': 'w', + 'L5_basket': 'b'} -# box filter -def boxfilt (x, winsz): - win = [1.0/winsz for i in range(int(winsz))] - return convolve(x,win,'same') # convolve with a hamming window -def hammfilt (x, winsz): - win = hamming(winsz) - win /= sum(win) - return convolve(x,win,'same') +def hammfilt(x, winsz): + win = hamming(winsz) + win /= sum(win) + return convolve(x, win, 'same') # adjust input gids for display purposes -def adjustinputgid (extinputs, gid): - if gid == extinputs.gid_prox: - return 0 - elif gid == extinputs.gid_dist: - return 1 - elif extinputs.is_prox_gid(gid): - return 2 - elif extinputs.is_dist_gid(gid): - return 3 - return gid +def adjustinputgid(extinputs, gid): + if gid == extinputs.gid_prox: + return 0 + elif gid == extinputs.gid_dist: + return 1 + elif extinputs.is_prox_gid(gid): + return 2 + elif extinputs.is_dist_gid(gid): + return 3 + return gid + def gid_to_type(extinputs, gid): - for gidtype, gids in extinputs.gid_ranges.items(): - if gid in gids: - return gidtype + for gidtype, gids in extinputs.gid_ranges.items(): + if gid in gids: + return gidtype + def getdspk(spikes, extinputs, tstop): - ddat = {} - ddat['spk'] = np.r_[spikes.spike_times, spikes.spike_gids].T - - dspk = {'Cell':([],[],[]),'Input':([],[],[])} - dhist = {} - for ty in dclr.keys(): dhist[ty] = [] - haveinputs = False - for (t,gid) in ddat['spk']: - ty = gid_to_type(extinputs,gid) - if ty in dclr: - dspk['Cell'][0].append(t) - dspk['Cell'][1].append(gid) - dspk['Cell'][2].append(dclr[ty]) - dhist[ty].append(t) - else: - dspk['Input'][0].append(t) - dspk['Input'][1].append(adjustinputgid(extinputs, gid)) - if extinputs.is_prox_gid(gid): - dspk['Input'][2].append('r') - elif extinputs.is_dist_gid(gid): - dspk['Input'][2].append('g') - else: - dspk['Input'][2].append('orange') - haveinputs = True - for ty in dhist.keys(): - dhist[ty] = np.histogram(dhist[ty],range=(0,tstop),bins=int(tstop/binsz)) - if smoothsz > 0: - #dhist[ty] = boxfilt(dhist[ty][0],smoothsz) - dhist[ty] = hammfilt(dhist[ty][0],smoothsz) - else: - dhist[ty] = dhist[ty][0] - return dspk,haveinputs,dhist - -def drawhist(dhist, fig, G, ntrial, tstop): - ax = fig.add_subplot(G[-4:-1,:]) - fctr = 1.0 - if ntrial > 0: fctr = 1.0 / ntrial - for ty in dhist.keys(): - ax.plot(np.arange(binsz/2,tstop+binsz/2,binsz),dhist[ty]*fctr,dclr[ty],linestyle='--') - ax.set_xlim((0,tstop)) - ax.set_ylabel('Cell Spikes') - return ax - - -def drawrast(params, dspk, extinputs, haveinputs, fig, G, tstop, bDrawHist, sz=8): - lax = [] - lk = ['Cell'] - row = 0 - - if haveinputs: - lk.append('Input') - lk.reverse() - - dinput = extinputs.inputs - - for _, k in enumerate(lk): - if k == 'Input': # input spiking - - bins = ceil(150. * tstop / 1000.) # bins needs to be an int - - EvokedInputs = usingEvokedInputs(params) - OngoingInputs = usingOngoingInputs(params) - PoissonInputs = usingPoissonInputs(params) - haveEvokedDist = (EvokedInputs and len(dinput['evdist'])>0) - haveOngoingDist = (OngoingInputs and len(dinput['dist'])>0) - haveEvokedProx = (EvokedInputs and len(dinput['evprox'])>0) - haveOngoingProx = (OngoingInputs and len(dinput['prox'])>0) - - if haveEvokedDist or haveOngoingDist: - ax = fig.add_subplot(G[row:row+2,:]); row += 2 - lax.append(ax) - if haveEvokedDist: extinputs.plot_hist(ax,'evdist',0,bins,(0,tstop),color='g',hty='step') - if haveOngoingDist: extinputs.plot_hist(ax,'dist',0,bins,(0,tstop),color='g') - ax.invert_yaxis() - ax.set_ylabel('Distal Input') - - if haveEvokedProx or haveOngoingProx: - ax2 = fig.add_subplot(G[row:row+2,:]); row += 2 - lax.append(ax2) - if haveEvokedProx: extinputs.plot_hist(ax2,'evprox',0,bins,(0,tstop),color='r',hty='step') - if haveOngoingProx: extinputs.plot_hist(ax2,'prox',0,bins,(0,tstop),color='r') - ax2.set_ylabel('Proximal Input') - - if PoissonInputs and len(dinput['pois']): - axp = fig.add_subplot(G[row:row+2,:]); row += 2 - lax.append(axp) - extinputs.plot_hist(axp,'pois',0,bins,(0,tstop),color='orange') - axp.set_ylabel('Poisson Input') - - else: # local circuit neuron spiking - ncell = len(extinputs.gid_ranges['L2_pyramidal']) + \ - len(extinputs.gid_ranges['L2_basket']) + \ - len(extinputs.gid_ranges['L5_pyramidal']) + \ - len(extinputs.gid_ranges['L5_basket']) - - endrow = -1 - if bDrawHist: endrow = -4 - - ax = fig.add_subplot(G[row:endrow,:]) - lax.append(ax) - - ax.scatter(dspk[k][0],dspk[k][1],c=dspk[k][2],s=sz**2) - ax.set_ylabel(k + ' ID') - white_patch = mpatches.Patch(color='white', label='L2/3 Basket') - green_patch = mpatches.Patch(color='green', label='L2/3 Pyr') - red_patch = mpatches.Patch(color='red', label='L5 Pyr') - blue_patch = mpatches.Patch(color='blue', label='L5 Basket') - ax.legend(handles=[white_patch,green_patch,blue_patch,red_patch],loc='best') - ax.set_ylim((-1,ncell+1)) - ax.invert_yaxis() - - return lax - -class SpikeCanvas (FigureCanvas): - def __init__ (self, params, sim_data, index, parent=None, width=12, height=10, dpi=120, title='Spike Viewer'): - FigureCanvas.__init__(self, Figure(figsize=(width, height), dpi=dpi)) - self.title = title - self.setParent(parent) - self.index = index - FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding) - FigureCanvas.updateGeometry(self) - self.params = params - self.sim_data = sim_data - self.alldat = {} - - self.invertedhistax = False - self.bDrawHist = True # whether to draw histograms (spike counts per time) - self.G = gridspec.GridSpec(16,1) - self.plot() - - def clearaxes(self): - for ax in self.lax: - ax.set_yticks([]) - ax.cla() - - def loadspk(self, idx): - if idx in self.alldat: - return - self.alldat[idx] = {} - - trials = [trial_idx for trial_idx in range(self.params['N_trials'])] - self.extinputs = ExtInputs(self.sim_data['spikes'], - self.sim_data['gid_ranges'], - trials, self.params) - - dspk,haveinputs,dhist = getdspk(self.sim_data['spikes'], - self.extinputs, self.params['tstop']) - self.alldat[idx]['dspk'] = dspk - self.alldat[idx]['haveinputs'] = haveinputs - self.alldat[idx]['dhist'] = dhist - self.alldat[idx]['extinputs'] = self.extinputs - - - def plot (self): - self.loadspk(self.index) - - idx = self.index - dspk = self.alldat[idx]['dspk'] - haveinputs = self.alldat[idx]['haveinputs'] - dhist = self.alldat[idx]['dhist'] - extinputs = self.alldat[idx]['extinputs'] - - self.lax = drawrast(self.params, dspk, extinputs, haveinputs, self.figure, self.G, rastmarksz, self.params['tstop'], self.bDrawHist) - if self.bDrawHist: self.lax.append(drawhist(dhist,self.figure,self.G,self.params['N_trials'],self.params['tstop'])) - - for ax in self.lax: - ax.set_facecolor('k') - ax.grid(True) - if self.params['tstop'] != -1: - ax.set_xlim((0, self.params['tstop'])) - - if idx == 0: - self.lax[0].set_title('All Trials') - else: - self.lax[0].set_title('Trial ' + str(self.index)) - - self.lax[-1].set_xlabel('Time (ms)') - - self.figure.subplots_adjust(bottom=0.0, left=0.06, right=1.0, - top=0.97, wspace=0.1, hspace=0.09) - - self.draw() + ddat = {} + ddat['spk'] = spikes + + dspk = {'Cell': ([], [], []), + 'Input': ([], [], [])} + dhist = {} + for ty in dclr.keys(): + dhist[ty] = [] + haveinputs = False + for (t, gid) in ddat['spk']: + ty = gid_to_type(extinputs, gid) + if ty in dclr: + dspk['Cell'][0].append(t) + dspk['Cell'][1].append(gid) + dspk['Cell'][2].append(dclr[ty]) + dhist[ty].append(t) + else: + dspk['Input'][0].append(t) + dspk['Input'][1].append(adjustinputgid(extinputs, gid)) + if extinputs.is_prox_gid(gid): + dspk['Input'][2].append('r') + elif extinputs.is_dist_gid(gid): + dspk['Input'][2].append('g') + else: + dspk['Input'][2].append('orange') + haveinputs = True + for ty in dhist.keys(): + dhist[ty] = np.histogram(dhist[ty], range=(0, tstop), + bins=int(tstop / binsz)) + if smoothsz > 0: + dhist[ty] = hammfilt(dhist[ty][0], smoothsz) + else: + dhist[ty] = dhist[ty][0] + return dspk, haveinputs, dhist + + +class SpikeCanvas(FigureCanvasQTAgg): + def __init__(self, params, sim_data, index, parent=None, width=12, + height=10, dpi=120, title='Spike Viewer'): + FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), + dpi=dpi)) + self.title = title + self.setParent(parent) + self.gui = parent + self.index = index + FigureCanvasQTAgg.setSizePolicy(self, QSizePolicy.Expanding, + QSizePolicy.Expanding) + FigureCanvasQTAgg.updateGeometry(self) + self.params = params + self.invertedhistax = False + self.G = gridspec.GridSpec(16, 1) + + self.sim_data = sim_data + self.alldat = {} + + # whether to draw histograms (spike counts per time) + self.bDrawHist = True + + self.plot() + + def clearaxes(self): + for ax in self.lax: + ax.set_yticks([]) + ax.cla() + + def drawhist(self, dhist, ntrial, tstop): + ax = self.figure.add_subplot(self.G[-4:-1, :]) + fctr = 1.0 + if ntrial > 0: + fctr = 1.0 / ntrial + for ty in dhist.keys(): + ax.plot(np.arange(binsz / 2, tstop + binsz / 2, binsz), + dhist[ty] * fctr, dclr[ty], linestyle='--') + ax.set_xlim((0, tstop)) + ax.set_ylabel('Cell Spikes') + return ax + + def drawrast(self, dspk, extinputs, haveinputs, fig, G, sz=8): + lax = [] + lk = ['Cell'] + row = 0 + tstop = self.params['tstop'] + + if haveinputs: + lk.append('Input') + lk.reverse() + + dinput = extinputs.inputs + + for _, k in enumerate(lk): + if k == 'Input': # input spiking + bins = ceil(150. * tstop / 1000.) # bins needs to be an int + + EvokedInputs = usingEvokedInputs(self.params) + OngoingInputs = usingOngoingInputs(self.params) + PoissonInputs = usingPoissonInputs(self.params) + haveEvokedDist = (EvokedInputs and len(dinput['evdist']) > 0) + haveOngoingDist = (OngoingInputs and len(dinput['dist']) > 0) + haveEvokedProx = (EvokedInputs and len(dinput['evprox']) > 0) + haveOngoingProx = (OngoingInputs and len(dinput['prox']) > 0) + + if haveEvokedDist or haveOngoingDist: + ax = fig.add_subplot(G[row:row + 2, :]) + row += 2 + lax.append(ax) + if haveEvokedDist: + extinputs.plot_hist(ax, 'evdist', 0, bins, (0, tstop), + color='g', hty='step') + if haveOngoingDist: + extinputs.plot_hist(ax, 'dist', 0, bins, (0, tstop), + color='g') + ax.invert_yaxis() + ax.set_ylabel('Distal Input') + + if haveEvokedProx or haveOngoingProx: + ax2 = fig.add_subplot(G[row:row + 2, :]) + row += 2 + lax.append(ax2) + if haveEvokedProx: + extinputs.plot_hist(ax2, 'evprox', 0, bins, (0, tstop), + color='r', hty='step') + if haveOngoingProx: + extinputs.plot_hist(ax2, 'prox', 0, bins, (0, tstop), + color='r') + ax2.set_ylabel('Proximal Input') + + if PoissonInputs and len(dinput['pois']): + axp = fig.add_subplot(G[row:row + 2, :]) + row += 2 + lax.append(axp) + extinputs.plot_hist(axp, 'pois', 0, bins, (0, tstop), + color='orange') + axp.set_ylabel('Poisson Input') + + else: # local circuit neuron spiking + ncell = len(extinputs.gid_ranges['L2_pyramidal']) + \ + len(extinputs.gid_ranges['L2_basket']) + \ + len(extinputs.gid_ranges['L5_pyramidal']) + \ + len(extinputs.gid_ranges['L5_basket']) + + endrow = -1 + if self.bDrawHist: + endrow = -4 + + ax = fig.add_subplot(G[row:endrow, :]) + lax.append(ax) + + ax.scatter(dspk[k][0], dspk[k][1], c=dspk[k][2], s=sz**2) + ax.set_ylabel(k + ' ID') + white_patch = mpatches.Patch(color='white', + label='L2/3 Basket') + green_patch = mpatches.Patch(color='green', label='L2/3 Pyr') + red_patch = mpatches.Patch(color='red', label='L5 Pyr') + blue_patch = mpatches.Patch(color='blue', label='L5 Basket') + ax.legend(handles=[white_patch, green_patch, blue_patch, + red_patch], loc='best') + ax.set_ylim((-1, ncell + 1)) + ax.invert_yaxis() + + return lax + + def loadspk(self, idx): + if idx in self.alldat: + return + self.alldat[idx] = {} + + trials = [trial_idx for trial_idx in range(self.params['N_trials'])] + self.extinputs = ExtInputs(self.sim_data['spikes'], + self.sim_data['gid_ranges'], + trials, self.params) + + if idx == 0 and self.params['N_trials'] > 1: + # combine spikes into a single list for all trials + spike_times = np.array(sum(self.sim_data['spikes'].spike_times, [])) + spike_gids = np.array(sum(self.sim_data['spikes'].spike_gids, [])) + else: + spike_times = self.sim_data['spikes'].spike_times[idx-1] + spike_gids = self.sim_data['spikes'].spike_gids[idx-1] + + empty_array = np.empty((len(spike_times), 0), np.float64) + spike_arr = np.array([spike_times, spike_gids]) + spike_arr = np.append(empty_array, spike_arr.transpose(), axis=1) + + dspk, haveinputs, dhist = getdspk(spike_arr, self.extinputs, + self.params['tstop']) + self.alldat[idx]['dspk'] = dspk + self.alldat[idx]['haveinputs'] = haveinputs + self.alldat[idx]['dhist'] = dhist + self.alldat[idx]['extinputs'] = self.extinputs + + def plot(self): + self.loadspk(self.index) + + idx = self.index + dspk = self.alldat[idx]['dspk'] + haveinputs = self.alldat[idx]['haveinputs'] + dhist = self.alldat[idx]['dhist'] + extinputs = self.alldat[idx]['extinputs'] + + self.lax = self.drawrast(dspk, extinputs, haveinputs, self.figure, + self.G, rastmarksz) + if self.bDrawHist: + self.lax.append(self.drawhist(dhist, self.params['N_trials'], + self.params['tstop'])) + + for ax in self.lax: + ax.set_facecolor('k') + ax.grid(True) + if self.params['tstop'] != -1: + ax.set_xlim((0, self.params['tstop'])) + + if idx == 0: + self.lax[0].set_title('All Trials') + else: + self.lax[0].set_title('Trial ' + str(self.index)) + + self.lax[-1].set_xlabel('Time (ms)') + + self.figure.subplots_adjust(bottom=0.0, left=0.06, right=1.0, + top=0.97, wspace=0.1, hspace=0.09) + + self.draw() + class SpikeViewGUI(DataViewGUI): - def __init__(self, CanvasType, params, sim_data, title): - super(SpikeViewGUI, self).__init__(CanvasType, params, sim_data, title) - self.addViewHistAction() - - def addViewHistAction(self): - """Add 'Toggle Histograms' to view menu""" - drawHistAction = QAction('Toggle Histograms',self) - drawHistAction.setStatusTip('Toggle Histogram Drawing.') - drawHistAction.triggered.connect(self.toggleHist) - self.viewMenu.addAction(drawHistAction) - - def toggleHist (self): - self.m.bDrawHist = not self.m.bDrawHist - self.initCanvas() - self.m.plot() + def __init__(self, CanvasType, params, sim_data, title): + self.params = params + self.sim_data = sim_data + super(SpikeViewGUI, self).__init__(CanvasType, params, sim_data, title) + self.addViewHistAction() + + def initCanvas(self): + super(SpikeViewGUI, self).initCanvas() + self.m.sim_data = self.sim_data + + def addViewHistAction(self): + """Add 'Toggle Histograms' to view menu""" + drawHistAction = QAction('Toggle Histograms', self) + drawHistAction.setStatusTip('Toggle Histogram Drawing.') + drawHistAction.triggered.connect(self.toggleHist) + self.viewMenu.addAction(drawHistAction) + + def toggleHist(self): + self.m.bDrawHist = not self.m.bDrawHist + self.initCanvas() + self.m.plot() From 1114cb52780da1c9012dba1da64fdb843d340ac1 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 2 Mar 2021 21:00:01 -0500 Subject: [PATCH 066/107] MAINT: fix spectrogram viewer for multiple trials --- hnn/visspec.py | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/hnn/visspec.py b/hnn/visspec.py index 45eeda2ee..8dcadad23 100644 --- a/hnn/visspec.py +++ b/hnn/visspec.py @@ -76,11 +76,11 @@ def __init__(self, params, sim_data, index, parent=None, width=12, self.params = params self.invertedhistax = False self.G = gridspec.GridSpec(10, 1) - self.dpls = [] - self.specs = [] + self.dpls = self.gui.dpls + self.specs = self.gui.specs + self.avg_dpl = self.gui.avg_dpl + self.avg_spec = self.gui.avg_spec self.lax = [] - self.avg_dpl = [] - self.avg_spec = [] if 'spec_cmap' in self.params: self.spec_cmap = self.params['spec_cmap'] @@ -101,7 +101,7 @@ def clearlextdatobj(self): o.set_visible(False) del self.lextdatobj - def drawspec(self, dpls, lspec, sdx, avgdipole, avgspec, fig, G, + def drawspec(self, dpls, lspec, avgdipole, avgspec, fig, G, ltextra=''): if len(lspec) == 0: return @@ -114,14 +114,14 @@ def drawspec(self, dpls, lspec, sdx, avgdipole, avgspec, fig, G, lax = [ax] tvec = avgdipole.times - if sdx == 0: + if self.index == 0: for dpltrial in dpls: ax.plot(tvec, dpltrial.data['agg'], linewidth=self.gui.linewidth, color='gray') ax.plot(tvec, avgdipole.data['agg'], linewidth=self.gui.linewidth + 1, color='black') else: - ax.plot(tvec, dpls[sdx].data['agg'], + ax.plot(tvec, dpls[self.index-1].data['agg'], linewidth=self.gui.linewidth + 1, color='gray') ax.set_xlim(tvec[0], tvec[-1]) @@ -131,10 +131,10 @@ def drawspec(self, dpls, lspec, sdx, avgdipole, avgspec, fig, G, ax = fig.add_subplot(gdx) - if sdx == 0: + if self.index == 0: ms = avgspec else: - ms = lspec[sdx - 1] + ms = lspec[self.index - 1] ax.imshow(ms.TFR, extent=[tvec[0], tvec[-1], ms.f[-1], ms.f[0]], aspect='auto', origin='upper', @@ -152,7 +152,7 @@ def plot(self): ltextra = 'Trial ' + str(self.index) if self.index == 0: ltextra = 'All Trials' - self.lax = self.drawspec(self.dpls, self.specs, self.index, + self.lax = self.drawspec(self.dpls, self.specs, self.avg_dpl, self.avg_spec, self.figure, self.G, ltextra=ltextra) self.figure.subplots_adjust(bottom=0.06, left=0.06, right=0.98, @@ -172,10 +172,6 @@ def __init__(self, CanvasType, params, sim_data, title): self.addLoadDataActions() self.loadDisplayData() - def initCanvas(self): - super(SpecViewGUI, self).initCanvas() - self.m.specs = self.specs - def addLoadDataActions(self): loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data.', self) loadDataFile.setShortcut('Ctrl+D') @@ -193,12 +189,18 @@ def addLoadDataActions(self): self.fileMenu.addAction(clearDataFileAct) def loadDisplayData(self): - specs, avg_spec = extract_spec(self.params, self.sim_data['dpls'], - self.sim_data['avg_dpl']) - self.m.dpls = self.sim_data['dpls'] - self.m.specs = specs - self.m.avg_dpl = self.sim_data['avg_dpl'] - self.m.avg_spec = avg_spec + # store copy of data in this object, that can be reused by canvas (self.m) + # on re-instantiation + self.avg_dpl = self.sim_data['avg_dpl'] + self.dpls = self.sim_data['dpls'] + self.specs, self.avg_spec = extract_spec(self.params, self.dpls, + self.avg_dpl) + + # populate the data inside canvas object before calling self.m.plot() + self.m.avg_dpl = self.avg_dpl + self.m.dpls = self.dpls + self.m.specs = self.specs + self.m.avg_spec = self.avg_spec self.updateCB() self.printStat('Extracted ' + str(len(self.m.specs)) + From 7415dcfce2ce36e97bf4106ffe22a10d2bb587d8 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 16 Mar 2021 09:29:03 -0400 Subject: [PATCH 067/107] MAINT: pep8 visrast.py --- hnn/visrast.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/hnn/visrast.py b/hnn/visrast.py index 6d549d32b..6f3c898f1 100644 --- a/hnn/visrast.py +++ b/hnn/visrast.py @@ -232,11 +232,12 @@ def loadspk(self, idx): if idx == 0 and self.params['N_trials'] > 1: # combine spikes into a single list for all trials - spike_times = np.array(sum(self.sim_data['spikes'].spike_times, [])) + spike_times = np.array(sum(self.sim_data['spikes'].spike_times, + [])) spike_gids = np.array(sum(self.sim_data['spikes'].spike_gids, [])) else: - spike_times = self.sim_data['spikes'].spike_times[idx-1] - spike_gids = self.sim_data['spikes'].spike_gids[idx-1] + spike_times = self.sim_data['spikes'].spike_times[idx - 1] + spike_gids = self.sim_data['spikes'].spike_gids[idx - 1] empty_array = np.empty((len(spike_times), 0), np.float64) spike_arr = np.array([spike_times, spike_gids]) @@ -286,14 +287,9 @@ def plot(self): class SpikeViewGUI(DataViewGUI): def __init__(self, CanvasType, params, sim_data, title): self.params = params - self.sim_data = sim_data super(SpikeViewGUI, self).__init__(CanvasType, params, sim_data, title) self.addViewHistAction() - def initCanvas(self): - super(SpikeViewGUI, self).initCanvas() - self.m.sim_data = self.sim_data - def addViewHistAction(self): """Add 'Toggle Histograms' to view menu""" drawHistAction = QAction('Toggle Histograms', self) From 9e6ee6f9a3b5911bb123b4241ec39c7d132d797e Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 16 Mar 2021 09:29:43 -0400 Subject: [PATCH 068/107] MAINT: fix reinit issues with DatViewGUI --- hnn/DataViewGUI.py | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/hnn/DataViewGUI.py b/hnn/DataViewGUI.py index 9e90f1f89..7f2e8fe45 100644 --- a/hnn/DataViewGUI.py +++ b/hnn/DataViewGUI.py @@ -5,7 +5,7 @@ import os -from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QWidget, QComboBox +from PyQt5.QtWidgets import QMainWindow, QAction, QWidget, QComboBox from PyQt5.QtWidgets import QGridLayout, QInputDialog from PyQt5.QtGui import QIcon from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT @@ -29,15 +29,17 @@ def __init__(self, CanvasType, params, sim_data, title): self.CanvasType = CanvasType self.ntrial = params['N_trials'] self.params = params - self.sim_data = sim_data self.title = title + self.m = None + self.toolbar = None + self.sim_data = sim_data self.initUI() def initMenu(self): - exitAction = QAction(QIcon.fromTheme('exit'), 'Exit', self) - exitAction.setShortcut('Ctrl+Q') - exitAction.setStatusTip('Exit ' + self.title + '.') - exitAction.triggered.connect(qApp.quit) + exitAction = QAction(QIcon.fromTheme('close'), 'Close', self) + exitAction.setShortcut('Ctrl+W') + exitAction.setStatusTip('Close ' + self.title + '.') + exitAction.triggered.connect(self.close) menubar = self.menuBar() self.fileMenu = menubar.addMenu('&File') @@ -93,20 +95,24 @@ def printStat(self, s): print(s) self.statusBar().showMessage(s) - def initCanvas(self): + def initCanvas(self, first_init=False): """Initialize canvas - This function will add widgets, which may create a memory leak if it - is called repeatedly (according to a comment in the previous code). - The previous code addressed it with the following: - - self.grid.removeWidget(self.m) - self.grid.removeWidget(self.toolbar) - self.m.setParent(None) - self.toolbar.setParent(None) - self.m = self.toolbar = None + Parameters + ---------- + first_init: bool | None + Whether canvas is being initialized for the first time + or if member objects have already been instantiated. If None, + then False is assumed. """ + if not first_init: + self.m.setParent(None) + self.toolbar.setParent(None) + self.grid.removeWidget(self.m) + self.grid.removeWidget(self.toolbar) + + self.grid.removeWidget(self.toolbar) self.m = self.CanvasType(self.params, self.sim_data, self.index, parent=self, width=12, height=10, dpi=getmplDPI()) @@ -136,7 +142,7 @@ def initUI(self): '.param')) self.grid = grid = QGridLayout() self.index = 0 - self.initCanvas() + self.initCanvas(True) self.cb = QComboBox(self) self.grid.addWidget(self.cb, 2, 0, 1, 4) From 968806e38dbd26b4650b7ab7a344e220c28a7d01 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 16 Mar 2021 09:30:17 -0400 Subject: [PATCH 069/107] MAINT: pep8 run.py --- hnn/run.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/hnn/run.py b/hnn/run.py index b4c1b5d74..6ce3ed071 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -7,7 +7,6 @@ import os.path as op import os import sys -import numpy as np from time import sleep from copy import deepcopy from math import ceil, isclose @@ -109,13 +108,15 @@ def simulate(params, n_procs=None): sim_data = {} # run the simulation with MPIBackend for faster completion time with MPIBackend(n_procs=n_procs, mpi_cmd='mpiexec'): - sim_data['raw_dpls'] = simulate_dipole(net, params['N_trials'], postproc=False) + sim_data['raw_dpls'] = simulate_dipole(net, params['N_trials'], + postproc=False) sim_data['gid_ranges'] = net.gid_ranges sim_data['spikes'] = net.cell_response return sim_data + # based on https://nikolak.com/pyqt-threading-tutorial/ class RunSimThread(QtCore.QThread): """The RunSimThread class. @@ -244,7 +245,7 @@ def run(self): except RuntimeError as e: msg = str(e) self.baseparamwin.optparamwin.toggleEnableUserFields( - self.cur_step, enable=True) + self.cur_step, enable=True) self.baseparamwin.optparamwin.clear_initial_opt_ranges() self.baseparamwin.optparamwin.optimization_running = False else: @@ -284,7 +285,7 @@ def _runsim(self, is_opt=False, banner=True, simlength=None): # exit using RuntimeError raise RuntimeError("Terminated") - self.ncore = ceil(self.ncore/2) + self.ncore = ceil(self.ncore / 2) txt = "INFO: Failed starting simulation, retrying with %d cores" \ % self.ncore print(txt) @@ -446,7 +447,7 @@ def optrun(new_params, grad=0): print(txt) self._updatewaitsimwin(os.linesep + 'Simulation finished: ' + txt + - os.linesep) + os.linesep) data_dir = op.join(get_output_dir(), 'data') sim_dir = op.join(data_dir, self.params['sim_prefix']) @@ -531,4 +532,4 @@ def optimize(params_input, evals, algorithm): self.step_ranges[var_name]['final'] = new_value else: self.step_ranges[var_name]['final'] = \ - self.step_ranges[var_name]['initial'] + self.step_ranges[var_name]['initial'] From 3f58605d61e7cea517f5db3fa8899d555ba15a0e Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 16 Mar 2021 09:52:22 -0400 Subject: [PATCH 070/107] MAINT: refactor spec plotting --- hnn/hnn_qt5.py | 72 ++++++++++------ hnn/simdat.py | 106 ++++++++++------------- hnn/specfn.py | 120 +++++++++++++++++--------- hnn/visspec.py | 222 ++++++++++++++++++++++++++++--------------------- 4 files changed, 297 insertions(+), 223 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index d682438e1..a69c92845 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -33,7 +33,7 @@ # HNN modules from .paramrw import usingOngoingInputs, usingEvokedInputs, get_output_dir from .paramrw import write_gids_param, get_fname -from .specfn import analysis_simp +from .specfn import spec_dpl_kernel, save_spec_data from .simdat import SIMCanvas, SimData from .run import RunSimThread, ParamSignal from .qt_lib import setscalegeom, setscalegeomcenter, getmplDPI, getscreengeom @@ -43,6 +43,7 @@ from .visdipole import DipoleCanvas from .visspec import SpecViewGUI, SpecCanvas from .visrast import SpikeViewGUI, SpikeCanvas +from .vispsd import PSDViewGUI, PSDCanvas # TODO: These globals should be made configurable via the GUI drawavgdpl = 0 @@ -1308,9 +1309,11 @@ def selParamFileDialog(self): self.baseparamwin.updateDispParam(params) self.setWindowTitle(self.baseparamwin.paramfn) - self.initSimCanvas() # recreate canvas + self.initSimCanvas() # recreate canvas - self.populateSimCB() # populate the combobox + # check if param file exists in combo box already + cb_index = self.cbsim.findText(self.baseparamwin.paramfn) + self.populateSimCB(cb_index) # populate the combobox if self.sim_data.get_exp_data_size() > 0: self.toggleEnableOptimization(True) @@ -1418,24 +1421,39 @@ def showSomaVPlot(self): Popen(lcmd) # nonblocking def showPSDPlot(self): - # start the PSD visualization process (separate window) - outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) - outparamf = os.path.join(outdir, - self.baseparamwin.params['sim_prefix'] + - '.param') - lcmd = [getPyComm(), 'vispsd.py',outparamf] - Popen(lcmd) # nonblocking + if self.baseparamwin.paramfn is None: + return + if self.baseparamwin.paramfn in self.sim_data._sim_data: + sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + else: + sim_data = None + PSDViewGUI(PSDCanvas, self.baseparamwin.params, sim_data, 'PSD Viewer') def showSpecPlot(self): - sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + if self.baseparamwin.paramfn is None: + return + if self.baseparamwin.paramfn in self.sim_data._sim_data: + sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + else: + sim_data = None SpecViewGUI(SpecCanvas, self.baseparamwin.params, sim_data, 'Spectrogram Viewer') def showRasterPlot(self): - sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + if self.baseparamwin.paramfn is None: + return + if self.baseparamwin.paramfn in self.sim_data._sim_data: + sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + else: + sim_data = None SpikeViewGUI(SpikeCanvas, self.baseparamwin.params, sim_data, 'Spike Viewer') def showDipolePlot(self): - sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + if self.baseparamwin.paramfn is None: + return + if self.baseparamwin.paramfn in self.sim_data._sim_data: + sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + else: + sim_data = None DataViewGUI(DipoleCanvas, self.baseparamwin.params, sim_data, 'Dipole Viewer') def showwaitsimwin(self): @@ -1808,7 +1826,13 @@ def initUI (self): gRow += 2 self.cbsim = QComboBox(self) - self.populateSimCB() # populate the combobox + try: + self.populateSimCB() + except ValueError: + # If no simulations could be loaded into combobox + # don't crash the initialization process + print("Warning: no simulations to load") + pass self.cbsim.activated[str].connect(self.onActivateSimCB) self.grid.addWidget(self.cbsim, gRow, 0, 1, 8)#, 1, 3) self.btnrmsim = QPushButton('Remove Simulation',self) @@ -1862,7 +1886,7 @@ def populateSimCB(self, index=None): if self.cbsim.count() == 0: raise ValueError("No simulations to add to combo box") - if index is None: + if index is None or index < 0: # set to last entry self.cbsim.setCurrentIndex(self.cbsim.count() - 1) else: @@ -2066,7 +2090,6 @@ def result_callback(self, result): glob = os.path.join(sim_dir, 'spk_%d.txt') sim_data['spikes'].write(glob) - spec_fns = [] # save dipole for each trial and perform spectral analysis for trial_idx, dpl in enumerate(sim_data['dpls']): dipole_fn = get_fname(sim_dir, 'normdpl', trial_idx) @@ -2078,17 +2101,14 @@ def result_callback(self, result): if params['save_spec_data'] or \ usingOngoingInputs(params): - spec_opts = {'type': 'dpl_laminar', - 'f_max': params['f_max_spec'], - 'save_data': 1, - 'runtype': 'parallel'} - spec_fn = get_fname(sim_dir, 'rawspec', trial_idx) - spec_fns.append(spec_fn) - - # run the spectral analysis and save to spec_fn - spec_results = analysis_simp(spec_opts, params, dpl, spec_fn) + spec_results = spec_dpl_kernel(dpl, params['f_max_spec'], + params['dt'], params['tstop']) sim_data['spec'].append(spec_results) + if params['save_spec_data']: + spec_fn = get_fname(sim_dir, 'rawspec', trial_idx) + save_spec_data(spec_fn, spec_results) + paramfn = os.path.join(get_output_dir(), 'param', params['sim_prefix'] + '.param') @@ -2146,7 +2166,7 @@ def done(self, optMode, except_msg): '. Saved data/figures in: ' + sim_dir) self.setWindowTitle(self.baseparamwin.paramfn) - self.populateSimCB() # populate the combobox + self.populateSimCB() # populate the combobox cb_index = self.cbsim.findText(self.baseparamwin.paramfn) if cb_index < 0: raise ValueError("Couldn't find simulation in combobox: %s" % diff --git a/hnn/simdat.py b/hnn/simdat.py index 2d896c160..b506b33d0 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -2,7 +2,6 @@ import numpy as np from math import ceil from glob import glob -from copy import deepcopy from scipy import signal from PyQt5 import QtWidgets @@ -16,6 +15,7 @@ from hnn_core.dipole import read_dipole, average_dipoles from .spikefn import ExtInputs +from .specfn import plot_spec from .paramrw import get_output_dir, get_fname, get_inputs from .paramrw import countEvokedInputs, read_gids_param from .qt_lib import getscreengeom @@ -275,7 +275,7 @@ def update_sim_data_from_disk(self, paramfn, params): Returns ---------- found: bool - Whether simulation data directory exists or not + Whether simulation data could be read """ sim_dir = os.path.join(self._data_dir, params['sim_prefix']) @@ -299,6 +299,7 @@ def update_sim_data_from_disk(self, paramfn, params): gid_ranges = read_gids_param(paramtxt_fn) except FileNotFoundError: print(warning_message, paramtxt_fn) + return False # spikes spikes = read_spktrials(sim_dir, gid_ranges) @@ -434,32 +435,6 @@ def _read_dpl(self, paramfn, trial_idx, ntrial): return dpltrial - def _plot_spec(self, ax, paramfn, ntrial, spec_cmap, xlim, fontsize): - """Use SimData to plot spectrogram""" - - # calculate TFR from spec trial data - # start with data from the first trial - spec_TFR = deepcopy(self._sim_data[paramfn]['data']['spec'][0]) - for key in ['TFR', 'TFR_L5', 'TFR_L2']: - spec_list = [self._sim_data[paramfn]['data']['spec'][i][key] - for i in range(ntrial)] - spec_TFR[key] = np.mean(np.array(spec_list), axis=0) - - # Plot TFR data and add colorbar - plot = ax.imshow(spec_TFR['TFR'], - extent=(spec_TFR['time'][0], - spec_TFR['time'][-1], - spec_TFR['freq'][-1], - spec_TFR['freq'][0]), - aspect='auto', origin='upper', - cmap=plt.get_cmap(spec_cmap)) - ax.set_ylabel('Frequency (Hz)', fontsize=fontsize) - ax.set_xlabel('Time (ms)', fontsize=fontsize) - ax.set_xlim(xlim) - ax.set_ylim(spec_TFR['freq'][-1], spec_TFR['freq'][0]) - - return plot - def save_spec_with_hist(self, paramfn, params): sim_dir = os.path.join(self._data_dir, params['sim_prefix']) ntrial = params['N_trials'] @@ -503,8 +478,9 @@ def save_spec_with_hist(self, paramfn, params): axspec = f.add_subplot(gs0[:, :]) axdipole = f.add_subplot(gs1[:, :]) - cax = self._plot_spec(axspec, paramfn, ntrial, - params['spec_cmap'], xlim, fontsize) + spec_data = self._sim_data[paramfn]['data']['spec'] + cax = plot_spec(axspec, spec_data, ntrial, + params['spec_cmap'], xlim, fontsize) f.colorbar(cax, ax=axspec) # set xlim based on TFR plot @@ -742,9 +718,10 @@ def getnextcolor(self): def _has_simdata(self): """check if any simulation data available""" - avg_dpl = self.sim_data._sim_data[self.paramfn]['data']['avg_dpl'] - if avg_dpl is not None: - return True + if self.paramfn in self.sim_data._sim_data: + avg_dpl = self.sim_data._sim_data[self.paramfn]['data']['avg_dpl'] + if avg_dpl is not None: + return True return False @@ -892,34 +869,36 @@ def plotsimdat(self): self.gRow = len(axes) only_create_axes = True - if not only_create_axes: - tstop = self.params['tstop'] - xlim = (0.0, tstop) - - sim_data = self.sim_data._sim_data[self.paramfn]['data'] - trials = [trial_idx for trial_idx in range(ntrial)] - extinputs = ExtInputs(sim_data['spikes'], sim_data['gid_ranges'], - trials, self.params) + if not only_create_axes: + tstop = self.params['tstop'] + xlim = (0.0, tstop) - feeds_to_plot = check_feeds_to_plot(extinputs.inputs, - self.params) - axes = self.plotinputhist(extinputs, feeds_to_plot) - self.gRow = len(axes) + sim_data = self.sim_data._sim_data[self.paramfn]['data'] + trials = [trial_idx for trial_idx in range(ntrial)] + extinputs = ExtInputs(sim_data['spikes'], + sim_data['gid_ranges'], + trials, self.params) - # check that dipole data is present - single_sim = self.sim_data._sim_data[self.paramfn]['data'] - if single_sim['avg_dpl'] is None: - failed_loading_dpl = True - - if single_sim['spec'] is None or len(single_sim['spec']) == 0: - failed_loading_spec = True - # whether to draw the specgram - should draw if user saved it or - # have ongoing, poisson, or tonic inputs - if (not failed_loading_spec) and single_sim['spec'] is not None \ - and (self.params['save_spec_data'] or - feeds_to_plot['ongoing'] or - feeds_to_plot['pois'] or feeds_to_plot['tonic']): - DrawSpec = True + feeds_to_plot = check_feeds_to_plot(extinputs.inputs, + self.params) + axes = self.plotinputhist(extinputs, feeds_to_plot) + self.gRow = len(axes) + + # check that dipole data is present + single_sim = self.sim_data._sim_data[self.paramfn]['data'] + if single_sim['avg_dpl'] is None: + failed_loading_dpl = True + + if single_sim['spec'] is None or len(single_sim['spec']) == 0: + failed_loading_spec = True + # whether to draw the specgram - should draw if user saved + # it or have ongoing, poisson, or tonic inputs + if (not failed_loading_spec) and single_sim['spec'] is \ + not None \ + and (self.params['save_spec_data'] or + feeds_to_plot['ongoing'] or + feeds_to_plot['pois'] or feeds_to_plot['tonic']): + DrawSpec = True if DrawSpec: # dipole axis takes fewer rows if also drawing specgram self.axdipole = self.figure.add_subplot(self.G[self.gRow:5, 0]) @@ -939,7 +918,7 @@ def plotsimdat(self): self.figure.subplots_adjust(left=left, right=0.99, bottom=bottom, top=0.99, hspace=0.1, wspace=0.1) - if failed_loading_dpl or only_create_axes: + if failed_loading_dpl or only_create_axes or self.params is None: return # get spectrogram if it exists, then adjust axis limits but only @@ -1038,9 +1017,10 @@ def plotsimdat(self): if DrawSpec: gRow = 6 self.axspec = self.figure.add_subplot(self.G[gRow:10, 0]) - cax = self.sim_data._plot_spec(self.axspec, self.paramfn, ntrial, - self.params['spec_cmap'], xlim, - fontsize) + spec_data = sim_data['spec'] + cax = plot_spec(self.axspec, spec_data, ntrial, + self.params['spec_cmap'], xlim, + fontsize) cbaxes = self.figure.add_axes([0.6, 0.49, 0.3, 0.005]) # plot colorbar horizontally to save space plt.colorbar(cax, cax=cbaxes, orientation='horizontal') diff --git a/hnn/specfn.py b/hnn/specfn.py index cadef9a13..3ec004e37 100644 --- a/hnn/specfn.py +++ b/hnn/specfn.py @@ -9,11 +9,15 @@ import numpy as np import scipy.signal as sps +from copy import deepcopy +import matplotlib.pyplot as plt + +fontsize = plt.rcParams['font.size'] = 10 # MorletSpec class based on a time vec tvec and a time series vec tsvec class MorletSpec(): - def __init__(self, tvec, tsvec, f_max=None, p_dict=None, tmin=50.0, + def __init__(self, tvec, tsvec, f_max, dt, tstop, tmin=50.0, f_min=1.): # Save variable portion of fdata_spec as identifying attribute # self.name = fdata_spec @@ -23,27 +27,24 @@ def __init__(self, tvec, tsvec, f_max=None, p_dict=None, tmin=50.0, self.tsvec = tsvec self.f_min = f_min - - self.params = p_dict + self.tstop = tstop + self.dt = dt # maximum frequency of analysis # Add 1 to ensure analysis is inclusive of maximum frequency - if not f_max: - self.f_max = self.params['f_max_spec'] + 1 - else: - self.f_max = f_max + 1 + self.f_max = f_max + 1 # cutoff time in ms self.tmin = tmin # truncate these vectors appropriately based on tmin - if self.params['tstop'] > self.tmin: + if self.tstop > self.tmin: # must be done in this order! timeseries first! self.tsvec = self.tsvec[self.tvec >= self.tmin] self.tvec = self.tvec[self.tvec >= self.tmin] # Check that tstop is greater than tmin - if self.params['tstop'] > self.tmin: + if self.tstop > self.tmin: # Array of frequencies over which to sort self.f = np.arange(self.f_min, self.f_max) @@ -51,7 +52,7 @@ def __init__(self, tvec, tsvec, f_max=None, p_dict=None, tmin=50.0, self.width = 7. # Calculate sampling frequency - self.fs = 1000. / self.params['dt'] + self.fs = 1000. / self.dt # Generate Spec data self.TFR = self.__traces2TFR() @@ -68,7 +69,7 @@ def __traces2TFR(self): # shift tvec to reflect change # this is in ms self.t = 1000. * np.arange(1, len(self.S_trans) + 1) / self.fs + \ - self.tmin - self.params['dt'] + self.tmin - self.dt # preallocation B = np.zeros((len(self.f), len(self.S_trans))) @@ -171,27 +172,22 @@ def __init__(self, t_vec, ts_vec, dt): return_onesided=True, scaling='spectrum') -# Kernel for spec analysis of dipole data -# necessary for parallelization -def spec_dpl_kernel(params, dpl, fspec, opts): - +def spec_dpl_kernel(dpl, f_max, dt, tstop): # Do the conversion prior to generating these spec # dpl.convert_fAm_to_nAm() + print("Extracting spectrogram from dipole") # Generate various spec results - spec_agg = MorletSpec(dpl.times, dpl.data['agg'], opts['f_max'], - p_dict=params) - spec_L2 = MorletSpec(dpl.times, dpl.data['L2'], opts['f_max'], - p_dict=params) - spec_L5 = MorletSpec(dpl.times, dpl.data['L5'], opts['f_max'], - p_dict=params) + spec_agg = MorletSpec(dpl.times, dpl.data['agg'], f_max, dt, tstop) + spec_L2 = MorletSpec(dpl.times, dpl.data['L2'], f_max, dt, tstop) + spec_L5 = MorletSpec(dpl.times, dpl.data['L5'], f_max, dt, tstop) # Get max spectral power data # BC (11/29/2020): no longer calculating this max_agg = [] # Generate periodogram resutls - pgram = Welch(dpl.times, dpl.data['agg'], params['dt']) + pgram = Welch(dpl.times, dpl.data['agg'], dt) spec_results = {'time': spec_agg.t, 'freq': spec_agg.f, 'TFR': spec_agg.TFR, 'max_agg': max_agg, @@ -199,24 +195,70 @@ def spec_dpl_kernel(params, dpl, fspec, opts): 'TFR_L2': spec_L2.TFR, 't_L5': spec_L5.t, 'f_L5': spec_L5.f, 'TFR_L5': spec_L5.TFR, 'pgram_p': pgram.P, 'pgram_f': pgram.f} - # Save spec results - np.savez_compressed(fspec, time=spec_agg.t, freq=spec_agg.f, - TFR=spec_agg.TFR, max_agg=max_agg, - t_L2=spec_L2.t, f_L2=spec_L2.f, TFR_L2=spec_L2.TFR, - t_L5=spec_L5.t, f_L5=spec_L5.f, TFR_L5=spec_L5.TFR, - pgram_p=pgram.P, pgram_f=pgram.f) return spec_results -def analysis_simp(opts, params, fdpl, fspec): - opts_run = {'type': 'dpl_laminar', - 'f_max': 100., - 'save_data': 0, - 'runtype': 'parallel', - } - for key, val in opts.items(): - if key in opts_run.keys(): - opts_run[key] = val - - return spec_dpl_kernel(params, fdpl, fspec, opts_run) +def save_spec_data(fspec, spec): + # Save spec results + print("Saving %s" % fspec) + np.savez_compressed(fspec, time=spec['time'], freq=spec['freq'], + TFR=spec['TFR'], max_agg=spec['max_agg'], + t_L2=spec['t_L2'], f_L2=spec['f_L2'], + TFR_L2=spec['TFR_L2'], t_L5=spec['t_L5'], + f_L5=spec['f_L5'], TFR_L5=spec['TFR_L5'], + pgram_p=spec['pgram_p'], pgram_f=spec['pgram_f']) + + +def plot_spec(ax, spec_data, ntrial, spec_cmap, xlim, fontsize=fontsize): + """Plot spectrogram""" + + # calculate TFR from spec trial data + # start with data from the first trial, but make deepcopy + # we will be modifying it in place + spec_TFR = deepcopy(spec_data[0]) + spec_list = [spec_data[i]['TFR'] for i in range(ntrial)] + spec_TFR['TFR'] = np.mean(np.array(spec_list), axis=0) + + # Plot TFR data and add colorbar + plot = ax.imshow(spec_TFR['TFR'], + extent=(spec_TFR['time'][0], + spec_TFR['time'][-1], + spec_TFR['freq'][-1], + spec_TFR['freq'][0]), + aspect='auto', origin='upper', + cmap=plt.get_cmap(spec_cmap)) + ax.set_ylabel('Frequency (Hz)', fontsize=fontsize) + ax.set_xlabel('Time (ms)', fontsize=fontsize) + ax.set_xlim(xlim) + ax.set_ylim(spec_TFR['freq'][-1], spec_TFR['freq'][0]) + + return plot + + +def extract_spec(dpls, f_max_spec): + """Extract Mortlet spectrograms from dipoles + + Parameters + ---------- + dpls: list of Dipole objects + List containing Dipoles of each trial + f_max_spec: float + Maximum frequency of analysis + + Returns + ---------- + specs: list of MortletSpec objects + List containing spectrograms of each trial + + """ + + specs = [] + for dpltrial in dpls: + dt = dpltrial.times[1] - dpltrial.times[0] + tstop = dpltrial.times[-1] + + spec_results = spec_dpl_kernel(dpltrial, f_max_spec, dt, tstop) + specs.append(spec_results) + + return specs diff --git a/hnn/visspec.py b/hnn/visspec.py index 8dcadad23..5815adf89 100644 --- a/hnn/visspec.py +++ b/hnn/visspec.py @@ -4,56 +4,21 @@ # Blake Caldwell import numpy as np +import os -from PyQt5.QtWidgets import QSizePolicy, QAction +from PyQt5.QtWidgets import QSizePolicy, QAction, QFileDialog from PyQt5.QtGui import QIcon import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure +from hnn_core.dipole import average_dipoles, Dipole from .DataViewGUI import DataViewGUI -from .specfn import MorletSpec +from .specfn import plot_spec, extract_spec fontsize = plt.rcParams['font.size'] = 10 - - -def extract_spec(params, dpls, avg_dpl): - """Extract Mortlet spectrograms from dipoles - - Parameters - ---------- - params : dict - Dictionary containing parameters - dpls: list of Dipole objects - List containing Dipoles of each trial - avg_dpl: Dipole object - Dipole object containing the average of individual trial Dipoles - - Returns - ---------- - specs: list of MortletSpec objects - List containing spectrograms of each trial - avg_spec: MortletSpec objects - spectrogram averaged over all trials - - """ - - specs = [] - for dpltrial in dpls: - ms = MorletSpec(dpltrial.times, dpltrial.data['agg'], None, - p_dict=params) - specs.append(ms) - - # !!should fix to average of individual spectrograms!! - avg_spec = MorletSpec(avg_dpl.times, avg_dpl.data['agg'], None, - p_dict=params) - - ltfr = [ms.TFR for ms in specs] - npspec = np.array(ltfr) - avg_spec.TFR = np.mean(npspec, axis=0) - - return specs, avg_spec +random_label = np.random.rand(100) class SpecCanvas(FigureCanvasQTAgg): @@ -79,7 +44,6 @@ def __init__(self, params, sim_data, index, parent=None, width=12, self.dpls = self.gui.dpls self.specs = self.gui.specs self.avg_dpl = self.gui.avg_dpl - self.avg_spec = self.gui.avg_spec self.lax = [] if 'spec_cmap' in self.params: @@ -95,54 +59,51 @@ def clearaxes(self): ax.set_yticks([]) ax.cla() - def clearlextdatobj(self): - if hasattr(self, 'lextdatobj'): - for o in self.lextdatobj: - o.set_visible(False) - del self.lextdatobj - - def drawspec(self, dpls, lspec, avgdipole, avgspec, fig, G, + def drawspec(self, dpls, avgdipole, spec_data, fig, G, ltextra=''): - if len(lspec) == 0: + global random_label + + ntrial = len(spec_data) + if ntrial == 0: return + if self.index == 0: + ntrial = 1 + plt.ion() gdx = 211 - ax = fig.add_subplot(gdx) + ax = fig.add_subplot(gdx, label=random_label) + random_label += 1 lax = [ax] - tvec = avgdipole.times + + # use spectogram limits (missing first 50 ms b/c edge effects) + xlim = (spec_data[0]['time'][0], + spec_data[0]['time'][-1]) if self.index == 0: for dpltrial in dpls: - ax.plot(tvec, dpltrial.data['agg'], + ax.plot(dpltrial.times, dpltrial.data['agg'], linewidth=self.gui.linewidth, color='gray') - ax.plot(tvec, avgdipole.data['agg'], + ax.plot(avgdipole.times, avgdipole.data['agg'], linewidth=self.gui.linewidth + 1, color='black') else: - ax.plot(tvec, dpls[self.index-1].data['agg'], - linewidth=self.gui.linewidth + 1, color='gray') + ax.plot(dpls[self.index - 1].times, + dpls[self.index - 1].data['agg'], + linewidth=self.gui.linewidth + 1, + color='gray') - ax.set_xlim(tvec[0], tvec[-1]) + ax.set_xlim(xlim) ax.set_ylabel('Dipole (nAm)') gdx = 212 - ax = fig.add_subplot(gdx) + ax = fig.add_subplot(gdx, label=random_label) + random_label += 1 + ntrial = len(dpls) - if self.index == 0: - ms = avgspec - else: - ms = lspec[self.index - 1] - - ax.imshow(ms.TFR, extent=[tvec[0], tvec[-1], ms.f[-1], ms.f[0]], - aspect='auto', origin='upper', - cmap=plt.get_cmap(self.spec_cmap)) - - ax.set_xlim(tvec[0], tvec[-1]) - ax.set_xlabel('Time (ms)') - ax.set_ylabel('Frequency (Hz)') + plot_spec(ax, spec_data, ntrial, self.spec_cmap, xlim) lax.append(ax) @@ -152,31 +113,36 @@ def plot(self): ltextra = 'Trial ' + str(self.index) if self.index == 0: ltextra = 'All Trials' - self.lax = self.drawspec(self.dpls, self.specs, - self.avg_dpl, self.avg_spec, self.figure, - self.G, ltextra=ltextra) + self.lax = self.drawspec(self.dpls, self.avg_dpl, self.specs, + self.figure, self.G, ltextra=ltextra) self.figure.subplots_adjust(bottom=0.06, left=0.06, right=0.98, top=0.97, wspace=0.1, hspace=0.09) self.draw() class SpecViewGUI(DataViewGUI): + """Class for displaying spectrogram viewer + + Required parameters in params dict: f_max_spec, sim_prefix, spec_cmap + """ def __init__(self, CanvasType, params, sim_data, title): - self.specs = [] # external data spec + self.specs = [] + self.lextfiles = [] # external data files self.dpls = None self.avg_dpl = [] - self.avg_spec = [] self.params = params + + # used by loadSimData + self.sim_data = sim_data super(SpecViewGUI, self).__init__(CanvasType, self.params, sim_data, title) - self.addLoadDataActions() - self.loadDisplayData() + self._addLoadDataActions() + self.loadSimData(self.params['sim_prefix'], self.params['f_max_spec']) - def addLoadDataActions(self): + def _addLoadDataActions(self): loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data.', self) loadDataFile.setShortcut('Ctrl+D') - loadDataFile.setStatusTip('Load experimental (.txt) / ' + - 'simulation (.param) data.') + loadDataFile.setStatusTip('Load experimental (.txt) data.') loadDataFile.triggered.connect(self.loadDisplayData) clearDataFileAct = QAction(QIcon.fromTheme('close'), 'Clear data.', @@ -188,23 +154,29 @@ def addLoadDataActions(self): self.fileMenu.addAction(loadDataFile) self.fileMenu.addAction(clearDataFileAct) - def loadDisplayData(self): - # store copy of data in this object, that can be reused by canvas (self.m) - # on re-instantiation - self.avg_dpl = self.sim_data['avg_dpl'] - self.dpls = self.sim_data['dpls'] - self.specs, self.avg_spec = extract_spec(self.params, self.dpls, - self.avg_dpl) - - # populate the data inside canvas object before calling self.m.plot() - self.m.avg_dpl = self.avg_dpl - self.m.dpls = self.dpls - self.m.specs = self.specs - self.m.avg_spec = self.avg_spec + def loadSimData(self, sim_prefix, f_max_spec): + """Load and plot from SimData""" + + # store copy of data in this object, that can be reused by + # canvas (self.m) on re-instantiation + if self.sim_data is not None: + self.avg_dpl = self.sim_data['avg_dpl'] + self.dpls = self.sim_data['dpls'] + self.specs = self.sim_data['spec'] + if self.specs is None or len(self.specs) == 0: + self.specs = extract_spec(self.dpls, f_max_spec) + + # populate the data inside canvas object before calling + # self.m.plot() + self.m.avg_dpl = self.avg_dpl + self.m.dpls = self.dpls + self.m.specs = self.specs + + self.ntrial = len(self.specs) self.updateCB() self.printStat('Extracted ' + str(len(self.m.specs)) + - ' spectrograms for ' + self.params['sim_prefix']) + ' spectrograms for ' + sim_prefix) if len(self.m.specs) > 0: self.printStat('Plotting Spectrograms.') @@ -212,7 +184,67 @@ def loadDisplayData(self): self.m.draw() # make sure new lines show up in plot self.printStat('') + def loadDisplayData(self): + """Load dipole(s) from .txt file and plot spectrograms""" + fname = QFileDialog.getOpenFileName(self, 'Open .txt file', 'data') + fname = os.path.abspath(fname[0]) + + if not os.path.isfile(fname): + return + + self.m.index = 0 + file_data = np.loadtxt(fname, dtype=float) + if file_data.shape[1] > 2: + # Multiple trials contained in this file. Only 'agg' dipole is + # present for each trial + dpls = [] + ntrials = file_data.shape[1] + for trial in range(1, ntrials): + dpl_data = np.c_[file_data[:, trial], + np.zeros(len(file_data[:, trial])), + np.zeros(len(file_data[:, trial]))] + dpl = Dipole(file_data[:, 0], dpl_data) + dpls.append(dpl) + self.dpls = dpls + self.avg_dpl = average_dipoles(dpls) + else: + # Normal dipole file saved by HNN. There is a single trial with + # column 0: times, column 1: 'agg' dipole, column 2: 'L2' dipole + # and column 3: 'L5' dipole + + ntrials = 1 + dpl_data = np.c_[file_data[:, 1], + file_data[:, 1], + file_data[:, 1]] + dpl = Dipole(file_data[:, 0], dpl_data) + + self.avg_dpl = dpl + self.dpls = [self.avg_dpl] + + print('Loaded data from %s: %d trials.' % (fname, ntrials)) + print('Extracting Spectrograms...') + # a progress bar would be helpful right here! + self.specs = extract_spec(self.dpls, self.params['f_max_spec']) + + # updateCB depends on ntrial being set + self.ntrial = len(self.specs) + self.updateCB() + self.printStat('Extracted ' + str(len(self.specs)) + + ' spectrograms from ' + fname) + self.lextfiles.append(fname) + + if len(self.specs) > 0: + self.printStat('Plotting Spectrograms.') + self.m.specs = self.specs + self.m.dpls = self.dpls + self.m.avg_dpl = self.avg_dpl + self.m.plot() + self.m.draw() # make sure new lines show up in plot + self.printStat('') + def clearDataFile(self): - self.m.clearlextdatobj() + """Clear data from file and revert to SimData""" self.specs = [] - self.m.draw() + self.lextfiles = [] + self.m.index = 0 + self.loadSimData(self.params['sim_prefix'], self.params['f_max_spec']) From fae9af1a980b2c908e999d8005b5618a7bc04923 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 16 Mar 2021 09:52:35 -0400 Subject: [PATCH 071/107] MAINT: fix PSD viewer --- hnn/vispsd.py | 554 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 331 insertions(+), 223 deletions(-) diff --git a/hnn/vispsd.py b/hnn/vispsd.py index 5a12fd7d6..2842453f3 100644 --- a/hnn/vispsd.py +++ b/hnn/vispsd.py @@ -1,235 +1,343 @@ -import sys, os -from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout -from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QDialog, QGridLayout, QLineEdit, QLabel -from PyQt5.QtWidgets import QCheckBox -from PyQt5.QtGui import QIcon, QFont, QPixmap -from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, pyqtSlot -from PyQt5 import QtCore +import os import numpy as np +from math import sqrt +from copy import deepcopy + +from PyQt5.QtWidgets import QAction, QSizePolicy, QFileDialog +from PyQt5.QtGui import QIcon import matplotlib.pyplot as plt import matplotlib.patches as mpatches -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure -import pylab as plt import matplotlib.gridspec as gridspec -from DataViewGUI import DataViewGUI -from math import sqrt -from specfn import MorletSpec -from paramrw import get_output_dir +from hnn_core.dipole import average_dipoles, Dipole -from hnn_core import read_params +from .DataViewGUI import DataViewGUI +from .specfn import spec_dpl_kernel, extract_spec fontsize = plt.rcParams['font.size'] = 10 +random_label = np.random.rand(100) -ddat = {} - -# assumes column 0 is time, rest of columns are time-series -def extractpsd (dat, fmax=120.0): - print('extractpsd',dat.shape) - lpsd = [] - tvec = dat[:,0] - dt = tvec[1] - tvec[0] - tstop = tvec[-1] - prm = {'f_max_spec':fmax,'dt':dt,'tstop':tstop} - for col in range(1,dat.shape[1],1): - ms = MorletSpec(tvec,dat[:,col],None,p_dict=prm) - lpsd.append(np.mean(ms.TFR,axis=1)) - return ms.f, np.array(lpsd) - -class PSDCanvas (FigureCanvas): - def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, title='PSD Viewer'): - FigureCanvas.__init__(self, Figure(figsize=(width, height), dpi=dpi)) - self.title = title - self.setParent(parent) - self.gui = parent - self.index = index - FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding) - FigureCanvas.updateGeometry(self) - self.paramf = paramf - self.invertedhistax = False - self.G = gridspec.GridSpec(10,1) - self.plot() - - def drawpsd (self, dspec, fig, G, ltextra=''): - - lax = [] - lkF = ['f_L2', 'f_L5', 'f_L2'] - lkS = ['TFR_L2', 'TFR_L5', 'TFR'] +def extract_psd(dpl, f_max_spec): + """Extract PSDs for layers using Morlet method - plt.ion() + Parameters + ---------- + dpls: Dipole object + Dipole for a single trial + f_max_spec: float + Maximum frequency of analysis - gdx = 311 - - ltitle = ['Layer 2/3', 'Layer 5', 'Aggregate'] - - yl = [1e9,-1e9] - - for i in [0,1,2]: - ddat['avg'+str(i)] = avg = np.mean(dspec[lkS[i]],axis=1) - ddat['std'+str(i)] = std = np.std(dspec[lkS[i]],axis=1) / sqrt(dspec[lkS[i]].shape[1]) - yl[0] = min(yl[0],np.amin(avg-std)) - yl[1] = max(yl[1],np.amax(avg+std)) - - yl = tuple(yl) - xl = (dspec['f_L2'][0],dspec['f_L2'][-1]) - - for i,title in zip([0, 1, 2],ltitle): - ax = fig.add_subplot(gdx) - lax.append(ax) - - if i == 2: ax.set_xlabel('Frequency (Hz)') - - ax.plot(dspec[lkF[i]],np.mean(dspec[lkS[i]],axis=1),color='w',linewidth=self.gui.linewidth+2) - avg = ddat['avg'+str(i)] - std = ddat['std'+str(i)] - ax.plot(dspec[lkF[i]],avg-std,color='gray',linewidth=self.gui.linewidth) - ax.plot(dspec[lkF[i]],avg+std,color='gray',linewidth=self.gui.linewidth) - - ax.set_ylim(yl) - ax.set_xlim(xl) - - ax.set_facecolor('k') - ax.grid(True) - ax.set_title(title) - ax.set_ylabel(r'$nAm^2$') - - gdx += 1 - return lax - - - def clearaxes(self): - for ax in self.lax: - ax.set_yticks([]) - ax.cla() - - - def clearlextdatobj (self): - if hasattr(self,'lextdatobj'): - for o in self.lextdatobj: - # try: - o.set_visible(False) - # except: - # o[0].set_visible(False) - del self.lextdatobj - - def plotextdat (self, lF, lextpsd, lextfiles): # plot 'external' data (e.g. from experiment/other simulation) - - print('len(lax)',len(self.lax)) - - self.lextdatobj = [] - white_patch = mpatches.Patch(color='white', label='Simulation') - self.lpatch = [white_patch] - - ax = self.lax[2] # plot on agg - - yl = ax.get_ylim() - - cmap=plt.get_cmap('nipy_spectral') - csm = plt.cm.ScalarMappable(cmap=cmap) - csm.set_clim((0,100)) - - for f,lpsd,fname in zip(lF,lextpsd,lextfiles): - print(fname,len(f),lpsd.shape) - clr = csm.to_rgba(int(np.random.RandomState().uniform(5,101,1))) - avg = np.mean(lpsd,axis=0) - std = np.std(lpsd,axis=0) / sqrt(lpsd.shape[1]) - self.lextdatobj.append(ax.plot(f,avg,color=clr,linewidth=self.gui.linewidth+2)) - self.lextdatobj.append(ax.plot(f,avg-std,'--',color=clr,linewidth=self.gui.linewidth)) - self.lextdatobj.append(ax.plot(f,avg+std,'--',color=clr,linewidth=self.gui.linewidth)) - yl = ((min(yl[0],min(avg))),(max(yl[1],max(avg)))) - new_patch = mpatches.Patch(color=clr, label=fname.split(os.path.sep)[-1].split('.txt')[0]) - self.lpatch.append(new_patch) - - ax.set_ylim(yl) - self.lextdatobj.append(ax.legend(handles=self.lpatch)) - - def plot (self): - if self.index == 0: - self.lax = self.drawpsd(ddat['spec'],self.figure, self.G, ltextra='All Trials') - else: - if 'spec'+str(self.index) not in ddat: - ddat['spec'+str(self.index)] = np.load(specpath) - self.lax=self.drawpsd(ddat['spec'+str(self.index)],self.figure, self.G, ltextra='Trial '+str(self.index)) - - self.figure.subplots_adjust(bottom=0.06, left=0.06, right=0.98, top=0.97, wspace=0.1, hspace=0.09) - - self.draw() - -class PSDViewGUI (DataViewGUI): - def __init__ (self,CanvasType,params,title): - super(PSDViewGUI,self).__init__(CanvasType,params,title) - self.addLoadDataActions() - self.lF = [] # frequencies associated with external data psd - self.lextpsd = [] # external data psd - self.lextfiles = [] # external data files - - if "TRAVIS_TESTING" in os.environ and os.environ["TRAVIS_TESTING"] == "1": - print("Exiting gracefully with TRAVIS_TESTING=1") - qApp.quit() - exit(0) - - def addLoadDataActions (self): - loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data file.', self) - loadDataFile.setShortcut('Ctrl+D') - loadDataFile.setStatusTip('Load data file.') - loadDataFile.triggered.connect(self.loadDisplayData) - - clearDataFileAct = QAction(QIcon.fromTheme('close'), 'Clear data file.', self) - clearDataFileAct.setShortcut('Ctrl+C') - clearDataFileAct.setStatusTip('Clear data file.') - clearDataFileAct.triggered.connect(self.clearDataFile) - - self.fileMenu.addAction(loadDataFile) - self.fileMenu.addAction(clearDataFileAct) - - def loadDisplayData (self): - extdataf,dat = self.loadDataFileDialog() - if not extdataf: return - f, lpsd = extractpsd(dat) - self.printStat('Extracted PSDs from ' + extdataf) - self.lextpsd.append(lpsd) - self.lextfiles.append(extdataf) - self.lF.append(f) - - if len(self.lextpsd) > 0: - self.printStat('Plotting ext data PSDs.') - self.m.plotextdat(self.lF,self.lextpsd,self.lextfiles) - self.m.draw() # make sure new lines show up in plot - self.printStat('') - - def loadDataFileDialog (self): - fn = QFileDialog.getOpenFileName(self, 'Open file', 'data') - if fn[0]: - extdataf = os.path.abspath(fn[0]) # data file - dat = np.loadtxt(extdataf) - self.printStat('Loaded data in ' + extdataf + '. Extracting PSDs.') - return extdataf,dat - - return None,None - - def clearDataFile (self): - self.m.clearlextdatobj() - self.lextpsd = [] - self.lextfiles = [] - self.lF = [] - self.m.draw() - - -if __name__ == '__main__': - for i in range(len(sys.argv)): - if sys.argv[i].endswith('.param'): - paramf = sys.argv[i] - params = read_params(paramf) - ntrial = params['N_trials'] - - basedir = os.path.join(get_output_dir(), 'data', params['sim_prefix']) - specpath = os.path.join(basedir,'rawspec.npz') - print('specpath',specpath) - ddat['spec'] = np.load(specpath) - - app = QApplication(sys.argv) - ex = PSDViewGUI(PSDCanvas,params,'PSD Viewer') - sys.exit(app.exec_()) + Returns + ---------- + F: array + Frequencies associated with Morlet spectral analysis + psds: list of MortletSpec objects + List containing results of spectral analysis for each layer + + """ + + psds = [] + dt = dpl.times[1] - dpl.times[0] + tstop = dpl.times[-1] + + spec_results = spec_dpl_kernel(dpl, f_max_spec, dt, tstop) + + for col in ['TFR', 'TFR_L2', 'TFR_L5']: + psds.append(np.mean(spec_results[col], axis=1)) + + return spec_results['freq'], np.array(psds) + + +class PSDCanvas(FigureCanvasQTAgg): + def __init__(self, params, sim_data, index, parent=None, width=12, + height=10, dpi=120, title='PSD Viewer'): + FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), + dpi=dpi)) + self.title = title + self.setParent(parent) + self.gui = parent + self.index = index + FigureCanvasQTAgg.setSizePolicy(self, QSizePolicy.Expanding, + QSizePolicy.Expanding) + FigureCanvasQTAgg.updateGeometry(self) + self.params = params + self.invertedhistax = False + self.G = gridspec.GridSpec(10, 1) + self.dpls = self.gui.dpls + self.specs = self.gui.specs + self.avg_spec = self.gui.avg_spec + self.avg_dpl = self.gui.avg_dpl + self.lextdatobj = [] + + self.plot() + + def drawpsd(self, dspec, fig, G, ltextra=''): + global random_label + + lax = [] + avgs = [] + stds = [] + + lkF = ['f_L2', 'f_L5', 'f_L2'] + lkS = ['TFR_L2', 'TFR_L5', 'TFR'] + + plt.ion() + + gdx = 311 + + ltitle = ['Layer 2/3', 'Layer 5', 'Aggregate'] + + yl = [1e9, -1e9] + + for _, kS in enumerate(lkS): + avg = np.mean(dspec[kS], axis=1) + std = np.std(dspec[kS], axis=1) / sqrt(dspec[kS].shape[1]) + yl[0] = min(yl[0], np.amin(avg - std)) + yl[1] = max(yl[1], np.amax(avg + std)) + avgs.append(avg) + stds.append(std) + + yl = tuple(yl) + xl = (dspec['f_L2'][0], dspec['f_L2'][-1]) + + for i, kS in enumerate(lkS): + ax = fig.add_subplot(gdx, label=random_label) + random_label += 1 + lax.append(ax) + + if i == 2: + ax.set_xlabel('Frequency (Hz)') + + ax.plot(dspec[lkF[i]], np.mean(dspec[lkS[i]], axis=1), color='w', + linewidth=self.gui.linewidth + 2) + ax.plot(dspec[lkF[i]], avgs[i] - stds[i], color='gray', + linewidth=self.gui.linewidth) + ax.plot(dspec[lkF[i]], avgs[i] + stds[i], color='gray', + linewidth=self.gui.linewidth) + + ax.set_ylim(yl) + ax.set_xlim(xl) + + ax.set_facecolor('k') + ax.grid(True) + ax.set_title(ltitle[i]) + ax.set_ylabel(r'$nAm^2$') + + gdx += 1 + return lax + + def clearaxes(self): + for ax in self.lax: + ax.set_yticks([]) + ax.cla() + + def clearlextdatobj(self): + # clear list of external data objects + for o in self.lextdatobj: + if isinstance(o, list): + # this is the plot. clear the line + o[0].set_visible(False) + else: + # this is the legend entry + o.set_visible(False) + del self.lextdatobj + self.lextdatobj = [] # reset list of external data objects + + def plotextdat(self, lF, lextpsd, lextfiles): + """plot 'external' data (e.g. from experiment/other simulation)""" + + white_patch = mpatches.Patch(color='white', label='Simulation') + self.lpatch = [white_patch] + + ax = self.lax[2] # plot on agg + + yl = ax.get_ylim() + + cmap = plt.get_cmap('nipy_spectral') + csm = plt.cm.ScalarMappable(cmap=cmap) + csm.set_clim((0, 100)) + + for f, lpsd, fname in zip(lF, lextpsd, lextfiles): + clr = csm.to_rgba(int(np.random.RandomState().uniform(5, 101, 1))) + avg = np.mean(lpsd, axis=0) + std = np.std(lpsd, axis=0) / sqrt(lpsd.shape[1]) + self.lextdatobj.append(ax.plot(f, avg, color=clr, + linewidth=self.gui.linewidth + 2)) + self.lextdatobj.append(ax.plot(f, avg - std, '--', color=clr, + linewidth=self.gui.linewidth)) + self.lextdatobj.append(ax.plot(f, avg + std, '--', color=clr, + linewidth=self.gui.linewidth)) + yl = ((min(yl[0], min(avg))), (max(yl[1], max(avg)))) + label_str = fname.split(os.path.sep)[-1].split('.txt')[0] + new_patch = mpatches.Patch(color=clr, label=label_str) + self.lpatch.append(new_patch) + + ax.set_ylim(yl) + self.lextdatobj.append(ax.legend(handles=self.lpatch)) + + def plot(self): + if len(self.specs) == 0: + # data hasn't been loaded yet + return + + if self.index == 0: + ltextra = 'All Trials' + self.lax = self.drawpsd(self.avg_spec, self.figure, self.G, + ltextra=ltextra) + else: + ltextra = 'Trial ' + str(self.index) + self.lax = self.drawpsd(self.specs[self.index - 1], self.figure, + self.G, ltextra=ltextra) + + self.figure.subplots_adjust(bottom=0.06, left=0.06, right=0.98, + top=0.97, wspace=0.1, hspace=0.09) + + self.draw() + + +class PSDViewGUI(DataViewGUI): + """Class for displaying spectrogram viewer + + Required parameters in params dict: f_max_spec, sim_prefix + """ + def __init__(self, CanvasType, params, sim_data, title): + self.specs = [] # used by drawspec + self.psds = [] # used by plotextdat + self.lextfiles = [] # external data files + self.lF = [] # frequencies associated with external data psd + self.dpls = None + self.avg_dpl = [] + self.avg_spec = {} + self.params = params + + # used by loadSimData + self.sim_data = sim_data + super(PSDViewGUI, self).__init__(CanvasType, params, sim_data, title) + self.addLoadDataActions() + self.loadSimData() + + def addLoadDataActions(self): + loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data file.', + self) + loadDataFile.setShortcut('Ctrl+D') + loadDataFile.setStatusTip('Load experimental (.txt) data.') + loadDataFile.triggered.connect(self.loadDisplayData) + + clearDataFileAct = QAction(QIcon.fromTheme('close'), + 'Clear data.', self) + clearDataFileAct.setShortcut('Ctrl+C') + clearDataFileAct.setStatusTip('Clear data.') + clearDataFileAct.triggered.connect(self.clearDataFile) + + self.fileMenu.addAction(loadDataFile) + self.fileMenu.addAction(clearDataFileAct) + + def loadSimData(self): + """Load and plot from SimData""" + + # store copy of data in this object, that can be reused by + # canvas (self.m) on re-instantiation + if self.sim_data is not None: + self.avg_dpl = self.sim_data['avg_dpl'] + self.dpls = self.sim_data['dpls'] + self.specs = self.sim_data['spec'] + if self.specs is None or len(self.specs) == 0: + self.specs = extract_spec(self.dpls, self.params['f_max_spec']) + + # calculate TFR from spec trial data + self.avg_spec = deepcopy(self.specs[0]) + ntrials = self.params['N_trials'] + TFR_list = [self.specs[i]['TFR'] for i in range(ntrials)] + TFR_L2_list = [self.specs[i]['TFR_L2'] for i in range(ntrials)] + TFR_L5_list = [self.specs[i]['TFR_L5'] for i in range(ntrials)] + self.avg_spec['TFR'] = np.mean(np.array(TFR_list), axis=0) + self.avg_spec['TFR_L2'] = np.mean(np.array(TFR_L2_list), axis=0) + self.avg_spec['TFR_L5'] = np.mean(np.array(TFR_L5_list), axis=0) + + # populate the data inside canvas object before calling + # self.m.plot() + self.m.avg_dpl = self.avg_dpl + self.m.dpls = self.dpls + self.m.specs = self.specs + self.m.avg_spec = self.avg_spec + + if len(self.specs) > 0: + self.printStat('Plotting simulation PSDs.') + self.m.lF = self.lF + self.m.dpls = self.dpls + self.m.avg_dpl = self.avg_dpl + self.m.plot() + self.m.draw() # make sure new lines show up in plot + self.printStat('') + + def loadDisplayData(self): + """Load dipole(s) from .txt file and plot PSD""" + fname = QFileDialog.getOpenFileName(self, 'Open .txt file', 'data') + fname = os.path.abspath(fname[0]) + + if not os.path.isfile(fname): + return + + self.m.index = 0 + file_data = np.loadtxt(fname, dtype=float) + if file_data.shape[1] > 2: + # Multiple trials contained in this file. Only 'agg' dipole is + # present for each trial + dpls = [] + ntrials = file_data.shape[1] + for trial in range(1, ntrials): + dpl_data = np.c_[file_data[:, trial], + np.zeros(len(file_data[:, trial])), + np.zeros(len(file_data[:, trial]))] + dpl = Dipole(file_data[:, 0], dpl_data) + dpls.append(dpl) + self.dpls = dpls + self.avg_dpl = average_dipoles(dpls) + else: + # Normal dipole file saved by HNN. There is a single trial with + # column 0: times, column 1: 'agg' dipole, column 2: 'L2' dipole + # and column 3: 'L5' dipole + + ntrials = 1 + dpl_data = np.c_[file_data[:, 1], + file_data[:, 1], + file_data[:, 1]] + dpl = Dipole(file_data[:, 0], dpl_data) + + self.avg_dpl = dpl + self.dpls = [self.avg_dpl] + + print('Loaded data from %s: %d trials.' % (fname, ntrials)) + print('Extracting Spectrograms...') + f_max_spec = 120.0 # use 120 Hz as maximum for PSD plots + + # a progress bar would be helpful right here! + f, psd = extract_psd(self.avg_dpl, f_max_spec) + self.psds.append(psd) + self.lF.append(f) + + # updateCB depends on ntrial being set + # self.ntrial = len(self.specs) + # self.updateCB() + self.printStat('Extracted ' + str(len(self.psds)) + ' PSDs from ' + + fname) + self.lextfiles.append(fname) + + if len(self.psds) > 0: + self.printStat('Plotting ext data PSDs.') + self.m.lF = self.lF + self.m.psds = self.psds + self.m.dpls = self.dpls + self.m.avg_dpl = self.avg_dpl + self.m.plotextdat(self.lF, self.psds, self.lextfiles) + self.m.draw() # make sure new lines show up in plot + self.printStat('') + + def clearDataFile(self): + self.m.clearlextdatobj() + self.lextpsd = [] + self.lextfiles = [] + self.lF = [] + self.m.draw() From 923e1c3900c0004bacfb9defe3e2d5ed4cc0ee1d Mon Sep 17 00:00:00 2001 From: "Christopher J. Bailey" Date: Fri, 12 Mar 2021 22:07:50 +0100 Subject: [PATCH 072/107] fix postproc and add default drives to Network --- hnn/hnn_qt5.py | 10 ++++++++-- hnn/run.py | 2 +- hnn/spikefn.py | 10 +++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index a69c92845..9b329626f 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -2061,9 +2061,15 @@ def result_callback(self, result): for trial_idx in range(ntrial): N_pyr_x = params['N_pyr_x'] N_pyr_y = params['N_pyr_y'] - winsz = params['dipole_smooth_win'] / params['dt'] + sim_data['dpls'][trial_idx]._baseline_renormalize(N_pyr_x, N_pyr_y) + sim_data['dpls'][trial_idx]._convert_fAm_to_nAm() + + window_len = params['dipole_smooth_win'] # specified in ms fctr = params['dipole_scalefctr'] - sim_data['dpls'][trial_idx].post_proc(N_pyr_x, N_pyr_y, winsz, fctr) + if window_len > 0: # param files set this to zero for no smoothing + sim_data['dpls'][trial_idx].smooth(window_len=window_len) + if fctr > 0: + sim_data['dpls'][trial_idx].scale(fctr) # save average dipole from individual trials in a single file if ntrial > 1: diff --git a/hnn/run.py b/hnn/run.py index 6ce3ed071..d7ac502d1 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -103,7 +103,7 @@ def simulate(params, n_procs=None): # create the network from the parameter file. note, NEURON objects haven't # been created yet - net = Network(params) + net = Network(params, add_drives_from_params=True) sim_data = {} # run the simulation with MPIBackend for faster completion time diff --git a/hnn/spikefn.py b/hnn/spikefn.py index 62bbe22f8..84ce37d52 100644 --- a/hnn/spikefn.py +++ b/hnn/spikefn.py @@ -50,6 +50,8 @@ def __init__(self, spikes, gid_ranges, trials, params): elif 'extinput' in self.gid_ranges: # hnn legacy extinput_key = 'extinput' + elif 'bursty1' in self.gid_ranges or 'bursty2' in self.gid_ranges: + extinput_key = ['bursty1', 'bursty2'] else: print(self.gid_ranges) raise ValueError("Unable to find key for external inputs") @@ -76,7 +78,13 @@ def _get_extinput_gids(self, extinput_key): feed """ - if len(self.gid_ranges[extinput_key]) == 2: + if isinstance(extinput_key, list): + gids = list() + for val in extinput_key: + gids.extend(self.gid_ranges[val]) + # the order here is defined by create_pext to be prox, dist + return gids + elif len(self.gid_ranges[extinput_key]) == 2: return self.gid_ranges[extinput_key] elif len(self.gid_ranges[extinput_key]) > 0: # Otherwise, only one feed exists in this sim From b701efb7982b44489fe118ed84835616a4b05501 Mon Sep 17 00:00:00 2001 From: "Christopher J. Bailey" Date: Sun, 14 Mar 2021 14:25:39 +0100 Subject: [PATCH 073/107] FIX duplicate normalisation and unit conversion (DUH) --- hnn/hnn_qt5.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 9b329626f..c73b69a61 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -2059,11 +2059,6 @@ def result_callback(self, result): sim_data['dpls'] = deepcopy(sim_data['raw_dpls']) ntrial = len(sim_data['raw_dpls']) for trial_idx in range(ntrial): - N_pyr_x = params['N_pyr_x'] - N_pyr_y = params['N_pyr_y'] - sim_data['dpls'][trial_idx]._baseline_renormalize(N_pyr_x, N_pyr_y) - sim_data['dpls'][trial_idx]._convert_fAm_to_nAm() - window_len = params['dipole_smooth_win'] # specified in ms fctr = params['dipole_scalefctr'] if window_len > 0: # param files set this to zero for no smoothing From 71a701c10087b89af63a5554804d967b5fae9d87 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 16 Mar 2021 10:09:34 -0400 Subject: [PATCH 074/107] MAINT: update for hnn-core dpl plot API --- hnn/simdat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hnn/simdat.py b/hnn/simdat.py index b506b33d0..91e978212 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -490,7 +490,7 @@ def save_spec_with_hist(self, paramfn, params): dpl = self._read_dpl(paramfn, trial_idx, ntrial) if dpl is None: break - dpl.plot(axdipole, 'agg', show=False) + dpl.plot(layer='agg', ax=axdipole, show=False) axdipole.set_xlim(xlim) spec_fig_fn = get_fname(sim_dir, 'figspec', trial_idx) @@ -536,7 +536,7 @@ def save_dipole_with_hist(self, paramfn, params): dpl = self._read_dpl(paramfn, trial_idx, ntrial) if dpl is None: break - dpl.plot(axdipole, 'agg', show=False) + dpl.plot(layer='agg', ax=axdipole, show=False) axdipole.set_xlim(xlim) dipole_fig_fn = get_fname(sim_dir, 'figdpl', trial_idx) From 974e1852b02fa169b998a1f51555047db4358a9f Mon Sep 17 00:00:00 2001 From: Mainak Jas Date: Fri, 12 Mar 2021 08:19:17 -0500 Subject: [PATCH 075/107] Misc improvements --- README.md | 8 + hnn/DataViewGUI.py | 1 + hnn/dialog.py | 1162 ++++++++++++++++++++++++++++++++++++++++++++ hnn/hnn_qt5.py | 1139 +------------------------------------------ hnn/run.py | 4 +- hnn/visdipole.py | 1 + hnn/vispsd.py | 13 +- hnn/visrast.py | 4 +- hnn/visspec.py | 1 + hnn/visvolt.py | 12 +- setup.py | 4 +- 11 files changed, 1211 insertions(+), 1138 deletions(-) create mode 100644 hnn/dialog.py diff --git a/README.md b/README.md index 84368ff5c..0235f470b 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,14 @@ potentials (ERPs) and low frequency rhythms (alpha/beta/gamma). Please follow the links on our [installation page](installer) to find instructions for your operating system. +## Quickstart + +Just do: + + $ ./hnn.sh + +to start the HNN graphical user interface + ## Command-line usage HNN is not designed to be invoked from the command line, but we have started diff --git a/hnn/DataViewGUI.py b/hnn/DataViewGUI.py index 7f2e8fe45..4a2f18dc2 100644 --- a/hnn/DataViewGUI.py +++ b/hnn/DataViewGUI.py @@ -8,6 +8,7 @@ from PyQt5.QtWidgets import QMainWindow, QAction, QWidget, QComboBox from PyQt5.QtWidgets import QGridLayout, QInputDialog from PyQt5.QtGui import QIcon + from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT import matplotlib.pyplot as plt diff --git a/hnn/dialog.py b/hnn/dialog.py new file mode 100644 index 000000000..9e819aed9 --- /dev/null +++ b/hnn/dialog.py @@ -0,0 +1,1162 @@ +"""Classes for creating the dialog boxes""" + +# Authors: Sam Neymotin +# Blake Caldwell + +import os +from collections import OrderedDict +from glob import glob + +import multiprocessing +from psutil import cpu_count + +from PyQt5.QtWidgets import (QDialog, QToolTip, QTabWidget, QWidget, + QPushButton, QMessageBox, QComboBox, QLabel, + QLineEdit, QTextEdit, QFormLayout, + QVBoxLayout, QHBoxLayout, QGridLayout) +from PyQt5.QtGui import QFont, QPixmap, QIcon + +from hnn_core import CellResponse, read_params + +from .paramrw import usingOngoingInputs, usingEvokedInputs, get_output_dir +from .qt_lib import setscalegeom, setscalegeomcenter, lookupresource, ClickLabel +from .qt_evoked import EvokedInputParamDialog, OptEvokedInputParamDialog + + +def bringwintotop(win): + # bring a pyqt5 window to the top (parents still stay behind children) + # from https://www.programcreek.com/python/example/ + # 101663/PyQt5.QtCore.Qt.WindowActive + # win.show() + # win.setWindowState(win.windowState() & ~Qt.WindowMinimized | + # Qt.WindowActive) + # win.raise_() + win.showNormal() + win.activateWindow() + # win.setWindowState((win.windowState() & ~Qt.WindowMinimized) | + # Qt.WindowActive) + # win.activateWindow() + # win.raise_() + # win.show() + + +def _get_defncore(): + """get default number of cores """ + + try: + defncore = len(os.sched_getaffinity(0)) + except AttributeError: + defncore = cpu_count(logical=False) + + if defncore is None or defncore == 0: + # in case psutil is not supported (e.g. BSD) + defncore = multiprocessing.cpu_count() + + return defncore + + +class DictDialog(QDialog): + """dictionary-based dialog with tabs + + should make all dialogs specifiable via cfg file format - + then can customize gui without changing py code + and can reduce code explosion / overlap between dialogs + """ + + def __init__(self, parent, din): + super(DictDialog, self).__init__(parent) + self.ldict = [] # subclasses should override + self.ltitle = [] + # for translating model variable name to more human-readable form + self.dtransvar = {} + self.stitle = '' + self.initd() + self.initUI() + self.initExtra() + self.setfromdin(din) # set values from input dictionary + # self.addtips() + + # TODO: add back tooltips + # def addtips (self): + # for ktip in dconf.keys(): + # if ktip in self.dqline: + # self.dqline[ktip].setToolTip(dconf[ktip]) + # elif ktip in self.dqextra: + # self.dqextra[ktip].setToolTip(dconf[ktip]) + + def __str__(self): + s = '' + for k, v in self.dqline.items(): + s += k + ': ' + v.text().strip() + os.linesep + return s + + def saveparams(self): + self.hide() + + def initd(self): + pass # implemented in subclass + + def getval(self, k): + if k in self.dqline.keys(): + return self.dqline[k].text().strip() + + def lines2val(self, ksearch, val): + for k in self.dqline.keys(): + if k.count(ksearch) > 0: + self.dqline[k].setText(str(val)) + + def setfromdin(self, din): + if not din: + return + for k, v in din.items(): + if k in self.dqline: + self.dqline[k].setText(str(v).strip()) + + def transvar(self, k): + if k in self.dtransvar: + return self.dtransvar[k] + return k + + def addtransvar(self, k, strans): + self.dtransvar[k] = strans + self.dtransvar[strans] = k + + def initExtra(self): + # extra items not written to param file + self.dqextra = {} + + def initUI(self): + self.layout = QVBoxLayout(self) + + # Add stretch to separate the form layout from the button + self.layout.addStretch(1) + + # Initialize tab screen + self.ltabs = [] + self.tabs = QTabWidget() + self.layout.addWidget(self.tabs) + + for _ in range(len(self.ldict)): + self.ltabs.append(QWidget()) + + self.tabs.resize(575, 200) + + # create tabs and their layouts + for tab, s in zip(self.ltabs, self.ltitle): + self.tabs.addTab(tab, s) + tab.layout = QFormLayout() + tab.setLayout(tab.layout) + + self.dqline = {} # QLineEdits dict; key is model variable + for d, tab in zip(self.ldict, self.ltabs): + for k, v in d.items(): + self.dqline[k] = QLineEdit(self) + self.dqline[k].setText(str(v)) + # add label,QLineEdit to the tab + tab.layout.addRow(self.transvar(k), self.dqline[k]) + + # Add tabs to widget + self.layout.addWidget(self.tabs) + self.setLayout(self.layout) + self.setWindowTitle(self.stitle) + + def TurnOff(self): + pass + + def addOffButton(self): + """Create a horizontal box layout to hold the button""" + self.button_box = QHBoxLayout() + self.btnoff = QPushButton('Turn Off Inputs', self) + self.btnoff.resize(self.btnoff.sizeHint()) + self.btnoff.clicked.connect(self.TurnOff) + self.btnoff.setToolTip('Turn Off Inputs') + self.button_box.addWidget(self.btnoff) + self.layout.addLayout(self.button_box) + + def addHideButton(self): + self.bbhidebox = QHBoxLayout() + self.btnhide = QPushButton('Hide Window', self) + self.btnhide.resize(self.btnhide.sizeHint()) + self.btnhide.clicked.connect(self.hide) + self.btnhide.setToolTip('Hide Window') + self.bbhidebox.addWidget(self.btnhide) + self.layout.addLayout(self.bbhidebox) + + +class OngoingInputParamDialog (DictDialog): + """widget to specify ongoing input params (proximal, distal)""" + + def __init__(self, parent, inty, din=None): + self.inty = inty + if self.inty.startswith('Proximal'): + self.prefix = 'input_prox_A_' + self.postfix = '_prox' + self.isprox = True + else: + self.prefix = 'input_dist_A_' + self.postfix = '_dist' + self.isprox = False + super(OngoingInputParamDialog, self).__init__(parent, din) + self.addOffButton() + self.addImages() + self.addHideButton() + + def addImages(self): + """add png cartoons to tabs""" + if self.isprox: + self.pix = QPixmap(lookupresource('proxfig')) + else: + self.pix = QPixmap(lookupresource('distfig')) + for tab in self.ltabs: + pixlbl = ClickLabel(self) + pixlbl.setPixmap(self.pix) + tab.layout.addRow(pixlbl) + + def TurnOff(self): + """ turn off by setting all weights to 0.0""" + self.lines2val('weight', 0.0) + + def initd(self): + self.dtiming = OrderedDict([('t0_input' + self.postfix, 1000.), + ('t0_input_stdev' + self.postfix, 0.), + ('tstop_input' + self.postfix, 250.), + ('f_input' + self.postfix, 10.), + ('f_stdev' + self.postfix, 20.), + ('events_per_cycle' + self.postfix, 2), + ('repeats' + self.postfix, 10)]) + + self.dL2 = OrderedDict([(self.prefix + 'weight_L2Pyr_ampa', 0.), + (self.prefix + 'weight_L2Pyr_nmda', 0.), + (self.prefix + 'weight_L2Basket_ampa', 0.), + (self.prefix + 'weight_L2Basket_nmda', 0.), + (self.prefix + 'delay_L2', 0.1)]) + + self.dL5 = OrderedDict([(self.prefix + 'weight_L5Pyr_ampa', 0.), + (self.prefix + 'weight_L5Pyr_nmda', 0.)]) + + if self.isprox: + self.dL5[self.prefix + 'weight_L5Basket_ampa'] = 0.0 + self.dL5[self.prefix + 'weight_L5Basket_nmda'] = 0.0 + self.dL5[self.prefix + 'delay_L5'] = 0.1 + + self.ldict = [self.dtiming, self.dL2, self.dL5] + self.ltitle = ['Timing', 'Layer 2/3', 'Layer 5'] + self.stitle = 'Set Rhythmic ' + self.inty + ' Inputs' + + dtmp = {'L2': 'L2/3 ', 'L5': 'L5 '} + for d in [self.dL2, self.dL5]: + for k in d.keys(): + lk = k.split('_') + if k.count('weight') > 0: + self.addtransvar(k, dtmp[lk[-2][0:2]] + lk[-2][2:] + ' ' + + lk[-1].upper() + u' weight (µS)') + else: + self.addtransvar(k, 'Delay (ms)') + + self.addtransvar('t0_input' + self.postfix, 'Start time mean (ms)') + self.addtransvar('t0_input_stdev' + self.postfix, + 'Start time stdev (ms)') + self.addtransvar('tstop_input' + self.postfix, 'Stop time (ms)') + self.addtransvar('f_input' + self.postfix, 'Burst frequency (Hz)') + self.addtransvar('f_stdev' + self.postfix, 'Burst stdev (ms)') + self.addtransvar('events_per_cycle' + self.postfix, 'Spikes/burst') + self.addtransvar('repeats' + self.postfix, 'Number bursts') + + +class EvokedOrRhythmicDialog (QDialog): + def __init__(self, parent, distal, evwin, rhythwin): + super(EvokedOrRhythmicDialog, self).__init__(parent) + if distal: + self.prefix = 'Distal' + else: + self.prefix = 'Proximal' + self.evwin = evwin + self.rhythwin = rhythwin + self.initUI() + # TODO: add back tooltips + # self.addtips() + + def initUI(self): + self.layout = QVBoxLayout(self) + # Add stretch to separate the form layout from the button + self.layout.addStretch(1) + + self.btnrhythmic = QPushButton('Rhythmic ' + self.prefix + ' Inputs', + self) + self.btnrhythmic.resize(self.btnrhythmic.sizeHint()) + self.btnrhythmic.clicked.connect(self.showrhythmicwin) + self.layout.addWidget(self.btnrhythmic) + + self.btnevoked = QPushButton('Evoked Inputs', self) + self.btnevoked.resize(self.btnevoked.sizeHint()) + self.btnevoked.clicked.connect(self.showevokedwin) + self.layout.addWidget(self.btnevoked) + + self.addHideButton() + + setscalegeom(self, 150, 150, 270, 120) + self.setWindowTitle("Pick Input Type") + + def showevokedwin(self): + bringwintotop(self.evwin) + self.hide() + + def showrhythmicwin(self): + bringwintotop(self.rhythwin) + self.hide() + + def addHideButton(self): + self.bbhidebox = QHBoxLayout() + self.btnhide = QPushButton('Hide Window', self) + self.btnhide.resize(self.btnhide.sizeHint()) + self.btnhide.clicked.connect(self.hide) + self.btnhide.setToolTip('Hide Window') + self.bbhidebox.addWidget(self.btnhide) + self.layout.addLayout(self.bbhidebox) + + +class SynGainParamDialog(QDialog): + def __init__(self, parent, netparamwin): + super(SynGainParamDialog, self).__init__(parent) + self.netparamwin = netparamwin + self.initUI() + + def scalegain(self, k, fctr): + oldval = float(self.netparamwin.dqline[k].text().strip()) + newval = oldval * fctr + self.netparamwin.dqline[k].setText(str(newval)) + return newval + + def isE(self, ty): + return ty.count('Pyr') > 0 + + def isI(self, ty): + return ty.count('Basket') > 0 + + def tounity(self): + for k in self.dqle.keys(): + self.dqle[k].setText('1.0') + + def scalegains(self): + for _, k in enumerate(self.dqle.keys()): + fctr = float(self.dqle[k].text().strip()) + if fctr < 0.: + fctr = 0. + self.dqle[k].setText(str(fctr)) + elif fctr == 1.0: + continue + for k2 in self.netparamwin.dqline.keys(): + types = k2.split('_') + ty1, ty2 = types[1], types[2] + if self.isE(ty1) and self.isE(ty2) and k == 'E -> E': + self.scalegain(k2, fctr) + elif self.isE(ty1) and self.isI(ty2) and k == 'E -> I': + self.scalegain(k2, fctr) + elif self.isI(ty1) and self.isE(ty2) and k == 'I -> E': + self.scalegain(k2, fctr) + elif self.isI(ty1) and self.isI(ty2) and k == 'I -> I': + self.scalegain(k2, fctr) + + # go back to unity since pressed OK - next call to this dialog will + # reset new values + self.tounity() + self.hide() + + def initUI(self): + grid = QGridLayout() + grid.setSpacing(10) + + self.dqle = {} + for row, k in enumerate(['E -> E', 'E -> I', 'I -> E', 'I -> I']): + lbl = QLabel(self) + lbl.setText(k) + lbl.adjustSize() + grid.addWidget(lbl, row, 0) + qle = QLineEdit(self) + qle.setText('1.0') + grid.addWidget(qle, row, 1) + self.dqle[k] = qle + + row += 1 + self.btnok = QPushButton('OK', self) + self.btnok.resize(self.btnok.sizeHint()) + self.btnok.clicked.connect(self.scalegains) + grid.addWidget(self.btnok, row, 0, 1, 1) + self.btncancel = QPushButton('Cancel', self) + self.btncancel.resize(self.btncancel.sizeHint()) + self.btncancel.clicked.connect(self.hide) + grid.addWidget(self.btncancel, row, 1, 1, 1) + + self.setLayout(grid) + setscalegeom(self, 150, 150, 270, 180) + self.setWindowTitle("Synaptic Gains") + + +# widget to specify tonic inputs +class TonicInputParamDialog(DictDialog): + def __init__(self, parent, din): + super(TonicInputParamDialog, self).__init__(parent, din) + self.addOffButton() + self.addHideButton() + + # turn off by setting all weights to 0.0 + def TurnOff(self): + self.lines2val('A', 0.0) + + def initd(self): + self.dL2 = OrderedDict([ + # IClamp params for L2Pyr + ('Itonic_A_L2Pyr_soma', 0.), + ('Itonic_t0_L2Pyr_soma', 0.), + ('Itonic_T_L2Pyr_soma', -1.), + # IClamp param for L2Basket + ('Itonic_A_L2Basket', 0.), + ('Itonic_t0_L2Basket', 0.), + ('Itonic_T_L2Basket', -1.)]) + + self.dL5 = OrderedDict([ + # IClamp params for L5Pyr + ('Itonic_A_L5Pyr_soma', 0.), + ('Itonic_t0_L5Pyr_soma', 0.), + ('Itonic_T_L5Pyr_soma', -1.), + # IClamp param for L5Basket + ('Itonic_A_L5Basket', 0.), + ('Itonic_t0_L5Basket', 0.), + ('Itonic_T_L5Basket', -1.)]) + + # temporary dictionary for string translation + dtmp = {'L2': 'L2/3 ', 'L5': 'L5 '} + for d in [self.dL2, self.dL5]: + for k in d.keys(): + cty = k.split('_')[2] # cell type + tcty = dtmp[cty[0:2]] + cty[2:] # translated cell type + if k.count('A') > 0: + self.addtransvar(k, tcty + ' amplitude (nA)') + elif k.count('t0') > 0: + self.addtransvar(k, tcty + ' start time (ms)') + elif k.count('T') > 0: + self.addtransvar(k, tcty + ' stop time (ms)') + + self.ldict = [self.dL2, self.dL5] + self.ltitle = ['Layer 2/3', 'Layer 5'] + self.stitle = 'Set Tonic Inputs' + + +# widget to specify ongoing poisson inputs +class PoissonInputParamDialog(DictDialog): + def __init__(self, parent, din): + super(PoissonInputParamDialog, self).__init__(parent, din) + self.addOffButton() + self.addHideButton() + + def TurnOff(self): + """turn off by setting all weights to 0.0""" + self.lines2val('weight', 0.0) + + def initd(self): + self.dL2, self.dL5 = {}, {} + ld = [self.dL2, self.dL5] + + for i, lyr in enumerate(['L2', 'L5']): + d = ld[i] + for ty in ['Pyr', 'Basket']: + for sy in ['ampa', 'nmda']: + d[lyr + ty + '_Pois_A_weight' + '_' + sy] = 0. + d[lyr + ty + '_Pois_lamtha'] = 0. + + self.dtiming = OrderedDict([('t0_pois', 0.), + ('T_pois', -1)]) + + self.addtransvar('t0_pois', 'Start time (ms)') + self.addtransvar('T_pois', 'Stop time (ms)') + + # temporary dictionary for string translation + dtmp = {'L2': 'L2/3 ', 'L5': 'L5 '} + for d in [self.dL2, self.dL5]: + for k in d.keys(): + ks = k.split('_') + cty = ks[0] # cell type + tcty = dtmp[cty[0:2]] + cty[2:] # translated cell type + if k.count('weight'): + self.addtransvar(k, tcty + ' ' + ks[-1].upper() + + u' weight (µS)') + elif k.endswith('lamtha'): + self.addtransvar(k, tcty + ' freq (Hz)') + + self.ldict = [self.dL2, self.dL5, self.dtiming] + self.ltitle = ['Layer 2/3', 'Layer 5', 'Timing'] + self.stitle = 'Set Poisson Inputs' + + +# widget to specify run params (tstop, dt, etc.) -- not many params here +class RunParamDialog(DictDialog): + def __init__(self, parent, din=None): + super(RunParamDialog, self).__init__(parent, din) + self.addHideButton() + self.parent = parent + + def initd(self): + + self.drun = OrderedDict([('tstop', 250.), # simulation end time (ms) + ('dt', 0.025), # timestep + ('celsius', 37.0), # temperature + ('N_trials', 1), # number of trials + ('threshold', 0.0)]) # firing threshold + # cvode - not currently used by simulation + + # analysis + self.danalysis = OrderedDict([('save_figs', 0), + ('save_spec_data', 0), + ('f_max_spec', 40), + ('dipole_scalefctr', 30e3), + ('dipole_smooth_win', 15.0), + ('save_vsoma', 0)]) + + self.drand = OrderedDict([('prng_seedcore_opt', 0), + ('prng_seedcore_input_prox', 0), + ('prng_seedcore_input_dist', 0), + ('prng_seedcore_extpois', 0), + ('prng_seedcore_extgauss', 0), + ('prng_seedcore_evprox_1', 0), + ('prng_seedcore_evdist_1', 0), + ('prng_seedcore_evprox_2', 0), + ('prng_seedcore_evdist_2', 0)]) + + self.ldict = [self.drun, self.danalysis, self.drand] + self.ltitle = ['Run', 'Analysis', 'Randomization Seeds'] + self.stitle = 'Run Parameters' + + self.addtransvar('tstop', 'Duration (ms)') + self.addtransvar('dt', 'Integration Timestep (ms)') + self.addtransvar('celsius', 'Temperature (C)') + self.addtransvar('threshold', 'Firing Threshold (mV)') + self.addtransvar('N_trials', 'Trials') + self.addtransvar('save_spec_data', 'Save Spectral Data') + self.addtransvar('save_figs', 'Save Figures') + self.addtransvar('f_max_spec', 'Max Spectral Frequency (Hz)') + self.addtransvar('spec_cmap', 'Spectrogram Colormap') + self.addtransvar('dipole_scalefctr', 'Dipole Scaling') + self.addtransvar('dipole_smooth_win', 'Dipole Smooth Window (ms)') + self.addtransvar('save_vsoma', 'Save Somatic Voltages') + self.addtransvar('prng_seedcore_opt', 'Parameter Optimization') + self.addtransvar('prng_seedcore_input_prox', 'Ongoing Proximal Input') + self.addtransvar('prng_seedcore_input_dist', 'Ongoing Distal Input') + self.addtransvar('prng_seedcore_extpois', 'External Poisson') + self.addtransvar('prng_seedcore_extgauss', 'External Gaussian') + self.addtransvar('prng_seedcore_evprox_1', 'Evoked Proximal 1') + self.addtransvar('prng_seedcore_evdist_1', 'Evoked Distal 1 ') + self.addtransvar('prng_seedcore_evprox_2', 'Evoked Proximal 2') + self.addtransvar('prng_seedcore_evdist_2', 'Evoked Distal 2') + + def selectionchange(self, i): + self.spec_cmap = self.cmaps[i] + self.parent.updatesaveparams({}) + + def initExtra(self): + DictDialog.initExtra(self) + self.dqextra['NumCores'] = QLineEdit(self) + self.defncore = _get_defncore() + self.dqextra['NumCores'].setText(str(self.defncore)) + self.addtransvar('NumCores', 'Number Cores') + self.ltabs[0].layout.addRow('NumCores', self.dqextra['NumCores']) + + self.spec_cmap_cb = None + + self.cmaps = ['jet', + 'viridis', + 'plasma', + 'inferno', + 'magma', + 'cividis'] + + self.spec_cmap_cb = QComboBox() + for cmap in self.cmaps: + self.spec_cmap_cb.addItem(cmap) + self.spec_cmap_cb.currentIndexChanged.connect(self.selectionchange) + self.ltabs[1].layout.addRow( + self.transvar('spec_cmap'), self.spec_cmap_cb) + + def getntrial(self): + ntrial = int(self.dqline['N_trials'].text().strip()) + if ntrial < 1: + self.dqline['N_trials'].setText(str(1)) + ntrial = 1 + return ntrial + + def getncore(self): + ncore = int(self.dqextra['NumCores'].text().strip()) + if ncore < 1: + self.dqline['NumCores'].setText(str(1)) + ncore = 1 + return ncore + + def setfromdin(self, din): + if not din: + return + + # number of cores may have changed if the configured number failed + self.dqextra['NumCores'].setText(str(self.defncore)) + + # update ordered dict of QLineEdit objects with new parameters + for k, v in din.items(): + if k in self.dqline: + self.dqline[k].setText(str(v).strip()) + elif k == 'spec_cmap': + self.spec_cmap = v + + # for spec_cmap we want the user to be able to change (e.g. 'viridis'), but the + # default is 'jet' to be consistent with prior publications on HNN + if 'spec_cmap' not in din: + self.spec_cmap = 'jet' + + # update the spec_cmap dropdown menu + self.spec_cmap_cb.setCurrentIndex(self.cmaps.index(self.spec_cmap)) + + def __str__(self): + s = '' + for k, v in self.dqline.items(): + s += k + ': ' + v.text().strip() + os.linesep + s += 'spec_cmap: ' + self.spec_cmap + os.linesep + return s + +# widget to specify (pyramidal) cell parameters (geometry, synapses, biophysics) + + +class CellParamDialog (DictDialog): + def __init__(self, parent=None, din=None): + super(CellParamDialog, self).__init__(parent, din) + self.addHideButton() + + def initd(self): + + self.dL2PyrGeom = OrderedDict([('L2Pyr_soma_L', 22.1), # Soma + ('L2Pyr_soma_diam', 23.4), + ('L2Pyr_soma_cm', 0.6195), + ('L2Pyr_soma_Ra', 200.), + # Dendrites + ('L2Pyr_dend_cm', 0.6195), + ('L2Pyr_dend_Ra', 200.), + ('L2Pyr_apicaltrunk_L', 59.5), + ('L2Pyr_apicaltrunk_diam', 4.25), + ('L2Pyr_apical1_L', 306.), + ('L2Pyr_apical1_diam', 4.08), + ('L2Pyr_apicaltuft_L', 238.), + ('L2Pyr_apicaltuft_diam', 3.4), + ('L2Pyr_apicaloblique_L', 340.), + ('L2Pyr_apicaloblique_diam', 3.91), + ('L2Pyr_basal1_L', 85.), + ('L2Pyr_basal1_diam', 4.25), + ('L2Pyr_basal2_L', 255.), + ('L2Pyr_basal2_diam', 2.72), + ('L2Pyr_basal3_L', 255.), + ('L2Pyr_basal3_diam', 2.72)]) + + self.dL2PyrSyn = OrderedDict([('L2Pyr_ampa_e', 0.), # Synapses + ('L2Pyr_ampa_tau1', 0.5), + ('L2Pyr_ampa_tau2', 5.), + ('L2Pyr_nmda_e', 0.), + ('L2Pyr_nmda_tau1', 1.), + ('L2Pyr_nmda_tau2', 20.), + ('L2Pyr_gabaa_e', -80.), + ('L2Pyr_gabaa_tau1', 0.5), + ('L2Pyr_gabaa_tau2', 5.), + ('L2Pyr_gabab_e', -80.), + ('L2Pyr_gabab_tau1', 1.), + ('L2Pyr_gabab_tau2', 20.)]) + + self.dL2PyrBiophys = OrderedDict([('L2Pyr_soma_gkbar_hh2', 0.01), # Biophysics soma + ('L2Pyr_soma_gnabar_hh2', 0.18), + ('L2Pyr_soma_el_hh2', -65.), + ('L2Pyr_soma_gl_hh2', 4.26e-5), + ('L2Pyr_soma_gbar_km', 250.), + # Biophysics dends + ('L2Pyr_dend_gkbar_hh2', 0.01), + ('L2Pyr_dend_gnabar_hh2', 0.15), + ('L2Pyr_dend_el_hh2', -65.), + ('L2Pyr_dend_gl_hh2', 4.26e-5), + ('L2Pyr_dend_gbar_km', 250.)]) + + self.dL5PyrGeom = OrderedDict([('L5Pyr_soma_L', 39.), # Soma + ('L5Pyr_soma_diam', 28.9), + ('L5Pyr_soma_cm', 0.85), + ('L5Pyr_soma_Ra', 200.), + # Dendrites + ('L5Pyr_dend_cm', 0.85), + ('L5Pyr_dend_Ra', 200.), + ('L5Pyr_apicaltrunk_L', 102.), + ('L5Pyr_apicaltrunk_diam', 10.2), + ('L5Pyr_apical1_L', 680.), + ('L5Pyr_apical1_diam', 7.48), + ('L5Pyr_apical2_L', 680.), + ('L5Pyr_apical2_diam', 4.93), + ('L5Pyr_apicaltuft_L', 425.), + ('L5Pyr_apicaltuft_diam', 3.4), + ('L5Pyr_apicaloblique_L', 255.), + ('L5Pyr_apicaloblique_diam', 5.1), + ('L5Pyr_basal1_L', 85.), + ('L5Pyr_basal1_diam', 6.8), + ('L5Pyr_basal2_L', 255.), + ('L5Pyr_basal2_diam', 8.5), + ('L5Pyr_basal3_L', 255.), + ('L5Pyr_basal3_diam', 8.5)]) + + self.dL5PyrSyn = OrderedDict([('L5Pyr_ampa_e', 0.), # Synapses + ('L5Pyr_ampa_tau1', 0.5), + ('L5Pyr_ampa_tau2', 5.), + ('L5Pyr_nmda_e', 0.), + ('L5Pyr_nmda_tau1', 1.), + ('L5Pyr_nmda_tau2', 20.), + ('L5Pyr_gabaa_e', -80.), + ('L5Pyr_gabaa_tau1', 0.5), + ('L5Pyr_gabaa_tau2', 5.), + ('L5Pyr_gabab_e', -80.), + ('L5Pyr_gabab_tau1', 1.), + ('L5Pyr_gabab_tau2', 20.)]) + + self.dL5PyrBiophys = OrderedDict([('L5Pyr_soma_gkbar_hh2', 0.01), # Biophysics soma + ('L5Pyr_soma_gnabar_hh2', 0.16), + ('L5Pyr_soma_el_hh2', -65.), + ('L5Pyr_soma_gl_hh2', 4.26e-5), + ('L5Pyr_soma_gbar_ca', 60.), + ('L5Pyr_soma_taur_cad', 20.), + ('L5Pyr_soma_gbar_kca', 2e-4), + ('L5Pyr_soma_gbar_km', 200.), + ('L5Pyr_soma_gbar_cat', 2e-4), + ('L5Pyr_soma_gbar_ar', 1e-6), + # Biophysics dends + ('L5Pyr_dend_gkbar_hh2', 0.01), + ('L5Pyr_dend_gnabar_hh2', 0.14), + ('L5Pyr_dend_el_hh2', -71.), + ('L5Pyr_dend_gl_hh2', 4.26e-5), + ('L5Pyr_dend_gbar_ca', 60.), + ('L5Pyr_dend_taur_cad', 20.), + ('L5Pyr_dend_gbar_kca', 2e-4), + ('L5Pyr_dend_gbar_km', 200.), + ('L5Pyr_dend_gbar_cat', 2e-4), + ('L5Pyr_dend_gbar_ar', 1e-6)]) + + dtrans = {'gkbar': 'Kv', 'gnabar': 'Na', 'km': 'Km', 'gl': 'leak', + 'ca': 'Ca', 'kca': 'KCa', 'cat': 'CaT', 'ar': 'HCN', 'cad': 'Ca decay time', + 'dend': 'Dendrite', 'soma': 'Soma', 'apicaltrunk': 'Apical Dendrite Trunk', + 'apical1': 'Apical Dendrite 1', 'apical2': 'Apical Dendrite 2', + 'apical3': 'Apical Dendrite 3', 'apicaltuft': 'Apical Dendrite Tuft', + 'apicaloblique': 'Oblique Apical Dendrite', 'basal1': 'Basal Dendrite 1', + 'basal2': 'Basal Dendrite 2', 'basal3': 'Basal Dendrite 3'} + + for d in [self.dL2PyrGeom, self.dL5PyrGeom]: + for k in d.keys(): + lk = k.split('_') + if lk[-1] == 'L': + self.addtransvar( + k, dtrans[lk[1]] + ' ' + r'length (micron)') + elif lk[-1] == 'diam': + self.addtransvar( + k, dtrans[lk[1]] + ' ' + r'diameter (micron)') + elif lk[-1] == 'cm': + self.addtransvar( + k, dtrans[lk[1]] + ' ' + r'capacitive density (F/cm2)') + elif lk[-1] == 'Ra': + self.addtransvar( + k, dtrans[lk[1]] + ' ' + r'resistivity (ohm-cm)') + + for d in [self.dL2PyrSyn, self.dL5PyrSyn]: + for k in d.keys(): + lk = k.split('_') + if k.endswith('e'): + self.addtransvar(k, lk[1].upper() + ' ' + ' reversal (mV)') + elif k.endswith('tau1'): + self.addtransvar(k, lk[1].upper() + + ' ' + ' rise time (ms)') + elif k.endswith('tau2'): + self.addtransvar(k, lk[1].upper() + + ' ' + ' decay time (ms)') + + for d in [self.dL2PyrBiophys, self.dL5PyrBiophys]: + for k in d.keys(): + lk = k.split('_') + if lk[2].count('g') > 0: + if lk[3] == 'km' or lk[3] == 'ca' or lk[3] == 'kca' or lk[3] == 'cat' or lk[3] == 'ar': + nv = dtrans[lk[1]] + ' ' + \ + dtrans[lk[3]] + ' ' + ' channel density ' + else: + nv = dtrans[lk[1]] + ' ' + \ + dtrans[lk[2]] + ' ' + ' channel density ' + if lk[3] == 'hh2' or lk[3] == 'cat' or lk[3] == 'ar': + nv += '(S/cm2)' + else: + nv += '(pS/micron2)' + elif lk[2].count('el') > 0: + nv = dtrans[lk[1]] + ' leak reversal (mV)' + elif lk[2].count('taur') > 0: + nv = dtrans[lk[1]] + ' ' + dtrans[lk[3]] + ' (ms)' + self.addtransvar(k, nv) + + self.ldict = [self.dL2PyrGeom, self.dL2PyrSyn, self.dL2PyrBiophys, + self.dL5PyrGeom, self.dL5PyrSyn, self.dL5PyrBiophys] + self.ltitle = ['L2/3 Pyr Geometry', 'L2/3 Pyr Synapses', 'L2/3 Pyr Biophysics', + 'L5 Pyr Geometry', 'L5 Pyr Synapses', 'L5 Pyr Biophysics'] + self.stitle = 'Cell Parameters' + + +# widget to specify network parameters (number cells, weights, etc.) +class NetworkParamDialog (DictDialog): + def __init__(self, parent=None, din=None): + super(NetworkParamDialog, self).__init__(parent, din) + self.addHideButton() + + def initd(self): + # number of cells + self.dcells = OrderedDict([('N_pyr_x', 10), + ('N_pyr_y', 10)]) + + # max conductances TO L2Pyr + self.dL2Pyr = OrderedDict([('gbar_L2Pyr_L2Pyr_ampa', 0.), + ('gbar_L2Pyr_L2Pyr_nmda', 0.), + ('gbar_L2Basket_L2Pyr_gabaa', 0.), + ('gbar_L2Basket_L2Pyr_gabab', 0.)]) + + # max conductances TO L2Baskets + self.dL2Bas = OrderedDict([('gbar_L2Pyr_L2Basket', 0.), + ('gbar_L2Basket_L2Basket', 0.)]) + + # max conductances TO L5Pyr + self.dL5Pyr = OrderedDict([('gbar_L2Pyr_L5Pyr', 0.), + ('gbar_L2Basket_L5Pyr', 0.), + ('gbar_L5Pyr_L5Pyr_ampa', 0.), + ('gbar_L5Pyr_L5Pyr_nmda', 0.), + ('gbar_L5Basket_L5Pyr_gabaa', 0.), + ('gbar_L5Basket_L5Pyr_gabab', 0.)]) + + # max conductances TO L5Baskets + self.dL5Bas = OrderedDict([('gbar_L2Pyr_L5Basket', 0.), + ('gbar_L5Pyr_L5Basket', 0.), + ('gbar_L5Basket_L5Basket', 0.)]) + + self.ldict = [self.dcells, self.dL2Pyr, + self.dL5Pyr, self.dL2Bas, self.dL5Bas] + self.ltitle = ['Cells', 'Layer 2/3 Pyr', + 'Layer 5 Pyr', 'Layer 2/3 Bas', 'Layer 5 Bas'] + self.stitle = 'Local Network Parameters' + + self.addtransvar('N_pyr_x', 'Num Pyr Cells (X direction)') + self.addtransvar('N_pyr_y', 'Num Pyr Cells (Y direction)') + + dtmp = {'L2': 'L2/3 ', 'L5': 'L5 '} + + for d in [self.dL2Pyr, self.dL5Pyr, self.dL2Bas, self.dL5Bas]: + for k in d.keys(): + lk = k.split('_') + sty1 = dtmp[lk[1][0:2]] + lk[1][2:] + sty2 = dtmp[lk[2][0:2]] + lk[2][2:] + if len(lk) == 3: + self.addtransvar(k, sty1+' -> '+sty2+u' weight (µS)') + else: + self.addtransvar(k, sty1+' -> '+sty2+' ' + + lk[3].upper()+u' weight (µS)') + + +class HelpDialog (QDialog): + def __init__(self, parent): + super(HelpDialog, self).__init__(parent) + self.initUI() + + def initUI(self): + self.layout = QVBoxLayout(self) + # Add stretch to separate the form layout from the button + self.layout.addStretch(1) + + setscalegeom(self, 100, 100, 300, 100) + self.setWindowTitle('Help') + + +class SchematicDialog (QDialog): + # class for holding model schematics (and parameter shortcuts) + def __init__(self, parent): + super(SchematicDialog, self).__init__(parent) + self.initUI() + + def initUI(self): + + self.setWindowTitle('Model Schematics') + QToolTip.setFont(QFont('SansSerif', 10)) + + self.grid = grid = QGridLayout() + grid.setSpacing(10) + + gRow = 0 + + self.locbtn = QPushButton( + 'Local Network'+os.linesep+'Connections', self) + self.locbtn.setIcon(QIcon(lookupresource('connfig'))) + self.locbtn.clicked.connect(self.parent().shownetparamwin) + self.grid.addWidget(self.locbtn, gRow, 0, 1, 1) + + self.proxbtn = QPushButton( + 'Proximal Drive'+os.linesep+'Thalamus', self) + self.proxbtn.setIcon(QIcon(lookupresource('proxfig'))) + self.proxbtn.clicked.connect(self.parent().showproxparamwin) + self.grid.addWidget(self.proxbtn, gRow, 1, 1, 1) + + self.distbtn = QPushButton( + 'Distal Drive NonLemniscal'+os.linesep+'Thal./Cortical Feedback', self) + self.distbtn.setIcon(QIcon(lookupresource('distfig'))) + self.distbtn.clicked.connect(self.parent().showdistparamwin) + self.grid.addWidget(self.distbtn, gRow, 2, 1, 1) + + gRow = 1 + + # for schematic dialog box + self.pixConn = QPixmap(lookupresource('connfig')) + self.pixConnlbl = ClickLabel(self) + self.pixConnlbl.setScaledContents(True) + # self.pixConnlbl.resize(self.pixConnlbl.size()) + self.pixConnlbl.setPixmap(self.pixConn) + # self.pixConnlbl.clicked.connect(self.shownetparamwin) + self.grid.addWidget(self.pixConnlbl, gRow, 0, 1, 1) + + self.pixProx = QPixmap(lookupresource('proxfig')) + self.pixProxlbl = ClickLabel(self) + self.pixProxlbl.setScaledContents(True) + self.pixProxlbl.setPixmap(self.pixProx) + # self.pixProxlbl.clicked.connect(self.showproxparamwin) + self.grid.addWidget(self.pixProxlbl, gRow, 1, 1, 1) + + self.pixDist = QPixmap(lookupresource('distfig')) + self.pixDistlbl = ClickLabel(self) + self.pixDistlbl.setScaledContents(True) + self.pixDistlbl.setPixmap(self.pixDist) + # self.pixDistlbl.clicked.connect(self.showdistparamwin) + self.grid.addWidget(self.pixDistlbl, gRow, 2, 1, 1) + + self.setLayout(grid) + + +class BaseParamDialog (QDialog): + # base widget for specifying params (contains buttons to create other widgets + def __init__(self, parent, paramfn, optrun_func): + super(BaseParamDialog, self).__init__(parent) + self.proxparamwin = self.distparamwin = self.netparamwin = self.syngainparamwin = None + self.runparamwin = RunParamDialog(self) + self.cellparamwin = CellParamDialog(self) + self.netparamwin = NetworkParamDialog(self) + self.syngainparamwin = SynGainParamDialog(self, self.netparamwin) + self.proxparamwin = OngoingInputParamDialog(self, 'Proximal') + self.distparamwin = OngoingInputParamDialog(self, 'Distal') + self.evparamwin = EvokedInputParamDialog(self, None) + self.optparamwin = OptEvokedInputParamDialog(self, optrun_func) + self.poisparamwin = PoissonInputParamDialog(self, None) + self.tonicparamwin = TonicInputParamDialog(self, None) + self.lsubwin = [self.runparamwin, self.cellparamwin, self.netparamwin, + self.proxparamwin, self.distparamwin, self.evparamwin, + self.poisparamwin, self.tonicparamwin, self.optparamwin] + self.paramfn = paramfn + self.parent = parent + + self.params = read_params(self.paramfn) + self.initUI() # requires self.params + self.updateDispParam(self.params) + + def updateDispParam(self, params=None): + global drawavgdpl + + if params is None: + try: + params = read_params(self.paramfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + self.paramfn) + return + + self.params = params + + if usingEvokedInputs(self.params): + # default for evoked is to show average dipole + drawavgdpl = True + elif usingOngoingInputs(self.params): + # default for ongoing is NOT to show average dipole + drawavgdpl = False + + for dlg in self.lsubwin: + dlg.setfromdin(self.params) # update to values from file + self.qle.setText(self.params['sim_prefix']) # update simulation name + + def setrunparam(self): bringwintotop(self.runparamwin) + def setcellparam(self): bringwintotop(self.cellparamwin) + def setnetparam(self): bringwintotop(self.netparamwin) + def setsyngainparam(self): bringwintotop(self.syngainparamwin) + def setproxparam(self): bringwintotop(self.proxparamwin) + def setdistparam(self): bringwintotop(self.distparamwin) + def setevparam(self): bringwintotop(self.evparamwin) + def setpoisparam(self): bringwintotop(self.poisparamwin) + def settonicparam(self): bringwintotop(self.tonicparamwin) + + def initUI(self): + grid = QGridLayout() + grid.setSpacing(10) + + row = 1 + + self.lbl = QLabel(self) + self.lbl.setText('Simulation Name:') + self.lbl.adjustSize() + self.lbl.setToolTip( + 'Simulation Name used to save parameter file and simulation data') + grid.addWidget(self.lbl, row, 0) + self.qle = QLineEdit(self) + self.qle.setText(self.params['sim_prefix']) + grid.addWidget(self.qle, row, 1) + row += 1 + + self.btnrun = QPushButton('Run', self) + self.btnrun.resize(self.btnrun.sizeHint()) + self.btnrun.setToolTip('Set Run Parameters') + self.btnrun.clicked.connect(self.setrunparam) + grid.addWidget(self.btnrun, row, 0, 1, 1) + + self.btncell = QPushButton('Cell', self) + self.btncell.resize(self.btncell.sizeHint()) + self.btncell.setToolTip( + 'Set Cell (Geometry, Synapses, Biophysics) Parameters') + self.btncell.clicked.connect(self.setcellparam) + grid.addWidget(self.btncell, row, 1, 1, 1) + row += 1 + + self.btnnet = QPushButton('Local Network', self) + self.btnnet.resize(self.btnnet.sizeHint()) + self.btnnet.setToolTip('Set Local Network Parameters') + self.btnnet.clicked.connect(self.setnetparam) + grid.addWidget(self.btnnet, row, 0, 1, 1) + + self.btnsyngain = QPushButton('Synaptic Gains', self) + self.btnsyngain.resize(self.btnsyngain.sizeHint()) + self.btnsyngain.setToolTip('Set Local Network Synaptic Gains') + self.btnsyngain.clicked.connect(self.setsyngainparam) + grid.addWidget(self.btnsyngain, row, 1, 1, 1) + + row += 1 + + self.btnprox = QPushButton('Rhythmic Proximal Inputs', self) + self.btnprox.resize(self.btnprox.sizeHint()) + self.btnprox.setToolTip('Set Rhythmic Proximal Inputs') + self.btnprox.clicked.connect(self.setproxparam) + grid.addWidget(self.btnprox, row, 0, 1, 2) + row += 1 + + self.btndist = QPushButton('Rhythmic Distal Inputs', self) + self.btndist.resize(self.btndist.sizeHint()) + self.btndist.setToolTip('Set Rhythmic Distal Inputs') + self.btndist.clicked.connect(self.setdistparam) + grid.addWidget(self.btndist, row, 0, 1, 2) + row += 1 + + self.btnev = QPushButton('Evoked Inputs', self) + self.btnev.resize(self.btnev.sizeHint()) + self.btnev.setToolTip('Set Evoked Inputs') + self.btnev.clicked.connect(self.setevparam) + grid.addWidget(self.btnev, row, 0, 1, 2) + row += 1 + + self.btnpois = QPushButton('Poisson Inputs', self) + self.btnpois.resize(self.btnpois.sizeHint()) + self.btnpois.setToolTip('Set Poisson Inputs') + self.btnpois.clicked.connect(self.setpoisparam) + grid.addWidget(self.btnpois, row, 0, 1, 2) + row += 1 + + self.btntonic = QPushButton('Tonic Inputs', self) + self.btntonic.resize(self.btntonic.sizeHint()) + self.btntonic.setToolTip('Set Tonic (Current Clamp) Inputs') + self.btntonic.clicked.connect(self.settonicparam) + grid.addWidget(self.btntonic, row, 0, 1, 2) + row += 1 + + self.btnsave = QPushButton('Save Parameters To File', self) + self.btnsave.resize(self.btnsave.sizeHint()) + self.btnsave.setToolTip( + 'Save All Parameters to File (Specified by Simulation Name)') + self.btnsave.clicked.connect(self.saveparams) + grid.addWidget(self.btnsave, row, 0, 1, 2) + row += 1 + + self.btnhide = QPushButton('Hide Window', self) + self.btnhide.resize(self.btnhide.sizeHint()) + self.btnhide.clicked.connect(self.hide) + self.btnhide.setToolTip('Hide Window') + grid.addWidget(self.btnhide, row, 0, 1, 2) + + self.setLayout(grid) + + self.setWindowTitle('Set Parameters') + + def saveparams(self, checkok=True): + param_dir = os.path.join(get_output_dir(), 'param') + tmpf = os.path.join(param_dir, self.qle.text() + '.param') + + oktosave = True + if os.path.isfile(tmpf) and checkok: + self.show() + msg = QMessageBox() + ret = msg.warning(self, 'Over-write file(s)?', + tmpf + ' already exists. Over-write?', + QMessageBox.Ok | QMessageBox.Cancel, + QMessageBox.Ok) + if ret == QMessageBox.Cancel: + oktosave = False + + if oktosave: + os.makedirs(param_dir, exist_ok=True) + + with open(tmpf, 'w') as fp: + fp.write(str(self)) + + self.paramfn = tmpf + data_dir = os.path.join(get_output_dir(), 'data') + sim_dir = os.path.join(data_dir, self.qle.text()) + os.makedirs(sim_dir, exist_ok=True) + + return oktosave + + def updatesaveparams(self, dtest): + # update parameter values in GUI (so user can see and so GUI will save these param values) + for win in self.lsubwin: + win.setfromdin(dtest) + # save parameters - do not ask if can over-write the param file + self.saveparams(checkok=False) + + def __str__(self): + s = 'sim_prefix: ' + self.qle.text() + os.linesep + s += 'expmt_groups: {' + self.qle.text() + '}' + os.linesep + for win in self.lsubwin: + s += str(win) + return s + + +class WaitSimDialog (QDialog): + def __init__(self, parent): + super(WaitSimDialog, self).__init__(parent) + self.initUI() + self.txt = '' # text for display + + def updatetxt(self, txt): + self.qtxt.append(txt) + + def initUI(self): + self.layout = QVBoxLayout(self) + self.layout.addStretch(1) + + self.qtxt = QTextEdit(self) + self.layout.addWidget(self.qtxt) + + self.stopbtn = stopbtn = QPushButton('Stop All Simulations', self) + stopbtn.setToolTip('Stop All Simulations') + stopbtn.resize(stopbtn.sizeHint()) + stopbtn.clicked.connect(self.stopsim) + self.layout.addWidget(stopbtn) + + setscalegeomcenter(self, 500, 250) + self.setWindowTitle("Simulation Log") + + def stopsim(self): + self.parent().stopsim() + self.hide() diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index c73b69a61..4914f3792 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -11,34 +11,33 @@ import numpy as np import traceback from subprocess import Popen -from collections import namedtuple, OrderedDict +from collections import namedtuple from copy import deepcopy -from glob import glob +from psutil import cpu_count # External libraries from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication -from PyQt5.QtWidgets import QFileDialog, QComboBox, QTabWidget -from PyQt5.QtWidgets import QToolTip, QPushButton, QFormLayout -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QDialog, QGridLayout -from PyQt5.QtWidgets import QLineEdit, QLabel, QTextEdit, QInputDialog -from PyQt5.QtWidgets import QMenu, QMessageBox, QWidget, QDialog, QLayout -from PyQt5.QtGui import QIcon, QPixmap, QFont +from PyQt5.QtWidgets import QFileDialog, QComboBox +from PyQt5.QtWidgets import QToolTip, QPushButton, QGridLayout, QInputDialog +from PyQt5.QtWidgets import QMenu, QMessageBox, QWidget, QLayout +from PyQt5.QtGui import QIcon, QFont from PyQt5.QtCore import pyqtSignal, QObject, Qt + from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT import matplotlib.pyplot as plt -from psutil import cpu_count -from hnn_core import read_params, CellResponse + +from hnn_core import read_params from hnn_core.dipole import average_dipoles # HNN modules -from .paramrw import usingOngoingInputs, usingEvokedInputs, get_output_dir -from .paramrw import write_gids_param, get_fname -from .specfn import spec_dpl_kernel, save_spec_data +from .dialog import (BaseParamDialog, EvokedOrRhythmicDialog, + WaitSimDialog, HelpDialog, SchematicDialog, + bringwintotop, _get_defncore) +from .paramrw import (usingOngoingInputs, get_output_dir, + write_gids_param, get_fname) from .simdat import SIMCanvas, SimData from .run import RunSimThread, ParamSignal -from .qt_lib import setscalegeom, setscalegeomcenter, getmplDPI, getscreengeom -from .qt_lib import lookupresource, ClickLabel -from .qt_evoked import EvokedInputParamDialog, OptEvokedInputParamDialog +from .qt_lib import getmplDPI, getscreengeom, lookupresource, setscalegeomcenter from .DataViewGUI import DataViewGUI from .visdipole import DipoleCanvas from .visspec import SpecViewGUI, SpecCanvas @@ -79,21 +78,6 @@ def _add_missing_frames(tb): return result -def _get_defncore(): - """get default number of cores """ - - try: - defncore = len(os.sched_getaffinity(0)) - except AttributeError: - defncore = cpu_count(logical=False) - - if defncore is None or defncore == 0: - # in case psutil is not supported (e.g. BSD) - defncore = multiprocessing.cpu_count() - - return defncore - - class DoneSignal(QObject): finishSim = pyqtSignal(bool, str) @@ -104,1099 +88,6 @@ def bringwintobot(win): win.hide() -def bringwintotop(win): - # bring a pyqt5 window to the top (parents still stay behind children) - # from https://www.programcreek.com/python/example/ - # 101663/PyQt5.QtCore.Qt.WindowActive - # win.show() - # win.setWindowState(win.windowState() & ~Qt.WindowMinimized | - # Qt.WindowActive) - # win.raise_() - win.showNormal() - win.activateWindow() - # win.setWindowState((win.windowState() & ~Qt.WindowMinimized) | - # Qt.WindowActive) - # win.activateWindow() - # win.raise_() - # win.show() - - -class DictDialog(QDialog): - """dictionary-based dialog with tabs - - should make all dialogs specifiable via cfg file format - - then can customize gui without changing py code - and can reduce code explosion / overlap between dialogs - """ - - def __init__(self, parent, din): - super(DictDialog, self).__init__(parent) - self.ldict = [] # subclasses should override - self.ltitle = [] - # for translating model variable name to more human-readable form - self.dtransvar = {} - self.stitle = '' - self.initd() - self.initUI() - self.initExtra() - self.setfromdin(din) # set values from input dictionary - # self.addtips() - - # TODO: add back tooltips - # def addtips (self): - # for ktip in dconf.keys(): - # if ktip in self.dqline: - # self.dqline[ktip].setToolTip(dconf[ktip]) - # elif ktip in self.dqextra: - # self.dqextra[ktip].setToolTip(dconf[ktip]) - - def __str__(self): - s = '' - for k, v in self.dqline.items(): - s += k + ': ' + v.text().strip() + os.linesep - return s - - def saveparams(self): - self.hide() - - def initd(self): - pass # implemented in subclass - - def getval(self, k): - if k in self.dqline.keys(): - return self.dqline[k].text().strip() - - def lines2val(self, ksearch, val): - for k in self.dqline.keys(): - if k.count(ksearch) > 0: - self.dqline[k].setText(str(val)) - - def setfromdin(self, din): - if not din: - return - for k, v in din.items(): - if k in self.dqline: - self.dqline[k].setText(str(v).strip()) - - def transvar(self, k): - if k in self.dtransvar: - return self.dtransvar[k] - return k - - def addtransvar(self, k, strans): - self.dtransvar[k] = strans - self.dtransvar[strans] = k - - def initExtra(self): - # extra items not written to param file - self.dqextra = {} - - def initUI(self): - self.layout = QVBoxLayout(self) - - # Add stretch to separate the form layout from the button - self.layout.addStretch(1) - - # Initialize tab screen - self.ltabs = [] - self.tabs = QTabWidget() - self.layout.addWidget(self.tabs) - - for _ in range(len(self.ldict)): - self.ltabs.append(QWidget()) - - self.tabs.resize(575, 200) - - # create tabs and their layouts - for tab, s in zip(self.ltabs, self.ltitle): - self.tabs.addTab(tab, s) - tab.layout = QFormLayout() - tab.setLayout(tab.layout) - - self.dqline = {} # QLineEdits dict; key is model variable - for d, tab in zip(self.ldict, self.ltabs): - for k, v in d.items(): - self.dqline[k] = QLineEdit(self) - self.dqline[k].setText(str(v)) - # add label,QLineEdit to the tab - tab.layout.addRow(self.transvar(k), self.dqline[k]) - - # Add tabs to widget - self.layout.addWidget(self.tabs) - self.setLayout(self.layout) - self.setWindowTitle(self.stitle) - - def TurnOff(self): - pass - - def addOffButton(self): - """Create a horizontal box layout to hold the button""" - self.button_box = QHBoxLayout() - self.btnoff = QPushButton('Turn Off Inputs', self) - self.btnoff.resize(self.btnoff.sizeHint()) - self.btnoff.clicked.connect(self.TurnOff) - self.btnoff.setToolTip('Turn Off Inputs') - self.button_box.addWidget(self.btnoff) - self.layout.addLayout(self.button_box) - - def addHideButton(self): - self.bbhidebox = QHBoxLayout() - self.btnhide = QPushButton('Hide Window', self) - self.btnhide.resize(self.btnhide.sizeHint()) - self.btnhide.clicked.connect(self.hide) - self.btnhide.setToolTip('Hide Window') - self.bbhidebox.addWidget(self.btnhide) - self.layout.addLayout(self.bbhidebox) - - -class OngoingInputParamDialog (DictDialog): - """widget to specify ongoing input params (proximal, distal)""" - def __init__(self, parent, inty, din=None): - self.inty = inty - if self.inty.startswith('Proximal'): - self.prefix = 'input_prox_A_' - self.postfix = '_prox' - self.isprox = True - else: - self.prefix = 'input_dist_A_' - self.postfix = '_dist' - self.isprox = False - super(OngoingInputParamDialog, self).__init__(parent, din) - self.addOffButton() - self.addImages() - self.addHideButton() - - def addImages(self): - """add png cartoons to tabs""" - if self.isprox: - self.pix = QPixmap(lookupresource('proxfig')) - else: - self.pix = QPixmap(lookupresource('distfig')) - for tab in self.ltabs: - pixlbl = ClickLabel(self) - pixlbl.setPixmap(self.pix) - tab.layout.addRow(pixlbl) - - def TurnOff(self): - """ turn off by setting all weights to 0.0""" - self.lines2val('weight', 0.0) - - def initd(self): - self.dtiming = OrderedDict([('t0_input' + self.postfix, 1000.), - ('t0_input_stdev' + self.postfix, 0.), - ('tstop_input' + self.postfix, 250.), - ('f_input' + self.postfix, 10.), - ('f_stdev' + self.postfix, 20.), - ('events_per_cycle' + self.postfix, 2), - ('repeats' + self.postfix, 10)]) - - self.dL2 = OrderedDict([(self.prefix + 'weight_L2Pyr_ampa', 0.), - (self.prefix + 'weight_L2Pyr_nmda', 0.), - (self.prefix + 'weight_L2Basket_ampa', 0.), - (self.prefix + 'weight_L2Basket_nmda', 0.), - (self.prefix + 'delay_L2', 0.1)]) - - self.dL5 = OrderedDict([(self.prefix + 'weight_L5Pyr_ampa', 0.), - (self.prefix + 'weight_L5Pyr_nmda', 0.)]) - - if self.isprox: - self.dL5[self.prefix + 'weight_L5Basket_ampa'] = 0.0 - self.dL5[self.prefix + 'weight_L5Basket_nmda'] = 0.0 - self.dL5[self.prefix + 'delay_L5'] = 0.1 - - self.ldict = [self.dtiming, self.dL2, self.dL5] - self.ltitle = ['Timing', 'Layer 2/3', 'Layer 5'] - self.stitle = 'Set Rhythmic ' + self.inty + ' Inputs' - - dtmp = {'L2': 'L2/3 ', 'L5': 'L5 '} - for d in [self.dL2, self.dL5]: - for k in d.keys(): - lk = k.split('_') - if k.count('weight') > 0: - self.addtransvar(k, dtmp[lk[-2][0:2]] + lk[-2][2:] + ' ' + - lk[-1].upper() + u' weight (µS)') - else: - self.addtransvar(k, 'Delay (ms)') - - self.addtransvar('t0_input' + self.postfix, 'Start time mean (ms)') - self.addtransvar('t0_input_stdev' + self.postfix, - 'Start time stdev (ms)') - self.addtransvar('tstop_input' + self.postfix, 'Stop time (ms)') - self.addtransvar('f_input' + self.postfix, 'Burst frequency (Hz)') - self.addtransvar('f_stdev' + self.postfix, 'Burst stdev (ms)') - self.addtransvar('events_per_cycle' + self.postfix, 'Spikes/burst') - self.addtransvar('repeats' + self.postfix, 'Number bursts') - - -class EvokedOrRhythmicDialog (QDialog): - def __init__(self, parent, distal, evwin, rhythwin): - super(EvokedOrRhythmicDialog, self).__init__(parent) - if distal: - self.prefix = 'Distal' - else: - self.prefix = 'Proximal' - self.evwin = evwin - self.rhythwin = rhythwin - self.initUI() - # TODO: add back tooltips - # self.addtips() - - def initUI(self): - self.layout = QVBoxLayout(self) - # Add stretch to separate the form layout from the button - self.layout.addStretch(1) - - self.btnrhythmic = QPushButton('Rhythmic ' + self.prefix + ' Inputs', - self) - self.btnrhythmic.resize(self.btnrhythmic.sizeHint()) - self.btnrhythmic.clicked.connect(self.showrhythmicwin) - self.layout.addWidget(self.btnrhythmic) - - self.btnevoked = QPushButton('Evoked Inputs', self) - self.btnevoked.resize(self.btnevoked.sizeHint()) - self.btnevoked.clicked.connect(self.showevokedwin) - self.layout.addWidget(self.btnevoked) - - self.addHideButton() - - setscalegeom(self, 150, 150, 270, 120) - self.setWindowTitle("Pick Input Type") - - def showevokedwin(self): - bringwintotop(self.evwin) - self.hide() - - def showrhythmicwin(self): - bringwintotop(self.rhythwin) - self.hide() - - def addHideButton(self): - self.bbhidebox = QHBoxLayout() - self.btnhide = QPushButton('Hide Window', self) - self.btnhide.resize(self.btnhide.sizeHint()) - self.btnhide.clicked.connect(self.hide) - self.btnhide.setToolTip('Hide Window') - self.bbhidebox.addWidget(self.btnhide) - self.layout.addLayout(self.bbhidebox) - - -class SynGainParamDialog(QDialog): - def __init__(self, parent, netparamwin): - super(SynGainParamDialog, self).__init__(parent) - self.netparamwin = netparamwin - self.initUI() - - def scalegain(self, k, fctr): - oldval = float(self.netparamwin.dqline[k].text().strip()) - newval = oldval * fctr - self.netparamwin.dqline[k].setText(str(newval)) - return newval - - def isE(self, ty): - return ty.count('Pyr') > 0 - - def isI(self, ty): - return ty.count('Basket') > 0 - - def tounity(self): - for k in self.dqle.keys(): - self.dqle[k].setText('1.0') - - def scalegains(self): - for _, k in enumerate(self.dqle.keys()): - fctr = float(self.dqle[k].text().strip()) - if fctr < 0.: - fctr = 0. - self.dqle[k].setText(str(fctr)) - elif fctr == 1.0: - continue - for k2 in self.netparamwin.dqline.keys(): - types = k2.split('_') - ty1, ty2 = types[1], types[2] - if self.isE(ty1) and self.isE(ty2) and k == 'E -> E': - self.scalegain(k2, fctr) - elif self.isE(ty1) and self.isI(ty2) and k == 'E -> I': - self.scalegain(k2, fctr) - elif self.isI(ty1) and self.isE(ty2) and k == 'I -> E': - self.scalegain(k2, fctr) - elif self.isI(ty1) and self.isI(ty2) and k == 'I -> I': - self.scalegain(k2, fctr) - - # go back to unity since pressed OK - next call to this dialog will - # reset new values - self.tounity() - self.hide() - - def initUI(self): - grid = QGridLayout() - grid.setSpacing(10) - - self.dqle = {} - for row, k in enumerate(['E -> E', 'E -> I', 'I -> E', 'I -> I']): - lbl = QLabel(self) - lbl.setText(k) - lbl.adjustSize() - grid.addWidget(lbl, row, 0) - qle = QLineEdit(self) - qle.setText('1.0') - grid.addWidget(qle, row, 1) - self.dqle[k] = qle - - row += 1 - self.btnok = QPushButton('OK', self) - self.btnok.resize(self.btnok.sizeHint()) - self.btnok.clicked.connect(self.scalegains) - grid.addWidget(self.btnok, row, 0, 1, 1) - self.btncancel = QPushButton('Cancel', self) - self.btncancel.resize(self.btncancel.sizeHint()) - self.btncancel.clicked.connect(self.hide) - grid.addWidget(self.btncancel, row, 1, 1, 1) - - self.setLayout(grid) - setscalegeom(self, 150, 150, 270, 180) - self.setWindowTitle("Synaptic Gains") - - -# widget to specify tonic inputs -class TonicInputParamDialog(DictDialog): - def __init__(self, parent, din): - super(TonicInputParamDialog, self).__init__(parent, din) - self.addOffButton() - self.addHideButton() - - # turn off by setting all weights to 0.0 - def TurnOff(self): - self.lines2val('A', 0.0) - - def initd(self): - self.dL2 = OrderedDict([ - # IClamp params for L2Pyr - ('Itonic_A_L2Pyr_soma', 0.), - ('Itonic_t0_L2Pyr_soma', 0.), - ('Itonic_T_L2Pyr_soma', -1.), - # IClamp param for L2Basket - ('Itonic_A_L2Basket', 0.), - ('Itonic_t0_L2Basket', 0.), - ('Itonic_T_L2Basket', -1.)]) - - self.dL5 = OrderedDict([ - # IClamp params for L5Pyr - ('Itonic_A_L5Pyr_soma', 0.), - ('Itonic_t0_L5Pyr_soma', 0.), - ('Itonic_T_L5Pyr_soma', -1.), - # IClamp param for L5Basket - ('Itonic_A_L5Basket', 0.), - ('Itonic_t0_L5Basket', 0.), - ('Itonic_T_L5Basket', -1.)]) - - # temporary dictionary for string translation - dtmp = {'L2': 'L2/3 ', 'L5': 'L5 '} - for d in [self.dL2, self.dL5]: - for k in d.keys(): - cty = k.split('_')[2] # cell type - tcty = dtmp[cty[0:2]] + cty[2:] # translated cell type - if k.count('A') > 0: - self.addtransvar(k, tcty + ' amplitude (nA)') - elif k.count('t0') > 0: - self.addtransvar(k, tcty + ' start time (ms)') - elif k.count('T') > 0: - self.addtransvar(k, tcty + ' stop time (ms)') - - self.ldict = [self.dL2, self.dL5] - self.ltitle = ['Layer 2/3', 'Layer 5'] - self.stitle = 'Set Tonic Inputs' - - -# widget to specify ongoing poisson inputs -class PoissonInputParamDialog(DictDialog): - def __init__(self, parent, din): - super(PoissonInputParamDialog, self).__init__(parent, din) - self.addOffButton() - self.addHideButton() - - def TurnOff(self): - """turn off by setting all weights to 0.0""" - self.lines2val('weight', 0.0) - - def initd(self): - self.dL2, self.dL5 = {}, {} - ld = [self.dL2, self.dL5] - - for i, lyr in enumerate(['L2', 'L5']): - d = ld[i] - for ty in ['Pyr', 'Basket']: - for sy in ['ampa', 'nmda']: - d[lyr + ty + '_Pois_A_weight' + '_' + sy] = 0. - d[lyr + ty + '_Pois_lamtha'] = 0. - - self.dtiming = OrderedDict([('t0_pois', 0.), - ('T_pois', -1)]) - - self.addtransvar('t0_pois', 'Start time (ms)') - self.addtransvar('T_pois', 'Stop time (ms)') - - # temporary dictionary for string translation - dtmp = {'L2': 'L2/3 ', 'L5': 'L5 '} - for d in [self.dL2, self.dL5]: - for k in d.keys(): - ks = k.split('_') - cty = ks[0] # cell type - tcty = dtmp[cty[0:2]] + cty[2:] # translated cell type - if k.count('weight'): - self.addtransvar(k, tcty + ' ' + ks[-1].upper() + - u' weight (µS)') - elif k.endswith('lamtha'): - self.addtransvar(k, tcty + ' freq (Hz)') - - self.ldict = [self.dL2, self.dL5, self.dtiming] - self.ltitle = ['Layer 2/3', 'Layer 5', 'Timing'] - self.stitle = 'Set Poisson Inputs' - - -# widget to specify run params (tstop, dt, etc.) -- not many params here -class RunParamDialog(DictDialog): - def __init__(self, parent, din = None): - super(RunParamDialog, self).__init__(parent,din) - self.addHideButton() - self.parent = parent - - def initd(self): - - self.drun = OrderedDict([('tstop', 250.), # simulation end time (ms) - ('dt', 0.025), # timestep - ('celsius',37.0), # temperature - ('N_trials',1), # number of trials - ('threshold',0.0)]) # firing threshold - # cvode - not currently used by simulation - - # analysis - self.danalysis = OrderedDict([('save_figs',0), - ('save_spec_data', 0), - ('f_max_spec', 40), - ('dipole_scalefctr',30e3), - ('dipole_smooth_win',15.0), - ('save_vsoma',0)]) - - self.drand = OrderedDict([('prng_seedcore_opt', 0), - ('prng_seedcore_input_prox', 0), - ('prng_seedcore_input_dist', 0), - ('prng_seedcore_extpois', 0), - ('prng_seedcore_extgauss', 0), - ('prng_seedcore_evprox_1', 0), - ('prng_seedcore_evdist_1', 0), - ('prng_seedcore_evprox_2', 0), - ('prng_seedcore_evdist_2', 0)]) - - self.ldict = [self.drun, self.danalysis, self.drand] - self.ltitle = ['Run', 'Analysis', 'Randomization Seeds'] - self.stitle = 'Run Parameters' - - self.addtransvar('tstop','Duration (ms)') - self.addtransvar('dt','Integration Timestep (ms)') - self.addtransvar('celsius','Temperature (C)') - self.addtransvar('threshold','Firing Threshold (mV)') - self.addtransvar('N_trials','Trials') - self.addtransvar('save_spec_data','Save Spectral Data') - self.addtransvar('save_figs','Save Figures') - self.addtransvar('f_max_spec', 'Max Spectral Frequency (Hz)') - self.addtransvar('spec_cmap', 'Spectrogram Colormap') - self.addtransvar('dipole_scalefctr','Dipole Scaling') - self.addtransvar('dipole_smooth_win','Dipole Smooth Window (ms)') - self.addtransvar('save_vsoma','Save Somatic Voltages') - self.addtransvar('prng_seedcore_opt','Parameter Optimization') - self.addtransvar('prng_seedcore_input_prox','Ongoing Proximal Input') - self.addtransvar('prng_seedcore_input_dist','Ongoing Distal Input') - self.addtransvar('prng_seedcore_extpois','External Poisson') - self.addtransvar('prng_seedcore_extgauss','External Gaussian') - self.addtransvar('prng_seedcore_evprox_1','Evoked Proximal 1') - self.addtransvar('prng_seedcore_evdist_1','Evoked Distal 1 ') - self.addtransvar('prng_seedcore_evprox_2','Evoked Proximal 2') - self.addtransvar('prng_seedcore_evdist_2','Evoked Distal 2') - - def selectionchange(self,i): - self.spec_cmap = self.cmaps[i] - self.parent.updatesaveparams({}) - - def initExtra (self): - DictDialog.initExtra(self) - self.dqextra['NumCores'] = QLineEdit(self) - self.defncore = _get_defncore() - self.dqextra['NumCores'].setText(str(self.defncore)) - self.addtransvar('NumCores','Number Cores') - self.ltabs[0].layout.addRow('NumCores',self.dqextra['NumCores']) - - self.spec_cmap_cb = None - - self.cmaps = ['jet', - 'viridis', - 'plasma', - 'inferno', - 'magma', - 'cividis'] - - self.spec_cmap_cb = QComboBox() - for cmap in self.cmaps: - self.spec_cmap_cb.addItem(cmap) - self.spec_cmap_cb.currentIndexChanged.connect(self.selectionchange) - self.ltabs[1].layout.addRow(self.transvar('spec_cmap'),self.spec_cmap_cb) - - def getntrial (self): - ntrial = int(self.dqline['N_trials'].text().strip()) - if ntrial < 1: - self.dqline['N_trials'].setText(str(1)) - ntrial = 1 - return ntrial - - def getncore (self): - ncore = int(self.dqextra['NumCores'].text().strip()) - if ncore < 1: - self.dqline['NumCores'].setText(str(1)) - ncore = 1 - return ncore - - def setfromdin (self,din): - if not din: return - - # number of cores may have changed if the configured number failed - self.dqextra['NumCores'].setText(str(self.defncore)) - - # update ordered dict of QLineEdit objects with new parameters - for k,v in din.items(): - if k in self.dqline: - self.dqline[k].setText(str(v).strip()) - elif k == 'spec_cmap': - self.spec_cmap = v - - # for spec_cmap we want the user to be able to change (e.g. 'viridis'), but the - # default is 'jet' to be consistent with prior publications on HNN - if 'spec_cmap' not in din: - self.spec_cmap = 'jet' - - # update the spec_cmap dropdown menu - self.spec_cmap_cb.setCurrentIndex(self.cmaps.index(self.spec_cmap)) - - def __str__ (self): - s = '' - for k,v in self.dqline.items(): s += k + ': ' + v.text().strip() + os.linesep - s += 'spec_cmap: ' + self.spec_cmap + os.linesep - return s - -# widget to specify (pyramidal) cell parameters (geometry, synapses, biophysics) -class CellParamDialog (DictDialog): - def __init__ (self, parent = None, din = None): - super(CellParamDialog, self).__init__(parent,din) - self.addHideButton() - - def initd (self): - - self.dL2PyrGeom = OrderedDict([('L2Pyr_soma_L', 22.1), # Soma - ('L2Pyr_soma_diam', 23.4), - ('L2Pyr_soma_cm', 0.6195), - ('L2Pyr_soma_Ra', 200.), - # Dendrites - ('L2Pyr_dend_cm', 0.6195), - ('L2Pyr_dend_Ra', 200.), - ('L2Pyr_apicaltrunk_L', 59.5), - ('L2Pyr_apicaltrunk_diam', 4.25), - ('L2Pyr_apical1_L', 306.), - ('L2Pyr_apical1_diam', 4.08), - ('L2Pyr_apicaltuft_L', 238.), - ('L2Pyr_apicaltuft_diam', 3.4), - ('L2Pyr_apicaloblique_L', 340.), - ('L2Pyr_apicaloblique_diam', 3.91), - ('L2Pyr_basal1_L', 85.), - ('L2Pyr_basal1_diam', 4.25), - ('L2Pyr_basal2_L', 255.), - ('L2Pyr_basal2_diam', 2.72), - ('L2Pyr_basal3_L', 255.), - ('L2Pyr_basal3_diam', 2.72)]) - - self.dL2PyrSyn = OrderedDict([('L2Pyr_ampa_e', 0.), # Synapses - ('L2Pyr_ampa_tau1', 0.5), - ('L2Pyr_ampa_tau2', 5.), - ('L2Pyr_nmda_e', 0.), - ('L2Pyr_nmda_tau1', 1.), - ('L2Pyr_nmda_tau2', 20.), - ('L2Pyr_gabaa_e', -80.), - ('L2Pyr_gabaa_tau1', 0.5), - ('L2Pyr_gabaa_tau2', 5.), - ('L2Pyr_gabab_e', -80.), - ('L2Pyr_gabab_tau1', 1.), - ('L2Pyr_gabab_tau2', 20.)]) - - self.dL2PyrBiophys = OrderedDict([('L2Pyr_soma_gkbar_hh2', 0.01), # Biophysics soma - ('L2Pyr_soma_gnabar_hh2', 0.18), - ('L2Pyr_soma_el_hh2', -65.), - ('L2Pyr_soma_gl_hh2', 4.26e-5), - ('L2Pyr_soma_gbar_km', 250.), - # Biophysics dends - ('L2Pyr_dend_gkbar_hh2', 0.01), - ('L2Pyr_dend_gnabar_hh2', 0.15), - ('L2Pyr_dend_el_hh2', -65.), - ('L2Pyr_dend_gl_hh2', 4.26e-5), - ('L2Pyr_dend_gbar_km', 250.)]) - - - self.dL5PyrGeom = OrderedDict([('L5Pyr_soma_L', 39.), # Soma - ('L5Pyr_soma_diam', 28.9), - ('L5Pyr_soma_cm', 0.85), - ('L5Pyr_soma_Ra', 200.), - # Dendrites - ('L5Pyr_dend_cm', 0.85), - ('L5Pyr_dend_Ra', 200.), - ('L5Pyr_apicaltrunk_L', 102.), - ('L5Pyr_apicaltrunk_diam', 10.2), - ('L5Pyr_apical1_L', 680.), - ('L5Pyr_apical1_diam', 7.48), - ('L5Pyr_apical2_L', 680.), - ('L5Pyr_apical2_diam', 4.93), - ('L5Pyr_apicaltuft_L', 425.), - ('L5Pyr_apicaltuft_diam', 3.4), - ('L5Pyr_apicaloblique_L', 255.), - ('L5Pyr_apicaloblique_diam', 5.1), - ('L5Pyr_basal1_L', 85.), - ('L5Pyr_basal1_diam', 6.8), - ('L5Pyr_basal2_L', 255.), - ('L5Pyr_basal2_diam', 8.5), - ('L5Pyr_basal3_L', 255.), - ('L5Pyr_basal3_diam', 8.5)]) - - self.dL5PyrSyn = OrderedDict([('L5Pyr_ampa_e', 0.), # Synapses - ('L5Pyr_ampa_tau1', 0.5), - ('L5Pyr_ampa_tau2', 5.), - ('L5Pyr_nmda_e', 0.), - ('L5Pyr_nmda_tau1', 1.), - ('L5Pyr_nmda_tau2', 20.), - ('L5Pyr_gabaa_e', -80.), - ('L5Pyr_gabaa_tau1', 0.5), - ('L5Pyr_gabaa_tau2', 5.), - ('L5Pyr_gabab_e', -80.), - ('L5Pyr_gabab_tau1', 1.), - ('L5Pyr_gabab_tau2', 20.)]) - - self.dL5PyrBiophys = OrderedDict([('L5Pyr_soma_gkbar_hh2', 0.01), # Biophysics soma - ('L5Pyr_soma_gnabar_hh2', 0.16), - ('L5Pyr_soma_el_hh2', -65.), - ('L5Pyr_soma_gl_hh2', 4.26e-5), - ('L5Pyr_soma_gbar_ca', 60.), - ('L5Pyr_soma_taur_cad', 20.), - ('L5Pyr_soma_gbar_kca', 2e-4), - ('L5Pyr_soma_gbar_km', 200.), - ('L5Pyr_soma_gbar_cat', 2e-4), - ('L5Pyr_soma_gbar_ar', 1e-6), - # Biophysics dends - ('L5Pyr_dend_gkbar_hh2', 0.01), - ('L5Pyr_dend_gnabar_hh2', 0.14), - ('L5Pyr_dend_el_hh2', -71.), - ('L5Pyr_dend_gl_hh2', 4.26e-5), - ('L5Pyr_dend_gbar_ca', 60.), - ('L5Pyr_dend_taur_cad', 20.), - ('L5Pyr_dend_gbar_kca', 2e-4), - ('L5Pyr_dend_gbar_km', 200.), - ('L5Pyr_dend_gbar_cat', 2e-4), - ('L5Pyr_dend_gbar_ar', 1e-6)]) - - dtrans = {'gkbar':'Kv', 'gnabar':'Na', 'km':'Km', 'gl':'leak',\ - 'ca':'Ca', 'kca':'KCa','cat':'CaT','ar':'HCN','cad':'Ca decay time',\ - 'dend':'Dendrite','soma':'Soma','apicaltrunk':'Apical Dendrite Trunk',\ - 'apical1':'Apical Dendrite 1','apical2':'Apical Dendrite 2',\ - 'apical3':'Apical Dendrite 3','apicaltuft':'Apical Dendrite Tuft',\ - 'apicaloblique':'Oblique Apical Dendrite','basal1':'Basal Dendrite 1',\ - 'basal2':'Basal Dendrite 2','basal3':'Basal Dendrite 3'} - - for d in [self.dL2PyrGeom, self.dL5PyrGeom]: - for k in d.keys(): - lk = k.split('_') - if lk[-1] == 'L': - self.addtransvar(k,dtrans[lk[1]] + ' ' + r'length (micron)') - elif lk[-1] == 'diam': - self.addtransvar(k,dtrans[lk[1]] + ' ' + r'diameter (micron)') - elif lk[-1] == 'cm': - self.addtransvar(k,dtrans[lk[1]] + ' ' + r'capacitive density (F/cm2)') - elif lk[-1] == 'Ra': - self.addtransvar(k,dtrans[lk[1]] + ' ' + r'resistivity (ohm-cm)') - - for d in [self.dL2PyrSyn, self.dL5PyrSyn]: - for k in d.keys(): - lk = k.split('_') - if k.endswith('e'): - self.addtransvar(k,lk[1].upper() + ' ' + ' reversal (mV)') - elif k.endswith('tau1'): - self.addtransvar(k,lk[1].upper() + ' ' + ' rise time (ms)') - elif k.endswith('tau2'): - self.addtransvar(k,lk[1].upper() + ' ' + ' decay time (ms)') - - for d in [self.dL2PyrBiophys, self.dL5PyrBiophys]: - for k in d.keys(): - lk = k.split('_') - if lk[2].count('g') > 0: - if lk[3]=='km' or lk[3]=='ca' or lk[3]=='kca' or lk[3]=='cat' or lk[3]=='ar': - nv = dtrans[lk[1]] + ' ' + dtrans[lk[3]] + ' ' + ' channel density ' - else: - nv = dtrans[lk[1]] + ' ' + dtrans[lk[2]] + ' ' + ' channel density ' - if lk[3] == 'hh2' or lk[3] == 'cat' or lk[3] == 'ar' : nv += '(S/cm2)' - else: nv += '(pS/micron2)' - elif lk[2].count('el') > 0: - nv = dtrans[lk[1]] + ' leak reversal (mV)' - elif lk[2].count('taur') > 0: - nv = dtrans[lk[1]] + ' ' + dtrans[lk[3]] + ' (ms)' - self.addtransvar(k,nv) - - self.ldict = [self.dL2PyrGeom, self.dL2PyrSyn, self.dL2PyrBiophys,\ - self.dL5PyrGeom, self.dL5PyrSyn, self.dL5PyrBiophys] - self.ltitle = [ 'L2/3 Pyr Geometry', 'L2/3 Pyr Synapses', 'L2/3 Pyr Biophysics',\ - 'L5 Pyr Geometry', 'L5 Pyr Synapses', 'L5 Pyr Biophysics'] - self.stitle = 'Cell Parameters' - - -# widget to specify network parameters (number cells, weights, etc.) -class NetworkParamDialog (DictDialog): - def __init__ (self, parent = None, din = None): - super(NetworkParamDialog, self).__init__(parent,din) - self.addHideButton() - - def initd (self): - # number of cells - self.dcells = OrderedDict([('N_pyr_x', 10), - ('N_pyr_y', 10)]) - - # max conductances TO L2Pyr - self.dL2Pyr = OrderedDict([('gbar_L2Pyr_L2Pyr_ampa', 0.), - ('gbar_L2Pyr_L2Pyr_nmda', 0.), - ('gbar_L2Basket_L2Pyr_gabaa', 0.), - ('gbar_L2Basket_L2Pyr_gabab', 0.)]) - - # max conductances TO L2Baskets - self.dL2Bas = OrderedDict([('gbar_L2Pyr_L2Basket', 0.), - ('gbar_L2Basket_L2Basket', 0.)]) - - # max conductances TO L5Pyr - self.dL5Pyr = OrderedDict([('gbar_L2Pyr_L5Pyr', 0.), - ('gbar_L2Basket_L5Pyr', 0.), - ('gbar_L5Pyr_L5Pyr_ampa', 0.), - ('gbar_L5Pyr_L5Pyr_nmda', 0.), - ('gbar_L5Basket_L5Pyr_gabaa', 0.), - ('gbar_L5Basket_L5Pyr_gabab', 0.)]) - - # max conductances TO L5Baskets - self.dL5Bas = OrderedDict([('gbar_L2Pyr_L5Basket', 0.), - ('gbar_L5Pyr_L5Basket', 0.), - ('gbar_L5Basket_L5Basket', 0.)]) - - self.ldict = [self.dcells, self.dL2Pyr, self.dL5Pyr, self.dL2Bas, self.dL5Bas] - self.ltitle = ['Cells', 'Layer 2/3 Pyr', 'Layer 5 Pyr', 'Layer 2/3 Bas', 'Layer 5 Bas'] - self.stitle = 'Local Network Parameters' - - self.addtransvar('N_pyr_x', 'Num Pyr Cells (X direction)') - self.addtransvar('N_pyr_y', 'Num Pyr Cells (Y direction)') - - dtmp = {'L2':'L2/3 ','L5':'L5 '} - - for d in [self.dL2Pyr, self.dL5Pyr, self.dL2Bas, self.dL5Bas]: - for k in d.keys(): - lk = k.split('_') - sty1 = dtmp[lk[1][0:2]] + lk[1][2:] - sty2 = dtmp[lk[2][0:2]] + lk[2][2:] - if len(lk) == 3: - self.addtransvar(k,sty1+' -> '+sty2+u' weight (µS)') - else: - self.addtransvar(k,sty1+' -> '+sty2+' '+lk[3].upper()+u' weight (µS)') - -class HelpDialog (QDialog): - def __init__ (self, parent): - super(HelpDialog, self).__init__(parent) - self.initUI() - - def initUI (self): - self.layout = QVBoxLayout(self) - # Add stretch to separate the form layout from the button - self.layout.addStretch(1) - - setscalegeom(self, 100, 100, 300, 100) - self.setWindowTitle('Help') - -class SchematicDialog (QDialog): - # class for holding model schematics (and parameter shortcuts) - def __init__ (self, parent): - super(SchematicDialog, self).__init__(parent) - self.initUI() - - def initUI (self): - - self.setWindowTitle('Model Schematics') - QToolTip.setFont(QFont('SansSerif', 10)) - - self.grid = grid = QGridLayout() - grid.setSpacing(10) - - gRow = 0 - - self.locbtn = QPushButton('Local Network'+os.linesep+'Connections',self) - self.locbtn.setIcon(QIcon(lookupresource('connfig'))) - self.locbtn.clicked.connect(self.parent().shownetparamwin) - self.grid.addWidget(self.locbtn,gRow,0,1,1) - - self.proxbtn = QPushButton('Proximal Drive'+os.linesep+'Thalamus',self) - self.proxbtn.setIcon(QIcon(lookupresource('proxfig'))) - self.proxbtn.clicked.connect(self.parent().showproxparamwin) - self.grid.addWidget(self.proxbtn,gRow,1,1,1) - - self.distbtn = QPushButton('Distal Drive NonLemniscal'+os.linesep+'Thal./Cortical Feedback',self) - self.distbtn.setIcon(QIcon(lookupresource('distfig'))) - self.distbtn.clicked.connect(self.parent().showdistparamwin) - self.grid.addWidget(self.distbtn,gRow,2,1,1) - - gRow = 1 - - # for schematic dialog box - self.pixConn = QPixmap(lookupresource('connfig')) - self.pixConnlbl = ClickLabel(self) - self.pixConnlbl.setScaledContents(True) - #self.pixConnlbl.resize(self.pixConnlbl.size()) - self.pixConnlbl.setPixmap(self.pixConn) - # self.pixConnlbl.clicked.connect(self.shownetparamwin) - self.grid.addWidget(self.pixConnlbl,gRow,0,1,1) - - self.pixProx = QPixmap(lookupresource('proxfig')) - self.pixProxlbl = ClickLabel(self) - self.pixProxlbl.setScaledContents(True) - self.pixProxlbl.setPixmap(self.pixProx) - # self.pixProxlbl.clicked.connect(self.showproxparamwin) - self.grid.addWidget(self.pixProxlbl,gRow,1,1,1) - - self.pixDist = QPixmap(lookupresource('distfig')) - self.pixDistlbl = ClickLabel(self) - self.pixDistlbl.setScaledContents(True) - self.pixDistlbl.setPixmap(self.pixDist) - # self.pixDistlbl.clicked.connect(self.showdistparamwin) - self.grid.addWidget(self.pixDistlbl,gRow,2,1,1) - - self.setLayout(grid) - -class BaseParamDialog (QDialog): - # base widget for specifying params (contains buttons to create other widgets - def __init__ (self, parent, paramfn, optrun_func): - super(BaseParamDialog, self).__init__(parent) - self.proxparamwin = self.distparamwin = self.netparamwin = self.syngainparamwin = None - self.runparamwin = RunParamDialog(self) - self.cellparamwin = CellParamDialog(self) - self.netparamwin = NetworkParamDialog(self) - self.syngainparamwin = SynGainParamDialog(self,self.netparamwin) - self.proxparamwin = OngoingInputParamDialog(self,'Proximal') - self.distparamwin = OngoingInputParamDialog(self,'Distal') - self.evparamwin = EvokedInputParamDialog(self,None) - self.optparamwin = OptEvokedInputParamDialog(self,optrun_func) - self.poisparamwin = PoissonInputParamDialog(self,None) - self.tonicparamwin = TonicInputParamDialog(self,None) - self.lsubwin = [self.runparamwin, self.cellparamwin, self.netparamwin, - self.proxparamwin, self.distparamwin, self.evparamwin, - self.poisparamwin, self.tonicparamwin, self.optparamwin] - self.paramfn = paramfn - self.parent = parent - - self.params = read_params(self.paramfn) - self.initUI() # requires self.params - self.updateDispParam(self.params) - - def updateDispParam(self, params=None): - global drawavgdpl - - if params is None: - try: - params = read_params(self.paramfn) - except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not" - "retrieve parameters from %s" % - self.paramfn) - return - - self.params = params - - if usingEvokedInputs(self.params): - # default for evoked is to show average dipole - drawavgdpl = True - elif usingOngoingInputs(self.params): - # default for ongoing is NOT to show average dipole - drawavgdpl = False - - for dlg in self.lsubwin: - dlg.setfromdin(self.params) # update to values from file - self.qle.setText(self.params['sim_prefix']) # update simulation name - - def setrunparam (self): bringwintotop(self.runparamwin) - def setcellparam (self): bringwintotop(self.cellparamwin) - def setnetparam (self): bringwintotop(self.netparamwin) - def setsyngainparam (self): bringwintotop(self.syngainparamwin) - def setproxparam (self): bringwintotop(self.proxparamwin) - def setdistparam (self): bringwintotop(self.distparamwin) - def setevparam (self): bringwintotop(self.evparamwin) - def setpoisparam (self): bringwintotop(self.poisparamwin) - def settonicparam (self): bringwintotop(self.tonicparamwin) - - def initUI (self): - grid = QGridLayout() - grid.setSpacing(10) - - row = 1 - - self.lbl = QLabel(self) - self.lbl.setText('Simulation Name:') - self.lbl.adjustSize() - self.lbl.setToolTip('Simulation Name used to save parameter file and simulation data') - grid.addWidget(self.lbl, row, 0) - self.qle = QLineEdit(self) - self.qle.setText(self.params['sim_prefix']) - grid.addWidget(self.qle, row, 1) - row+=1 - - self.btnrun = QPushButton('Run',self) - self.btnrun.resize(self.btnrun.sizeHint()) - self.btnrun.setToolTip('Set Run Parameters') - self.btnrun.clicked.connect(self.setrunparam) - grid.addWidget(self.btnrun, row, 0, 1, 1) - - self.btncell = QPushButton('Cell',self) - self.btncell.resize(self.btncell.sizeHint()) - self.btncell.setToolTip('Set Cell (Geometry, Synapses, Biophysics) Parameters') - self.btncell.clicked.connect(self.setcellparam) - grid.addWidget(self.btncell, row, 1, 1, 1) - row+=1 - - self.btnnet = QPushButton('Local Network',self) - self.btnnet.resize(self.btnnet.sizeHint()) - self.btnnet.setToolTip('Set Local Network Parameters') - self.btnnet.clicked.connect(self.setnetparam) - grid.addWidget(self.btnnet, row, 0, 1, 1) - - self.btnsyngain = QPushButton('Synaptic Gains',self) - self.btnsyngain.resize(self.btnsyngain.sizeHint()) - self.btnsyngain.setToolTip('Set Local Network Synaptic Gains') - self.btnsyngain.clicked.connect(self.setsyngainparam) - grid.addWidget(self.btnsyngain, row, 1, 1, 1) - - row+=1 - - self.btnprox = QPushButton('Rhythmic Proximal Inputs',self) - self.btnprox.resize(self.btnprox.sizeHint()) - self.btnprox.setToolTip('Set Rhythmic Proximal Inputs') - self.btnprox.clicked.connect(self.setproxparam) - grid.addWidget(self.btnprox, row, 0, 1, 2); row+=1 - - self.btndist = QPushButton('Rhythmic Distal Inputs',self) - self.btndist.resize(self.btndist.sizeHint()) - self.btndist.setToolTip('Set Rhythmic Distal Inputs') - self.btndist.clicked.connect(self.setdistparam) - grid.addWidget(self.btndist, row, 0, 1, 2) - row+=1 - - self.btnev = QPushButton('Evoked Inputs',self) - self.btnev.resize(self.btnev.sizeHint()) - self.btnev.setToolTip('Set Evoked Inputs') - self.btnev.clicked.connect(self.setevparam) - grid.addWidget(self.btnev, row, 0, 1, 2) - row+=1 - - self.btnpois = QPushButton('Poisson Inputs',self) - self.btnpois.resize(self.btnpois.sizeHint()) - self.btnpois.setToolTip('Set Poisson Inputs') - self.btnpois.clicked.connect(self.setpoisparam) - grid.addWidget(self.btnpois, row, 0, 1, 2) - row+=1 - - self.btntonic = QPushButton('Tonic Inputs',self) - self.btntonic.resize(self.btntonic.sizeHint()) - self.btntonic.setToolTip('Set Tonic (Current Clamp) Inputs') - self.btntonic.clicked.connect(self.settonicparam) - grid.addWidget(self.btntonic, row, 0, 1, 2) - row+=1 - - self.btnsave = QPushButton('Save Parameters To File',self) - self.btnsave.resize(self.btnsave.sizeHint()) - self.btnsave.setToolTip('Save All Parameters to File (Specified by Simulation Name)') - self.btnsave.clicked.connect(self.saveparams) - grid.addWidget(self.btnsave, row, 0, 1, 2) - row+=1 - - self.btnhide = QPushButton('Hide Window',self) - self.btnhide.resize(self.btnhide.sizeHint()) - self.btnhide.clicked.connect(self.hide) - self.btnhide.setToolTip('Hide Window') - grid.addWidget(self.btnhide, row, 0, 1, 2) - - self.setLayout(grid) - - self.setWindowTitle('Set Parameters') - - def saveparams (self, checkok = True): - param_dir = os.path.join(get_output_dir(), 'param') - tmpf = os.path.join(param_dir, self.qle.text() + '.param') - - oktosave = True - if os.path.isfile(tmpf) and checkok: - self.show() - msg = QMessageBox() - ret = msg.warning(self, 'Over-write file(s)?', - tmpf + ' already exists. Over-write?', - QMessageBox.Ok | QMessageBox.Cancel, - QMessageBox.Ok) - if ret == QMessageBox.Cancel: - oktosave = False - - if oktosave: - os.makedirs(param_dir, exist_ok=True) - - with open(tmpf,'w') as fp: - fp.write(str(self)) - - self.paramfn = tmpf - data_dir = os.path.join(get_output_dir(), 'data') - sim_dir = os.path.join(data_dir, self.qle.text()) - os.makedirs(sim_dir, exist_ok=True) - - return oktosave - - def updatesaveparams (self, dtest): - # update parameter values in GUI (so user can see and so GUI will save these param values) - for win in self.lsubwin: win.setfromdin(dtest) - # save parameters - do not ask if can over-write the param file - self.saveparams(checkok = False) - - def __str__ (self): - s = 'sim_prefix: ' + self.qle.text() + os.linesep - s += 'expmt_groups: {' + self.qle.text() + '}' + os.linesep - for win in self.lsubwin: s += str(win) - return s - -class WaitSimDialog (QDialog): - def __init__ (self, parent): - super(WaitSimDialog, self).__init__(parent) - self.initUI() - self.txt = '' # text for display - - def updatetxt (self,txt): - self.qtxt.append(txt) - - def initUI (self): - self.layout = QVBoxLayout(self) - self.layout.addStretch(1) - - self.qtxt = QTextEdit(self) - self.layout.addWidget(self.qtxt) - - self.stopbtn = stopbtn = QPushButton('Stop All Simulations', self) - stopbtn.setToolTip('Stop All Simulations') - stopbtn.resize(stopbtn.sizeHint()) - stopbtn.clicked.connect(self.stopsim) - self.layout.addWidget(stopbtn) - - setscalegeomcenter(self, 500, 250) - self.setWindowTitle("Simulation Log") - - def stopsim (self): - self.parent().stopsim() - self.hide() - - class HNNGUI (QMainWindow): # main HNN GUI class def __init__ (self): diff --git a/hnn/run.py b/hnn/run.py index d7ac502d1..93e7447bb 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -11,11 +11,11 @@ from copy import deepcopy from math import ceil, isclose from contextlib import redirect_stdout +from psutil import wait_procs, process_iter, NoSuchProcess +import nlopt from PyQt5 import QtCore from hnn_core import simulate_dipole, Network, MPIBackend -import nlopt -from psutil import wait_procs, process_iter, NoSuchProcess from .paramrw import write_legacy_paramf, get_output_dir diff --git a/hnn/visdipole.py b/hnn/visdipole.py index 166c66fc4..14ea2d22a 100644 --- a/hnn/visdipole.py +++ b/hnn/visdipole.py @@ -6,6 +6,7 @@ import numpy as np from PyQt5.QtWidgets import QSizePolicy + import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg diff --git a/hnn/vispsd.py b/hnn/vispsd.py index 2842453f3..670b1e67a 100644 --- a/hnn/vispsd.py +++ b/hnn/vispsd.py @@ -1,17 +1,24 @@ +import sys import os -import numpy as np from math import sqrt -from copy import deepcopy -from PyQt5.QtWidgets import QAction, QSizePolicy, QFileDialog +from PyQt5.QtWidgets import (QSizePolicy, QAction, QFileDialog, + QApplication, qApp) from PyQt5.QtGui import QIcon + +import numpy as np + import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure import matplotlib.gridspec as gridspec + from hnn_core.dipole import average_dipoles, Dipole +from .specfn import MorletSpec +from .paramrw import get_output_dir + from .DataViewGUI import DataViewGUI from .specfn import spec_dpl_kernel, extract_spec diff --git a/hnn/visrast.py b/hnn/visrast.py index 6f3c898f1..31c21d895 100644 --- a/hnn/visrast.py +++ b/hnn/visrast.py @@ -8,13 +8,15 @@ from math import ceil from PyQt5.QtWidgets import QAction, QSizePolicy + import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure -from pylab import convolve import matplotlib.gridspec as gridspec +from pylab import convolve + from .DataViewGUI import DataViewGUI from .paramrw import usingEvokedInputs, usingOngoingInputs, usingPoissonInputs from .spikefn import ExtInputs diff --git a/hnn/visspec.py b/hnn/visspec.py index 5815adf89..10618b578 100644 --- a/hnn/visspec.py +++ b/hnn/visspec.py @@ -8,6 +8,7 @@ from PyQt5.QtWidgets import QSizePolicy, QAction, QFileDialog from PyQt5.QtGui import QIcon + import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg diff --git a/hnn/visvolt.py b/hnn/visvolt.py index 5a2f4ca95..c74258c02 100644 --- a/hnn/visvolt.py +++ b/hnn/visvolt.py @@ -2,19 +2,17 @@ import numpy as np import pickle -from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QToolTip, QPushButton, QFormLayout -from PyQt5.QtWidgets import QMenu, QSizePolicy, QMessageBox, QWidget, QFileDialog, QComboBox, QTabWidget -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QDialog, QGridLayout, QLineEdit, QLabel -from PyQt5.QtWidgets import QCheckBox, QInputDialog -from PyQt5.QtGui import QIcon, QFont, QPixmap -from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal, QObject, pyqtSlot -from PyQt5 import QtCore +from PyQt5.QtWidgets import (QSizePolicy, QGridLayout, QWidget, + QComboBox) +from PyQt5.QtGui import QIcon + import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure import matplotlib.gridspec as gridspec + from hnn_core import read_params from .qt_lib import getmplDPI diff --git a/setup.py b/setup.py index d2be1c90a..0dc8621fa 100644 --- a/setup.py +++ b/setup.py @@ -36,4 +36,6 @@ platforms='any', packages=find_packages(), package_data={'hnn': - ['../param/*.param']}) + ['../param/*.param']}, + install_requires=['hnn-core'] + ) From d1012740fcb8e60c194c96735f411c22e76d989b Mon Sep 17 00:00:00 2001 From: Mainak Jas Date: Tue, 16 Mar 2021 11:07:50 -0400 Subject: [PATCH 076/107] MAINT: Update readme + rename file --- README.md | 2 +- hnn/{dialog.py => qt_dialog.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename hnn/{dialog.py => qt_dialog.py} (100%) diff --git a/README.md b/README.md index 0235f470b..197dafc81 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Please follow the links on our [installation page](installer) to find instructio Just do: - $ ./hnn.sh + $ python hnn.py to start the HNN graphical user interface diff --git a/hnn/dialog.py b/hnn/qt_dialog.py similarity index 100% rename from hnn/dialog.py rename to hnn/qt_dialog.py From a4397ed599801549a3089b7a5a865316bcaa58a4 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 16 Mar 2021 13:35:01 -0400 Subject: [PATCH 077/107] MAINT: flake8 --- hnn/qt_dialog.py | 172 +++++++++++++++++++++++++++++------------------ hnn/vispsd.py | 8 +-- setup.py | 2 +- 3 files changed, 108 insertions(+), 74 deletions(-) diff --git a/hnn/qt_dialog.py b/hnn/qt_dialog.py index 9e819aed9..0eade08e3 100644 --- a/hnn/qt_dialog.py +++ b/hnn/qt_dialog.py @@ -5,7 +5,6 @@ import os from collections import OrderedDict -from glob import glob import multiprocessing from psutil import cpu_count @@ -16,10 +15,11 @@ QVBoxLayout, QHBoxLayout, QGridLayout) from PyQt5.QtGui import QFont, QPixmap, QIcon -from hnn_core import CellResponse, read_params +from hnn_core import read_params from .paramrw import usingOngoingInputs, usingEvokedInputs, get_output_dir -from .qt_lib import setscalegeom, setscalegeomcenter, lookupresource, ClickLabel +from .qt_lib import (setscalegeom, setscalegeomcenter, lookupresource, + ClickLabel) from .qt_evoked import EvokedInputParamDialog, OptEvokedInputParamDialog @@ -604,8 +604,9 @@ def setfromdin(self, din): elif k == 'spec_cmap': self.spec_cmap = v - # for spec_cmap we want the user to be able to change (e.g. 'viridis'), but the - # default is 'jet' to be consistent with prior publications on HNN + # for spec_cmap we want the user to be able to change + # (e.g. 'viridis'), but the default is 'jet' to be consistent with + # prior publications on HNN if 'spec_cmap' not in din: self.spec_cmap = 'jet' @@ -619,7 +620,8 @@ def __str__(self): s += 'spec_cmap: ' + self.spec_cmap + os.linesep return s -# widget to specify (pyramidal) cell parameters (geometry, synapses, biophysics) +# widget to specify (pyramidal) cell parameters (geometry, synapses, +# biophysics) class CellParamDialog (DictDialog): @@ -664,17 +666,18 @@ def initd(self): ('L2Pyr_gabab_tau1', 1.), ('L2Pyr_gabab_tau2', 20.)]) - self.dL2PyrBiophys = OrderedDict([('L2Pyr_soma_gkbar_hh2', 0.01), # Biophysics soma - ('L2Pyr_soma_gnabar_hh2', 0.18), - ('L2Pyr_soma_el_hh2', -65.), - ('L2Pyr_soma_gl_hh2', 4.26e-5), - ('L2Pyr_soma_gbar_km', 250.), - # Biophysics dends - ('L2Pyr_dend_gkbar_hh2', 0.01), - ('L2Pyr_dend_gnabar_hh2', 0.15), - ('L2Pyr_dend_el_hh2', -65.), - ('L2Pyr_dend_gl_hh2', 4.26e-5), - ('L2Pyr_dend_gbar_km', 250.)]) + self.dL2PyrBiophys = OrderedDict([ # Biophysics soma + ('L2Pyr_soma_gkbar_hh2', 0.01), + ('L2Pyr_soma_gnabar_hh2', 0.18), + ('L2Pyr_soma_el_hh2', -65.), + ('L2Pyr_soma_gl_hh2', 4.26e-5), + ('L2Pyr_soma_gbar_km', 250.), + # Biophysics dends + ('L2Pyr_dend_gkbar_hh2', 0.01), + ('L2Pyr_dend_gnabar_hh2', 0.15), + ('L2Pyr_dend_el_hh2', -65.), + ('L2Pyr_dend_gl_hh2', 4.26e-5), + ('L2Pyr_dend_gbar_km', 250.)]) self.dL5PyrGeom = OrderedDict([('L5Pyr_soma_L', 39.), # Soma ('L5Pyr_soma_diam', 28.9), @@ -713,35 +716,40 @@ def initd(self): ('L5Pyr_gabab_tau1', 1.), ('L5Pyr_gabab_tau2', 20.)]) - self.dL5PyrBiophys = OrderedDict([('L5Pyr_soma_gkbar_hh2', 0.01), # Biophysics soma - ('L5Pyr_soma_gnabar_hh2', 0.16), - ('L5Pyr_soma_el_hh2', -65.), - ('L5Pyr_soma_gl_hh2', 4.26e-5), - ('L5Pyr_soma_gbar_ca', 60.), - ('L5Pyr_soma_taur_cad', 20.), - ('L5Pyr_soma_gbar_kca', 2e-4), - ('L5Pyr_soma_gbar_km', 200.), - ('L5Pyr_soma_gbar_cat', 2e-4), - ('L5Pyr_soma_gbar_ar', 1e-6), - # Biophysics dends - ('L5Pyr_dend_gkbar_hh2', 0.01), - ('L5Pyr_dend_gnabar_hh2', 0.14), - ('L5Pyr_dend_el_hh2', -71.), - ('L5Pyr_dend_gl_hh2', 4.26e-5), - ('L5Pyr_dend_gbar_ca', 60.), - ('L5Pyr_dend_taur_cad', 20.), - ('L5Pyr_dend_gbar_kca', 2e-4), - ('L5Pyr_dend_gbar_km', 200.), - ('L5Pyr_dend_gbar_cat', 2e-4), - ('L5Pyr_dend_gbar_ar', 1e-6)]) + self.dL5PyrBiophys = OrderedDict([ # Biophysics soma + ('L5Pyr_soma_gkbar_hh2', 0.01), + ('L5Pyr_soma_gnabar_hh2', 0.16), + ('L5Pyr_soma_el_hh2', -65.), + ('L5Pyr_soma_gl_hh2', 4.26e-5), + ('L5Pyr_soma_gbar_ca', 60.), + ('L5Pyr_soma_taur_cad', 20.), + ('L5Pyr_soma_gbar_kca', 2e-4), + ('L5Pyr_soma_gbar_km', 200.), + ('L5Pyr_soma_gbar_cat', 2e-4), + ('L5Pyr_soma_gbar_ar', 1e-6), + # Biophysics dends + ('L5Pyr_dend_gkbar_hh2', 0.01), + ('L5Pyr_dend_gnabar_hh2', 0.14), + ('L5Pyr_dend_el_hh2', -71.), + ('L5Pyr_dend_gl_hh2', 4.26e-5), + ('L5Pyr_dend_gbar_ca', 60.), + ('L5Pyr_dend_taur_cad', 20.), + ('L5Pyr_dend_gbar_kca', 2e-4), + ('L5Pyr_dend_gbar_km', 200.), + ('L5Pyr_dend_gbar_cat', 2e-4), + ('L5Pyr_dend_gbar_ar', 1e-6)]) dtrans = {'gkbar': 'Kv', 'gnabar': 'Na', 'km': 'Km', 'gl': 'leak', - 'ca': 'Ca', 'kca': 'KCa', 'cat': 'CaT', 'ar': 'HCN', 'cad': 'Ca decay time', - 'dend': 'Dendrite', 'soma': 'Soma', 'apicaltrunk': 'Apical Dendrite Trunk', - 'apical1': 'Apical Dendrite 1', 'apical2': 'Apical Dendrite 2', - 'apical3': 'Apical Dendrite 3', 'apicaltuft': 'Apical Dendrite Tuft', - 'apicaloblique': 'Oblique Apical Dendrite', 'basal1': 'Basal Dendrite 1', - 'basal2': 'Basal Dendrite 2', 'basal3': 'Basal Dendrite 3'} + 'ca': 'Ca', 'kca': 'KCa', 'cat': 'CaT', 'ar': 'HCN', + 'cad': 'Ca decay time', 'dend': 'Dendrite', 'soma': 'Soma', + 'apicaltrunk': 'Apical Dendrite Trunk', + 'apical1': 'Apical Dendrite 1', + 'apical2': 'Apical Dendrite 2', + 'apical3': 'Apical Dendrite 3', + 'apicaltuft': 'Apical Dendrite Tuft', + 'apicaloblique': 'Oblique Apical Dendrite', + 'basal1': 'Basal Dendrite 1', 'basal2': 'Basal Dendrite 2', + 'basal3': 'Basal Dendrite 3'} for d in [self.dL2PyrGeom, self.dL5PyrGeom]: for k in d.keys(): @@ -775,7 +783,8 @@ def initd(self): for k in d.keys(): lk = k.split('_') if lk[2].count('g') > 0: - if lk[3] == 'km' or lk[3] == 'ca' or lk[3] == 'kca' or lk[3] == 'cat' or lk[3] == 'ar': + if lk[3] == 'km' or lk[3] == 'ca' or lk[3] == 'kca' \ + or lk[3] == 'cat' or lk[3] == 'ar': nv = dtrans[lk[1]] + ' ' + \ dtrans[lk[3]] + ' ' + ' channel density ' else: @@ -793,8 +802,9 @@ def initd(self): self.ldict = [self.dL2PyrGeom, self.dL2PyrSyn, self.dL2PyrBiophys, self.dL5PyrGeom, self.dL5PyrSyn, self.dL5PyrBiophys] - self.ltitle = ['L2/3 Pyr Geometry', 'L2/3 Pyr Synapses', 'L2/3 Pyr Biophysics', - 'L5 Pyr Geometry', 'L5 Pyr Synapses', 'L5 Pyr Biophysics'] + self.ltitle = ['L2/3 Pyr Geometry', 'L2/3 Pyr Synapses', + 'L2/3 Pyr Biophysics', 'L5 Pyr Geometry', + 'L5 Pyr Synapses', 'L5 Pyr Biophysics'] self.stitle = 'Cell Parameters' @@ -849,10 +859,10 @@ def initd(self): sty1 = dtmp[lk[1][0:2]] + lk[1][2:] sty2 = dtmp[lk[2][0:2]] + lk[2][2:] if len(lk) == 3: - self.addtransvar(k, sty1+' -> '+sty2+u' weight (µS)') + self.addtransvar(k, sty1 + ' -> ' + sty2 + u' weight (µS)') else: - self.addtransvar(k, sty1+' -> '+sty2+' ' + - lk[3].upper()+u' weight (µS)') + self.addtransvar(k, sty1 + ' -> ' + sty2 + ' ' + + lk[3].upper() + u' weight (µS)') class HelpDialog (QDialog): @@ -886,19 +896,20 @@ def initUI(self): gRow = 0 self.locbtn = QPushButton( - 'Local Network'+os.linesep+'Connections', self) + 'Local Network' + os.linesep + 'Connections', self) self.locbtn.setIcon(QIcon(lookupresource('connfig'))) self.locbtn.clicked.connect(self.parent().shownetparamwin) self.grid.addWidget(self.locbtn, gRow, 0, 1, 1) self.proxbtn = QPushButton( - 'Proximal Drive'+os.linesep+'Thalamus', self) + 'Proximal Drive' + os.linesep + 'Thalamus', self) self.proxbtn.setIcon(QIcon(lookupresource('proxfig'))) self.proxbtn.clicked.connect(self.parent().showproxparamwin) self.grid.addWidget(self.proxbtn, gRow, 1, 1, 1) self.distbtn = QPushButton( - 'Distal Drive NonLemniscal'+os.linesep+'Thal./Cortical Feedback', self) + 'Distal Drive NonLemniscal' + os.linesep + + 'Thal./Cortical Feedback', self) self.distbtn.setIcon(QIcon(lookupresource('distfig'))) self.distbtn.clicked.connect(self.parent().showdistparamwin) self.grid.addWidget(self.distbtn, gRow, 2, 1, 1) @@ -932,10 +943,16 @@ def initUI(self): class BaseParamDialog (QDialog): - # base widget for specifying params (contains buttons to create other widgets + """Base widget for specifying params + + contains buttons to create other widgets + """ def __init__(self, parent, paramfn, optrun_func): super(BaseParamDialog, self).__init__(parent) - self.proxparamwin = self.distparamwin = self.netparamwin = self.syngainparamwin = None + self.proxparamwin = None + self.distparamwin = None + self.netparamwin = None + self.syngainparamwin = None self.runparamwin = RunParamDialog(self) self.cellparamwin = CellParamDialog(self) self.netparamwin = NetworkParamDialog(self) @@ -948,7 +965,8 @@ def __init__(self, parent, paramfn, optrun_func): self.tonicparamwin = TonicInputParamDialog(self, None) self.lsubwin = [self.runparamwin, self.cellparamwin, self.netparamwin, self.proxparamwin, self.distparamwin, self.evparamwin, - self.poisparamwin, self.tonicparamwin, self.optparamwin] + self.poisparamwin, self.tonicparamwin, + self.optparamwin] self.paramfn = paramfn self.parent = parent @@ -971,7 +989,7 @@ def updateDispParam(self, params=None): self.params = params if usingEvokedInputs(self.params): - # default for evoked is to show average dipole + # default for evoked is to show average dipole drawavgdpl = True elif usingOngoingInputs(self.params): # default for ongoing is NOT to show average dipole @@ -981,15 +999,32 @@ def updateDispParam(self, params=None): dlg.setfromdin(self.params) # update to values from file self.qle.setText(self.params['sim_prefix']) # update simulation name - def setrunparam(self): bringwintotop(self.runparamwin) - def setcellparam(self): bringwintotop(self.cellparamwin) - def setnetparam(self): bringwintotop(self.netparamwin) - def setsyngainparam(self): bringwintotop(self.syngainparamwin) - def setproxparam(self): bringwintotop(self.proxparamwin) - def setdistparam(self): bringwintotop(self.distparamwin) - def setevparam(self): bringwintotop(self.evparamwin) - def setpoisparam(self): bringwintotop(self.poisparamwin) - def settonicparam(self): bringwintotop(self.tonicparamwin) + def setrunparam(self): + bringwintotop(self.runparamwin) + + def setcellparam(self): + bringwintotop(self.cellparamwin) + + def setnetparam(self): + bringwintotop(self.netparamwin) + + def setsyngainparam(self): + bringwintotop(self.syngainparamwin) + + def setproxparam(self): + bringwintotop(self.proxparamwin) + + def setdistparam(self): + bringwintotop(self.distparamwin) + + def setevparam(self): + bringwintotop(self.evparamwin) + + def setpoisparam(self): + bringwintotop(self.poisparamwin) + + def settonicparam(self): + bringwintotop(self.tonicparamwin) def initUI(self): grid = QGridLayout() @@ -1118,7 +1153,10 @@ def saveparams(self, checkok=True): return oktosave def updatesaveparams(self, dtest): - # update parameter values in GUI (so user can see and so GUI will save these param values) + """ Update parameter values in GUI + + So user can see and so GUI will save these param values + """ for win in self.lsubwin: win.setfromdin(dtest) # save parameters - do not ask if can over-write the param file diff --git a/hnn/vispsd.py b/hnn/vispsd.py index 670b1e67a..dec15ae65 100644 --- a/hnn/vispsd.py +++ b/hnn/vispsd.py @@ -1,9 +1,8 @@ -import sys import os from math import sqrt +from copy import deepcopy -from PyQt5.QtWidgets import (QSizePolicy, QAction, QFileDialog, - QApplication, qApp) +from PyQt5.QtWidgets import QSizePolicy, QAction, QFileDialog from PyQt5.QtGui import QIcon import numpy as np @@ -16,9 +15,6 @@ from hnn_core.dipole import average_dipoles, Dipole -from .specfn import MorletSpec -from .paramrw import get_output_dir - from .DataViewGUI import DataViewGUI from .specfn import spec_dpl_kernel, extract_spec diff --git a/setup.py b/setup.py index 0dc8621fa..59d909c2f 100644 --- a/setup.py +++ b/setup.py @@ -38,4 +38,4 @@ package_data={'hnn': ['../param/*.param']}, install_requires=['hnn-core'] - ) + ) From 740e91cd0b4dd1a4235e087a2374a80025eee50a Mon Sep 17 00:00:00 2001 From: Mainak Jas Date: Tue, 16 Mar 2021 15:37:06 -0400 Subject: [PATCH 078/107] FIX import --- hnn/hnn_qt5.py | 8 ++++---- hnn/qt_evoked.py | 2 -- hnn/simdat.py | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 4914f3792..d58ca1076 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -30,9 +30,9 @@ from hnn_core.dipole import average_dipoles # HNN modules -from .dialog import (BaseParamDialog, EvokedOrRhythmicDialog, - WaitSimDialog, HelpDialog, SchematicDialog, - bringwintotop, _get_defncore) +from .qt_dialog import (BaseParamDialog, EvokedOrRhythmicDialog, + WaitSimDialog, HelpDialog, SchematicDialog, + bringwintotop, _get_defncore) from .paramrw import (usingOngoingInputs, get_output_dir, write_gids_param, get_fname) from .simdat import SIMCanvas, SimData @@ -1068,5 +1068,5 @@ def done(self, optMode, except_msg): if __name__ == '__main__': app = QApplication(sys.argv) - ex = HNNGUI() + HNNGUI() sys.exit(app.exec_()) diff --git a/hnn/qt_evoked.py b/hnn/qt_evoked.py index f7c78d8de..9558e4654 100644 --- a/hnn/qt_evoked.py +++ b/hnn/qt_evoked.py @@ -525,8 +525,6 @@ def lines2val (self,ksearch,val): if k.count(ksearch) > 0: self.dqline[k].setText(str(val)) - def allOff (self): self.lines2val('gbar',0.0) - def __str__ (self): s = '' for k,v in self.dqline.items(): s += k + ': ' + v.text().strip() + os.linesep diff --git a/hnn/simdat.py b/hnn/simdat.py index 91e978212..220158422 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -11,6 +11,7 @@ import matplotlib.pyplot as plt import matplotlib.patches as mpatches import matplotlib.gridspec as gridspec + from hnn_core import read_spikes from hnn_core.dipole import read_dipole, average_dipoles From da0fb66ca2f525018b8623a8b43973a1a24c28d0 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 16 Mar 2021 20:42:55 -0400 Subject: [PATCH 079/107] MAINT: assign random label when adding subplots --- hnn/visdipole.py | 21 ++++++++++----------- hnn/vispsd.py | 2 +- hnn/visrast.py | 23 ++++++++++++++++++----- hnn/visspec.py | 3 ++- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/hnn/visdipole.py b/hnn/visdipole.py index 14ea2d22a..300eadb06 100644 --- a/hnn/visdipole.py +++ b/hnn/visdipole.py @@ -13,9 +13,15 @@ from matplotlib.figure import Figure fontsize = plt.rcParams['font.size'] = 10 +random_label = np.random.rand(100) class DipoleCanvas(FigureCanvasQTAgg): + """Class for displaying Dipole Viewer + + Required parameters: dipole_scalefctr, tstop, N_trials + """ + def __init__(self, params, sim_data, index, parent=None, width=12, height=10, dpi=120, title='Dipole Viewer'): FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), @@ -38,12 +44,9 @@ def __init__(self, params, sim_data, index, parent=None, width=12, self.plot() - def clearaxes(self): - for ax in self.lax: - ax.set_yticks([]) - ax.cla() + def plot(self): + global random_label - def drawdipole(self, fig): gdx = 311 ltitle = ['Layer 2/3', 'Layer 5', 'Aggregate'] @@ -68,11 +71,9 @@ def drawdipole(self, fig): yl[1] = max(yl[1], np.amax(dpltrial.data[key])) yl = tuple(yl) - self.lax = [] - for key, title in zip(dipole_keys, ltitle): - ax = fig.add_subplot(gdx) - self.lax.append(ax) + ax = self.figure.add_subplot(gdx, label=random_label) + random_label += 1 if key == 'agg': ax.set_xlabel('Time (ms)') @@ -112,6 +113,4 @@ def drawdipole(self, fig): self.figure.subplots_adjust(bottom=0.06, left=0.06, right=1.0, top=0.97, wspace=0.1, hspace=0.09) - def plot(self): - self.drawdipole(self.figure) self.draw() diff --git a/hnn/vispsd.py b/hnn/vispsd.py index dec15ae65..71359f597 100644 --- a/hnn/vispsd.py +++ b/hnn/vispsd.py @@ -203,7 +203,7 @@ def plot(self): class PSDViewGUI(DataViewGUI): """Class for displaying spectrogram viewer - Required parameters in params dict: f_max_spec, sim_prefix + Required parameters: N_trials, f_max_spec, sim_prefix """ def __init__(self, CanvasType, params, sim_data, title): self.specs = [] # used by drawspec diff --git a/hnn/visrast.py b/hnn/visrast.py index 31c21d895..48744cbd8 100644 --- a/hnn/visrast.py +++ b/hnn/visrast.py @@ -26,6 +26,7 @@ rastmarksz = 5 # raster dot size binsz = 5.0 smoothsz = 0 # no smoothing +random_label = np.random.rand(100) # colors for the different cell types dclr = {'L2_pyramidal': 'g', @@ -127,7 +128,9 @@ def clearaxes(self): ax.cla() def drawhist(self, dhist, ntrial, tstop): - ax = self.figure.add_subplot(self.G[-4:-1, :]) + global random_label + ax = self.figure.add_subplot(self.G[-4:-1, :], label=random_label) + random_label += 1 fctr = 1.0 if ntrial > 0: fctr = 1.0 / ntrial @@ -139,6 +142,8 @@ def drawhist(self, dhist, ntrial, tstop): return ax def drawrast(self, dspk, extinputs, haveinputs, fig, G, sz=8): + global random_label + lax = [] lk = ['Cell'] row = 0 @@ -163,7 +168,8 @@ def drawrast(self, dspk, extinputs, haveinputs, fig, G, sz=8): haveOngoingProx = (OngoingInputs and len(dinput['prox']) > 0) if haveEvokedDist or haveOngoingDist: - ax = fig.add_subplot(G[row:row + 2, :]) + ax = fig.add_subplot(G[row:row + 2, :], label=random_label) + random_label += 1 row += 2 lax.append(ax) if haveEvokedDist: @@ -176,7 +182,8 @@ def drawrast(self, dspk, extinputs, haveinputs, fig, G, sz=8): ax.set_ylabel('Distal Input') if haveEvokedProx or haveOngoingProx: - ax2 = fig.add_subplot(G[row:row + 2, :]) + ax2 = fig.add_subplot(G[row:row + 2, :], label=random_label) + random_label += 1 row += 2 lax.append(ax2) if haveEvokedProx: @@ -188,7 +195,8 @@ def drawrast(self, dspk, extinputs, haveinputs, fig, G, sz=8): ax2.set_ylabel('Proximal Input') if PoissonInputs and len(dinput['pois']): - axp = fig.add_subplot(G[row:row + 2, :]) + axp = fig.add_subplot(G[row:row + 2, :], label=random_label) + random_label += 1 row += 2 lax.append(axp) extinputs.plot_hist(axp, 'pois', 0, bins, (0, tstop), @@ -205,7 +213,8 @@ def drawrast(self, dspk, extinputs, haveinputs, fig, G, sz=8): if self.bDrawHist: endrow = -4 - ax = fig.add_subplot(G[row:endrow, :]) + ax = fig.add_subplot(G[row:endrow, :], label=random_label) + random_label += 1 lax.append(ax) ax.scatter(dspk[k][0], dspk[k][1], c=dspk[k][2], s=sz**2) @@ -287,6 +296,10 @@ def plot(self): class SpikeViewGUI(DataViewGUI): + """Class for displaying spiking raster plot viewer + + Required parameters: tstop, N_trials + """ def __init__(self, CanvasType, params, sim_data, title): self.params = params super(SpikeViewGUI, self).__init__(CanvasType, params, sim_data, title) diff --git a/hnn/visspec.py b/hnn/visspec.py index 10618b578..d127fc2b0 100644 --- a/hnn/visspec.py +++ b/hnn/visspec.py @@ -124,7 +124,8 @@ def plot(self): class SpecViewGUI(DataViewGUI): """Class for displaying spectrogram viewer - Required parameters in params dict: f_max_spec, sim_prefix, spec_cmap + Required parameters: N_trials, f_max_spec, sim_prefix, + spec_cmap """ def __init__(self, CanvasType, params, sim_data, title): self.specs = [] From 0d5128415b6d2611bac855552aef860eb95071da Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 16 Mar 2021 20:46:58 -0400 Subject: [PATCH 080/107] MAINT: change parameter save_vsoma to record_vsoma --- hnn/qt_dialog.py | 4 ++-- param/Alpha.param | 2 +- param/AlphaAndBeta.param | 2 +- param/AlphaAndBeta2.param | 2 +- param/AlphaAndBetaJitter0.param | 2 +- param/AlphaAndBetaSpike.param | 2 +- param/AlphaAndMoreBeta.param | 2 +- param/ERPNo100Trials.param | 2 +- param/ERPYes100Trials.param | 2 +- param/ERPYesSupraT.param | 2 +- param/OnlyRhythmicDist.param | 2 +- param/OnlyRhythmicProx.param | 2 +- param/SRJ_2007_Fig5A_Super.param | 2 +- param/SRJ_2007_Fig5A_ThreshNonP.param | 2 +- param/SRJ_2007_Fig6_ThreshP.param | 2 +- param/default.param | 2 +- param/gamma_L5ping_L2ping.param | 2 +- param/gamma_L5weak_L2weak.param | 2 +- param/gamma_L5weak_L2weak_bursty.param | 2 +- param/gamma_rhythmic_drive.param | 2 +- param/gamma_rhythmic_drive_more_noise.param | 2 +- 21 files changed, 22 insertions(+), 22 deletions(-) diff --git a/hnn/qt_dialog.py b/hnn/qt_dialog.py index 0eade08e3..70bea7470 100644 --- a/hnn/qt_dialog.py +++ b/hnn/qt_dialog.py @@ -510,7 +510,7 @@ def initd(self): ('f_max_spec', 40), ('dipole_scalefctr', 30e3), ('dipole_smooth_win', 15.0), - ('save_vsoma', 0)]) + ('record_vsoma', 0)]) self.drand = OrderedDict([('prng_seedcore_opt', 0), ('prng_seedcore_input_prox', 0), @@ -537,7 +537,7 @@ def initd(self): self.addtransvar('spec_cmap', 'Spectrogram Colormap') self.addtransvar('dipole_scalefctr', 'Dipole Scaling') self.addtransvar('dipole_smooth_win', 'Dipole Smooth Window (ms)') - self.addtransvar('save_vsoma', 'Save Somatic Voltages') + self.addtransvar('record_vsoma', 'Record Somatic Voltages') self.addtransvar('prng_seedcore_opt', 'Parameter Optimization') self.addtransvar('prng_seedcore_input_prox', 'Ongoing Proximal Input') self.addtransvar('prng_seedcore_input_dist', 'Ongoing Distal Input') diff --git a/param/Alpha.param b/param/Alpha.param index ced4c5e2c..fed1c533b 100644 --- a/param/Alpha.param +++ b/param/Alpha.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 40. dipole_scalefctr: 300000.0 dipole_smooth_win: 0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_input_prox: 13 prng_seedcore_input_dist: 14 prng_seedcore_extpois: 4 diff --git a/param/AlphaAndBeta.param b/param/AlphaAndBeta.param index 3721bb727..60941e3ca 100644 --- a/param/AlphaAndBeta.param +++ b/param/AlphaAndBeta.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 40. dipole_scalefctr: 300000.0 dipole_smooth_win: 0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 13 prng_seedcore_input_dist: 14 diff --git a/param/AlphaAndBeta2.param b/param/AlphaAndBeta2.param index 3dbaa3ed8..da0e5e97c 100644 --- a/param/AlphaAndBeta2.param +++ b/param/AlphaAndBeta2.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 40. dipole_scalefctr: 30000.0 dipole_smooth_win: 0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/AlphaAndBetaJitter0.param b/param/AlphaAndBetaJitter0.param index c240802ac..98bba1003 100644 --- a/param/AlphaAndBetaJitter0.param +++ b/param/AlphaAndBetaJitter0.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 40. dipole_scalefctr: 150000.0 dipole_smooth_win: 0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/AlphaAndBetaSpike.param b/param/AlphaAndBetaSpike.param index 5b90bcf99..e2bfe3a2c 100644 --- a/param/AlphaAndBetaSpike.param +++ b/param/AlphaAndBetaSpike.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 120 dipole_scalefctr: 150000.0 dipole_smooth_win: 0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/AlphaAndMoreBeta.param b/param/AlphaAndMoreBeta.param index 94d347eea..3fdc3432d 100644 --- a/param/AlphaAndMoreBeta.param +++ b/param/AlphaAndMoreBeta.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 40. dipole_scalefctr: 150000.0 dipole_smooth_win: 0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/ERPNo100Trials.param b/param/ERPNo100Trials.param index 3e21909c8..22da2767f 100644 --- a/param/ERPNo100Trials.param +++ b/param/ERPNo100Trials.param @@ -10,7 +10,7 @@ save_spec_data: 0 f_max_spec: 100 dipole_scalefctr: 3000 dipole_smooth_win: 30 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/ERPYes100Trials.param b/param/ERPYes100Trials.param index 222e39db2..3c33db67a 100644 --- a/param/ERPYes100Trials.param +++ b/param/ERPYes100Trials.param @@ -10,7 +10,7 @@ save_spec_data: 0 f_max_spec: 100 dipole_scalefctr: 3000 dipole_smooth_win: 30 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/ERPYesSupraT.param b/param/ERPYesSupraT.param index 08ca4d818..147e41363 100644 --- a/param/ERPYesSupraT.param +++ b/param/ERPYesSupraT.param @@ -10,7 +10,7 @@ save_spec_data: 0 f_max_spec: 100 dipole_scalefctr: 3000 dipole_smooth_win: 20 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/OnlyRhythmicDist.param b/param/OnlyRhythmicDist.param index 078226e9a..2314018cf 100644 --- a/param/OnlyRhythmicDist.param +++ b/param/OnlyRhythmicDist.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 40. dipole_scalefctr: 150000.0 dipole_smooth_win: 0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/OnlyRhythmicProx.param b/param/OnlyRhythmicProx.param index a7e482f18..7412b7e86 100644 --- a/param/OnlyRhythmicProx.param +++ b/param/OnlyRhythmicProx.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 40. dipole_scalefctr: 150000.0 dipole_smooth_win: 0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/SRJ_2007_Fig5A_Super.param b/param/SRJ_2007_Fig5A_Super.param index a2afb3318..2dfe20579 100644 --- a/param/SRJ_2007_Fig5A_Super.param +++ b/param/SRJ_2007_Fig5A_Super.param @@ -10,7 +10,7 @@ save_spec_data: 0 f_max_spec: 100 dipole_scalefctr: 3000 dipole_smooth_win: 12.5 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/SRJ_2007_Fig5A_ThreshNonP.param b/param/SRJ_2007_Fig5A_ThreshNonP.param index 86f620b3e..f14a5293c 100644 --- a/param/SRJ_2007_Fig5A_ThreshNonP.param +++ b/param/SRJ_2007_Fig5A_ThreshNonP.param @@ -10,7 +10,7 @@ save_spec_data: 0 f_max_spec: 100 dipole_scalefctr: 3000 dipole_smooth_win: 1.0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/SRJ_2007_Fig6_ThreshP.param b/param/SRJ_2007_Fig6_ThreshP.param index 52906329a..451e6f270 100644 --- a/param/SRJ_2007_Fig6_ThreshP.param +++ b/param/SRJ_2007_Fig6_ThreshP.param @@ -10,7 +10,7 @@ save_spec_data: 0 f_max_spec: 100 dipole_scalefctr: 3000 dipole_smooth_win: 1.0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/default.param b/param/default.param index 51cfaf865..89ca3e752 100644 --- a/param/default.param +++ b/param/default.param @@ -10,7 +10,7 @@ save_spec_data: 0 f_max_spec: 100 dipole_scalefctr: 3000 dipole_smooth_win: 30 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 4 prng_seedcore_input_dist: 4 diff --git a/param/gamma_L5ping_L2ping.param b/param/gamma_L5ping_L2ping.param index 385bc2c74..d482c9150 100644 --- a/param/gamma_L5ping_L2ping.param +++ b/param/gamma_L5ping_L2ping.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 80. dipole_scalefctr: 30000.0 dipole_smooth_win: 5.0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 0 prng_seedcore_input_dist: 0 diff --git a/param/gamma_L5weak_L2weak.param b/param/gamma_L5weak_L2weak.param index cff75b722..e92372e92 100644 --- a/param/gamma_L5weak_L2weak.param +++ b/param/gamma_L5weak_L2weak.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 100 dipole_scalefctr: 30000.0 dipole_smooth_win: 0.0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 0 prng_seedcore_input_dist: 0 diff --git a/param/gamma_L5weak_L2weak_bursty.param b/param/gamma_L5weak_L2weak_bursty.param index b1a514b29..5409adae0 100644 --- a/param/gamma_L5weak_L2weak_bursty.param +++ b/param/gamma_L5weak_L2weak_bursty.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 100 dipole_scalefctr: 5.0 dipole_smooth_win: 0.0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 0 prng_seedcore_input_dist: 0 diff --git a/param/gamma_rhythmic_drive.param b/param/gamma_rhythmic_drive.param index 91032898d..2f187ccbd 100644 --- a/param/gamma_rhythmic_drive.param +++ b/param/gamma_rhythmic_drive.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 140.0 dipole_scalefctr: 30000.0 dipole_smooth_win: 0.0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 0 prng_seedcore_input_dist: 0 diff --git a/param/gamma_rhythmic_drive_more_noise.param b/param/gamma_rhythmic_drive_more_noise.param index c7438caff..a961ba1ec 100644 --- a/param/gamma_rhythmic_drive_more_noise.param +++ b/param/gamma_rhythmic_drive_more_noise.param @@ -10,7 +10,7 @@ save_spec_data: 1 f_max_spec: 140.0 dipole_scalefctr: 30000.0 dipole_smooth_win: 0.0 -save_vsoma: 0 +record_vsoma: 0 prng_seedcore_opt: 0 prng_seedcore_input_prox: 0 prng_seedcore_input_dist: 0 From ed6088b790ba7a804946d16d6ed7c95984878271 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Tue, 16 Mar 2021 20:47:50 -0400 Subject: [PATCH 081/107] MAINT: fix somatic voltage plotting for hnn-core --- hnn/hnn_qt5.py | 100 +++++++++-------- hnn/run.py | 5 +- hnn/simdat.py | 62 ++++++++++- hnn/visvolt.py | 297 ++++++++++++++++++++++++------------------------- 4 files changed, 263 insertions(+), 201 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 4914f3792..e1695ceb0 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -30,7 +30,7 @@ from hnn_core.dipole import average_dipoles # HNN modules -from .dialog import (BaseParamDialog, EvokedOrRhythmicDialog, +from .qt_dialog import (BaseParamDialog, EvokedOrRhythmicDialog, WaitSimDialog, HelpDialog, SchematicDialog, bringwintotop, _get_defncore) from .paramrw import (usingOngoingInputs, get_output_dir, @@ -38,8 +38,10 @@ from .simdat import SIMCanvas, SimData from .run import RunSimThread, ParamSignal from .qt_lib import getmplDPI, getscreengeom, lookupresource, setscalegeomcenter +from .specfn import spec_dpl_kernel, save_spec_data from .DataViewGUI import DataViewGUI from .visdipole import DipoleCanvas +from .visvolt import VoltViewGUI, VoltCanvas from .visspec import SpecViewGUI, SpecCanvas from .visrast import SpikeViewGUI, SpikeCanvas from .vispsd import PSDViewGUI, PSDCanvas @@ -293,59 +295,61 @@ def showHelpDialog(self): # show the help dialog box bringwintotop(self.helpwin) - def showSomaVPlot(self): - # start the somatic voltage visualization process (separate window) - if not float(self.baseparamwin.runparamwin.getval('save_vsoma')): - smsg='In order to view somatic voltages you must first rerun the simulation with saving somatic voltages. To do so from the main GUI, click on Set Parameters -> Run -> Analysis -> Save Somatic Voltages, enter a 1 and then rerun the simulation.' - msg = QMessageBox() - msg.setIcon(QMessageBox.Information) - msg.setText(smsg) - msg.setWindowTitle('Rerun simulation') - msg.setStandardButtons(QMessageBox.Ok) - msg.exec_() - else: - outdir = os.path.join(get_output_dir(), 'data', self.baseparamwin.params['sim_prefix']) - outparamf = os.path.join(outdir, - self.baseparamwin.params['sim_prefix'] + - '.param') - lcmd = [getPyComm(), 'visvolt.py',outparamf] - Popen(lcmd) # nonblocking - - def showPSDPlot(self): - if self.baseparamwin.paramfn is None: + def show_plot(self, plot_type): + paramfn = self.baseparamwin.paramfn + if paramfn is None: return - if self.baseparamwin.paramfn in self.sim_data._sim_data: - sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + if paramfn in self.sim_data._sim_data: + sim_data = self.sim_data._sim_data[paramfn]['data'] else: sim_data = None - PSDViewGUI(PSDCanvas, self.baseparamwin.params, sim_data, 'PSD Viewer') - def showSpecPlot(self): - if self.baseparamwin.paramfn is None: - return - if self.baseparamwin.paramfn in self.sim_data._sim_data: - sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + if plot_type == 'dipole': + DataViewGUI(DipoleCanvas, self.baseparamwin.params, sim_data, + 'Dipole Viewer') + elif plot_type == 'volt': + VoltViewGUI(VoltCanvas, self.baseparamwin.params, sim_data, + 'Dipole Viewer') + elif plot_type == 'PSD': + PSDViewGUI(PSDCanvas, self.baseparamwin.params, sim_data, + 'PSD Viewer') + elif plot_type == 'spec': + SpecViewGUI(SpecCanvas, self.baseparamwin.params, sim_data, + 'Spectrogram Viewer') + elif plot_type == 'spike': + SpikeViewGUI(SpikeCanvas, self.baseparamwin.params, sim_data, + 'Spike Viewer') else: - sim_data = None - SpecViewGUI(SpecCanvas, self.baseparamwin.params, sim_data, 'Spectrogram Viewer') + raise ValueError("Unknown plot type") - def showRasterPlot(self): - if self.baseparamwin.paramfn is None: - return - if self.baseparamwin.paramfn in self.sim_data._sim_data: - sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] + def showSomaVPlot(self): + # start the somatic voltage visualization process (separate window) + if not float(self.baseparamwin.params['record_vsoma']): + smsg='In order to view somatic voltages you must first rerun' + \ + ' the simulation with saving somatic voltages. To do so' + \ + ' from the main GUI, click on Set Parameters -> Run ->' + \ + ' Analysis -> Save Somatic Voltages, enter a 1 and then' + \ + ' rerun the simulation.' + msg = QMessageBox() + msg.setIcon(QMessageBox.Information) + msg.setText(smsg) + msg.setWindowTitle('Rerun simulation') + msg.setStandardButtons(QMessageBox.Ok) + msg.exec_() else: - sim_data = None - SpikeViewGUI(SpikeCanvas, self.baseparamwin.params, sim_data, 'Spike Viewer') + self.show_plot('volt') + + def showPSDPlot(self): + self.show_plot('PSD') + + def showSpecPlot(self): + self.show_plot('spec') + + def showRasterPlot(self): + self.show_plot('spike') def showDipolePlot(self): - if self.baseparamwin.paramfn is None: - return - if self.baseparamwin.paramfn in self.sim_data._sim_data: - sim_data = self.sim_data._sim_data[self.baseparamwin.paramfn]['data'] - else: - sim_data = None - DataViewGUI(DipoleCanvas, self.baseparamwin.params, sim_data, 'Dipole Viewer') + self.show_plot('dipole') def showwaitsimwin(self): # show the wait sim window (has simulation log) @@ -1007,7 +1011,7 @@ def result_callback(self, result): self.sim_data.update_sim_data(paramfn, params, sim_data['dpls'], sim_data['avg_dpl'], sim_data['spikes'], sim_data['gid_ranges'], - sim_data['spec']) + sim_data['spec'], sim_data['vsoma']) def done(self, optMode, except_msg): # called when the simulation completes running @@ -1050,6 +1054,10 @@ def done(self, optMode, except_msg): self.sim_data.save_spec_with_hist(self.baseparamwin.paramfn, self.baseparamwin.params) + if self.baseparamwin.params['record_vsoma']: + self.sim_data.save_vsoma(self.baseparamwin.paramfn, + self.baseparamwin.params) + data_dir = os.path.join(get_output_dir(), 'data') sim_dir = os.path.join(data_dir, self.baseparamwin.params['sim_prefix']) diff --git a/hnn/run.py b/hnn/run.py index 93e7447bb..625f97911 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -108,11 +108,14 @@ def simulate(params, n_procs=None): sim_data = {} # run the simulation with MPIBackend for faster completion time with MPIBackend(n_procs=n_procs, mpi_cmd='mpiexec'): + record_vsoma = bool(params['record_vsoma']) sim_data['raw_dpls'] = simulate_dipole(net, params['N_trials'], - postproc=False) + postproc=False, + record_vsoma=record_vsoma) sim_data['gid_ranges'] = net.gid_ranges sim_data['spikes'] = net.cell_response + sim_data['vsoma'] = net.cell_response.vsoma return sim_data diff --git a/hnn/simdat.py b/hnn/simdat.py index 91e978212..bf3a1e699 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -2,6 +2,7 @@ import numpy as np from math import ceil from glob import glob +from pickle import dump, load from scipy import signal from PyQt5 import QtWidgets @@ -99,6 +100,33 @@ def read_spectrials(sim_dir): return spec_list +def read_vsomatrials(sim_dir): + """read somatic voltage data files for individual trials""" + vsoma_list = [] + + vsoma_fname_pattern = os.path.join(sim_dir, 'vsoma_*.pkl') + glob_list = sorted(glob(str(vsoma_fname_pattern))) + if len(glob_list) == 0: + # get the old style filename + glob_list = [get_fname(sim_dir, 'vsoma')] + + for vsoma_fn in glob_list: + vsoma_trial = None + try: + with open(vsoma_fn, 'rb') as f: + vsoma_trial = load(f) + except OSError: + if os.path.exists(sim_dir): + print('Warning: could not read file:', vsoma_fn) + except ValueError: + if os.path.exists(sim_dir): + print('Warning: could not read file:', vsoma_fn) + + vsoma_list.append(vsoma_trial) + + return vsoma_list + + def read_spktrials(sim_dir, gid_ranges): spk_fname_pattern = os.path.join(sim_dir, 'spk_*.txt') if len(glob(str(spk_fname_pattern))) == 0: @@ -221,13 +249,13 @@ def remove_sim_by_fn(self, paramfn): del self._sim_data[paramfn] def update_sim_data(self, paramfn, params, dpls, avg_dpl, spikes, - gid_ranges, spec=None): + gid_ranges, spec=None, vsoma=None): self._sim_data[paramfn] = {'params': params, 'data': {'dpls': dpls, 'avg_dpl': avg_dpl, 'spikes': spikes, 'gid_ranges': gid_ranges, - 'spec': spec}} + 'spec': spec, 'vsoma': vsoma}} def clear_exp_data(self): """Clear all experimental data from SimData""" @@ -321,8 +349,18 @@ def update_sim_data_from_disk(self, paramfn, params): print("Warning: only read %d of %d spec files in %s" % (len(spec), params['N_trials'], sim_dir)) + # somatic voltages + vsoma = None + if params['record_vsoma']: + vsoma = read_vsomatrials(sim_dir) + if len(vsoma) == 0: + print("Warning: no somatic voltages read from %s" % sim_dir) + elif len(vsoma) < params['N_trials']: + print("Warning: only read %d of %d voltage files in %s" % + (len(vsoma), params['N_trials'], sim_dir)) + self.update_sim_data(paramfn, params, dpls, avg_dpl, spikes, - gid_ranges, spec) + gid_ranges, spec, vsoma) return True @@ -543,6 +581,24 @@ def save_dipole_with_hist(self, paramfn, params): f.savefig(dipole_fig_fn, dpi=300) plt.close(f) + def save_vsoma(self, paramfn, params): + ntrial = params['N_trials'] + sim_dir = os.path.join(self._data_dir, params['sim_prefix']) + current_sim_data = self._sim_data[paramfn]['data'] + + for trial_idx in range(ntrial): + vsoma_outfn = get_fname(sim_dir, 'vsoma', trial_idx) + + if trial_idx + 1 > len(current_sim_data['vsoma']): + raise ValueError("No vsoma data for trial %d" % trial_idx) + + vsoma = current_sim_data['vsoma'][trial_idx] + + # store tvec with voltages. it will be the same for + # all trials + vsoma['vtime'] = current_sim_data['dpls'][0].times + with open(str(vsoma_outfn), 'wb') as f: + dump(vsoma, f) class SIMCanvas (FigureCanvasQTAgg): # matplotlib/pyqt-compatible canvas for drawing simulation & external data diff --git a/hnn/visvolt.py b/hnn/visvolt.py index c74258c02..183b6ac2c 100644 --- a/hnn/visvolt.py +++ b/hnn/visvolt.py @@ -1,161 +1,156 @@ -import sys, os -import numpy as np -import pickle +"""Create the somatic voltage viewing window""" + +# Authors: Sam Neymotin +# Blake Caldwell + +from PyQt5.QtWidgets import QSizePolicy -from PyQt5.QtWidgets import (QSizePolicy, QGridLayout, QWidget, - QComboBox) -from PyQt5.QtGui import QIcon +import numpy as np import matplotlib.pyplot as plt import matplotlib.patches as mpatches -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure import matplotlib.gridspec as gridspec -from hnn_core import read_params - -from .qt_lib import getmplDPI -from .paramrw import get_output_dir from .DataViewGUI import DataViewGUI fontsize = plt.rcParams['font.size'] = 10 - -# colors for the different cell types -dclr = {'L2_pyramidal' : 'g', - 'L5_pyramidal' : 'r', - 'L2_basket' : 'w', - 'L5_basket' : 'b'} - -maxperty = 10 # how many cells of a type to draw - -if ntrial <= 1: - voltpath = os.path.join(get_output_dir(), 'data', params['sim_prefix'], 'vsoma.pkl') -else: - voltpath = os.path.join(get_output_dir(), 'data', params['sim_prefix'], 'vsoma_1.pkl') - -class VoltCanvas (FigureCanvas): - def __init__ (self, paramf, index, parent=None, width=12, height=10, dpi=120, title='Voltage Viewer'): - FigureCanvas.__init__(self, Figure(figsize=(width, height), dpi=dpi)) - self.title = title - self.setParent(parent) - self.gui = parent - self.index = index - self.invertedax = False - FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding) - FigureCanvas.updateGeometry(self) - self.paramf = paramf - self.G = gridspec.GridSpec(10,1) - self.plot() - - def drawvolt (self, dvolt, fig, G, sz=8, ltextra=''): - row = 0 - ax = fig.add_subplot(G[row:-1,:]) - lax = [ax] - dcnt = {} # counts number of times cell of a type drawn - vtime = dvolt['vtime'] - yoff = 0 - # print(dvolt.keys()) - for gid,it in dvolt.items(): - ty,vsoma = it[0],it[1] - # print('ty:',ty,'gid:',gid) - if type(gid) != int: continue - if ty not in dcnt: dcnt[ty] = 1 - if dcnt[ty] > maxperty: continue - #ax.plot(vtime, -vsoma + yoff, dclr[ty], linewidth = self.gui.linewidth) - ax.plot(vtime, -vsoma + yoff, dclr[ty], linewidth = self.gui.linewidth) - yoff += max(vsoma) - min(vsoma) - dcnt[ty] += 1 - - white_patch = mpatches.Patch(color='white', label='L2/3 Basket') - green_patch = mpatches.Patch(color='green', label='L2/3 Pyr') - red_patch = mpatches.Patch(color='red', label='L5 Pyr') - blue_patch = mpatches.Patch(color='blue', label='L5 Basket') - ax.legend(handles=[white_patch,green_patch,blue_patch,red_patch]) - - if not self.invertedax: - ax.set_ylim(ax.get_ylim()[::-1]) - self.invertedax = True - #if not self.invertedax: - # ax.invert_yaxis() - # self.invertedax = True - - ax.set_yticks([]) - - ax.set_facecolor('k') - ax.grid(True) - if tstop != -1: ax.set_xlim((0,tstop)) - if i ==0: ax.set_title(ltextra) - ax.set_xlabel('Time (ms)') - - self.figure.subplots_adjust(bottom=0.01, left=0.01, right=0.99, top=0.99, wspace=0.1, hspace=0.09) - - return lax - - def plot (self): - if self.index == 0: - if ntrial == 1: - dvolt = pickle.load(open(voltpath,'rb')) - else: - dvolt = pickle.load(open(voltpath,'rb')) - self.lax = self.drawvolt(dvolt,self.figure, self.G, 5, ltextra='All Trials') - else: - voltpathtrial = os.path.join(get_output_dir(), 'data', params['sim_prefix'], 'vsoma_'+str(self.index)+'.pkl') - dvolttrial = pickle.load(open(voltpathtrial,'rb')) - self.lax=self.drawvolt(dvolttrial,self.figure, self.G, 5, ltextra='Trial '+str(self.index)) - self.draw() - -class VoltGUI(DataViewGUI): - def __init__(self, CanvasType, params, title): - self.params = params - super(VoltGUI, self).__init__(CanvasType, self.params, title) - self.addLoadDataActions() - self.loadDisplayData() - - def initCanvas (self): - self.invertedax = False - # to avoid memory leaks remove any pre-existing widgets before adding new ones - self.grid.removeWidget(self.m) - self.grid.removeWidget(self.toolbar) - self.m.setParent(None) - self.toolbar.setParent(None) - self.m = self.toolbar = None - - self.m = VoltCanvas(paramf, self.index, parent = self, width=12, height=10, dpi=getmplDPI()) - # this is the Navigation widget - # it takes the Canvas widget and a parent - self.toolbar = NavigationToolbar(self.m, self) - self.grid.addWidget(self.toolbar, 0, 0, 1, 4) - self.grid.addWidget(self.m, 1, 0, 1, 4) - - def initUI (self): - self.initMenu() - self.statusBar() - self.setGeometry(300, 300, 1300, 1100) - self.setWindowTitle('Volt Viewer - ' + paramf) - self.grid = grid = QGridLayout() - self.index = 0 - self.initCanvas() - self.cb = QComboBox(self) - self.grid.addWidget(self.cb,2,0,1,4) - - for i in range(ntrial): self.cb.addItem('Trial ' + str(i+1)) - self.cb.activated[int].connect(self.onActivated) - - # need a separate widget to put grid on - widget = QWidget(self) - widget.setLayout(grid) - self.setCentralWidget(widget) - - self.setWindowIcon(QIcon(os.path.join('res','icon.png'))) - - self.show() - - def onActivated(self, idx): - if idx != self.index: - self.index = idx - self.statusBar().showMessage('Loading data from trial ' + str(self.index+1) + '.') - self.m.index = self.index - self.initCanvas() - self.m.plot() - self.statusBar().showMessage('') +random_label = np.random.rand(100) + + +class VoltCanvas(FigureCanvasQTAgg): + """Class for the somatic voltages viewer + + This is designed to be called from VoltViewGUI class to add functionality + for loading and clearing data + """ + + def __init__(self, params, sim_data, index, parent=None, width=12, + height=10, dpi=120, title='Voltage Viewer'): + FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), + dpi=dpi)) + self.title = title + self.setParent(parent) + self.gui = parent + self.index = index + FigureCanvasQTAgg.setSizePolicy(self, QSizePolicy.Expanding, + QSizePolicy.Expanding) + FigureCanvasQTAgg.updateGeometry(self) + self.params = params + self.invertedax = False + self.G = gridspec.GridSpec(10, 1) + + self.sim_data = sim_data + # colors for the different cell types + self.dclr = {'L2_pyramidal': 'g', + 'L5_pyramidal': 'r', + 'L2_basket': 'w', + 'L5_basket': 'b'} + + self.plot() + + def drawvolt(self, dvolt, times, fig, G, maxperty=10, ltextra=''): + """Create voltage plots + + + Parameters + ---------- + + dvolt: dict + Dictionary with somatic voltages for a single trial. Keys are + gids and value is array of somatic voltages at each timestep + times: array + 1-D array containing the corresponding times to each somatic + voltage + fig: Figure object + The figure to plot voltages + G: GridSpec object + Grid on which to place axes + maxperty: int + How many cells of a type to draw. If None, 10 cells will be shown + ltextra: str + String containing title of window + """ + + global random_label + + ax = fig.add_subplot(G[0:-1, :], label=random_label) + random_label += 1 + + dcnt = {} # counts number of times cell of a type drawn + yoff = 0 + + for gid_type in self.dclr.keys(): + for gid in self.sim_data['gid_ranges'][gid_type]: + if gid_type not in dcnt: + dcnt[gid_type] = 0 + elif dcnt[gid_type] > maxperty: + continue + vsoma = np.array(dvolt[gid]) + ax.plot(times, -vsoma + yoff, self.dclr[gid_type], + linewidth=self.gui.linewidth) + yoff += max(vsoma) - min(vsoma) + dcnt[gid_type] += 1 + + white_patch = mpatches.Patch(color='white', label='L2/3 Basket') + green_patch = mpatches.Patch(color='green', label='L2/3 Pyr') + red_patch = mpatches.Patch(color='red', label='L5 Pyr') + blue_patch = mpatches.Patch(color='blue', label='L5 Basket') + ax.legend(handles=[white_patch, green_patch, blue_patch, red_patch]) + + if not self.invertedax: + ax.set_ylim(ax.get_ylim()[::-1]) + self.invertedax = True + + ax.set_yticks([]) + + ax.set_facecolor('k') + ax.grid(True) + if self.params['tstop'] > 0: + ax.set_xlim((0, self.params['tstop'])) + + ax.set_title(ltextra) + ax.set_xlabel('Time (ms)') + + def plot(self): + if len(self.sim_data['vsoma']) == 0: + # data hasn't been loaded yet + return + + ltextra = 'Trial ' + str(self.index) + volt_data = self.sim_data['vsoma'][self.index] + times = self.sim_data['dpls'][0].times + + self.drawvolt(volt_data, times, self.figure, self.G, + ltextra=ltextra) + self.figure.subplots_adjust(bottom=0.01, left=0.01, right=0.99, + top=0.99, wspace=0.1, hspace=0.09) + + self.draw() + + +class VoltViewGUI(DataViewGUI): + """Class for displaying somatic voltages viewer + + Required parameters in params dict: N_trials, tstop + """ + def __init__(self, CanvasType, params, sim_data, title): + self.params = params + super(VoltViewGUI, self).__init__(CanvasType, params, sim_data, title) + + def updateCB(self): + self.cb.clear() + for i in range(self.ntrial): + self.cb.addItem('Show Trial ' + str(i + 1)) + self.cb.activated[int].connect(self.onActivated) + + def onActivated(self, idx): + if idx != self.index: + self.index = idx + self.statusBar().showMessage('Loading data from trial ' + + str(self.index) + '.') + self.m.index = self.index + self.initCanvas() + self.m.plot() + self.statusBar().showMessage('') From 27539282c5d9138edc3003193b79becd1b954571 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 17 Mar 2021 12:00:08 -0400 Subject: [PATCH 082/107] MAINT: ensure that N_trials is never 0 --- hnn/hnn_qt5.py | 6 ++++++ param/Alpha.param | 2 +- param/AlphaAndBeta.param | 2 +- param/AlphaAndBeta2.param | 2 +- param/AlphaAndBetaSpike.param | 2 +- param/AlphaAndMoreBeta.param | 2 +- param/OnlyRhythmicDist.param | 2 +- param/OnlyRhythmicProx.param | 2 +- param/gamma_L5ping_L2ping.param | 2 +- param/gamma_L5weak_L2weak.param | 2 +- param/gamma_rhythmic_drive.param | 2 +- param/gamma_rhythmic_drive_more_noise.param | 2 +- 12 files changed, 17 insertions(+), 11 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 8dfdcd9f1..fe049cc64 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -195,6 +195,12 @@ def selParamFileDialog(self): tmpfn) return + # check that valid number of trials was given + if 'N_trials' not in params or params['N_trials'] == 0: + print("Warning: invalid configured number of trials." + " Setting 'N_trials' to 1.") + params['N_trials'] = 1 + # Now update GUI components self.baseparamwin.paramfn = tmpfn diff --git a/param/Alpha.param b/param/Alpha.param index fed1c533b..4224a3dc5 100644 --- a/param/Alpha.param +++ b/param/Alpha.param @@ -3,7 +3,7 @@ expmt_groups: {Alpha} tstop: 710. dt: 0.025 celsius: 37.0 -N_trials: 0 +N_trials: 1 threshold: 0.0 save_figs: 0 save_spec_data: 1 diff --git a/param/AlphaAndBeta.param b/param/AlphaAndBeta.param index 60941e3ca..7f41ab9f8 100644 --- a/param/AlphaAndBeta.param +++ b/param/AlphaAndBeta.param @@ -3,7 +3,7 @@ expmt_groups: {AlphaAndBeta} tstop: 710. dt: 0.025 celsius: 37.0 -N_trials: 0 +N_trials: 1 threshold: 0.0 save_figs: 0 save_spec_data: 1 diff --git a/param/AlphaAndBeta2.param b/param/AlphaAndBeta2.param index da0e5e97c..b134dda21 100644 --- a/param/AlphaAndBeta2.param +++ b/param/AlphaAndBeta2.param @@ -3,7 +3,7 @@ expmt_groups: {AlphaAndBeta2} tstop: 710. dt: 0.025 celsius: 37.0 -N_trials: 0 +N_trials: 1 threshold: 0.0 save_figs: 0 save_spec_data: 1 diff --git a/param/AlphaAndBetaSpike.param b/param/AlphaAndBetaSpike.param index e2bfe3a2c..5a51862c0 100644 --- a/param/AlphaAndBetaSpike.param +++ b/param/AlphaAndBetaSpike.param @@ -3,7 +3,7 @@ expmt_groups: {AlphaAndBetaSpike} tstop: 710. dt: 0.025 celsius: 37.0 -N_trials: 0 +N_trials: 1 threshold: 0.0 save_figs: 0 save_spec_data: 1 diff --git a/param/AlphaAndMoreBeta.param b/param/AlphaAndMoreBeta.param index 3fdc3432d..cd4c30410 100644 --- a/param/AlphaAndMoreBeta.param +++ b/param/AlphaAndMoreBeta.param @@ -3,7 +3,7 @@ expmt_groups: {AlphaAndMoreBeta} tstop: 710. dt: 0.025 celsius: 37.0 -N_trials: 0 +N_trials: 1 threshold: 0.0 save_figs: 0 save_spec_data: 1 diff --git a/param/OnlyRhythmicDist.param b/param/OnlyRhythmicDist.param index 2314018cf..394aaa6af 100644 --- a/param/OnlyRhythmicDist.param +++ b/param/OnlyRhythmicDist.param @@ -3,7 +3,7 @@ expmt_groups: {OnlyRhythmicDist} tstop: 710. dt: 0.025 celsius: 37.0 -N_trials: 0 +N_trials: 1 threshold: 0.0 save_figs: 0 save_spec_data: 1 diff --git a/param/OnlyRhythmicProx.param b/param/OnlyRhythmicProx.param index 7412b7e86..7d1e2d41b 100644 --- a/param/OnlyRhythmicProx.param +++ b/param/OnlyRhythmicProx.param @@ -3,7 +3,7 @@ expmt_groups: {OnlyRhythmicProx} tstop: 710. dt: 0.025 celsius: 37.0 -N_trials: 0 +N_trials: 1 threshold: 0.0 save_figs: 0 save_spec_data: 1 diff --git a/param/gamma_L5ping_L2ping.param b/param/gamma_L5ping_L2ping.param index d482c9150..e0ff34c48 100644 --- a/param/gamma_L5ping_L2ping.param +++ b/param/gamma_L5ping_L2ping.param @@ -3,7 +3,7 @@ expmt_groups: {gamma_L5ping_L2ping} tstop: 550. dt: 0.025 celsius: 37.0 -N_trials: 0 +N_trials: 1 threshold: 0.0 save_figs: 0 save_spec_data: 1 diff --git a/param/gamma_L5weak_L2weak.param b/param/gamma_L5weak_L2weak.param index e92372e92..5532bf69b 100644 --- a/param/gamma_L5weak_L2weak.param +++ b/param/gamma_L5weak_L2weak.param @@ -3,7 +3,7 @@ expmt_groups: {gamma_L5weak_L2weak} tstop: 550. dt: 0.025 celsius: 37.0 -N_trials: 0 +N_trials: 1 threshold: 0.0 save_figs: 0 save_spec_data: 1 diff --git a/param/gamma_rhythmic_drive.param b/param/gamma_rhythmic_drive.param index 2f187ccbd..43b4970e7 100644 --- a/param/gamma_rhythmic_drive.param +++ b/param/gamma_rhythmic_drive.param @@ -3,7 +3,7 @@ expmt_groups: {gamma_rhythmic_drive} tstop: 550. dt: 0.025 celsius: 37.0 -N_trials: 0 +N_trials: 1 threshold: 0.0 save_figs: 0 save_spec_data: 1 diff --git a/param/gamma_rhythmic_drive_more_noise.param b/param/gamma_rhythmic_drive_more_noise.param index a961ba1ec..1bcf33a08 100644 --- a/param/gamma_rhythmic_drive_more_noise.param +++ b/param/gamma_rhythmic_drive_more_noise.param @@ -3,7 +3,7 @@ expmt_groups: {gamma_rhythmic_drive_more_noise} tstop: 550. dt: 0.025 celsius: 37.0 -N_trials: 0 +N_trials: 1 threshold: 0.0 save_figs: 0 save_spec_data: 1 From 28b97cd8af2495d40bd36fabbe45079a58699b5e Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 17 Mar 2021 12:01:52 -0400 Subject: [PATCH 083/107] MAINT: cleanup DataViewGUI inheritance --- hnn/visdipole.py | 3 --- hnn/visrast.py | 1 - hnn/visvolt.py | 4 ---- 3 files changed, 8 deletions(-) diff --git a/hnn/visdipole.py b/hnn/visdipole.py index 300eadb06..1c4f79401 100644 --- a/hnn/visdipole.py +++ b/hnn/visdipole.py @@ -17,10 +17,7 @@ class DipoleCanvas(FigureCanvasQTAgg): - """Class for displaying Dipole Viewer - Required parameters: dipole_scalefctr, tstop, N_trials - """ def __init__(self, params, sim_data, index, parent=None, width=12, height=10, dpi=120, title='Dipole Viewer'): diff --git a/hnn/visrast.py b/hnn/visrast.py index 48744cbd8..b5533b59b 100644 --- a/hnn/visrast.py +++ b/hnn/visrast.py @@ -301,7 +301,6 @@ class SpikeViewGUI(DataViewGUI): Required parameters: tstop, N_trials """ def __init__(self, CanvasType, params, sim_data, title): - self.params = params super(SpikeViewGUI, self).__init__(CanvasType, params, sim_data, title) self.addViewHistAction() diff --git a/hnn/visvolt.py b/hnn/visvolt.py index 183b6ac2c..a812f30f7 100644 --- a/hnn/visvolt.py +++ b/hnn/visvolt.py @@ -135,10 +135,6 @@ class VoltViewGUI(DataViewGUI): Required parameters in params dict: N_trials, tstop """ - def __init__(self, CanvasType, params, sim_data, title): - self.params = params - super(VoltViewGUI, self).__init__(CanvasType, params, sim_data, title) - def updateCB(self): self.cb.clear() for i in range(self.ntrial): From eceb0166144d17fc493a51652cd5a52d5ca8e39f Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 17 Mar 2021 12:02:18 -0400 Subject: [PATCH 084/107] MAINT: cleanup signals for RunSimThread --- hnn/hnn_qt5.py | 37 +++++++++++-------------------------- hnn/run.py | 50 +++++++++++++++++++------------------------------- 2 files changed, 30 insertions(+), 57 deletions(-) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index fe049cc64..9c4f69ef8 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -36,7 +36,7 @@ from .paramrw import (usingOngoingInputs, get_output_dir, write_gids_param, get_fname) from .simdat import SIMCanvas, SimData -from .run import RunSimThread, ParamSignal +from .run import RunSimThread from .qt_lib import getmplDPI, getscreengeom, lookupresource, setscalegeomcenter from .specfn import spec_dpl_kernel, save_spec_data from .DataViewGUI import DataViewGUI @@ -79,11 +79,6 @@ def _add_missing_frames(tb): frame = frame.f_back return result - -class DoneSignal(QObject): - finishSim = pyqtSignal(bool, str) - - def bringwintobot(win): # win.show() # win.lower() @@ -111,7 +106,7 @@ def __init__ (self): self.sim_canvas = self.toolbar = None paramfn = os.path.join(hnn_root_dir, 'param', 'default.param') self.baseparamwin = BaseParamDialog(self, paramfn, self.startoptmodel) - self.optMode = False + self.is_optimization = False self.sim_data = SimData() self.initUI() self.helpwin = HelpDialog(self) @@ -750,12 +745,6 @@ def initUI (self): widget.setLayout(grid) self.setCentralWidget(widget) - self.param_signal = ParamSignal() - self.param_signal.psig.connect(self.baseparamwin.updateDispParam) - - self.done_signal = DoneSignal() - self.done_signal.finishSim.connect(self.done) - self.setWindowIcon(QIcon(os.path.join('res', 'icon.png'))) self.schemwin.show() # so it's underneath main window @@ -793,7 +782,7 @@ def populateSimCB(self, index=None): else: self.cbsim.setCurrentIndex(index) - def initSimCanvas(self, recalcErr=True, optMode=False, gRow=1, reInit=True): + def initSimCanvas(self, recalcErr=True, gRow=1, reInit=True): # initialize the simulation canvas, loading any required data gCol = 0 @@ -813,7 +802,7 @@ def initSimCanvas(self, recalcErr=True, optMode=False, gRow=1, reInit=True): self.sim_canvas = SIMCanvas(self.baseparamwin.paramfn, self.baseparamwin.params, parent=self, width=10, height=1, dpi=getmplDPI(), - optMode=optMode) + is_optimization=self.is_optimization) # this is the Navigation widget # it takes the Canvas widget and a parent @@ -846,7 +835,7 @@ def startoptmodel(self): if self.runningsim: self.stopsim() # stop sim works but leaves subproc as zombie until this main GUI thread exits else: - self.optMode = True + self.is_optimization = True try: self.optmodel(self.baseparamwin.runparamwin.getncore()) except RuntimeError: @@ -859,7 +848,7 @@ def controlsim(self): # thread exits self.stopsim() else: - self.optMode = False + self.is_optimization = False self.startsim(self.baseparamwin.runparamwin.getncore()) def stopsim(self): @@ -894,10 +883,8 @@ def optmodel(self, ncore): self.statusBar().showMessage("Optimizing model. . .") self.runthread = RunSimThread(ncore, self.baseparamwin.params, - self.param_signal, self.done_signal, - self.waitsimwin, self.baseparamwin, self.result_callback, - mainwin=self, opt=True) + mainwin=self, is_optimization=True) # We have all the events we need connected we can start the thread self.runthread.start() @@ -935,10 +922,8 @@ def startsim(self, ncore): params['N_trials'] = 1 self.runthread = RunSimThread(ncore, params, - self.param_signal, self.done_signal, - self.waitsimwin, self.baseparamwin, self.result_callback, - mainwin=self, opt=False) + mainwin=self, is_optimization=False) # We have all the events we need connected we can start the thread self.runthread.start() @@ -1019,7 +1004,7 @@ def result_callback(self, result): sim_data['gid_ranges'], sim_data['spec'], sim_data['vsoma']) - def done(self, optMode, except_msg): + def done(self, except_msg): # called when the simulation completes running self.runningsim = False self.waitsimwin.hide() @@ -1027,7 +1012,7 @@ def done(self, optMode, except_msg): self.btnsim.setText("Run Simulation") self.qbtn.setEnabled(True) # recreate canvas (plots too) to avoid incorrect axes - self.initSimCanvas(optMode=optMode) + self.initSimCanvas() # self.sim_canvas.plot() self.setcursors(Qt.ArrowCursor) @@ -1038,7 +1023,7 @@ def done(self, optMode, except_msg): else: msg = "Finished " - if optMode: + if self.is_optimization: msg += "running optimization " self.baseparamwin.optparamwin.btnrunop.setText( 'Prepare for Another Optimization') diff --git a/hnn/run.py b/hnn/run.py index 625f97911..1446fc266 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -37,7 +37,7 @@ class ParamSignal(QtCore.QObject): class CanvSignal(QtCore.QObject): """for updating main GUI canvas""" - csig = QtCore.pyqtSignal(bool, bool) + csig = QtCore.pyqtSignal(bool) class ResultObj(QtCore.QObject): @@ -131,17 +131,11 @@ class RunSimThread(QtCore.QThread): Number of cores to run this simulation over params : dict Dictionary of params describing simulation config - param_signal : ParamSignal - Signal to main process to send back params - done_signal : DoneSignal - Signal to main process that the simulation has finished waitsimwin : WaitSimDialog Handle to the Qt dialog during a simulation - baseparamwin : BaseParamDialog - Handle to the Qt dialog with parameter values mainwin : HNNGUI Handle to the main application window - opt : bool + is_optimization: bool Whether this simulation thread is running an optimization Attributes @@ -150,14 +144,6 @@ class RunSimThread(QtCore.QThread): Number of cores to run this simulation over params : dict Dictionary of params describing simulation config - param_signal : ParamSignal - Signal to main process to send back params - done_signal : DoneSignal - Signal to main process that the simulation has finished - waitsimwin : WaitSimDialog - Handle to the Qt dialog during a simulation - baseparamwin : BaseParamDialog - Handle to the Qt dialog with parameter values mainwin : HNNGUI Handle to the main application window opt : bool @@ -170,25 +156,27 @@ class RunSimThread(QtCore.QThread): result_signal = QtCore.pyqtSignal(object) - def __init__(self, ncore, params, param_signal, done_signal, waitsimwin, - baseparamwin, result_callback, mainwin, opt=False): + def __init__(self, ncore, params, result_callback, mainwin, + is_optimization=False): QtCore.QThread.__init__(self) self.ncore = ncore self.params = params - self.param_signal = param_signal - self.done_signal = done_signal - self.waitsimwin = waitsimwin - self.baseparamwin = baseparamwin self.mainwin = mainwin + self.baseparamwin = self.mainwin.baseparamwin self.result_signal.connect(result_callback) - self.opt = opt self.killed = False self.paramfn = os.path.join(get_output_dir(), 'param', self.params['sim_prefix'] + '.param') self.txtComm = TextSignal() - self.txtComm.tsig.connect(self.waitsimwin.updatetxt) + self.txtComm.tsig.connect(self.mainwin.waitsimwin.updatetxt) + + self.param_signal = ParamSignal() + self.param_signal.psig.connect(self.baseparamwin.updateDispParam) + + self.done_signal = TextSignal() + self.done_signal.tsig.connect(self.mainwin.done) self.prmComm = ParamSignal() self.prmComm.psig.connect(self.baseparamwin.updatesaveparams) @@ -225,8 +213,8 @@ def _updatedispparam(self): def _updatedrawerr(self): """Signals mainwin to redraw canvas with RMSE""" - # When self.opt is false, do not recalculate error - self.canvComm.csig.emit(False, self.opt) + # When not running optimization, do not recalculate error + self.canvComm.csig.emit(False) def stop(self): """Terminate running simulation""" @@ -242,7 +230,7 @@ def run(self): msg = '' - if self.opt: + if self.mainwin.is_optimization: try: self.optmodel() # run optimization except RuntimeError as e: @@ -260,10 +248,10 @@ def run(self): except RuntimeError as e: msg = str(e) - self.done_signal.finishSim.emit(self.opt, msg) + self.done_signal.tsig.emit(msg) # run sim command via mpi, then delete the temp file. - def _runsim(self, is_opt=False, banner=True, simlength=None): + def _runsim(self, banner=True, simlength=None): self.killed = False while True: @@ -379,7 +367,7 @@ def optmodel(self): self.first_step = False # one final sim with the best parameters to update display - self._runsim(is_opt=True, banner=False) + self._runsim(banner=False) # update lsimdat and its current sim index update_sim_data_from_disk(self.paramfn, self.params, ddat['dpl']) @@ -431,7 +419,7 @@ def optrun(new_params, grad=0): sleep(1) # run the simulation, but stop early if possible - self._runsim(is_opt=True, banner=False, simlength=self.opt_end) + self._runsim(banner=False, simlength=self.opt_end) # calculate wRMSE for all steps calcerr(self.paramfn, self.opt_end, tstart=self.opt_start, From 9eade35db8b0a651455473fbfa695021c99b6e69 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 17 Mar 2021 12:03:14 -0400 Subject: [PATCH 085/107] MAINT: cleanup simdat dipole plotting --- hnn/simdat.py | 293 ++++++++++++++++++++++++++------------------------ 1 file changed, 153 insertions(+), 140 deletions(-) diff --git a/hnn/simdat.py b/hnn/simdat.py index b4c76a09b..736a4fbdc 100644 --- a/hnn/simdat.py +++ b/hnn/simdat.py @@ -303,7 +303,7 @@ def update_sim_data_from_disk(self, paramfn, params): Returns ---------- - found: bool + success: bool Whether simulation data could be read """ @@ -601,12 +601,103 @@ def save_vsoma(self, paramfn, params): with open(str(vsoma_outfn), 'wb') as f: dump(vsoma, f) -class SIMCanvas (FigureCanvasQTAgg): + def plot_dipole(self, paramfn, ax, linewidth, dipole_scalefctr, N_pyr_x=0, + N_pyr_y=0, is_optimization=False): + """Plot the dipole(s) HNN style + + Parameters + ---------- + paramfn : str + Simulation parameter filename to lookup data from prior simulation + ax : axis object + Axis on which to plot dipoles(s) + linewidth : int + Base width for dipole lines. Averages will be one size larger + dipole_scalefctr : float + Scaling factor applied to dipole data + N_pyr_x : int + Nr of cells (x) + N_pyr_y : int + Nr of cells (y) + is_optimization : bool + True if plots should be specific for optimization results + """ + + yl = [0, 0] + dpl = self._sim_data[paramfn]['data']['avg_dpl'] + yl[0] = min(yl[0], np.amin(dpl.data['agg'])) + yl[1] = max(yl[1], np.amax(dpl.data['agg'])) + + if not is_optimization: + # plot average dipoles from prior simulations + old_dpl = self._sim_data[paramfn]['data']['avg_dpl'] + ax.plot(old_dpl.times, old_dpl.data['agg'], '--', color='black', + linewidth=linewidth) + + sim_data = self._sim_data[paramfn]['data'] + ntrial = len(sim_data['dpls']) + # plot dipoles from individual trials + if ntrial > 1 and drawindivdpl: + for dpltrial in sim_data['dpls']: + ax.plot(dpltrial.times, dpltrial.data['agg'], + color='gray', + linewidth=linewidth) + yl[0] = min(yl[0], dpltrial.data['agg'].min()) + yl[1] = max(yl[1], dpltrial.data['agg'].max()) + + if drawavgdpl or ntrial == 1: + # this is the average dipole (across trials) + # it's also the ONLY dipole when running a single trial + ax.plot(dpl.times, dpl.data['agg'], 'k', + linewidth=linewidth + 1) + yl[0] = min(yl[0], dpl.data['agg'].min()) + yl[1] = max(yl[1], dpl.data['agg'].max()) + else: + if self._opt_data['avg_dpl'] is not None: + # show optimized dipole as gray line + optdpl = self._opt_data['avg_dpl'] + ax.plot(optdpl.times, optdpl.data['agg'], 'k', + color='gray', + linewidth=linewidth + 1) + yl[0] = min(yl[0], optdpl.data['agg'].min()) + yl[1] = max(yl[1], optdpl.data['agg'].max()) + + if self._opt_data['initial_dpl'] is not None: + # show initial dipole in dotted black line + plot_data = self._opt_data['initial_dpl'] + times = plot_data.times + plot_dpl = plot_data.data['agg'] + ax.plot(times, plot_dpl, '--', color='black', + linewidth=linewidth) + dpl = self._opt_data['initial_dpl'].data['agg'] + yl[0] = min(yl[0], dpl.min()) + yl[1] = max(yl[1], dpl.max()) + + # get the number of pyramidal neurons used in the simulation and + # multiply by scale factor to get estimated number of pyramidal + # neurons for y-axis label + num_pyr = int(N_pyr_x * N_pyr_y * 2) + NEstPyr = int(num_pyr * float(dipole_scalefctr)) + if NEstPyr > 0: + ax.set_ylabel(r'Dipole (nAm $\times$ ' + + str(dipole_scalefctr) + + ')\nFrom Estimated ' + + str(NEstPyr) + ' Cells', + fontsize=fontsize) + else: + # is this handling overflow? + ax.set_ylabel(r'Dipole (nAm $\times$ ' + + str(dipole_scalefctr) + + ')\n', fontsize=fontsize) + ax.set_ylim(yl) + + +class SIMCanvas(FigureCanvasQTAgg): # matplotlib/pyqt-compatible canvas for drawing simulation & external data # based on https://pythonspot.com/en/pyqt5-matplotlib/ def __init__(self, paramfn, params, parent=None, width=5, height=4, - dpi=40, optMode=False, title='Simulation Viewer'): + dpi=40, is_optimization=False, title='Simulation Viewer'): FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), dpi=dpi)) @@ -628,8 +719,8 @@ def __init__(self, paramfn, params, parent=None, width=5, height=4, self.G = gridspec.GridSpec(10, 1) self._data_dir = os.path.join(get_output_dir(), 'data') - self.optMode = optMode - if not optMode: + self.is_optimization = is_optimization + if not is_optimization: self.sim_data.clear_opt_data() self.saved_exception = None @@ -798,7 +889,7 @@ def plotextdat(self, recalcErr=True): self.lerr, self.errtot = self.sim_data.calcerr(self.paramfn, tstop) - if self.optMode: + if self.is_optimization: initial_err = self.sim_data._opt_data['initial_error'] if self.axdipole is None: @@ -828,7 +919,7 @@ def plotextdat(self, recalcErr=True): if self.lerr: tx, ty = dat[fx, 0], dat[fx, c] txt = 'RMSE: %.2f' % round(self.lerr[ddx], 2) - if not self.optMode: + if not self.is_optimization: self.axdipole.annotate(txt, xy=(dat[0, 0], dat[0, c]), xytext=(tx, ty), color=clr, fontweight='bold') @@ -844,7 +935,7 @@ def plotextdat(self, recalcErr=True): if self.errtot: tx, ty = 0, 0 - if self.optMode and initial_err: + if self.is_optimization and initial_err: clr = 'black' txt = 'RMSE: %.2f' % round(initial_err, 2) textcoords = 'axes fraction' @@ -887,7 +978,7 @@ def clearlextdatobj(self): self.lpatch = [] # reset legend self.clridx = 5 # reset index for next color for drawing ext data - if self.optMode: + if self.is_optimization: self.lpatch.append(mpatches.Patch(color='grey', label='Optimization')) self.lpatch.append(mpatches.Patch(color='black', label='Initial')) @@ -903,33 +994,34 @@ def plotsimdat(self): global drawindivdpl, drawavgdpl, fontsize - self.gRow = 0 - bottom = 0.0 - - failed_loading_dpl = False - failed_loading_spec = False - only_create_axes = False - DrawSpec = False xlim = (0.0, 1.0) - only_create_axes = False + ylim = (-0.001, 0.001) - if self.params is not None: + if self.params is None: + data_to_plot = False + gRow = 0 + else: + # for later ntrial = self.params['N_trials'] + tstop = self.params['tstop'] + dipole_scalefctr = self.params['dipole_scalefctr'] + N_pyr_x = self.params['N_pyr_x'] + N_pyr_y = self.params['N_pyr_y'] + + # update xlim to tstop + xlim = (0.0, tstop) + # for trying to plot a simulation read from disk (e.g. default) - if self.paramfn not in self.sim_data._sim_data: - found = self.sim_data.update_sim_data_from_disk(self.paramfn, - self.params) - if not found: - # best we can do is plot the distributions of the inputs - axes = self.plotinputhist() - self.gRow = len(axes) - only_create_axes = True - - if not only_create_axes: - tstop = self.params['tstop'] - xlim = (0.0, tstop) + if self.paramfn in self.sim_data._sim_data: + data_to_plot = True + pass + else: + # load simulation data from disk + data_to_plot = self.sim_data.update_sim_data_from_disk( + self.paramfn, self.params) + if data_to_plot: sim_data = self.sim_data._sim_data[self.paramfn]['data'] trials = [trial_idx for trial_idx in range(ntrial)] extinputs = ExtInputs(sim_data['spikes'], @@ -938,32 +1030,44 @@ def plotsimdat(self): feeds_to_plot = check_feeds_to_plot(extinputs.inputs, self.params) - axes = self.plotinputhist(extinputs, feeds_to_plot) - self.gRow = len(axes) + else: + # best we can do is plot the distributions of the inputs + extinputs = feeds_to_plot = None + + axes = self.plotinputhist(extinputs, feeds_to_plot) + gRow = len(axes) + if data_to_plot: # check that dipole data is present single_sim = self.sim_data._sim_data[self.paramfn]['data'] if single_sim['avg_dpl'] is None: - failed_loading_dpl = True + data_to_plot = False - if single_sim['spec'] is None or len(single_sim['spec']) == 0: - failed_loading_spec = True # whether to draw the specgram - should draw if user saved # it or have ongoing, poisson, or tonic inputs - if (not failed_loading_spec) and single_sim['spec'] is \ - not None \ + if single_sim['spec'] is not None \ + and len(single_sim['spec']) > 0 \ and (self.params['save_spec_data'] or feeds_to_plot['ongoing'] or feeds_to_plot['pois'] or feeds_to_plot['tonic']): DrawSpec = True + first_spec_trial = single_sim['spec'][0] + + # adjust dipole to match spectogram limits (e.g. missing first + # 50 ms b/c edge effects) + xlim = (first_spec_trial['time'][0], + first_spec_trial['time'][-1]) + if DrawSpec: # dipole axis takes fewer rows if also drawing specgram - self.axdipole = self.figure.add_subplot(self.G[self.gRow:5, 0]) + self.axdipole = self.figure.add_subplot(self.G[gRow:5, 0]) bottom = 0.08 else: - self.axdipole = self.figure.add_subplot(self.G[self.gRow:-1, 0]) + self.axdipole = self.figure.add_subplot(self.G[gRow:-1, 0]) + # there is no spec plot below, so label dipole with time on x-axis + self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) + bottom = 0.0 - ylim = (-0.001, 0.001) self.axdipole.set_ylim(ylim) self.axdipole.set_xlim(xlim) @@ -975,114 +1079,23 @@ def plotsimdat(self): self.figure.subplots_adjust(left=left, right=0.99, bottom=bottom, top=0.99, hspace=0.1, wspace=0.1) - if failed_loading_dpl or only_create_axes or self.params is None: + if not data_to_plot: + # no dipole or spec data to plot return - # get spectrogram if it exists, then adjust axis limits but only - # if drawing spectrogram - if DrawSpec: - single_sim = self.sim_data._sim_data[self.paramfn]['data'] - if single_sim['spec'] is not None: - first_spec_trial = single_sim['spec'][0] - # use spectogram limits (missing first 50 ms b/c edge effects) - xlim = (first_spec_trial['time'][0], - first_spec_trial['time'][-1]) - else: - DrawSpec = False - - yl = [0, 0] - dpl = self.sim_data._sim_data[self.paramfn]['data']['avg_dpl'] - yl[0] = min(yl[0], np.amin(dpl.data['agg'])) - yl[1] = max(yl[1], np.amax(dpl.data['agg'])) - - if not self.optMode: - # skip for optimization - # plot average dipoles from prior simulations - for paramfn in self.sim_data._sim_data.keys(): - old_data = self.sim_data._sim_data[paramfn]['data']['avg_dpl'] - if old_data is None: - continue - times = old_data.times - old_dpl = old_data.data['agg'] - self.axdipole.plot(times, old_dpl, '--', color='black', - linewidth=self.linewidth) - - sim_data = self.sim_data._sim_data[self.paramfn]['data'] - # plot dipoles from individual trials - if ntrial > 1 and drawindivdpl and \ - len(sim_data['dpls']) > 0: - for dpltrial in sim_data['dpls']: - self.axdipole.plot(dpltrial.times, dpltrial.data['agg'], - color='gray', - linewidth=self.linewidth) - yl[0] = min(yl[0], dpltrial.data['agg'].min()) - yl[1] = max(yl[1], dpltrial.data['agg'].max()) - - if drawavgdpl or ntrial <= 1: - # this is the average dipole (across trials) - # it's also the ONLY dipole when running a single trial - self.axdipole.plot(dpl.times, dpl.data['agg'], 'k', - linewidth=self.linewidth + 1) - yl[0] = min(yl[0], dpl.data['agg'].min()) - yl[1] = max(yl[1], dpl.data['agg'].max()) - else: - if self.sim_data._opt_data['avg_dpl'] is not None: - # show optimized dipole as gray line - optdpl = self.sim_data._opt_data['avg_dpl'] - self.axdipole.plot(optdpl.times, optdpl.data['agg'], 'k', - color='gray', - linewidth=self.linewidth + 1) - yl[0] = min(yl[0], optdpl.data['agg'].min()) - yl[1] = max(yl[1], optdpl.data['agg'].max()) - - if self.sim_data._opt_data['initial_dpl'] is not None: - # show initial dipole in dotted black line - plot_data = self.sim_data._opt_data['initial_dpl'] - times = plot_data.times - plot_dpl = plot_data.data['agg'] - self.axdipole.plot(times, plot_dpl, '--', color='black', - linewidth=self.linewidth) - dpl = self.sim_data._opt_data['initial_dpl'].data['agg'] - yl[0] = min(yl[0], dpl.min()) - yl[1] = max(yl[1], dpl.max()) - - scalefctr = float(self.params['dipole_scalefctr']) - - # get the number of pyramidal neurons used in the simulation - try: - x = self.params['N_pyr_x'] - y = self.params['N_pyr_y'] - num_pyr = int(x * y * 2) - except KeyError: - num_pyr = 0 - - NEstPyr = int(num_pyr * scalefctr) - - if NEstPyr > 0: - self.axdipole.set_ylabel(r'Dipole (nAm $\times$ ' + - str(scalefctr) + - ')\nFrom Estimated ' + - str(NEstPyr) + ' Cells', - fontsize=fontsize) - else: - self.axdipole.set_ylabel(r'Dipole (nAm $\times$ ' + - str(scalefctr) + - ')\n', fontsize=fontsize) - self.axdipole.set_xlim(xlim) - self.axdipole.set_ylim(yl) + self.sim_data.plot_dipole(self.paramfn, self.axdipole, self.linewidth, + dipole_scalefctr, N_pyr_x, N_pyr_y, + self.is_optimization) if DrawSpec: - gRow = 6 - self.axspec = self.figure.add_subplot(self.G[gRow:10, 0]) - spec_data = sim_data['spec'] - cax = plot_spec(self.axspec, spec_data, ntrial, + self.axspec = self.figure.add_subplot(self.G[6:10, 0]) + cax = plot_spec(self.axspec, sim_data['spec'], ntrial, self.params['spec_cmap'], xlim, fontsize) - cbaxes = self.figure.add_axes([0.6, 0.49, 0.3, 0.005]) + # plot colorbar horizontally to save space + cbaxes = self.figure.add_axes([0.6, 0.49, 0.3, 0.005]) plt.colorbar(cax, cax=cbaxes, orientation='horizontal') - else: - self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) def plotarrows(self): # run after scales have been updated From 9ec46b2d4f657cb727970f8a7078b880ad36dd6e Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 17 Mar 2021 12:16:29 -0400 Subject: [PATCH 086/107] MAINT: rename simdat.py to simdata.py --- hnn/hnn_qt5.py | 3 +- hnn/qt_canvas.py | 448 ++++++++++++++++++++++++++++++++++ hnn/{simdat.py => simdata.py} | 436 +-------------------------------- 3 files changed, 451 insertions(+), 436 deletions(-) create mode 100644 hnn/qt_canvas.py rename hnn/{simdat.py => simdata.py} (59%) diff --git a/hnn/hnn_qt5.py b/hnn/hnn_qt5.py index 9c4f69ef8..e8e0f3dd8 100644 --- a/hnn/hnn_qt5.py +++ b/hnn/hnn_qt5.py @@ -35,7 +35,8 @@ bringwintotop, _get_defncore) from .paramrw import (usingOngoingInputs, get_output_dir, write_gids_param, get_fname) -from .simdat import SIMCanvas, SimData +from .simdata import SimData +from .qt_canvas import SIMCanvas from .run import RunSimThread from .qt_lib import getmplDPI, getscreengeom, lookupresource, setscalegeomcenter from .specfn import spec_dpl_kernel, save_spec_data diff --git a/hnn/qt_canvas.py b/hnn/qt_canvas.py new file mode 100644 index 000000000..2693e9d34 --- /dev/null +++ b/hnn/qt_canvas.py @@ -0,0 +1,448 @@ +import os +import numpy as np +from math import ceil + +from PyQt5 import QtWidgets +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg +from matplotlib.figure import Figure +import matplotlib.patches as mpatches +import matplotlib.gridspec as gridspec + +from .paramrw import countEvokedInputs +from .qt_lib import getscreengeom +from .paramrw import get_output_dir, get_inputs +from .simdata import check_feeds_to_plot, plot_hists_on_gridspec +from .specfn import plot_spec +from .spikefn import ExtInputs + +fontsize = plt.rcParams['font.size'] = 10 + + +class SIMCanvas(FigureCanvasQTAgg): + # matplotlib/pyqt-compatible canvas for drawing simulation & external data + # based on https://pythonspot.com/en/pyqt5-matplotlib/ + + def __init__(self, paramfn, params, parent=None, width=5, height=4, + dpi=40, is_optimization=False, title='Simulation Viewer'): + FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), + dpi=dpi)) + + self.title = title + self.sim_data = parent.sim_data + self.lextdatobj = [] # external data object + self.clridx = 5 # index for next color for drawing external data + + # legend for dipole signals + self.lpatch = [mpatches.Patch(color='black', label='Sim.')] + self.setParent(parent) + self.linewidth = parent.linewidth + FigureCanvasQTAgg.setSizePolicy(self, QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Expanding) + FigureCanvasQTAgg.updateGeometry(self) + self.params = params + self.paramfn = paramfn + self.axdipole = self.axspec = None + self.G = gridspec.GridSpec(10, 1) + self._data_dir = os.path.join(get_output_dir(), 'data') + + self.is_optimization = is_optimization + if not is_optimization: + self.sim_data.clear_opt_data() + + self.saved_exception = None + try: + self.plot() + except Exception as err: + self.saved_exception = err + + def plotinputhist(self, extinputs=None, feeds_to_plot=None): + """ plot input histograms""" + + xmin = 0. + xmax = self.params['tstop'] + xlim = (xmin, xmax) + axes = [] + + sim_dt = self.params['dt'] + num_step = ceil(xmax / sim_dt) + 1 + times = np.linspace(xmin, xmax, num_step) + + plot_distribs = True + if extinputs is not None and feeds_to_plot is not None: + if feeds_to_plot is None: + feeds_to_plot = check_feeds_to_plot(extinputs.inputs, + self.params) + + if feeds_to_plot['ongoing'] or feeds_to_plot['evoked'] or \ + feeds_to_plot['pois']: + # hist gridspec + axes = plot_hists_on_gridspec(self.figure, self.G, + feeds_to_plot, extinputs, times, + xlim, self.linewidth) + + plot_distribs = False + + if plot_distribs: + dinput = self.getInputDistrib() + feeds_to_plot = check_feeds_to_plot(dinput, self.params) + if not (feeds_to_plot['ongoing'] or feeds_to_plot['evoked'] or + feeds_to_plot['pois']): + # no plots to create + return axes + + n_hists = 0 + + if feeds_to_plot['evdist']: + dist_tot = np.zeros(len(dinput['evdist'][0][0])) + for dist in dinput['evdist']: + dist_tot += dist[1] + + axdist = self.figure.add_subplot(self.G[n_hists, :]) + n_hists += 1 + + axdist.plot(dinput['evdist'][0][0], dist_tot, color='g', + lw=self.linewidth, + label='evdist distribution') + axdist.set_xlim(dinput['evdist'][0][0][0], + dinput['evdist'][0][0][-1]) + axdist.invert_yaxis() # invert the distal input axes + axes.append(axdist) + + if feeds_to_plot['evprox']: + prox_tot = np.zeros(len(dinput['evprox'][0][0])) + for prox in dinput['evprox']: + prox_tot += prox[1] + + axprox = self.figure.add_subplot(self.G[n_hists, :]) + n_hists += 1 + + axprox.plot(dinput['evprox'][0][0], prox_tot, color='r', + lw=self.linewidth, + label='evprox distribution') + axprox.set_xlim(dinput['evprox'][0][0][0], + dinput['evprox'][0][0][-1]) + axes.append(axprox) + + return axes + + def clearaxes(self): + # clear the figures axes + for ax in self.figure.get_axes(): + if ax: + ax.cla() + + def getInputDistrib(self): + import scipy.stats as stats + + dinput = {'evprox': [], 'evdist': [], 'prox': [], 'dist': [], + 'pois': []} + try: + sim_tstop = self.params['tstop'] + sim_dt = self.params['dt'] + except KeyError: + return dinput + + num_step = ceil(sim_tstop / sim_dt) + 1 + times = np.linspace(0, sim_tstop, num_step) + ltprox, ltdist = self.getEVInputTimes() + for prox in ltprox: + pdf = stats.norm.pdf(times, prox[0], prox[1]) + dinput['evprox'].append((times, pdf)) + for dist in ltdist: + pdf = stats.norm.pdf(times, dist[0], dist[1]) + dinput['evdist'].append((times, pdf)) + return dinput + + def getEVInputTimes(self): + # get the evoked input times + + if self.params is None: + raise ValueError("No valid params found") + + nprox, ndist = countEvokedInputs(self.params) + ltprox, ltdist = [], [] + for i in range(nprox): + input_mu = self.params['t_evprox_' + str(i + 1)] + input_sigma = self.params['sigma_t_evprox_' + str(i + 1)] + ltprox.append((input_mu, input_sigma)) + for i in range(ndist): + input_mu = self.params['t_evdist_' + str(i + 1)] + input_sigma = self.params['sigma_t_evdist_' + str(i + 1)] + ltdist.append((input_mu, input_sigma)) + return ltprox, ltdist + + def drawEVInputTimes(self, ax, yl, h=0.1, hw=15, hl=15): + # draw the evoked input times using arrows + ltprox, ltdist = self.getEVInputTimes() + yrange = abs(yl[1] - yl[0]) + + for tt in ltprox: + ax.arrow(tt[0], yl[0], 0, h * yrange, fc='r', ec='r', + head_width=hw, head_length=hl) + for tt in ltdist: + ax.arrow(tt[0], yl[1], 0, -h * yrange, fc='g', ec='g', + head_width=hw, head_length=hl) + + def getnextcolor(self): + # get next color for external data (colors selected in order) + self.clridx += 5 + if self.clridx > 100: + self.clridx = 5 + return self.clridx + + def _has_simdata(self): + """check if any simulation data available""" + if self.paramfn in self.sim_data._sim_data: + avg_dpl = self.sim_data._sim_data[self.paramfn]['data']['avg_dpl'] + if avg_dpl is not None: + return True + + return False + + def plotextdat(self, recalcErr=True): + global fontsize + + if self.sim_data._exp_data is None or \ + len(self.sim_data._exp_data) == 0: + return + + initial_err = None + # plot 'external' data (e.g. from experiment/other simulation) + if self._has_simdata(): # has the simulation been run yet? + if recalcErr: + tstop = self.params['tstop'] + # recalculate/save the error? + self.lerr, self.errtot = self.sim_data.calcerr(self.paramfn, + tstop) + + if self.is_optimization: + initial_err = self.sim_data._opt_data['initial_error'] + + if self.axdipole is None: + self.axdipole = self.figure.add_subplot(self.G[0:-1, 0]) + xl = (0.0, 1.0) + yl = (-0.001, 0.001) + else: + xl = self.axdipole.get_xlim() + yl = self.axdipole.get_ylim() + + cmap = plt.get_cmap('nipy_spectral') + csm = plt.cm.ScalarMappable(cmap=cmap) + csm.set_clim((0, 100)) + + self.clearlextdatobj() # clear annotation objects + + ddx = 0 + for fn, dat in self.sim_data._exp_data.items(): + shp = dat.shape + clr = csm.to_rgba(self.getnextcolor()) + c = min(shp[1], 1) + self.lextdatobj.append(self.axdipole.plot(dat[:, 0], dat[:, c], + color=clr, linewidth=self.linewidth + 1)) + xl = ((min(xl[0], min(dat[:, 0]))), (max(xl[1], max(dat[:, 0])))) + yl = ((min(yl[0], min(dat[:, c]))), (max(yl[1], max(dat[:, c])))) + fx = int(shp[0] * float(c) / shp[1]) + if self.lerr: + tx, ty = dat[fx, 0], dat[fx, c] + txt = 'RMSE: %.2f' % round(self.lerr[ddx], 2) + if not self.is_optimization: + self.axdipole.annotate(txt, xy=(dat[0, 0], dat[0, c]), + xytext=(tx, ty), color=clr, + fontweight='bold') + label = fn.split(os.path.sep)[-1].split('.txt')[0] + self.lpatch.append(mpatches.Patch(color=clr, label=label)) + ddx += 1 + + self.axdipole.set_xlim(xl) + self.axdipole.set_ylim(yl) + + if self.lpatch: + self.axdipole.legend(handles=self.lpatch, loc=2) + + if self.errtot: + tx, ty = 0, 0 + if self.is_optimization and initial_err: + clr = 'black' + txt = 'RMSE: %.2f' % round(initial_err, 2) + textcoords = 'axes fraction' + self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), + xytext=(0.005, 0.005), + textcoords=textcoords, + color=clr, + fontweight='bold') + clr = 'gray' + txt = 'RMSE: %.2f' % round(self.errtot, 2) + self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), + xytext=(0.86, 0.005), + textcoords=textcoords, + color=clr, + fontweight='bold') + else: + clr = 'black' + txt = 'Avg. RMSE: %.2f' % round(self.errtot, 2) + self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), + xytext=(0.005, 0.005), + textcoords=textcoords, + color=clr, + fontweight='bold') + + if not self._has_simdata(): # need axis labels + self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) + self.axdipole.set_ylabel('Dipole (nAm)', fontsize=fontsize) + myxl = self.axdipole.get_xlim() + if myxl[0] < 0.0: + self.axdipole.set_xlim((0.0, myxl[1] + myxl[0])) + + def clearlextdatobj(self): + # clear list of external data objects + for o in self.lextdatobj: + if isinstance(o, list): + # this is the plot. clear the line + o[0].set_visible(False) + del self.lextdatobj + self.lextdatobj = [] # reset list of external data objects + self.lpatch = [] # reset legend + self.clridx = 5 # reset index for next color for drawing ext data + + if self.is_optimization: + self.lpatch.append(mpatches.Patch(color='grey', + label='Optimization')) + self.lpatch.append(mpatches.Patch(color='black', label='Initial')) + elif self._has_simdata(): + self.lpatch.append(mpatches.Patch(color='black', + label='Simulation')) + if hasattr(self, 'annot_avg'): + self.annot_avg.set_visible(False) + del self.annot_avg + + def plotsimdat(self): + """plot the simulation data""" + + global fontsize + + DrawSpec = False + xlim = (0.0, 1.0) + ylim = (-0.001, 0.001) + + if self.params is None: + data_to_plot = False + gRow = 0 + else: + # for later + ntrial = self.params['N_trials'] + tstop = self.params['tstop'] + dipole_scalefctr = self.params['dipole_scalefctr'] + N_pyr_x = self.params['N_pyr_x'] + N_pyr_y = self.params['N_pyr_y'] + + # update xlim to tstop + xlim = (0.0, tstop) + + # for trying to plot a simulation read from disk (e.g. default) + if self.paramfn in self.sim_data._sim_data: + data_to_plot = True + pass + else: + # load simulation data from disk + data_to_plot = self.sim_data.update_sim_data_from_disk( + self.paramfn, self.params) + + if data_to_plot: + sim_data = self.sim_data._sim_data[self.paramfn]['data'] + trials = [trial_idx for trial_idx in range(ntrial)] + extinputs = ExtInputs(sim_data['spikes'], + sim_data['gid_ranges'], + trials, self.params) + + feeds_to_plot = check_feeds_to_plot(extinputs.inputs, + self.params) + else: + # best we can do is plot the distributions of the inputs + extinputs = feeds_to_plot = None + + axes = self.plotinputhist(extinputs, feeds_to_plot) + gRow = len(axes) + + if data_to_plot: + # check that dipole data is present + single_sim = self.sim_data._sim_data[self.paramfn]['data'] + if single_sim['avg_dpl'] is None: + data_to_plot = False + + # whether to draw the specgram - should draw if user saved + # it or have ongoing, poisson, or tonic inputs + if single_sim['spec'] is not None \ + and len(single_sim['spec']) > 0 \ + and (self.params['save_spec_data'] or + feeds_to_plot['ongoing'] or + feeds_to_plot['pois'] or feeds_to_plot['tonic']): + DrawSpec = True + + first_spec_trial = single_sim['spec'][0] + + # adjust dipole to match spectogram limits (e.g. missing first + # 50 ms b/c edge effects) + xlim = (first_spec_trial['time'][0], + first_spec_trial['time'][-1]) + + if DrawSpec: # dipole axis takes fewer rows if also drawing specgram + self.axdipole = self.figure.add_subplot(self.G[gRow:5, 0]) + bottom = 0.08 + else: + self.axdipole = self.figure.add_subplot(self.G[gRow:-1, 0]) + # there is no spec plot below, so label dipole with time on x-axis + self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) + bottom = 0.0 + + self.axdipole.set_ylim(ylim) + self.axdipole.set_xlim(xlim) + + left = 0.08 + w, _ = getscreengeom() + if w < 2800: + left = 0.1 + # reduce padding + self.figure.subplots_adjust(left=left, right=0.99, bottom=bottom, + top=0.99, hspace=0.1, wspace=0.1) + + if not data_to_plot: + # no dipole or spec data to plot + return + + self.sim_data.plot_dipole(self.paramfn, self.axdipole, self.linewidth, + dipole_scalefctr, N_pyr_x, N_pyr_y, + self.is_optimization) + + if DrawSpec: + self.axspec = self.figure.add_subplot(self.G[6:10, 0]) + cax = plot_spec(self.axspec, sim_data['spec'], ntrial, + self.params['spec_cmap'], xlim, + fontsize) + + # plot colorbar horizontally to save space + cbaxes = self.figure.add_axes([0.6, 0.49, 0.3, 0.005]) + plt.colorbar(cax, cax=cbaxes, orientation='horizontal') + + def plotarrows(self): + # run after scales have been updated + xl = self.axdipole.get_xlim() + yl = self.axdipole.get_ylim() + + using_feeds = get_inputs(self.params) + if using_feeds['evoked']: + self.drawEVInputTimes(self.axdipole, yl, 0.1, + (xl[1] - xl[0]) * .02, + (yl[1] - yl[0]) * .02) + + def plot(self, recalcErr=True): + self.clearaxes() + plt.close(self.figure) + self.figure.clf() + self.axdipole = None + + self.plotsimdat() # creates self.axdipole + self.plotextdat(recalcErr) + self.plotarrows() + + self.draw() diff --git a/hnn/simdat.py b/hnn/simdata.py similarity index 59% rename from hnn/simdat.py rename to hnn/simdata.py index 736a4fbdc..056179df1 100644 --- a/hnn/simdat.py +++ b/hnn/simdata.py @@ -5,12 +5,8 @@ from pickle import dump, load from scipy import signal -from PyQt5 import QtWidgets -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg -from matplotlib.figure import Figure import matplotlib as mpl import matplotlib.pyplot as plt -import matplotlib.patches as mpatches import matplotlib.gridspec as gridspec from hnn_core import read_spikes @@ -19,8 +15,7 @@ from .spikefn import ExtInputs from .specfn import plot_spec from .paramrw import get_output_dir, get_fname, get_inputs -from .paramrw import countEvokedInputs, read_gids_param -from .qt_lib import getscreengeom +from .paramrw import read_gids_param drawindivdpl = 1 drawavgdpl = 1 @@ -690,432 +685,3 @@ def plot_dipole(self, paramfn, ax, linewidth, dipole_scalefctr, N_pyr_x=0, str(dipole_scalefctr) + ')\n', fontsize=fontsize) ax.set_ylim(yl) - - -class SIMCanvas(FigureCanvasQTAgg): - # matplotlib/pyqt-compatible canvas for drawing simulation & external data - # based on https://pythonspot.com/en/pyqt5-matplotlib/ - - def __init__(self, paramfn, params, parent=None, width=5, height=4, - dpi=40, is_optimization=False, title='Simulation Viewer'): - FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), - dpi=dpi)) - - self.title = title - self.sim_data = parent.sim_data - self.lextdatobj = [] # external data object - self.clridx = 5 # index for next color for drawing external data - - # legend for dipole signals - self.lpatch = [mpatches.Patch(color='black', label='Sim.')] - self.setParent(parent) - self.linewidth = parent.linewidth - FigureCanvasQTAgg.setSizePolicy(self, QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) - FigureCanvasQTAgg.updateGeometry(self) - self.params = params - self.paramfn = paramfn - self.axdipole = self.axspec = None - self.G = gridspec.GridSpec(10, 1) - self._data_dir = os.path.join(get_output_dir(), 'data') - - self.is_optimization = is_optimization - if not is_optimization: - self.sim_data.clear_opt_data() - - self.saved_exception = None - try: - self.plot() - except Exception as err: - self.saved_exception = err - - def plotinputhist(self, extinputs=None, feeds_to_plot=None): - """ plot input histograms""" - - xmin = 0. - xmax = self.params['tstop'] - xlim = (xmin, xmax) - axes = [] - - sim_dt = self.params['dt'] - num_step = ceil(xmax / sim_dt) + 1 - times = np.linspace(xmin, xmax, num_step) - - plot_distribs = True - if extinputs is not None and feeds_to_plot is not None: - if feeds_to_plot is None: - feeds_to_plot = check_feeds_to_plot(extinputs.inputs, - self.params) - - if feeds_to_plot['ongoing'] or feeds_to_plot['evoked'] or \ - feeds_to_plot['pois']: - # hist gridspec - axes = plot_hists_on_gridspec(self.figure, self.G, - feeds_to_plot, extinputs, times, - xlim, self.linewidth) - - plot_distribs = False - - if plot_distribs: - dinput = self.getInputDistrib() - feeds_to_plot = check_feeds_to_plot(dinput, self.params) - if not (feeds_to_plot['ongoing'] or feeds_to_plot['evoked'] or - feeds_to_plot['pois']): - # no plots to create - return axes - - n_hists = 0 - - if feeds_to_plot['evdist']: - dist_tot = np.zeros(len(dinput['evdist'][0][0])) - for dist in dinput['evdist']: - dist_tot += dist[1] - - axdist = self.figure.add_subplot(self.G[n_hists, :]) - n_hists += 1 - - axdist.plot(dinput['evdist'][0][0], dist_tot, color='g', - lw=self.linewidth, - label='evdist distribution') - axdist.set_xlim(dinput['evdist'][0][0][0], - dinput['evdist'][0][0][-1]) - axdist.invert_yaxis() # invert the distal input axes - axes.append(axdist) - - if feeds_to_plot['evprox']: - prox_tot = np.zeros(len(dinput['evprox'][0][0])) - for prox in dinput['evprox']: - prox_tot += prox[1] - - axprox = self.figure.add_subplot(self.G[n_hists, :]) - n_hists += 1 - - axprox.plot(dinput['evprox'][0][0], prox_tot, color='r', - lw=self.linewidth, - label='evprox distribution') - axprox.set_xlim(dinput['evprox'][0][0][0], - dinput['evprox'][0][0][-1]) - axes.append(axprox) - - return axes - - def clearaxes(self): - # clear the figures axes - for ax in self.figure.get_axes(): - if ax: - ax.cla() - - def getInputDistrib(self): - import scipy.stats as stats - - dinput = {'evprox': [], 'evdist': [], 'prox': [], 'dist': [], - 'pois': []} - try: - sim_tstop = self.params['tstop'] - sim_dt = self.params['dt'] - except KeyError: - return dinput - - num_step = ceil(sim_tstop / sim_dt) + 1 - times = np.linspace(0, sim_tstop, num_step) - ltprox, ltdist = self.getEVInputTimes() - for prox in ltprox: - pdf = stats.norm.pdf(times, prox[0], prox[1]) - dinput['evprox'].append((times, pdf)) - for dist in ltdist: - pdf = stats.norm.pdf(times, dist[0], dist[1]) - dinput['evdist'].append((times, pdf)) - return dinput - - def getEVInputTimes(self): - # get the evoked input times - - if self.params is None: - raise ValueError("No valid params found") - - nprox, ndist = countEvokedInputs(self.params) - ltprox, ltdist = [], [] - for i in range(nprox): - input_mu = self.params['t_evprox_' + str(i + 1)] - input_sigma = self.params['sigma_t_evprox_' + str(i + 1)] - ltprox.append((input_mu, input_sigma)) - for i in range(ndist): - input_mu = self.params['t_evdist_' + str(i + 1)] - input_sigma = self.params['sigma_t_evdist_' + str(i + 1)] - ltdist.append((input_mu, input_sigma)) - return ltprox, ltdist - - def drawEVInputTimes(self, ax, yl, h=0.1, hw=15, hl=15): - # draw the evoked input times using arrows - ltprox, ltdist = self.getEVInputTimes() - yrange = abs(yl[1] - yl[0]) - - for tt in ltprox: - ax.arrow(tt[0], yl[0], 0, h * yrange, fc='r', ec='r', - head_width=hw, head_length=hl) - for tt in ltdist: - ax.arrow(tt[0], yl[1], 0, -h * yrange, fc='g', ec='g', - head_width=hw, head_length=hl) - - def getnextcolor(self): - # get next color for external data (colors selected in order) - self.clridx += 5 - if self.clridx > 100: - self.clridx = 5 - return self.clridx - - def _has_simdata(self): - """check if any simulation data available""" - if self.paramfn in self.sim_data._sim_data: - avg_dpl = self.sim_data._sim_data[self.paramfn]['data']['avg_dpl'] - if avg_dpl is not None: - return True - - return False - - def plotextdat(self, recalcErr=True): - global fontsize - - if self.sim_data._exp_data is None or \ - len(self.sim_data._exp_data) == 0: - return - - initial_err = None - # plot 'external' data (e.g. from experiment/other simulation) - if self._has_simdata(): # has the simulation been run yet? - if recalcErr: - tstop = self.params['tstop'] - # recalculate/save the error? - self.lerr, self.errtot = self.sim_data.calcerr(self.paramfn, - tstop) - - if self.is_optimization: - initial_err = self.sim_data._opt_data['initial_error'] - - if self.axdipole is None: - self.axdipole = self.figure.add_subplot(self.G[0:-1, 0]) - xl = (0.0, 1.0) - yl = (-0.001, 0.001) - else: - xl = self.axdipole.get_xlim() - yl = self.axdipole.get_ylim() - - cmap = plt.get_cmap('nipy_spectral') - csm = plt.cm.ScalarMappable(cmap=cmap) - csm.set_clim((0, 100)) - - self.clearlextdatobj() # clear annotation objects - - ddx = 0 - for fn, dat in self.sim_data._exp_data.items(): - shp = dat.shape - clr = csm.to_rgba(self.getnextcolor()) - c = min(shp[1], 1) - self.lextdatobj.append(self.axdipole.plot(dat[:, 0], dat[:, c], - color=clr, linewidth=self.linewidth + 1)) - xl = ((min(xl[0], min(dat[:, 0]))), (max(xl[1], max(dat[:, 0])))) - yl = ((min(yl[0], min(dat[:, c]))), (max(yl[1], max(dat[:, c])))) - fx = int(shp[0] * float(c) / shp[1]) - if self.lerr: - tx, ty = dat[fx, 0], dat[fx, c] - txt = 'RMSE: %.2f' % round(self.lerr[ddx], 2) - if not self.is_optimization: - self.axdipole.annotate(txt, xy=(dat[0, 0], dat[0, c]), - xytext=(tx, ty), color=clr, - fontweight='bold') - label = fn.split(os.path.sep)[-1].split('.txt')[0] - self.lpatch.append(mpatches.Patch(color=clr, label=label)) - ddx += 1 - - self.axdipole.set_xlim(xl) - self.axdipole.set_ylim(yl) - - if self.lpatch: - self.axdipole.legend(handles=self.lpatch, loc=2) - - if self.errtot: - tx, ty = 0, 0 - if self.is_optimization and initial_err: - clr = 'black' - txt = 'RMSE: %.2f' % round(initial_err, 2) - textcoords = 'axes fraction' - self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), - xytext=(0.005, 0.005), - textcoords=textcoords, - color=clr, - fontweight='bold') - clr = 'gray' - txt = 'RMSE: %.2f' % round(self.errtot, 2) - self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), - xytext=(0.86, 0.005), - textcoords=textcoords, - color=clr, - fontweight='bold') - else: - clr = 'black' - txt = 'Avg. RMSE: %.2f' % round(self.errtot, 2) - self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), - xytext=(0.005, 0.005), - textcoords=textcoords, - color=clr, - fontweight='bold') - - if not self._has_simdata(): # need axis labels - self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) - self.axdipole.set_ylabel('Dipole (nAm)', fontsize=fontsize) - myxl = self.axdipole.get_xlim() - if myxl[0] < 0.0: - self.axdipole.set_xlim((0.0, myxl[1] + myxl[0])) - - def clearlextdatobj(self): - # clear list of external data objects - for o in self.lextdatobj: - if isinstance(o, list): - # this is the plot. clear the line - o[0].set_visible(False) - del self.lextdatobj - self.lextdatobj = [] # reset list of external data objects - self.lpatch = [] # reset legend - self.clridx = 5 # reset index for next color for drawing ext data - - if self.is_optimization: - self.lpatch.append(mpatches.Patch(color='grey', - label='Optimization')) - self.lpatch.append(mpatches.Patch(color='black', label='Initial')) - elif self._has_simdata(): - self.lpatch.append(mpatches.Patch(color='black', - label='Simulation')) - if hasattr(self, 'annot_avg'): - self.annot_avg.set_visible(False) - del self.annot_avg - - def plotsimdat(self): - """plot the simulation data""" - - global drawindivdpl, drawavgdpl, fontsize - - DrawSpec = False - xlim = (0.0, 1.0) - ylim = (-0.001, 0.001) - - if self.params is None: - data_to_plot = False - gRow = 0 - else: - # for later - ntrial = self.params['N_trials'] - tstop = self.params['tstop'] - dipole_scalefctr = self.params['dipole_scalefctr'] - N_pyr_x = self.params['N_pyr_x'] - N_pyr_y = self.params['N_pyr_y'] - - # update xlim to tstop - xlim = (0.0, tstop) - - # for trying to plot a simulation read from disk (e.g. default) - if self.paramfn in self.sim_data._sim_data: - data_to_plot = True - pass - else: - # load simulation data from disk - data_to_plot = self.sim_data.update_sim_data_from_disk( - self.paramfn, self.params) - - if data_to_plot: - sim_data = self.sim_data._sim_data[self.paramfn]['data'] - trials = [trial_idx for trial_idx in range(ntrial)] - extinputs = ExtInputs(sim_data['spikes'], - sim_data['gid_ranges'], - trials, self.params) - - feeds_to_plot = check_feeds_to_plot(extinputs.inputs, - self.params) - else: - # best we can do is plot the distributions of the inputs - extinputs = feeds_to_plot = None - - axes = self.plotinputhist(extinputs, feeds_to_plot) - gRow = len(axes) - - if data_to_plot: - # check that dipole data is present - single_sim = self.sim_data._sim_data[self.paramfn]['data'] - if single_sim['avg_dpl'] is None: - data_to_plot = False - - # whether to draw the specgram - should draw if user saved - # it or have ongoing, poisson, or tonic inputs - if single_sim['spec'] is not None \ - and len(single_sim['spec']) > 0 \ - and (self.params['save_spec_data'] or - feeds_to_plot['ongoing'] or - feeds_to_plot['pois'] or feeds_to_plot['tonic']): - DrawSpec = True - - first_spec_trial = single_sim['spec'][0] - - # adjust dipole to match spectogram limits (e.g. missing first - # 50 ms b/c edge effects) - xlim = (first_spec_trial['time'][0], - first_spec_trial['time'][-1]) - - if DrawSpec: # dipole axis takes fewer rows if also drawing specgram - self.axdipole = self.figure.add_subplot(self.G[gRow:5, 0]) - bottom = 0.08 - else: - self.axdipole = self.figure.add_subplot(self.G[gRow:-1, 0]) - # there is no spec plot below, so label dipole with time on x-axis - self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) - bottom = 0.0 - - self.axdipole.set_ylim(ylim) - self.axdipole.set_xlim(xlim) - - left = 0.08 - w, _ = getscreengeom() - if w < 2800: - left = 0.1 - # reduce padding - self.figure.subplots_adjust(left=left, right=0.99, bottom=bottom, - top=0.99, hspace=0.1, wspace=0.1) - - if not data_to_plot: - # no dipole or spec data to plot - return - - self.sim_data.plot_dipole(self.paramfn, self.axdipole, self.linewidth, - dipole_scalefctr, N_pyr_x, N_pyr_y, - self.is_optimization) - - if DrawSpec: - self.axspec = self.figure.add_subplot(self.G[6:10, 0]) - cax = plot_spec(self.axspec, sim_data['spec'], ntrial, - self.params['spec_cmap'], xlim, - fontsize) - - # plot colorbar horizontally to save space - cbaxes = self.figure.add_axes([0.6, 0.49, 0.3, 0.005]) - plt.colorbar(cax, cax=cbaxes, orientation='horizontal') - - def plotarrows(self): - # run after scales have been updated - xl = self.axdipole.get_xlim() - yl = self.axdipole.get_ylim() - - using_feeds = get_inputs(self.params) - if using_feeds['evoked']: - self.drawEVInputTimes(self.axdipole, yl, 0.1, - (xl[1] - xl[0]) * .02, - (yl[1] - yl[0]) * .02) - - def plot(self, recalcErr=True): - self.clearaxes() - plt.close(self.figure) - self.figure.clf() - self.axdipole = None - - self.plotsimdat() # creates self.axdipole - self.plotextdat(recalcErr) - self.plotarrows() - - self.draw() From 77954e53230c0ae3246399343a4ac474f75ead45 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 17 Mar 2021 12:31:40 -0400 Subject: [PATCH 087/107] MAINT: hnn_qt5.py -> qt_main.py --- hnn/__init__.py | 2 +- hnn/{hnn_qt5.py => qt_main.py} | 0 scripts/run-pytest.sh | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename hnn/{hnn_qt5.py => qt_main.py} (100%) diff --git a/hnn/__init__.py b/hnn/__init__.py index 1b32f85ca..f368b4e7d 100644 --- a/hnn/__init__.py +++ b/hnn/__init__.py @@ -1,3 +1,3 @@ __version__ = "1.4.0" -from .hnn_qt5 import HNNGUI +from .qt_main import HNNGUI diff --git a/hnn/hnn_qt5.py b/hnn/qt_main.py similarity index 100% rename from hnn/hnn_qt5.py rename to hnn/qt_main.py diff --git a/scripts/run-pytest.sh b/scripts/run-pytest.sh index 67e23acf3..2440c5baf 100755 --- a/scripts/run-pytest.sh +++ b/scripts/run-pytest.sh @@ -10,7 +10,7 @@ fi # first check code style with flake8 (ignored currently) echo "Checking code style compliance with flake8..." flake8 --quiet --count \ - --exclude hnn_qt5.py,qt_evoked.py,run.py,paramrw.py,visdipole.py,visrast.py,visvolt.py,visspec.py,vispsd.py,DataViewGUI.py \ + --exclude __init__.py,qt_main.py,qt_evoked.py,run.py,paramrw.py \ hnn echo "Running unit tests with pytest..." From c50fd5842d81d3ad1795804f073229ecd80258f8 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 17 Mar 2021 12:33:03 -0400 Subject: [PATCH 088/107] MAINT: flake8 --- hnn/qt_canvas.py | 4 ++-- hnn/simdata.py | 22 ++++++++++------------ hnn/visdipole.py | 4 +++- hnn/visrast.py | 6 ++++-- hnn/visvolt.py | 2 +- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/hnn/qt_canvas.py b/hnn/qt_canvas.py index 2693e9d34..4eae08342 100644 --- a/hnn/qt_canvas.py +++ b/hnn/qt_canvas.py @@ -381,8 +381,8 @@ def plotsimdat(self): first_spec_trial = single_sim['spec'][0] - # adjust dipole to match spectogram limits (e.g. missing first - # 50 ms b/c edge effects) + # adjust dipole to match spectogram limits (e.g. missing + # first 50 ms b/c edge effects) xlim = (first_spec_trial['time'][0], first_spec_trial['time'][-1]) diff --git a/hnn/simdata.py b/hnn/simdata.py index 056179df1..00250ab3f 100644 --- a/hnn/simdata.py +++ b/hnn/simdata.py @@ -353,7 +353,7 @@ def update_sim_data_from_disk(self, paramfn, params): print("Warning: no somatic voltages read from %s" % sim_dir) elif len(vsoma) < params['N_trials']: print("Warning: only read %d of %d voltage files in %s" % - (len(vsoma), params['N_trials'], sim_dir)) + (len(vsoma), params['N_trials'], sim_dir)) self.update_sim_data(paramfn, params, dpls, avg_dpl, spikes, gid_ranges, spec, vsoma) @@ -644,16 +644,15 @@ def plot_dipole(self, paramfn, ax, linewidth, dipole_scalefctr, N_pyr_x=0, # this is the average dipole (across trials) # it's also the ONLY dipole when running a single trial ax.plot(dpl.times, dpl.data['agg'], 'k', - linewidth=linewidth + 1) + linewidth=linewidth + 1) yl[0] = min(yl[0], dpl.data['agg'].min()) yl[1] = max(yl[1], dpl.data['agg'].max()) else: if self._opt_data['avg_dpl'] is not None: # show optimized dipole as gray line optdpl = self._opt_data['avg_dpl'] - ax.plot(optdpl.times, optdpl.data['agg'], 'k', - color='gray', - linewidth=linewidth + 1) + ax.plot(optdpl.times, optdpl.data['agg'], 'k', color='gray', + linewidth=linewidth + 1) yl[0] = min(yl[0], optdpl.data['agg'].min()) yl[1] = max(yl[1], optdpl.data['agg'].max()) @@ -663,7 +662,7 @@ def plot_dipole(self, paramfn, ax, linewidth, dipole_scalefctr, N_pyr_x=0, times = plot_data.times plot_dpl = plot_data.data['agg'] ax.plot(times, plot_dpl, '--', color='black', - linewidth=linewidth) + linewidth=linewidth) dpl = self._opt_data['initial_dpl'].data['agg'] yl[0] = min(yl[0], dpl.min()) yl[1] = max(yl[1], dpl.max()) @@ -675,13 +674,12 @@ def plot_dipole(self, paramfn, ax, linewidth, dipole_scalefctr, N_pyr_x=0, NEstPyr = int(num_pyr * float(dipole_scalefctr)) if NEstPyr > 0: ax.set_ylabel(r'Dipole (nAm $\times$ ' + - str(dipole_scalefctr) + - ')\nFrom Estimated ' + - str(NEstPyr) + ' Cells', - fontsize=fontsize) + str(dipole_scalefctr) + + ')\nFrom Estimated ' + + str(NEstPyr) + ' Cells', fontsize=fontsize) else: # is this handling overflow? ax.set_ylabel(r'Dipole (nAm $\times$ ' + - str(dipole_scalefctr) + - ')\n', fontsize=fontsize) + str(dipole_scalefctr) + + ')\n', fontsize=fontsize) ax.set_ylim(yl) diff --git a/hnn/visdipole.py b/hnn/visdipole.py index 1c4f79401..c1d6907c1 100644 --- a/hnn/visdipole.py +++ b/hnn/visdipole.py @@ -17,8 +17,10 @@ class DipoleCanvas(FigureCanvasQTAgg): + """Class for displaying Dipole Viewer - + Required parameters in params dict: N_trials, tstop, dipole_scalefctr + """ def __init__(self, params, sim_data, index, parent=None, width=12, height=10, dpi=120, title='Dipole Viewer'): FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), diff --git a/hnn/visrast.py b/hnn/visrast.py index b5533b59b..e0f0de441 100644 --- a/hnn/visrast.py +++ b/hnn/visrast.py @@ -182,7 +182,8 @@ def drawrast(self, dspk, extinputs, haveinputs, fig, G, sz=8): ax.set_ylabel('Distal Input') if haveEvokedProx or haveOngoingProx: - ax2 = fig.add_subplot(G[row:row + 2, :], label=random_label) + ax2 = fig.add_subplot(G[row:row + 2, :], + label=random_label) random_label += 1 row += 2 lax.append(ax2) @@ -195,7 +196,8 @@ def drawrast(self, dspk, extinputs, haveinputs, fig, G, sz=8): ax2.set_ylabel('Proximal Input') if PoissonInputs and len(dinput['pois']): - axp = fig.add_subplot(G[row:row + 2, :], label=random_label) + axp = fig.add_subplot(G[row:row + 2, :], + label=random_label) random_label += 1 row += 2 lax.append(axp) diff --git a/hnn/visvolt.py b/hnn/visvolt.py index a812f30f7..991f42e0c 100644 --- a/hnn/visvolt.py +++ b/hnn/visvolt.py @@ -145,7 +145,7 @@ def onActivated(self, idx): if idx != self.index: self.index = idx self.statusBar().showMessage('Loading data from trial ' + - str(self.index) + '.') + str(self.index) + '.') self.m.index = self.index self.initCanvas() self.m.plot() From b33c744ddd14486ad8fb06316fb5f9e651bc7b84 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 17 Mar 2021 12:54:01 -0400 Subject: [PATCH 089/107] MAINT: match input histograms xlim to spec plot --- hnn/qt_canvas.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hnn/qt_canvas.py b/hnn/qt_canvas.py index 4eae08342..58afdf673 100644 --- a/hnn/qt_canvas.py +++ b/hnn/qt_canvas.py @@ -361,8 +361,8 @@ def plotsimdat(self): # best we can do is plot the distributions of the inputs extinputs = feeds_to_plot = None - axes = self.plotinputhist(extinputs, feeds_to_plot) - gRow = len(axes) + hist_axes = self.plotinputhist(extinputs, feeds_to_plot) + gRow = len(hist_axes) if data_to_plot: # check that dipole data is present @@ -389,6 +389,11 @@ def plotsimdat(self): if DrawSpec: # dipole axis takes fewer rows if also drawing specgram self.axdipole = self.figure.add_subplot(self.G[gRow:5, 0]) bottom = 0.08 + + # set the axes of input histograms to match dipole and spec plots + for ax in hist_axes: + ax.set_xlim(xlim) + else: self.axdipole = self.figure.add_subplot(self.G[gRow:-1, 0]) # there is no spec plot below, so label dipole with time on x-axis From 12246ea086c5e06ee4fca12a3ffa4e12654bab03 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 17 Mar 2021 13:19:23 -0400 Subject: [PATCH 090/107] BUG: make NumCores persistent while GUI is running --- hnn/qt_dialog.py | 29 +++++++++-------------------- hnn/qt_main.py | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/hnn/qt_dialog.py b/hnn/qt_dialog.py index 70bea7470..427856d91 100644 --- a/hnn/qt_dialog.py +++ b/hnn/qt_dialog.py @@ -40,21 +40,6 @@ def bringwintotop(win): # win.show() -def _get_defncore(): - """get default number of cores """ - - try: - defncore = len(os.sched_getaffinity(0)) - except AttributeError: - defncore = cpu_count(logical=False) - - if defncore is None or defncore == 0: - # in case psutil is not supported (e.g. BSD) - defncore = multiprocessing.cpu_count() - - return defncore - - class DictDialog(QDialog): """dictionary-based dialog with tabs @@ -490,7 +475,8 @@ def initd(self): # widget to specify run params (tstop, dt, etc.) -- not many params here class RunParamDialog(DictDialog): - def __init__(self, parent, din=None): + def __init__(self, parent, mainwin, din=None): + self.mainwin = mainwin super(RunParamDialog, self).__init__(parent, din) self.addHideButton() self.parent = parent @@ -555,8 +541,7 @@ def selectionchange(self, i): def initExtra(self): DictDialog.initExtra(self) self.dqextra['NumCores'] = QLineEdit(self) - self.defncore = _get_defncore() - self.dqextra['NumCores'].setText(str(self.defncore)) + self.dqextra['NumCores'].setText(str(self.mainwin.defncore)) self.addtransvar('NumCores', 'Number Cores') self.ltabs[0].layout.addRow('NumCores', self.dqextra['NumCores']) @@ -588,6 +573,10 @@ def getncore(self): if ncore < 1: self.dqline['NumCores'].setText(str(1)) ncore = 1 + + # update value in HNNGUI for persistence + self.mainwin.defncore = ncore + return ncore def setfromdin(self, din): @@ -595,7 +584,7 @@ def setfromdin(self, din): return # number of cores may have changed if the configured number failed - self.dqextra['NumCores'].setText(str(self.defncore)) + self.dqextra['NumCores'].setText(str(self.mainwin.defncore)) # update ordered dict of QLineEdit objects with new parameters for k, v in din.items(): @@ -953,7 +942,7 @@ def __init__(self, parent, paramfn, optrun_func): self.distparamwin = None self.netparamwin = None self.syngainparamwin = None - self.runparamwin = RunParamDialog(self) + self.runparamwin = RunParamDialog(self, parent) self.cellparamwin = CellParamDialog(self) self.netparamwin = NetworkParamDialog(self) self.syngainparamwin = SynGainParamDialog(self, self.netparamwin) diff --git a/hnn/qt_main.py b/hnn/qt_main.py index e8e0f3dd8..82e695da2 100644 --- a/hnn/qt_main.py +++ b/hnn/qt_main.py @@ -32,7 +32,7 @@ # HNN modules from .qt_dialog import (BaseParamDialog, EvokedOrRhythmicDialog, WaitSimDialog, HelpDialog, SchematicDialog, - bringwintotop, _get_defncore) + bringwintotop) from .paramrw import (usingOngoingInputs, get_output_dir, write_gids_param, get_fname) from .simdata import SimData @@ -51,6 +51,22 @@ drawavgdpl = 0 fontsize = plt.rcParams['font.size'] = 10 + +def _get_defncore(): + """get default number of cores """ + + try: + defncore = len(os.sched_getaffinity(0)) + except AttributeError: + defncore = cpu_count(logical=False) + + if defncore is None or defncore == 0: + # in case psutil is not supported (e.g. BSD) + defncore = multiprocessing.cpu_count() + + return defncore + + def isWindows(): # are we on windows? or linux/mac ? return sys.platform.startswith('win') @@ -98,6 +114,7 @@ def __init__ (self): hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) + self.defncore = _get_defncore() self.runningsim = False self.runthread = None self.fontsize = fontsize From 046e729c04e069550fba3ed705d044c4f8b929e0 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Wed, 17 Mar 2021 14:05:23 -0400 Subject: [PATCH 091/107] MAINT: flake8 --- hnn/qt_dialog.py | 3 - hnn/qt_main.py | 560 ++++++++++++++++++++++++++--------------------- 2 files changed, 310 insertions(+), 253 deletions(-) diff --git a/hnn/qt_dialog.py b/hnn/qt_dialog.py index 427856d91..75cc6fe87 100644 --- a/hnn/qt_dialog.py +++ b/hnn/qt_dialog.py @@ -6,9 +6,6 @@ import os from collections import OrderedDict -import multiprocessing -from psutil import cpu_count - from PyQt5.QtWidgets import (QDialog, QToolTip, QTabWidget, QWidget, QPushButton, QMessageBox, QComboBox, QLabel, QLineEdit, QTextEdit, QFormLayout, diff --git a/hnn/qt_main.py b/hnn/qt_main.py index 82e695da2..97ac9b2c9 100644 --- a/hnn/qt_main.py +++ b/hnn/qt_main.py @@ -10,18 +10,17 @@ import multiprocessing import numpy as np import traceback -from subprocess import Popen from collections import namedtuple from copy import deepcopy from psutil import cpu_count # External libraries -from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication -from PyQt5.QtWidgets import QFileDialog, QComboBox -from PyQt5.QtWidgets import QToolTip, QPushButton, QGridLayout, QInputDialog -from PyQt5.QtWidgets import QMenu, QMessageBox, QWidget, QLayout +from PyQt5.QtWidgets import (QMainWindow, QAction, qApp, QApplication, + QFileDialog, QComboBox, QToolTip, QPushButton, + QGridLayout, QInputDialog, QMenu, QMessageBox, + QWidget, QLayout) from PyQt5.QtGui import QIcon, QFont -from PyQt5.QtCore import pyqtSignal, QObject, Qt +from PyQt5.QtCore import Qt from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT import matplotlib.pyplot as plt @@ -38,7 +37,8 @@ from .simdata import SimData from .qt_canvas import SIMCanvas from .run import RunSimThread -from .qt_lib import getmplDPI, getscreengeom, lookupresource, setscalegeomcenter +from .qt_lib import (getmplDPI, getscreengeom, lookupresource, + setscalegeomcenter) from .specfn import spec_dpl_kernel, save_spec_data from .DataViewGUI import DataViewGUI from .visdipole import DipoleCanvas @@ -96,104 +96,116 @@ def _add_missing_frames(tb): frame = frame.f_back return result + def bringwintobot(win): # win.show() # win.lower() win.hide() -class HNNGUI (QMainWindow): - # main HNN GUI class - def __init__ (self): - # initialize the main HNN GUI - - super().__init__() - sys.excepthook = self.excepthook - - global fontsize - - hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) - - self.defncore = _get_defncore() - self.runningsim = False - self.runthread = None - self.fontsize = fontsize - self.linewidth = plt.rcParams['lines.linewidth'] = 1 - self.markersize = plt.rcParams['lines.markersize'] = 5 - self.schemwin = SchematicDialog(self) - self.sim_canvas = self.toolbar = None - paramfn = os.path.join(hnn_root_dir, 'param', 'default.param') - self.baseparamwin = BaseParamDialog(self, paramfn, self.startoptmodel) - self.is_optimization = False - self.sim_data = SimData() - self.initUI() - self.helpwin = HelpDialog(self) - self.erselectdistal = EvokedOrRhythmicDialog(self, True, self.baseparamwin.evparamwin, self.baseparamwin.distparamwin) - self.erselectprox = EvokedOrRhythmicDialog(self, False, self.baseparamwin.evparamwin, self.baseparamwin.proxparamwin) - self.waitsimwin = WaitSimDialog(self) - - default_param = os.path.join(get_output_dir(), 'data', 'default') - first_load = not (os.path.exists(default_param)) - - if first_load: - QMessageBox.information(self, "HNN", "Welcome to HNN! Default parameter file loaded. " - "Press 'Run Simulation' to display simulation output") - else: - self.statusBar().showMessage("Loaded %s"%default_param) - # successful initialization, catch all further exceptions +class HNNGUI(QMainWindow): + """main HNN GUI class""" - def excepthook(self, exc_type, exc_value, exc_tb): - enriched_tb = _add_missing_frames(exc_tb) if exc_tb else exc_tb - # Note: sys.__excepthook__(...) would not work here. - # We need to use print_exception(...): - traceback.print_exception(exc_type, exc_value, enriched_tb) - msgBox = QMessageBox(self) - msgBox.information(self, "Exception", "WARNING: an exception occurred! " - "Details can be found in the console output. Please " - "include this output when opening an issue on GitHub: " - "" - "https://github.com/jonescompneurolab/hnn/issues") + def __init__(self): + """initialize the main HNN GUI""" + + super().__init__() + sys.excepthook = self.excepthook + + global fontsize + relative_root_path = os.path.join(os.path.dirname(__file__), '..') + hnn_root_dir = os.path.realpath(relative_root_path) + + self.defncore = _get_defncore() + self.runningsim = False + self.runthread = None + self.fontsize = fontsize + self.linewidth = plt.rcParams['lines.linewidth'] = 1 + self.markersize = plt.rcParams['lines.markersize'] = 5 + self.schemwin = SchematicDialog(self) + self.sim_canvas = self.toolbar = None + paramfn = os.path.join(hnn_root_dir, 'param', 'default.param') + self.baseparamwin = BaseParamDialog(self, paramfn, self.startoptmodel) + self.is_optimization = False + self.sim_data = SimData() + self.initUI() + self.helpwin = HelpDialog(self) + self.erselectdistal = \ + EvokedOrRhythmicDialog(self, True, self.baseparamwin.evparamwin, + self.baseparamwin.distparamwin) + self.erselectprox = \ + EvokedOrRhythmicDialog(self, False, self.baseparamwin.evparamwin, + self.baseparamwin.proxparamwin) + self.waitsimwin = WaitSimDialog(self) + + default_param = os.path.join(get_output_dir(), 'data', 'default') + first_load = not (os.path.exists(default_param)) + + if first_load: + QMessageBox.information(self, "HNN", "Welcome to HNN! Default" + " parameter file loaded. Press 'Run" + " Simulation' to display simulation" + " output") + else: + self.statusBar().showMessage("Loaded %s" % default_param) + + def excepthook(self, exc_type, exc_value, exc_tb): + enriched_tb = _add_missing_frames(exc_tb) if exc_tb else exc_tb + # Note: sys.__excepthook__(...) would not work here. + # We need to use print_exception(...): + traceback.print_exception(exc_type, exc_value, enriched_tb) + msgBox = QMessageBox(self) + msgBox.information( + self, "Exception", "WARNING: an exception occurred" + "! Details can be found in the console output. Please " + "include this output when opening an issue on GitHub: " + "" + "https://github.com/jonescompneurolab/hnn/issues") def redraw(self): - # redraw simulation & external data - self.sim_canvas.plot() - self.sim_canvas.draw() + """redraw simulation and external data""" + self.sim_canvas.plot() + self.sim_canvas.draw() def changeFontSize(self): - # bring up window to change font sizes - global fontsize + """bring up window to change font sizes""" + global fontsize - i, ok = QInputDialog.getInt(self, "Set Font Size","Font Size:", plt.rcParams['font.size'], 1, 100, 1) - if ok: - self.fontsize = plt.rcParams['font.size'] = fontsize = i - self.redraw() + i, ok = QInputDialog.getInt(self, "Set Font Size", "Font Size:", + plt.rcParams['font.size'], 1, 100, 1) + if ok: + self.fontsize = plt.rcParams['font.size'] = fontsize = i + self.redraw() def changeLineWidth(self): - # bring up window to change line width(s) - i, ok = QInputDialog.getInt(self, "Set Line Width","Line Width:", plt.rcParams['lines.linewidth'], 1, 20, 1) - if ok: - self.linewidth = plt.rcParams['lines.linewidth'] = i - self.redraw() + """bring up window to change line width(s)""" + i, ok = QInputDialog.getInt(self, "Set Line Width", "Line Width:", + plt.rcParams['lines.linewidth'], 1, 20, 1) + if ok: + self.linewidth = plt.rcParams['lines.linewidth'] = i + self.redraw() def changeMarkerSize(self): - # bring up window to change marker size - i, ok = QInputDialog.getInt(self, "Set Marker Size","Font Size:", self.markersize, 1, 100, 1) - if ok: - self.markersize = plt.rcParams['lines.markersize'] = i - self.redraw() + """bring up window to change marker size""" + i, ok = QInputDialog.getInt(self, "Set Marker Size", "Font Size:", + self.markersize, 1, 100, 1) + if ok: + self.markersize = plt.rcParams['lines.markersize'] = i + self.redraw() def selParamFileDialog(self): - # bring up window to select simulation parameter file + """bring up window to select simulation parameter file""" - hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) + relative_root_path = os.path.join(os.path.dirname(__file__), '..') + hnn_root_dir = os.path.realpath(relative_root_path) qfd = QFileDialog() qfd.setHistory([os.path.join(get_output_dir(), 'param'), os.path.join(hnn_root_dir, 'param')]) fn = qfd.getOpenFileName(self, 'Open param file', - os.path.join(hnn_root_dir,'param'), - "Param files (*.param)") + os.path.join(hnn_root_dir, 'param'), + "Param files (*.param)") if len(fn) > 0 and fn[0] == '': # no file selected in dialog return @@ -231,88 +243,99 @@ def selParamFileDialog(self): self.toggleEnableOptimization(True) def loadDataFile(self, fn): - # load a dipole data file + """load a dipole data file""" extdata = None try: extdata = np.loadtxt(fn) except ValueError: - # possible that data file is comma delimted instead of whitespace delimted + # possible that data file is comma delimited instead of whitespace + # delimited try: extdata = np.loadtxt(fn, delimiter=',') except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not load data file %s" % fn) + QMessageBox.information(self, "HNN", "WARNING: could not load data" + " file %s" % fn) return False except IsADirectoryError: - QMessageBox.information(self, "HNN", "WARNING: could not load data file %s" % fn) + QMessageBox.information(self, "HNN", "WARNING: could not load data" + " file %s" % fn) return False self.sim_data.update_exp_data(fn, extdata) print('Loaded data in ', fn) self.sim_canvas.plot() - self.sim_canvas.draw() # make sure new lines show up in plot + self.sim_canvas.draw() # make sure new lines show up in plot if self.baseparamwin.paramfn: self.toggleEnableOptimization(True) return True def loadDataFileDialog(self): - # bring up window to select/load external dipole data file - hnn_root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) - - qfd = QFileDialog() - qfd.setHistory([os.path.join(get_output_dir(), 'data'), - os.path.join(hnn_root_dir, 'data')]) - fn = qfd.getOpenFileName(self, 'Open data file', - os.path.join(hnn_root_dir,'data'), - "Data files (*.txt)") - if len(fn) > 0 and fn[0] == '': - # no file selected in dialog - return + """bring up window to select/load external dipole data""" + hnn_root_dir = \ + os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) + + qfd = QFileDialog() + qfd.setHistory([os.path.join(get_output_dir(), 'data'), + os.path.join(hnn_root_dir, 'data')]) + fn = qfd.getOpenFileName(self, 'Open data file', + os.path.join(hnn_root_dir, 'data'), + "Data files (*.txt)") + if len(fn) > 0 and fn[0] == '': + # no file selected in dialog + return - self.loadDataFile(os.path.abspath(fn[0])) # use abspath to make sure have right path separators + # use abspath to make sure have right path separators + self.loadDataFile(os.path.abspath(fn[0])) def clearDataFile(self): - # clear external dipole data - self.sim_canvas.clearlextdatobj() - self.sim_data.clear_exp_data() - self.toggleEnableOptimization(False) - self.sim_canvas.plot() # recreate canvas - self.sim_canvas.draw() + """clear external dipole data""" + self.sim_canvas.clearlextdatobj() + self.sim_data.clear_exp_data() + self.toggleEnableOptimization(False) + self.sim_canvas.plot() # recreate canvas + self.sim_canvas.draw() def setparams(self): - # show set parameters dialog window - if self.baseparamwin: - for win in self.baseparamwin.lsubwin: bringwintobot(win) - bringwintotop(self.baseparamwin) + """show set parameters dialog window""" + if self.baseparamwin: + for win in self.baseparamwin.lsubwin: + bringwintobot(win) + bringwintotop(self.baseparamwin) def showAboutDialog(self): - # show HNN's about dialog box - from hnn import __version__ - msgBox = QMessageBox(self) - msgBox.setTextFormat(Qt.RichText) - msgBox.setWindowTitle('About') - msgBox.setText("Human Neocortical Neurosolver (HNN) v" + __version__ + "
"+\ - "https://hnn.brown.edu
"+\ - "HNN On Github
"+\ - "© 2017-2019 Brown University, Providence, RI
"+\ - "Software License") - msgBox.setStandardButtons(QMessageBox.Ok) - msgBox.exec_() + """show HNN's about dialog box""" + from hnn import __version__ + msgBox = QMessageBox(self) + msgBox.setTextFormat(Qt.RichText) + msgBox.setWindowTitle('About') + msgBox.setText("Human Neocortical Neurosolver (HNN) v" + __version__ + + "
" + + "https://hnn.brown.edu" + + "
" + + "" + + "HNN On Github
© 2017-2019 " + "Brown University" + + ", Providence, RI
Software License") + msgBox.setStandardButtons(QMessageBox.Ok) + msgBox.exec_() def showOptWarnDialog(self): - # TODO : not implemented yet - msgBox = QMessageBox(self) - msgBox.setTextFormat(Qt.RichText) - msgBox.setWindowTitle('Warning') - msgBox.setText("") - msgBox.setStandardButtons(QMessageBox.Ok) - msgBox.exec_() + # TODO : not implemented yet + msgBox = QMessageBox(self) + msgBox.setTextFormat(Qt.RichText) + msgBox.setWindowTitle('Warning') + msgBox.setText("") + msgBox.setStandardButtons(QMessageBox.Ok) + msgBox.exec_() def showHelpDialog(self): - # show the help dialog box - bringwintotop(self.helpwin) + # show the help dialog box + bringwintotop(self.helpwin) def show_plot(self, plot_type): paramfn = self.baseparamwin.paramfn @@ -331,7 +354,7 @@ def show_plot(self, plot_type): 'Dipole Viewer') elif plot_type == 'PSD': PSDViewGUI(PSDCanvas, self.baseparamwin.params, sim_data, - 'PSD Viewer') + 'PSD Viewer') elif plot_type == 'spec': SpecViewGUI(SpecCanvas, self.baseparamwin.params, sim_data, 'Spectrogram Viewer') @@ -344,11 +367,11 @@ def show_plot(self, plot_type): def showSomaVPlot(self): # start the somatic voltage visualization process (separate window) if not float(self.baseparamwin.params['record_vsoma']): - smsg='In order to view somatic voltages you must first rerun' + \ - ' the simulation with saving somatic voltages. To do so' + \ - ' from the main GUI, click on Set Parameters -> Run ->' + \ - ' Analysis -> Save Somatic Voltages, enter a 1 and then' + \ - ' rerun the simulation.' + smsg = 'In order to view somatic voltages you must first rerun' + \ + ' the simulation with saving somatic voltages. To do so' + \ + ' from the main GUI, click on Set Parameters -> Run ->' + \ + ' Analysis -> Save Somatic Voltages, enter a 1 and then' + \ + ' rerun the simulation.' msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText(smsg) @@ -371,11 +394,11 @@ def showDipolePlot(self): self.show_plot('dipole') def showwaitsimwin(self): - # show the wait sim window (has simulation log) + """show the wait sim window (has simulation log)""" bringwintotop(self.waitsimwin) def togAvgDpl(self): - # toggle drawing of the average (across trials) dipole + """toggle drawing of the average (across trials) dipole""" global drawavgdpl drawavgdpl = not drawavgdpl @@ -383,35 +406,40 @@ def togAvgDpl(self): self.sim_canvas.draw() def hidesubwin(self): - # hide GUI's sub windows + """hide GUI's sub windows""" self.baseparamwin.hide() self.schemwin.hide() self.baseparamwin.syngainparamwin.hide() - for win in self.baseparamwin.lsubwin: win.hide() + for win in self.baseparamwin.lsubwin: + win.hide() self.activateWindow() def distribsubwin(self): - # distribute GUI's sub-windows on screen - sw,sh = getscreengeom() + """distribute GUI's sub-windows on screen""" + sw, sh = getscreengeom() lwin = [win for win in self.baseparamwin.lsubwin if win.isVisible()] - if self.baseparamwin.isVisible(): lwin.insert(0,self.baseparamwin) - if self.schemwin.isVisible(): lwin.insert(0,self.schemwin) - if self.baseparamwin.syngainparamwin.isVisible(): lwin.append(self.baseparamwin.syngainparamwin) - curx,cury,maxh=0,0,0 + if self.baseparamwin.isVisible(): + lwin.insert(0, self.baseparamwin) + if self.schemwin.isVisible(): + lwin.insert(0, self.schemwin) + if self.baseparamwin.syngainparamwin.isVisible(): + lwin.append(self.baseparamwin.syngainparamwin) + curx, cury, maxh = 0, 0, 0 for win in lwin: win.move(curx, cury) curx += win.width() - maxh = max(maxh,win.height()) + maxh = max(maxh, win.height()) if curx >= sw: curx = 0 cury += maxh maxh = win.height() - if cury >= sh: cury = cury = 0 + if cury >= sh: + cury = cury = 0 def updateDatCanv(self, params): - # now update the GUI components to reflect the param file selected + """update GUI to reflect param file selected""" self.baseparamwin.updateDispParam(params) - self.initSimCanvas() # recreate canvas + self.initSimCanvas() # recreate canvas self.setWindowTitle(self.baseparamwin.paramfn) def updateSelectedSim(self, sim_idx): @@ -458,8 +486,8 @@ def prevSim(self): else: self.updateSelectedSim(new_simidx) - def nextSim (self): - # go to next simulation + def nextSim(self): + """go to next simulation""" if self.cbsim.currentIndex() + 2 > self.cbsim.count(): print("There is no next simulation") @@ -468,41 +496,44 @@ def nextSim (self): new_simidx = self.cbsim.currentIndex() + 1 self.updateSelectedSim(new_simidx) - def clearSimulationData (self): - - # clear the simulation data + def clearSimulationData(self): + """clear the simulation data""" self.baseparamwin.params = None self.baseparamwin.paramfn = None self.sim_data.clear_sim_data() - self.cbsim.clear() # un-populate the combobox + self.cbsim.clear() # un-populate the combobox self.toggleEnableOptimization(False) + def clearSimulations(self): + """clear all simulation data - def clearSimulations (self): - # clear all simulation data and erase simulations from canvas (does not clear external data) + erase simulations from canvas (does not clear external data) + """ self.clearSimulationData() - self.initSimCanvas() # recreate canvas + self.initSimCanvas() # recreate canvas self.sim_canvas.draw() self.setWindowTitle('') - def clearCanvas (self): - # clear all simulation & external data and erase everything from the canvas - self.sim_canvas.clearlextdatobj() # clear the external data + def clearCanvas(self): + # clear all simulation & external data and erase everything from the + # canvas + self.sim_canvas.clearlextdatobj() # clear the external data self.clearSimulationData() self.sim_data.clear_exp_data() - self.initSimCanvas() # recreate canvas + self.initSimCanvas() # recreate canvas self.sim_canvas.draw() self.setWindowTitle('') - def initMenu (self): - # initialize the GUI's menu + def initMenu(self): + """initialize the GUI's menu""" exitAction = QAction(QIcon.fromTheme('exit'), 'Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit HNN application') exitAction.triggered.connect(qApp.quit) - selParamFile = QAction(QIcon.fromTheme('open'), 'Load parameter file', self) + selParamFile = QAction(QIcon.fromTheme('open'), 'Load parameter file', + self) selParamFile.setShortcut('Ctrl+P') selParamFile.setStatusTip('Load simulation parameter (.param) file') selParamFile.triggered.connect(self.selParamFileDialog) @@ -513,16 +544,18 @@ def initMenu (self): clearCanv.triggered.connect(self.clearCanvas) clearSims = QAction('Clear simulation(s)', self) - #clearSims.setShortcut('Ctrl+X') + # clearSims.setShortcut('Ctrl+X') clearSims.setStatusTip('Clear simulation(s)') clearSims.triggered.connect(self.clearSimulations) - loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data file', self) + loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data file', + self) loadDataFile.setShortcut('Ctrl+D') loadDataFile.setStatusTip('Load (dipole) data file') loadDataFile.triggered.connect(self.loadDataFileDialog) - clearDataFileAct = QAction(QIcon.fromTheme('close'), 'Clear data file(s)', self) + clearDataFileAct = QAction(QIcon.fromTheme('close'), + 'Clear data file(s)', self) clearDataFileAct.setShortcut('Ctrl+C') clearDataFileAct.setStatusTip('Clear (dipole) data file(s)') clearDataFileAct.triggered.connect(self.clearDataFile) @@ -542,27 +575,30 @@ def initMenu (self): fileMenu.addSeparator() fileMenu.addAction(exitAction) - # part of edit menu for changing drawing properties (line thickness, font size, toggle avg dipole drawing) + # part of edit menu for changing drawing properties (line thickness, + # font size, toggle avg dipole drawing) editMenu = self.menubar.addMenu('&Edit') - viewAvgDplAction = QAction('Toggle Average Dipole Drawing',self) + viewAvgDplAction = QAction('Toggle Average Dipole Drawing', self) viewAvgDplAction.setStatusTip('Toggle Average Dipole Drawing') viewAvgDplAction.triggered.connect(self.togAvgDpl) editMenu.addAction(viewAvgDplAction) - changeFontSizeAction = QAction('Change Font Size',self) + changeFontSizeAction = QAction('Change Font Size', self) changeFontSizeAction.setStatusTip('Change Font Size.') changeFontSizeAction.triggered.connect(self.changeFontSize) editMenu.addAction(changeFontSizeAction) - changeLineWidthAction = QAction('Change Line Width',self) + changeLineWidthAction = QAction('Change Line Width', self) changeLineWidthAction.setStatusTip('Change Line Width.') changeLineWidthAction.triggered.connect(self.changeLineWidth) editMenu.addAction(changeLineWidthAction) - changeMarkerSizeAction = QAction('Change Marker Size',self) + changeMarkerSizeAction = QAction('Change Marker Size', self) changeMarkerSizeAction.setStatusTip('Change Marker Size.') changeMarkerSizeAction.triggered.connect(self.changeMarkerSize) editMenu.addAction(changeMarkerSizeAction) editMenu.addSeparator() editMenu.addAction(clearSims) - clearDataFileAct2 = QAction(QIcon.fromTheme('close'), 'Clear data file(s)', self) # need new act to avoid DBus warning + # need new act to avoid DBus warning + clearDataFileAct2 = QAction(QIcon.fromTheme('close'), + 'Clear data file(s)', self) clearDataFileAct2.setStatusTip('Clear (dipole) data file(s)') clearDataFileAct2.triggered.connect(self.clearDataFile) editMenu.addAction(clearDataFileAct2) @@ -570,87 +606,93 @@ def initMenu (self): # view menu - to view drawing/visualizations viewMenu = self.menubar.addMenu('&View') - self.viewDipoleAction = QAction('View Simulation Dipoles',self) + self.viewDipoleAction = QAction('View Simulation Dipoles', + self) self.viewDipoleAction.setStatusTip('View Simulation Dipoles') self.viewDipoleAction.triggered.connect(self.showDipolePlot) viewMenu.addAction(self.viewDipoleAction) - self.viewRasterAction = QAction('View Simulation Spiking Activity',self) + self.viewRasterAction = QAction('View Simulation Spiking Activity', + self) self.viewRasterAction.setStatusTip('View Simulation Raster Plot') self.viewRasterAction.triggered.connect(self.showRasterPlot) viewMenu.addAction(self.viewRasterAction) - self.viewPSDAction = QAction('View PSD',self) + self.viewPSDAction = QAction('View PSD', self) self.viewPSDAction.setStatusTip('View PSD') self.viewPSDAction.triggered.connect(self.showPSDPlot) viewMenu.addAction(self.viewPSDAction) - self.viewSomaVAction = QAction('View Somatic Voltage',self) + self.viewSomaVAction = QAction('View Somatic Voltage', self) self.viewSomaVAction.setStatusTip('View Somatic Voltage') self.viewSomaVAction.triggered.connect(self.showSomaVPlot) viewMenu.addAction(self.viewSomaVAction) - self.viewSpecAction = QAction('View Spectrograms',self) - self.viewSpecAction.setStatusTip('View Spectrograms/Dipoles from Experimental Data') + self.viewSpecAction = QAction('View Spectrograms', self) + self.viewSpecAction.setStatusTip('View Spectrograms/Dipoles' + ' from Experimental Data') self.viewSpecAction.triggered.connect(self.showSpecPlot) viewMenu.addAction(self.viewSpecAction) viewMenu.addSeparator() - viewSchemAction = QAction('View Model Schematics',self) + viewSchemAction = QAction('View Model Schematics', self) viewSchemAction.setStatusTip('View Model Schematics') viewSchemAction.triggered.connect(self.showschematics) viewMenu.addAction(viewSchemAction) - viewSimLogAction = QAction('View Simulation Log',self) + viewSimLogAction = QAction('View Simulation Log', self) viewSimLogAction.setStatusTip('View Detailed Simulation Log') viewSimLogAction.triggered.connect(self.showwaitsimwin) viewMenu.addAction(viewSimLogAction) viewMenu.addSeparator() - distributeWindowsAction = QAction('Distribute Windows',self) - distributeWindowsAction.setStatusTip('Distribute Parameter Windows Across Screen.') + distributeWindowsAction = QAction('Distribute Windows', self) + distributeWindowsAction.setStatusTip('Distribute Parameter Windows' + ' Across Screen.') distributeWindowsAction.triggered.connect(self.distribsubwin) viewMenu.addAction(distributeWindowsAction) - hideWindowsAction = QAction('Hide Windows',self) + hideWindowsAction = QAction('Hide Windows', self) hideWindowsAction.setStatusTip('Hide Parameter Windows.') hideWindowsAction.triggered.connect(self.hidesubwin) hideWindowsAction.setShortcut('Ctrl+H') viewMenu.addAction(hideWindowsAction) simMenu = self.menubar.addMenu('&Simulation') - setParmAct = QAction('Set Parameters',self) + setParmAct = QAction('Set Parameters', self) setParmAct.setStatusTip('Set Simulation Parameters') setParmAct.triggered.connect(self.setparams) simMenu.addAction(setParmAct) simMenu.addAction(runSimAct) setOptParamAct = QAction('Configure Optimization', self) setOptParamAct.setShortcut('Ctrl+O') - setOptParamAct.setStatusTip('Set parameters for evoked input optimization') + setOptParamAct.setStatusTip('Set parameters for evoked input' + ' optimization') setOptParamAct.triggered.connect(self.showoptparamwin) simMenu.addAction(setOptParamAct) self.toggleEnableOptimization(False) - prevSimAct = QAction('Go to Previous Simulation',self) + prevSimAct = QAction('Go to Previous Simulation', self) prevSimAct.setShortcut('Ctrl+Z') prevSimAct.setStatusTip('Go Back to Previous Simulation') prevSimAct.triggered.connect(self.prevSim) simMenu.addAction(prevSimAct) - nextSimAct = QAction('Go to Next Simulation',self) + nextSimAct = QAction('Go to Next Simulation', self) nextSimAct.setShortcut('Ctrl+Y') nextSimAct.setStatusTip('Go Forward to Next Simulation') nextSimAct.triggered.connect(self.nextSim) simMenu.addAction(nextSimAct) - clearSims2 = QAction('Clear simulation(s)', self) # need another QAction to avoid DBus warning + # need another QAction to avoid DBus warning + clearSims2 = QAction('Clear simulation(s)', self) clearSims2.setStatusTip('Clear simulation(s)') clearSims2.triggered.connect(self.clearSimulations) simMenu.addAction(clearSims2) aboutMenu = self.menubar.addMenu('&About') - aboutAction = QAction('About HNN',self) + aboutAction = QAction('About HNN', self) aboutAction.setStatusTip('About HNN') aboutAction.triggered.connect(self.showAboutDialog) aboutMenu.addAction(aboutAction) - helpAction = QAction('Help',self) + helpAction = QAction('Help', self) helpAction.setStatusTip('Help on how to use HNN (parameters).') helpAction.triggered.connect(self.showHelpDialog) - #aboutMenu.addAction(helpAction) + # aboutMenu.addAction(helpAction) - def toggleEnableOptimization (self, toEnable): + def toggleEnableOptimization(self, toEnable): for menu in self.menubar.findChildren(QMenu): if menu.title() == '&Simulation': for item in menu.actions(): @@ -659,7 +701,7 @@ def toggleEnableOptimization (self, toEnable): break break - def addButtons (self, gRow): + def addButtons(self, gRow): self.pbtn = pbtn = QPushButton('Set Parameters', self) pbtn.setToolTip('Set Parameters') pbtn.resize(pbtn.sizeHint()) @@ -683,39 +725,51 @@ def addButtons (self, gRow): qbtn.resize(qbtn.sizeHint()) self.grid.addWidget(self.qbtn, gRow, 9, 1, 3) - def shownetparamwin (self): bringwintotop(self.baseparamwin.netparamwin) - def showoptparamwin (self): bringwintotop(self.baseparamwin.optparamwin) - def showdistparamwin (self): bringwintotop(self.erselectdistal) - def showproxparamwin (self): bringwintotop(self.erselectprox) - def showschematics (self): bringwintotop(self.schemwin) + def shownetparamwin(self): + bringwintotop(self.baseparamwin.netparamwin) + + def showoptparamwin(self): + bringwintotop(self.baseparamwin.optparamwin) + + def showdistparamwin(self): + bringwintotop(self.erselectdistal) - def addParamImageButtons (self,gRow): - # add parameter image buttons to the GUI + def showproxparamwin(self): + bringwintotop(self.erselectprox) - self.locbtn = QPushButton('Local Network'+os.linesep+'Connections',self) + def showschematics(self): + bringwintotop(self.schemwin) + + def addParamImageButtons(self, gRow): + """add parameter image buttons to the GUI""" + + self.locbtn = QPushButton('Local Network' + os.linesep + 'Connections', + self) self.locbtn.setIcon(QIcon(lookupresource('connfig'))) self.locbtn.clicked.connect(self.shownetparamwin) - self.grid.addWidget(self.locbtn,gRow,0,1,4) + self.grid.addWidget(self.locbtn, gRow, 0, 1, 4) - self.proxbtn = QPushButton('Proximal Drive'+os.linesep+'Thalamus',self) + self.proxbtn = QPushButton('Proximal Drive' + os.linesep + 'Thalamus', + self) self.proxbtn.setIcon(QIcon(lookupresource('proxfig'))) self.proxbtn.clicked.connect(self.showproxparamwin) - self.grid.addWidget(self.proxbtn,gRow,4,1,4) + self.grid.addWidget(self.proxbtn, gRow, 4, 1, 4) - self.distbtn = QPushButton('Distal Drive Non3Lemniscal'+os.linesep+'Thal./Cortical Feedback',self) + self.distbtn = QPushButton('Distal Drive NonLemniscal' + os.linesep + + 'Thal./Cortical Feedback', self) self.distbtn.setIcon(QIcon(lookupresource('distfig'))) self.distbtn.clicked.connect(self.showdistparamwin) - self.grid.addWidget(self.distbtn,gRow,8,1,4) + self.grid.addWidget(self.distbtn, gRow, 8, 1, 4) gRow += 1 - def initUI (self): - # initialize the user interface (UI) + def initUI(self): + """initialize the user interface (UI)""" self.initMenu() self.statusBar() - # start GUI in center of screenm, scale based on screen w x h + # start GUI in center of screenm, scale based on screen w x h setscalegeomcenter(self, 1500, 1300) # move param windows to be offset from main GUI @@ -728,7 +782,7 @@ def initUI (self): QToolTip.setFont(QFont('SansSerif', 10)) self.grid = grid = QGridLayout() - #grid.setSpacing(10) + # grid.setSpacing(10) gRow = 0 @@ -748,8 +802,8 @@ def initUI (self): print("Warning: no simulations to load") pass self.cbsim.activated[str].connect(self.onActivateSimCB) - self.grid.addWidget(self.cbsim, gRow, 0, 1, 8)#, 1, 3) - self.btnrmsim = QPushButton('Remove Simulation',self) + self.grid.addWidget(self.cbsim, gRow, 0, 1, 8) + self.btnrmsim = QPushButton('Remove Simulation', self) self.btnrmsim.resize(self.btnrmsim.sizeHint()) self.btnrmsim.clicked.connect(self.removeSim) self.btnrmsim.setToolTip('Remove Currently Selected Simulation') @@ -765,12 +819,12 @@ def initUI (self): self.setWindowIcon(QIcon(os.path.join('res', 'icon.png'))) - self.schemwin.show() # so it's underneath main window + self.schemwin.show() # so it's underneath main window self.show() def onActivateSimCB(self, paramfn): - # load simulation when activating simulation combobox + """load simulation when activating simulation combobox""" if paramfn != self.baseparamwin.paramfn: try: @@ -785,7 +839,7 @@ def onActivateSimCB(self, paramfn): self.updateDatCanv(params) def populateSimCB(self, index=None): - # populate the simulation combobox + """populate the simulation combobox""" self.cbsim.clear() for paramfn in self.sim_data._sim_data.keys(): @@ -801,10 +855,10 @@ def populateSimCB(self, index=None): self.cbsim.setCurrentIndex(index) def initSimCanvas(self, recalcErr=True, gRow=1, reInit=True): - # initialize the simulation canvas, loading any required data + """initialize the simulation canvas, loading any required data""" gCol = 0 - if reInit == True: + if reInit: self.grid.itemAtPosition(gRow, gCol).widget().deleteLater() self.grid.itemAtPosition(gRow + 1, gCol).widget().deleteLater() @@ -818,9 +872,11 @@ def initSimCanvas(self, recalcErr=True, gRow=1, reInit=True): self.baseparamwin.paramfn) return - self.sim_canvas = SIMCanvas(self.baseparamwin.paramfn, self.baseparamwin.params, - parent=self, width=10, height=1, dpi=getmplDPI(), - is_optimization=self.is_optimization) + self.sim_canvas = SIMCanvas(self.baseparamwin.paramfn, + self.baseparamwin.params, + parent=self, width=10, height=1, + dpi=getmplDPI(), + is_optimization=self.is_optimization) # this is the Navigation widget # it takes the Canvas widget and a parent @@ -836,7 +892,8 @@ def initSimCanvas(self, recalcErr=True, gRow=1, reInit=True): raise self.sim_canvas.saved_exception def setcursors(self, cursor): - # set cursors of self and children + """set cursors of self and children""" + self.setCursor(cursor) self.update() kids = self.children() @@ -849,18 +906,20 @@ def setcursors(self, cursor): k.update() def startoptmodel(self): - # start model optimization - if self.runningsim: - self.stopsim() # stop sim works but leaves subproc as zombie until this main GUI thread exits - else: - self.is_optimization = True - try: - self.optmodel(self.baseparamwin.runparamwin.getncore()) - except RuntimeError: - print("ERR: Optimization aborted") + """start model optimization""" + if self.runningsim: + # stop sim works but leaves subproc as zombie until this main GUI + # thread exits + self.stopsim() + else: + self.is_optimization = True + try: + self.optmodel(self.baseparamwin.runparamwin.getncore()) + except RuntimeError: + print("ERR: Optimization aborted") def controlsim(self): - # control the simulation + """control the simulation""" if self.runningsim: # stop sim works but leaves subproc as zombie until this main GUI # thread exits @@ -870,7 +929,7 @@ def controlsim(self): self.startsim(self.baseparamwin.runparamwin.getncore()) def stopsim(self): - # stop the simulation + """stop the simulation""" if self.runningsim: self.waitsimwin.hide() print('Terminating simulation. . .') @@ -883,7 +942,7 @@ def stopsim(self): self.setcursors(Qt.ArrowCursor) def optmodel(self, ncore): - # make sure params saved and ok to run + """make sure params saved and ok to run""" if not self.baseparamwin.saveparams(): return @@ -913,9 +972,9 @@ def optmodel(self, ncore): bringwintotop(self.waitsimwin) def startsim(self, ncore): - # start the simulation + """start the simulation""" if not self.baseparamwin.saveparams(self.baseparamwin.paramfn): - return # make sure params saved and ok to run + return # make sure params saved and ok to run # reread the params to get anything new try: @@ -936,7 +995,8 @@ def startsim(self, ncore): # check that valid number of trials was given if 'N_trials' not in params or params['N_trials'] == 0: - print("Warning: invalid configured number of trials. Setting to 1.") + print("Warning: invalid configured number of trials." + " Setting to 1.") params['N_trials'] = 1 self.runthread = RunSimThread(ncore, params, @@ -947,9 +1007,9 @@ def startsim(self, ncore): self.runthread.start() # At this point we want to allow user to stop/terminate the thread # so we enable that button - self.btnsim.setText("Stop Simulation") # setEnabled(False) - # We don't want to enable user to start another thread while this one is - # running so we disable the start button. + self.btnsim.setText("Stop Simulation") # setEnabled(False) + # We don't want to enable user to start another thread while this one + # is running so we disable the start button. # self.btn_start.setEnabled(False) self.qbtn.setEnabled(False) @@ -1015,7 +1075,7 @@ def result_callback(self, result): save_spec_data(spec_fn, spec_results) paramfn = os.path.join(get_output_dir(), 'param', - params['sim_prefix'] + '.param') + params['sim_prefix'] + '.param') self.sim_data.update_sim_data(paramfn, params, sim_data['dpls'], sim_data['avg_dpl'], sim_data['spikes'], @@ -1023,7 +1083,7 @@ def result_callback(self, result): sim_data['spec'], sim_data['vsoma']) def done(self, except_msg): - # called when the simulation completes running + """called when the simulation completes running""" self.runningsim = False self.waitsimwin.hide() self.statusBar().showMessage("") @@ -1034,7 +1094,7 @@ def done(self, except_msg): # self.sim_canvas.plot() self.setcursors(Qt.ArrowCursor) - failed=False + failed = False if len(except_msg) > 0: failed = True msg = "%s: Failed " % except_msg @@ -1053,9 +1113,9 @@ def done(self, except_msg): if failed: QMessageBox.critical(self, "Failed!", msg + "using " + - self.baseparamwin.paramfn + - '. Check simulation log or console for error ' - 'messages') + self.baseparamwin.paramfn + + '. Check simulation log or console for error ' + 'messages') else: if self.baseparamwin.params['save_figs']: self.sim_data.save_dipole_with_hist(self.baseparamwin.paramfn, @@ -1069,7 +1129,7 @@ def done(self, except_msg): data_dir = os.path.join(get_output_dir(), 'data') sim_dir = os.path.join(data_dir, - self.baseparamwin.params['sim_prefix']) + self.baseparamwin.params['sim_prefix']) QMessageBox.information(self, "Done!", msg + "using " + self.baseparamwin.paramfn + '. Saved data/figures in: ' + sim_dir) @@ -1079,11 +1139,11 @@ def done(self, except_msg): cb_index = self.cbsim.findText(self.baseparamwin.paramfn) if cb_index < 0: raise ValueError("Couldn't find simulation in combobox: %s" % - self.baseparamwin.paramfn) + self.baseparamwin.paramfn) self.cbsim.setCurrentIndex(cb_index) -if __name__ == '__main__': +if __name__ == '__main__': app = QApplication(sys.argv) HNNGUI() sys.exit(app.exec_()) From f8634230364ec09a897abf0b6d5f96f5a9b3127c Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Fri, 19 Mar 2021 21:01:46 -0400 Subject: [PATCH 092/107] ENH: legacy_param_str_dict for reading GUI params --- hnn/paramrw.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/hnn/paramrw.py b/hnn/paramrw.py index e68f509ba..be0478fc5 100644 --- a/hnn/paramrw.py +++ b/hnn/paramrw.py @@ -199,11 +199,12 @@ def usingTonicInputs (d): if t0 < t1 or t1 == -1.0: return True return False -def read_gids_param (fparam): +def read_gids_param(fparam): lines = clean_lines(fparam) gid_dict = {} for line in lines: - if line.startswith('#'): continue + if line.startswith('#'): + continue keystring, val = line.split(": ") key = keystring.strip() if val[0] == '[': @@ -217,6 +218,32 @@ def read_gids_param (fparam): return gid_dict + +def legacy_param_str_to_dict(param_str): + boolean_params = ['sync_evinput', 'record_vsoma', 'save_spec_data', + 'save_figs'] + + param_dict = {} + for line in param_str.splitlines(): + keystring, val = line.split(': ') + key = keystring.strip() + if key == 'expmt_groups': + continue + elif key == 'sim_prefix' or key == 'spec_cmap': + param_dict[key] = val + elif key.startswith('N_') or key.startswith('numspikes_') or \ + key.startswith('events_per_cycle_') or \ + key.startswith('repeats_') or \ + key.startswith('prng_seedcore_'): + param_dict[key] = int(val) + elif key in boolean_params: + param_dict[key] = int(val) + else: + param_dict[key] = float(val) + + return param_dict + + # write the params to a filename def write_legacy_paramf(fparam, p): """ now sorting From 9e61c269fb7c3b2555e7ac58f7751d44a6143fa2 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Fri, 19 Mar 2021 21:04:55 -0400 Subject: [PATCH 093/107] MAINT: clean up sim_data plotting --- hnn/qt_canvas.py | 80 ++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/hnn/qt_canvas.py b/hnn/qt_canvas.py index 58afdf673..c7bf1fb60 100644 --- a/hnn/qt_canvas.py +++ b/hnn/qt_canvas.py @@ -32,6 +32,8 @@ def __init__(self, paramfn, params, parent=None, width=5, height=4, self.sim_data = parent.sim_data self.lextdatobj = [] # external data object self.clridx = 5 # index for next color for drawing external data + self.errtot = None + self.lerr = [] # legend for dipole signals self.lpatch = [mpatches.Patch(color='black', label='Sim.')] @@ -200,24 +202,19 @@ def _has_simdata(self): return False - def plotextdat(self, recalcErr=True): + def plotextdat(self): global fontsize if self.sim_data._exp_data is None or \ len(self.sim_data._exp_data) == 0: return - initial_err = None # plot 'external' data (e.g. from experiment/other simulation) if self._has_simdata(): # has the simulation been run yet? - if recalcErr: - tstop = self.params['tstop'] - # recalculate/save the error? - self.lerr, self.errtot = self.sim_data.calcerr(self.paramfn, - tstop) - - if self.is_optimization: - initial_err = self.sim_data._opt_data['initial_error'] + tstop = self.params['tstop'] + # recalculate/save the error + self.lerr, self.errtot = self.sim_data.calcerr(self.paramfn, + tstop) if self.axdipole is None: self.axdipole = self.figure.add_subplot(self.G[0:-1, 0]) @@ -257,35 +254,33 @@ def plotextdat(self, recalcErr=True): self.axdipole.set_xlim(xl) self.axdipole.set_ylim(yl) - if self.lpatch: + if len(self.lpatch) > 0: self.axdipole.legend(handles=self.lpatch, loc=2) - if self.errtot: - tx, ty = 0, 0 - if self.is_optimization and initial_err: - clr = 'black' - txt = 'RMSE: %.2f' % round(initial_err, 2) - textcoords = 'axes fraction' - self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), - xytext=(0.005, 0.005), - textcoords=textcoords, - color=clr, - fontweight='bold') - clr = 'gray' - txt = 'RMSE: %.2f' % round(self.errtot, 2) - self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), - xytext=(0.86, 0.005), - textcoords=textcoords, - color=clr, - fontweight='bold') - else: - clr = 'black' - txt = 'Avg. RMSE: %.2f' % round(self.errtot, 2) - self.annot_avg = self.axdipole.annotate(txt, xy=(0, 0), - xytext=(0.005, 0.005), - textcoords=textcoords, - color=clr, - fontweight='bold') + if self.errtot is not None: + textcoords = 'axes fraction' + clr = 'black' + txt = 'Avg. RMSE: %.2f' % round(self.errtot, 2) + if self.is_optimization: + if 'initial_error' in self.sim_data._opt_data: + initial_error = self.sim_data._opt_data['initial_error'] + txt = 'Initial RMSE: %.2f' % round(initial_error, 2) + annot_initial = \ + self.axdipole.annotate(txt, xy=(0, 0), + xytext=(0.86, 0.005), + textcoords=textcoords, + color=clr, + fontweight='bold') + self.lextdatobj.append(annot_initial) + txt = 'Opt RMSE: %.2f' % round(self.errtot, 2) + clr = 'gray' + + annot_avg = self.axdipole.annotate(txt, xy=(0, 0), + xytext=(0.005, 0.005), + textcoords=textcoords, + color=clr, + fontweight='bold') + self.lextdatobj.append(annot_avg) if not self._has_simdata(): # need axis labels self.axdipole.set_xlabel('Time (ms)', fontsize=fontsize) @@ -340,13 +335,12 @@ def plotsimdat(self): xlim = (0.0, tstop) # for trying to plot a simulation read from disk (e.g. default) - if self.paramfn in self.sim_data._sim_data: - data_to_plot = True - pass - else: + if self.paramfn not in self.sim_data._sim_data: # load simulation data from disk data_to_plot = self.sim_data.update_sim_data_from_disk( self.paramfn, self.params) + else: + data_to_plot = True if data_to_plot: sim_data = self.sim_data._sim_data[self.paramfn]['data'] @@ -440,14 +434,14 @@ def plotarrows(self): (xl[1] - xl[0]) * .02, (yl[1] - yl[0]) * .02) - def plot(self, recalcErr=True): + def plot(self): self.clearaxes() plt.close(self.figure) self.figure.clf() self.axdipole = None self.plotsimdat() # creates self.axdipole - self.plotextdat(recalcErr) + self.plotextdat() self.plotarrows() self.draw() From 68201905a90ffe9f8059c457961ca6827e6c1e15 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Fri, 19 Mar 2021 21:08:17 -0400 Subject: [PATCH 094/107] MAINT: separate updating GUI param and save file --- hnn/qt_dialog.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/hnn/qt_dialog.py b/hnn/qt_dialog.py index 75cc6fe87..6534c7da3 100644 --- a/hnn/qt_dialog.py +++ b/hnn/qt_dialog.py @@ -12,9 +12,10 @@ QVBoxLayout, QHBoxLayout, QGridLayout) from PyQt5.QtGui import QFont, QPixmap, QIcon -from hnn_core import read_params +from hnn_core import read_params, Params -from .paramrw import usingOngoingInputs, usingEvokedInputs, get_output_dir +from .paramrw import (usingOngoingInputs, usingEvokedInputs, get_output_dir, + legacy_param_str_to_dict) from .qt_lib import (setscalegeom, setscalegeomcenter, lookupresource, ClickLabel) from .qt_evoked import EvokedInputParamDialog, OptEvokedInputParamDialog @@ -495,7 +496,8 @@ def initd(self): ('dipole_smooth_win', 15.0), ('record_vsoma', 0)]) - self.drand = OrderedDict([('prng_seedcore_opt', 0), + self.drand = OrderedDict([('prng_seedcore_opt', + self.mainwin.prng_seedcore_opt), ('prng_seedcore_input_prox', 0), ('prng_seedcore_input_dist', 0), ('prng_seedcore_extpois', 0), @@ -533,7 +535,7 @@ def initd(self): def selectionchange(self, i): self.spec_cmap = self.cmaps[i] - self.parent.updatesaveparams({}) + self.parent.update_gui_params({}) def initExtra(self): DictDialog.initExtra(self) @@ -576,6 +578,15 @@ def getncore(self): return ncore + def get_prng_seedcore_opt(self): + prng_seedcore_opt = self.dqline['prng_seedcore_opt'].text().strip() + + # update value in HNNGUI for persistence + self.mainwin.prng_seedcore_opt = int(prng_seedcore_opt) + + return int(self.mainwin.prng_seedcore_opt) + + def setfromdin(self, din): if not din: return @@ -931,9 +942,10 @@ def initUI(self): class BaseParamDialog (QDialog): """Base widget for specifying params - contains buttons to create other widgets + The params dictionary is stored within this class. Other Dialogs access it + here. """ - def __init__(self, parent, paramfn, optrun_func): + def __init__(self, parent, paramfn): super(BaseParamDialog, self).__init__(parent) self.proxparamwin = None self.distparamwin = None @@ -946,7 +958,7 @@ def __init__(self, parent, paramfn, optrun_func): self.proxparamwin = OngoingInputParamDialog(self, 'Proximal') self.distparamwin = OngoingInputParamDialog(self, 'Distal') self.evparamwin = EvokedInputParamDialog(self, None) - self.optparamwin = OptEvokedInputParamDialog(self, optrun_func) + self.optparamwin = OptEvokedInputParamDialog(self, parent) self.poisparamwin = PoissonInputParamDialog(self, None) self.tonicparamwin = TonicInputParamDialog(self, None) self.lsubwin = [self.runparamwin, self.cellparamwin, self.netparamwin, @@ -1126,8 +1138,10 @@ def saveparams(self, checkok=True): oktosave = False if oktosave: - os.makedirs(param_dir, exist_ok=True) + # update params dict with values from GUI + self.params = Params(legacy_param_str_to_dict(str(self))) + os.makedirs(param_dir, exist_ok=True) with open(tmpf, 'w') as fp: fp.write(str(self)) @@ -1138,15 +1152,13 @@ def saveparams(self, checkok=True): return oktosave - def updatesaveparams(self, dtest): + def update_gui_params(self, dtest): """ Update parameter values in GUI So user can see and so GUI will save these param values """ for win in self.lsubwin: win.setfromdin(dtest) - # save parameters - do not ask if can over-write the param file - self.saveparams(checkok=False) def __str__(self): s = 'sim_prefix: ' + self.qle.text() + os.linesep From a51ec1c052c8d4647523edc8d46f3d402c6bf33d Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Fri, 19 Mar 2021 21:11:37 -0400 Subject: [PATCH 095/107] MAINT: optimization works after hnn-core integrtin --- hnn/qt_evoked.py | 26 +- hnn/qt_main.py | 179 ++++++++------ hnn/run.py | 613 +++++++++++++++++++++++++++-------------------- hnn/simdata.py | 125 ++++++++-- 4 files changed, 577 insertions(+), 366 deletions(-) diff --git a/hnn/qt_evoked.py b/hnn/qt_evoked.py index 9558e4654..457e13b8a 100644 --- a/hnn/qt_evoked.py +++ b/hnn/qt_evoked.py @@ -597,7 +597,7 @@ def addDist (self): class OptEvokedInputParamDialog (EvokedInputBaseDialog): - def __init__ (self, parent, optrun_func): + def __init__(self, parent, mainwin): super(OptEvokedInputParamDialog, self).__init__() self.nprox = self.ndist = 0 # number of proximal,distal inputs self.ld = [] # list of dictionaries for proximal/distal inputs @@ -631,7 +631,7 @@ def __init__ (self, parent, optrun_func): self.sim_dt = 0.0 self.default_num_step_sims = 30 self.default_num_total_sims = 50 - self.optrun_func = optrun_func + self.mainwin = mainwin self.optimization_running = False self.initUI() self.parent = parent @@ -1014,10 +1014,11 @@ def runOptimization(self): # update the opt info dict to capture num_sims from GUI self.rebuildOptStepInfo() self.optimization_running = True + self.populate_initial_opt_ranges() - # run the actual optimization. optrun_func comes from HNNGUI.startoptmodel(): - # passed to BaseParamDialog then finally OptEvokedInputParamDialog - self.optrun_func() + # run the actual optimization + num_steps = self.get_num_chunks() + self.mainwin.startoptmodel(num_steps) def get_chunk_start(self, step): return self.chunk_list[step]['opt_start'] @@ -1066,6 +1067,15 @@ def get_chunk_ranges(self, step): return ranges + def get_initial_params(self): + initial_params = {} + for input_name in self.opt_params.keys(): + for label in self.opt_params[input_name]['ranges'].keys(): + initial_params[label] = \ + self.opt_params[input_name]['ranges'][label]['initial'] + + return initial_params + def get_num_params(self, step): num_params = 0 @@ -1078,9 +1088,7 @@ def get_num_params(self, step): return num_params - def push_chunk_ranges(self, step, ranges): - import re - + def push_chunk_ranges(self, ranges): for label, value in ranges.items(): for tab_name in self.opt_params.keys(): if label in self.opt_params[tab_name]['ranges']: @@ -1218,7 +1226,7 @@ def rebuildOptStepInfo(self): self.dtab_names = temp_dtab_names self.dtab_idx = temp_dtab_idx - def toggleEnableUserFields(self, step, enable=True): + def toggle_enable_user_fields(self, step, enable=True): if not enable: # the optimization called this to disable parameters on # for the step passed in to this function diff --git a/hnn/qt_main.py b/hnn/qt_main.py index 97ac9b2c9..b6def4c3f 100644 --- a/hnn/qt_main.py +++ b/hnn/qt_main.py @@ -36,7 +36,7 @@ write_gids_param, get_fname) from .simdata import SimData from .qt_canvas import SIMCanvas -from .run import RunSimThread +from .run import SimThread, OptThread from .qt_lib import (getmplDPI, getscreengeom, lookupresource, setscalegeomcenter) from .specfn import spec_dpl_kernel, save_spec_data @@ -126,7 +126,8 @@ def __init__(self): self.schemwin = SchematicDialog(self) self.sim_canvas = self.toolbar = None paramfn = os.path.join(hnn_root_dir, 'param', 'default.param') - self.baseparamwin = BaseParamDialog(self, paramfn, self.startoptmodel) + self.prng_seedcore_opt = 0 + self.baseparamwin = BaseParamDialog(self, paramfn) self.is_optimization = False self.sim_data = SimData() self.initUI() @@ -162,6 +163,7 @@ def excepthook(self, exc_type, exc_value, exc_tb): "include this output when opening an issue on GitHub: " "" "https://github.com/jonescompneurolab/hnn/issues") + self.done('Exception') def redraw(self): """redraw simulation and external data""" @@ -854,7 +856,7 @@ def populateSimCB(self, index=None): else: self.cbsim.setCurrentIndex(index) - def initSimCanvas(self, recalcErr=True, gRow=1, reInit=True): + def initSimCanvas(self, gRow=1, reInit=True): """initialize the simulation canvas, loading any required data""" gCol = 0 @@ -884,9 +886,6 @@ def initSimCanvas(self, recalcErr=True, gRow=1, reInit=True): gWidth = 12 self.grid.addWidget(self.toolbar, gRow, gCol, 1, gWidth) self.grid.addWidget(self.sim_canvas, gRow + 1, gCol, 1, gWidth) - if self.sim_data.get_exp_data_size() > 0: - self.sim_canvas.plot(recalcErr) - self.sim_canvas.draw() if self.sim_canvas.saved_exception is not None: raise self.sim_canvas.saved_exception @@ -905,24 +904,50 @@ def setcursors(self, cursor): k.setCursor(cursor) k.update() - def startoptmodel(self): + def startoptmodel(self, num_steps): """start model optimization""" if self.runningsim: - # stop sim works but leaves subproc as zombie until this main GUI - # thread exits - self.stopsim() - else: - self.is_optimization = True - try: - self.optmodel(self.baseparamwin.runparamwin.getncore()) - except RuntimeError: - print("ERR: Optimization aborted") + raise ValueError("Optimization already running") + + self.is_optimization = True + if not self.baseparamwin.saveparams(): + # user may have pressed 'cancel' + return + + # optimize the model + print('Starting model optimization. . .') + # save initial parameters file + # data_dir = op.join(get_output_dir(), 'data') + # sim_dir = op.join(data_dir, self.params['sim_prefix']) + # param_out = os.path.join(sim_dir, 'before_opt.param') + # write_legacy_paramf(param_out, self.params) + seed = self.baseparamwin.runparamwin.get_prng_seedcore_opt() + ncore = self.baseparamwin.runparamwin.getncore() + self.runthread = OptThread(ncore, self.baseparamwin.params, num_steps, + seed, self.sim_data, + self.sim_result_callback, + self.opt_callback, mainwin=self) + self.runningsim = True + self.runthread.start() + + # update optimization dialog + self.baseparamwin.optparamwin.btnreset.setEnabled(False) + self.baseparamwin.optparamwin.btnrunop.setText('Stop Optimization') + self.baseparamwin.optparamwin.btnrunop.clicked.disconnect() + self.baseparamwin.optparamwin.btnrunop.clicked.connect(self.stopsim) + + # update GUI + self.statusBar().showMessage("Optimizing model. . .") + self.setcursors(Qt.WaitCursor) + self.btnsim.setText("Stop Optimization") + self.qbtn.setEnabled(False) + self.waitsimwin.updatetxt('Optimizing model. . .') + bringwintotop(self.waitsimwin) def controlsim(self): """control the simulation""" if self.runningsim: # stop sim works but leaves subproc as zombie until this main GUI - # thread exits self.stopsim() else: self.is_optimization = False @@ -936,44 +961,24 @@ def stopsim(self): self.statusBar().showMessage('Terminating sim. . .') self.runningsim = False self.runthread.stop() # killed = True # terminate() + self.runthread.wait(1000) + self.runthread.terminate() self.btnsim.setText("Run Simulation") self.qbtn.setEnabled(True) self.statusBar().showMessage('') self.setcursors(Qt.ArrowCursor) - def optmodel(self, ncore): - """make sure params saved and ok to run""" - if not self.baseparamwin.saveparams(): - return - - self.baseparamwin.optparamwin.btnreset.setEnabled(False) - self.baseparamwin.optparamwin.btnrunop.setText('Stop Optimization') - self.baseparamwin.optparamwin.btnrunop.clicked.disconnect() - self.baseparamwin.optparamwin.btnrunop.clicked.connect(self.stopsim) - - # optimize the model - self.setcursors(Qt.WaitCursor) - print('Starting model optimization. . .') - - self.runningsim = True - - self.statusBar().showMessage("Optimizing model. . .") - - self.runthread = RunSimThread(ncore, self.baseparamwin.params, - self.result_callback, - mainwin=self, is_optimization=True) - - # We have all the events we need connected we can start the thread - self.runthread.start() - # At this point we want to allow user to stop/terminate the thread - # so we enable that button - self.btnsim.setText("Stop Optimization") - self.qbtn.setEnabled(False) - bringwintotop(self.waitsimwin) + if self.is_optimization: + self.baseparamwin.optparamwin.btnrunop.setText( + 'Run Optimization') + self.baseparamwin.optparamwin.btnrunop.clicked.disconnect() + self.baseparamwin.optparamwin.btnrunop.clicked.connect( + self.baseparamwin.optparamwin.runOptimization) + self.is_optimization = False def startsim(self, ncore): """start the simulation""" - if not self.baseparamwin.saveparams(self.baseparamwin.paramfn): + if not self.baseparamwin.saveparams(): return # make sure params saved and ok to run # reread the params to get anything new @@ -999,23 +1004,19 @@ def startsim(self, ncore): " Setting to 1.") params['N_trials'] = 1 - self.runthread = RunSimThread(ncore, params, - self.result_callback, - mainwin=self, is_optimization=False) + self.runthread = SimThread(ncore, params, self.sim_result_callback, + mainwin=self) # We have all the events we need connected we can start the thread self.runthread.start() # At this point we want to allow user to stop/terminate the thread # so we enable that button self.btnsim.setText("Stop Simulation") # setEnabled(False) - # We don't want to enable user to start another thread while this one - # is running so we disable the start button. - # self.btn_start.setEnabled(False) self.qbtn.setEnabled(False) bringwintotop(self.waitsimwin) - def result_callback(self, result): + def sim_result_callback(self, result): sim_data = result.data sim_data['spec'] = [] params = result.params @@ -1082,41 +1083,62 @@ def result_callback(self, result): sim_data['gid_ranges'], sim_data['spec'], sim_data['vsoma']) - def done(self, except_msg): + def opt_callback(self): + # re-enable all the range sliders (last step) + self.baseparamwin.optparamwin.toggle_enable_user_fields( + self.baseparamwin.optparamwin.get_num_chunks() - 1, + enable=True) + + self.baseparamwin.optparamwin.clear_initial_opt_ranges() + self.baseparamwin.optparamwin.optimization_running = False + # self.done() + + def done(self, except_msg=''): """called when the simulation completes running""" self.runningsim = False self.waitsimwin.hide() self.statusBar().showMessage("") self.btnsim.setText("Run Simulation") self.qbtn.setEnabled(True) - # recreate canvas (plots too) to avoid incorrect axes - self.initSimCanvas() - # self.sim_canvas.plot() - self.setcursors(Qt.ArrowCursor) - failed = False if len(except_msg) > 0: failed = True - msg = "%s: Failed " % except_msg - else: - msg = "Finished " - - if self.is_optimization: - msg += "running optimization " - self.baseparamwin.optparamwin.btnrunop.setText( - 'Prepare for Another Optimization') - self.baseparamwin.optparamwin.btnrunop.clicked.disconnect() - self.baseparamwin.optparamwin.btnrunop.clicked.connect( - self.baseparamwin.optparamwin.prepareOptimization) else: - msg += "running sim " + failed = False if failed: + msg = "%s: Failed " % except_msg + + if self.is_optimization: + msg += "running optimization " + self.baseparamwin.optparamwin.btnrunop.setText( + 'Run Optimization') + self.baseparamwin.optparamwin.btnrunop.clicked.disconnect() + self.baseparamwin.optparamwin.btnrunop.clicked.connect( + self.baseparamwin.optparamwin.runOptimization) + else: + msg += "running sim " + QMessageBox.critical(self, "Failed!", msg + "using " + self.baseparamwin.paramfn + '. Check simulation log or console for error ' 'messages') else: + # save params to file after successful completion + self.baseparamwin.saveparams(checkok=False) + + msg = "Finished " + + if self.is_optimization: + msg += "running optimization " + self.baseparamwin.optparamwin.btnrunop.setText( + 'Prepare for Another Optimization') + self.baseparamwin.optparamwin.btnrunop.clicked.disconnect() + self.baseparamwin.optparamwin.btnrunop.clicked.connect( + self.baseparamwin.optparamwin.prepareOptimization) + else: + msg += "running sim " + if self.baseparamwin.params['save_figs']: self.sim_data.save_dipole_with_hist(self.baseparamwin.paramfn, self.baseparamwin.params) @@ -1133,15 +1155,20 @@ def done(self, except_msg): QMessageBox.information(self, "Done!", msg + "using " + self.baseparamwin.paramfn + '. Saved data/figures in: ' + sim_dir) + + # recreate canvas (plots too) to avoid incorrect axes + self.initSimCanvas() + # self.sim_canvas.plot() + self.setcursors(Qt.ArrowCursor) + self.setWindowTitle(self.baseparamwin.paramfn) self.populateSimCB() # populate the combobox cb_index = self.cbsim.findText(self.baseparamwin.paramfn) - if cb_index < 0: - raise ValueError("Couldn't find simulation in combobox: %s" % - self.baseparamwin.paramfn) - self.cbsim.setCurrentIndex(cb_index) + if cb_index >= 0: + self.cbsim.setCurrentIndex(cb_index) + self.is_optimization = False if __name__ == '__main__': app = QApplication(sys.argv) diff --git a/hnn/run.py b/hnn/run.py index 1446fc266..6c47d960d 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -4,20 +4,36 @@ # Sam Neymotin # Shane Lee -import os.path as op import os import sys -from time import sleep -from copy import deepcopy from math import ceil, isclose from contextlib import redirect_stdout from psutil import wait_procs, process_iter, NoSuchProcess +import threading +import traceback +from queue import Queue import nlopt from PyQt5 import QtCore from hnn_core import simulate_dipole, Network, MPIBackend +from hnn_core.dipole import Dipole -from .paramrw import write_legacy_paramf, get_output_dir +from .paramrw import get_output_dir + + +class BasicSignal(QtCore.QObject): + """for signaling""" + sig = QtCore.pyqtSignal() + + +class ObjectSignal(QtCore.QObject): + """for returning an object""" + sig = QtCore.pyqtSignal(object) + + +class QueueSignal(QtCore.QObject): + """for synchronization""" + qsig = QtCore.pyqtSignal(Queue, str, float) class TextSignal(QtCore.QObject): @@ -30,6 +46,11 @@ class DataSignal(QtCore.QObject): dsig = QtCore.pyqtSignal(str, dict) +class OptDataSignal(QtCore.QObject): + """for signalling update to opt_data""" + odsig = QtCore.pyqtSignal(str, dict, Dipole) + + class ParamSignal(QtCore.QObject): """for updating GUI & param file during optimization""" psig = QtCore.pyqtSignal(dict) @@ -113,6 +134,9 @@ def simulate(params, n_procs=None): postproc=False, record_vsoma=record_vsoma) + # hnn-core changes this to bool, change back to int + if isinstance(params['record_vsoma'], bool): + params['record_vsoma'] = int(params['record_vsoma']) sim_data['gid_ranges'] = net.gid_ranges sim_data['spikes'] = net.cell_response sim_data['vsoma'] = net.cell_response.vsoma @@ -121,8 +145,8 @@ def simulate(params, n_procs=None): # based on https://nikolak.com/pyqt-threading-tutorial/ -class RunSimThread(QtCore.QThread): - """The RunSimThread class. +class SimThread(QtCore.QThread): + """The SimThread class. Parameters ---------- @@ -131,12 +155,12 @@ class RunSimThread(QtCore.QThread): Number of cores to run this simulation over params : dict Dictionary of params describing simulation config + result_callback: function + Handle to for callback to call after every sim completion waitsimwin : WaitSimDialog Handle to the Qt dialog during a simulation mainwin : HNNGUI Handle to the main application window - is_optimization: bool - Whether this simulation thread is running an optimization Attributes ---------- @@ -150,20 +174,17 @@ class RunSimThread(QtCore.QThread): Whether this simulation thread is running an optimization killed : bool Whether this simulation was forcefully terminated - killed : bool - Whether this simulation was forcefully terminated """ - result_signal = QtCore.pyqtSignal(object) - - def __init__(self, ncore, params, result_callback, mainwin, - is_optimization=False): + def __init__(self, ncore, params, result_callback, mainwin): QtCore.QThread.__init__(self) self.ncore = ncore self.params = params self.mainwin = mainwin + self.is_optimization = self.mainwin.is_optimization self.baseparamwin = self.mainwin.baseparamwin - self.result_signal.connect(result_callback) + self.result_signal = ObjectSignal() + self.result_signal.sig.connect(result_callback) self.killed = False self.paramfn = os.path.join(get_output_dir(), 'param', @@ -178,12 +199,6 @@ def __init__(self, ncore, params, result_callback, mainwin, self.done_signal = TextSignal() self.done_signal.tsig.connect(self.mainwin.done) - self.prmComm = ParamSignal() - self.prmComm.psig.connect(self.baseparamwin.updatesaveparams) - - self.canvComm = CanvSignal() - self.canvComm.csig.connect(self.mainwin.initSimCanvas) - def _updatewaitsimwin(self, txt): """Used to write messages to simulation window""" self.txtComm.tsig.emit(txt) @@ -203,55 +218,29 @@ def write(self, message): def flush(self): self.out.flush() - def _updatebaseparamwin(self, d): - """Signals baseparamwin to update its parameter from passed dict""" - self.prmComm.psig.emit(d) - - def _updatedispparam(self): - """Signals baseparamwin to run updateDispParam""" - self.param_signal.psig.emit(self.params) - - def _updatedrawerr(self): - """Signals mainwin to redraw canvas with RMSE""" - # When not running optimization, do not recalculate error - self.canvComm.csig.emit(False) - def stop(self): """Terminate running simulation""" _kill_and_check_nrniv_procs() self.killed = True - def __del__(self): - self.quit() - self.wait() - - def run(self): + def run(self, simlength=None): """Start simulation""" msg = '' - - if self.mainwin.is_optimization: - try: - self.optmodel() # run optimization - except RuntimeError as e: - msg = str(e) - self.baseparamwin.optparamwin.toggleEnableUserFields( - self.cur_step, enable=True) - self.baseparamwin.optparamwin.clear_initial_opt_ranges() - self.baseparamwin.optparamwin.optimization_running = False - else: - try: - self._runsim() # run simulation - - # update params in all windows (optimization) - self._updatedispparam() - except RuntimeError as e: - msg = str(e) - - self.done_signal.tsig.emit(msg) - - # run sim command via mpi, then delete the temp file. - def _runsim(self, banner=True, simlength=None): + banner = not self.is_optimization + try: + self._run(banner=banner, simlength=simlength) # run simulation + # update params in all windows (optimization) + except RuntimeError as e: + msg = str(e) + self.done_signal.tsig.emit(msg) + return + + if not self.is_optimization: + self.param_signal.psig.emit(self.params) + self.done_signal.tsig.emit(msg) + + def _run(self, banner=True, simlength=None): self.killed = False while True: @@ -283,49 +272,106 @@ def _runsim(self, banner=True, simlength=None): self._updatewaitsimwin(txt) # put sim_data into the val attribute of a ResultObj - self.result_signal.emit(ResultObj(sim_data, self.params)) + self.result_signal.sig.emit(ResultObj(sim_data, self.params)) + + +class OptThread(SimThread): + """The OptThread class. + + Parameters + ---------- + + ncore : int + Number of cores to run this simulation over + params : dict + Dictionary of params describing simulation config + waitsimwin : WaitSimDialog + Handle to the Qt dialog during a simulation + result_callback: function + Handle to for callback to call after every sim completion + mainwin : HNNGUI + Handle to the main application window + + Attributes + ---------- + ncore : int + Number of cores to run this simulation over + params : dict + Dictionary of params describing simulation config + mainwin : HNNGUI instance + Handle to the main application window + baseparamwin: BaseParamDialog instance + Handle to base parameters dialog + paramfn : str + Full pathname of the written parameter file name + """ + def __init__(self, ncore, params, num_steps, seed, sim_data, + result_callback, opt_callback, mainwin): + super().__init__(ncore, params, result_callback, mainwin) + self.waitsimwin = self.mainwin.waitsimwin + self.optparamwin = self.baseparamwin.optparamwin + self.cur_itr = 0 + self.num_steps = num_steps + self.sim_data = sim_data + self.result_callback = result_callback + self.seed = seed + self.best_step_werr = 1e9 + self.sim_running = False + self.killed = False + + self.done_signal.tsig.connect(opt_callback) + + self.refresh_signal = BasicSignal() + self.refresh_signal.sig.connect(self.mainwin.initSimCanvas) + + self.update_opt_data = OptDataSignal() + self.update_opt_data.odsig.connect(sim_data.update_opt_data) + + self.update_sim_data_from_opt_data = TextSignal() + self.update_sim_data_from_opt_data.tsig.connect( + sim_data.update_sim_data_from_opt_data) + + self.update_opt_data_from_sim_data = TextSignal() + self.update_opt_data_from_sim_data.tsig.connect( + sim_data.update_opt_data_from_sim_data) + + self.update_initial_opt_data_from_sim_data = TextSignal() + self.update_initial_opt_data_from_sim_data.tsig.connect( + sim_data.update_initial_opt_data_from_sim_data) + + self.get_err_from_sim_data = QueueSignal() + self.get_err_from_sim_data.qsig.connect(sim_data.get_err_wrapper) - def optmodel(self): - need_initial_ddat = False + def run(self): + msg = '' + try: + self._run() # run optimization + except RuntimeError as e: + msg = str(e) + + self.done_signal.tsig.emit(msg) + + def stop(self): + """Terminate running simulation""" + self.sim_thread.stop() + self.sim_thread.terminate() + self.sim_thread.wait() + self.killed = True + self.done_signal.tsig.emit("Optimization terminated") + def _run(self): # initialize RNG with seed from config - seed = self.params['prng_seedcore_opt'] - nlopt.srand(seed) - - # initial_ddat stores the initial fit (from "Run Simulation"). - # To be displayed in final dipole plot as black dashed line. - if len(ddat) > 0: - initial_ddat['dpl'] = deepcopy(ddat['dpl']) - initial_ddat['errtot'] = deepcopy(ddat['errtot']) - else: - need_initial_ddat = True - - self.baseparamwin.optparamwin.populate_initial_opt_ranges() - - # save initial parameters file - data_dir = op.join(get_output_dir(), 'data') - sim_dir = op.join(data_dir, self.params['sim_prefix']) - param_out = os.path.join(sim_dir, 'before_opt.param') - write_legacy_paramf(param_out, self.params) - - self._updatewaitsimwin('Optimizing model. . .') - - self.last_step = False - self.first_step = True - num_steps = self.baseparamwin.optparamwin.get_num_chunks() - for step in range(num_steps): + nlopt.srand(self.seed) + self.get_initial_data() + + for step in range(self.num_steps): self.cur_step = step - if step == num_steps - 1: - self.last_step = True # disable range sliders for each step once that step has begun - self.baseparamwin.optparamwin.toggleEnableUserFields(step, - enable=False) + self.optparamwin.toggle_enable_user_fields(step, enable=False) - self.step_ranges = \ - self.baseparamwin.optparamwin.get_chunk_ranges(step) - self.step_sims = \ - self.baseparamwin.optparamwin.get_sims_for_chunk(step) + self.step_ranges = self.optparamwin.get_chunk_ranges(step) + self.step_sims = self.optparamwin.get_sims_for_chunk(step) if self.step_sims == 0: txt = "Skipping optimization step %d (0 simulations)" % \ @@ -339,188 +385,235 @@ def optmodel(self): self._updatewaitsimwin(txt) continue - txt = "Starting optimization step %d/%d" % (step + 1, num_steps) + txt = "Starting optimization step %d/%d" % (step + 1, + self.num_steps) self._updatewaitsimwin(txt) - self.runOptStep(step) + print(txt) - if 'dpl' in self.best_ddat: - ddat['dpl'] = deepcopy(self.best_ddat['dpl']) - if 'errtot' in self.best_ddat: - ddat['errtot'] = deepcopy(self.best_ddat['errtot']) + opt_results = self.run_opt_step() - if need_initial_ddat: - save_initial_sim_data() - initial_ddat = deepcopy(ddat) + # update with optimzed params for the next round + for var_name, new_value in zip(self.step_ranges, opt_results): + old_value = self.step_ranges[var_name]['initial'] - # update optdat with best from this step - update_opt_data(self.paramfn, self.params, ddat['dpl']) + # only change the parameter value if it changed significantly + if not isclose(old_value, new_value, abs_tol=1e-9): + self.step_ranges[var_name]['final'] = new_value + else: + self.step_ranges[var_name]['final'] = \ + self.step_ranges[var_name]['initial'] - # put best opt results into GUI and save to param file + # push into GUI and save to param file so that next simulation + # starts from there. push_values = {} for param_name in self.step_ranges.keys(): push_values[param_name] = self.step_ranges[param_name]['final'] - self._updatebaseparamwin(push_values) - self.baseparamwin.optparamwin.push_chunk_ranges(step, push_values) - - sleep(1) - - self.first_step = False - - # one final sim with the best parameters to update display - self._runsim(banner=False) - - # update lsimdat and its current sim index - update_sim_data_from_disk(self.paramfn, self.params, ddat['dpl']) + self.baseparamwin.update_gui_params(push_values) + + # update optimization dialog window + self.optparamwin.push_chunk_ranges(push_values) + + # update opt_data with the final best + self.update_sim_data_from_opt_data.tsig.emit(self.paramfn) + + # check that optimization improved RMSE + err_queue = Queue() + self.get_err_from_sim_data.qsig.emit(err_queue, self.paramfn, self.params['tstop']) + final_err = err_queue.get() + if final_err > self.initial_err: + txt = "Warning: optimization failed to improve RMSE below" + \ + " %.2f. Reverting to old parameters." % \ + round(self.initial_err, 2) + self._updatewaitsimwin(txt) + print(txt) - # update optdat with the final best - update_opt_data_from_disk(self.paramfn, self.params, ddat['dpl']) + initial_params = self.optparamwin.get_initial_params() + # populate param values into GUI and save params to file + self.baseparamwin.update_gui_params(initial_params) - # re-enable all the range sliders - self.baseparamwin.optparamwin.toggleEnableUserFields(step, - enable=True) + # update optimization dialog window + self.optparamwin.push_chunk_ranges(initial_params) - self.baseparamwin.optparamwin.clear_initial_opt_ranges() - self.baseparamwin.optparamwin.optimization_running = False + # run a full length simulation + self.sim_thread = SimThread(self.ncore, self.params, + self.result_callback, + mainwin=self.mainwin) + self.sim_running = True + try: + self.sim_thread.run() + self.sim_thread.wait() + if self.killed: + self.quit() + self.sim_running = False + except Exception: + traceback.print_exc() + raise RuntimeError("Failed to run final simulation. " + "See previous traceback.") + + def run_opt_step(self): + self.cur_itr = 0 + self.opt_start = self.optparamwin.get_chunk_start(self.cur_step) + self.opt_end = self.optparamwin.get_chunk_end(self.cur_step) + txt = 'Optimizing from [%3.3f-%3.3f] ms' % (self.opt_start, + self.opt_end) + self._updatewaitsimwin(txt) - def runOptStep(self, step): - self.optsim = 0 - self.minopterr = 1e9 - self.stepminopterr = self.minopterr - self.best_ddat = {} - self.opt_start = self.baseparamwin.optparamwin.get_chunk_start(step) - self.opt_end = self.baseparamwin.optparamwin.get_chunk_end(step) + # weights calculated once per step self.opt_weights = \ - self.baseparamwin.optparamwin.get_chunk_weights(step) + self.optparamwin.get_chunk_weights(self.cur_step) - def optrun(new_params, grad=0): - txt = "Optimization step %d, simulation %d" % (step + 1, - self.optsim + 1) + # run an opt step + algorithm = nlopt.LN_COBYLA + self.num_params = len(self.step_ranges) + self.opt = nlopt.opt(algorithm, self.num_params) + opt_results = self.optimize(self.step_ranges, self.step_sims, + algorithm) + + return opt_results + + def get_initial_data(self): + # Has this simulation been run before (is there data?) + if not self.sim_data.in_sim_data(self.paramfn): + # run a full length simulation + txt = "Running a simulation with initial parameter set before" + \ + " beginning optimization." self._updatewaitsimwin(txt) print(txt) - dtest = {} - for param_name, test_value in zip(self.step_ranges.keys(), - new_params): - if test_value >= self.step_ranges[param_name]['minval'] and \ - test_value <= self.step_ranges[param_name]['maxval']: - dtest[param_name] = test_value - else: - # This test is not strictly necessary with COBYLA, but in - # case the algorithm is changed at some point in the future - print('INFO: optimization chose ' - '%.3f for %s outside of [%.3f-%.3f].' - % (test_value, param_name, - self.step_ranges[param_name]['minval'], - self.step_ranges[param_name]['maxval'])) - return 1e9 # invalid param value -> large error - - # put new param values into GUI and save params to file - self._updatebaseparamwin(dtest) - sleep(1) - - # run the simulation, but stop early if possible - self._runsim(banner=False, simlength=self.opt_end) - - # calculate wRMSE for all steps - calcerr(self.paramfn, self.opt_end, tstart=self.opt_start, - weights=self.opt_weights) - err = ddat['werrtot'] - - if self.last_step: - # weighted RMSE with weights of all 1's is the same as - # regular RMSE - ddat['errtot'] = ddat['werrtot'] - txt = "RMSE = %f" % err + self.sim_thread = SimThread(self.ncore, self.params, + self.result_callback, + mainwin=self.mainwin) + self.sim_running = True + try: + self.sim_thread.run() + self.sim_thread.wait() + if self.killed: + self.quit() + self.sim_running = False + except Exception: + traceback.print_exc() + raise RuntimeError("Failed to run initial simulation. " + "See previous traceback.") + + # results are in self.sim_data now + + # store the initial fit for display in final dipole plot as + # black dashed line. + self.update_opt_data_from_sim_data.tsig.emit(self.paramfn) + self.update_initial_opt_data_from_sim_data.tsig.emit(self.paramfn) + err_queue = Queue() + self.get_err_from_sim_data.qsig.emit(err_queue, self.paramfn, self.params['tstop']) + self.initial_err = err_queue.get() + + def opt_sim(self, new_params, grad=0): + txt = "Optimization step %d, simulation %d" % (self.cur_step + 1, + self.cur_itr + 1) + self._updatewaitsimwin(txt) + print(txt) + + # Prepare a dict of parameters for this simulation to populate in GUI + opt_params = {} + for param_name, param_value in zip(self.step_ranges.keys(), + new_params): + if param_value >= self.step_ranges[param_name]['minval'] and \ + param_value <= self.step_ranges[param_name]['maxval']: + opt_params[param_name] = param_value else: - # calculate regular RMSE for displaying on plot - calcerr(self.paramfn, self.opt_end, tstart=self.opt_start) - txt = "weighted RMSE = %f, RMSE = %f" % (err, ddat['errtot']) + # This test is not strictly necessary with COBYLA, but in + # case the algorithm is changed at some point in the future + print('INFO: optimization chose ' + '%.3f for %s outside of [%.3f-%.3f].' + % (param_value, param_name, + self.step_ranges[param_name]['minval'], + self.step_ranges[param_name]['maxval'])) + return 1e9 # invalid param value -> large error + + # populate param values into GUI and save params to file + self.baseparamwin.update_gui_params(opt_params) + + # run the simulation, but stop at self.opt_end + self.sim_thread = SimThread(self.ncore, self.params, + self.result_callback, + mainwin=self.mainwin) + + self.sim_running = True + try: + self.sim_thread.run(simlength=self.opt_end) + self.sim_thread.wait() + if self.killed: + self.quit() + self.sim_running = False + except Exception: + traceback.print_exc() + raise RuntimeError("Failed to run simulation. " + "See previous traceback.") + + # calculate wRMSE for all steps + werr = self.sim_data.get_werr(self.paramfn, self.opt_weights, + self.opt_end, tstart=self.opt_start) + txt = "Weighted RMSE = %f" % werr + print(txt) + self._updatewaitsimwin(os.linesep + 'Simulation finished: ' + txt + + os.linesep) + + # save params numbered by cur_itr + # data_dir = op.join(get_output_dir(), 'data') + # sim_dir = op.join(data_dir, self.params['sim_prefix']) + # param_out = os.path.join(sim_dir, 'step_%d_sim_%d.param' % + # (self.cur_step, self.cur_itr)) + # write_legacy_paramf(param_out, self.params) + + if werr < self.best_step_werr: + self._updatewaitsimwin("new best with RMSE %f" % werr) + + self.update_opt_data_from_sim_data.tsig.emit(self.paramfn) + + self.best_step_werr = werr + # save best param file + # param_out = os.path.join(sim_dir, 'step_%d_best.param' % + # self.cur_step) + # write_legacy_paramf(param_out, self.params) + + if self.cur_itr == 0 and self.cur_step > 0: + # Update plots for the first simulation only of this step + # (best results from last round). Skip the first step because + # there are no optimization results to show yet. + self.refresh_signal.sig.emit() # redraw with updated RMSE + + self.cur_itr += 1 + + return werr + + def optimize(self, params_input, num_sims, algorithm): + opt_params = [] + lb = [] + ub = [] + + for param_name in params_input.keys(): + upper = params_input[param_name]['maxval'] + lower = params_input[param_name]['minval'] + if upper == lower: + continue - print(txt) - self._updatewaitsimwin(os.linesep + 'Simulation finished: ' + txt + - os.linesep) - - data_dir = op.join(get_output_dir(), 'data') - sim_dir = op.join(data_dir, self.params['sim_prefix']) - - fnoptinf = os.path.join(sim_dir, 'optinf.txt') - with open(fnoptinf, 'a') as fpopt: - fpopt.write(str(ddat['errtot']) + os.linesep) # write error - - # save params numbered by optsim - param_out = os.path.join(sim_dir, 'step_%d_sim_%d.param' % - (self.cur_step, self.optsim)) - write_legacy_paramf(param_out, self.params) - - if err < self.stepminopterr: - self._updatewaitsimwin("new best with RMSE %f" % err) - - self.stepminopterr = err - # save best param file - param_out = os.path.join(sim_dir, 'step_%d_best.param' % - self.cur_step) - write_legacy_paramf(param_out, self.params) - if 'dpl' in ddat: - self.best_ddat['dpl'] = ddat['dpl'] - if 'errtot' in ddat: - self.best_ddat['errtot'] = ddat['errtot'] - - if self.optsim == 0 and not self.first_step: - # Update plots for the first simulation only of this step - # (best results from last round). Skip the first step because - # there are no optimization results to show yet. - self._updatedrawerr() # send event to draw updated RMSE - - self.optsim += 1 - - return err # return RMSE - - def optimize(params_input, evals, algorithm): - opt_params = [] - lb = [] - ub = [] - - for param_name in params_input.keys(): - upper = params_input[param_name]['maxval'] - lower = params_input[param_name]['minval'] - if upper == lower: - continue - - ub.append(upper) - lb.append(lower) - opt_params.append(params_input[param_name]['initial']) - - if algorithm == nlopt.G_MLSL_LDS or algorithm == nlopt.G_MLSL: - # In case these mixed mode (global + local) algorithms are - # used in the future - local_opt = nlopt.opt(nlopt.LN_COBYLA, num_params) - opt.set_local_optimizer(local_opt) - - opt.set_lower_bounds(lb) - opt.set_upper_bounds(ub) - opt.set_min_objective(optrun) - opt.set_xtol_rel(1e-4) - opt.set_maxeval(evals) - opt_results = opt.optimize(opt_params) - - return opt_results + ub.append(upper) + lb.append(lower) + opt_params.append(params_input[param_name]['initial']) - txt = 'Optimizing from [%3.3f-%3.3f] ms' % (self.opt_start, - self.opt_end) - self._updatewaitsimwin(txt) + if algorithm == nlopt.G_MLSL_LDS or algorithm == nlopt.G_MLSL: + # In case these mixed mode (global + local) algorithms are + # used in the future + local_opt = nlopt.opt(nlopt.LN_COBYLA, self.num_params) + self.opt.set_local_optimizer(local_opt) - num_params = len(self.step_ranges) - algorithm = nlopt.LN_COBYLA - opt = nlopt.opt(algorithm, num_params) - opt_results = optimize(self.step_ranges, self.step_sims, algorithm) + self.opt.set_lower_bounds(lb) + self.opt.set_upper_bounds(ub) - # update opt params for the next round - for var_name, new_value in zip(self.step_ranges, opt_results): - old_value = self.step_ranges[var_name]['initial'] + # minimize the wRMSE returned by self.opt_sim + self.opt.set_min_objective(self.opt_sim) + self.opt.set_xtol_rel(1e-4) + self.opt.set_maxeval(num_sims) - # only change the parameter value if it changed significantly - if not isclose(old_value, new_value, abs_tol=1e-9): - self.step_ranges[var_name]['final'] = new_value - else: - self.step_ranges[var_name]['final'] = \ - self.step_ranges[var_name]['initial'] + # start the optimization: run self.runsim for # iterations in num_sims + opt_results = self.opt.optimize(opt_params) + + return opt_results diff --git a/hnn/simdata.py b/hnn/simdata.py index 00250ab3f..2271d7efc 100644 --- a/hnn/simdata.py +++ b/hnn/simdata.py @@ -3,6 +3,7 @@ from math import ceil from glob import glob from pickle import dump, load +from copy import deepcopy from scipy import signal import matplotlib as mpl @@ -418,7 +419,7 @@ def calcerr(self, paramfn, tstop, tstart=0.0, weights=None): for c in range(1, shp[1], 1): sim_dpl = self._sim_data[paramfn]['data']['avg_dpl'] - dpl1 = sim_dpl[sim_start_index:sim_end_index, 1] + dpl1 = sim_dpl.data['agg'][sim_start_index:sim_end_index] dpl2 = dat[exp_start_index:exp_end_index, c] if (sim_length > exp_length): @@ -450,9 +451,82 @@ def clear_opt_data(self): self._initial_opt = {} self._opt_data = {} - def update_opt_data(self, paramfn, params, avg_dpl): - self._opt_data = {'paramfn': paramfn, 'params': params, - 'data': {'avg_dpl': avg_dpl}} + def in_sim_data(self, paramfn): + if paramfn in self._sim_data: + return True + return False + + def update_opt_data(self, paramfn, params, avg_dpl, dpls=None, + spikes=None, gid_ranges=None, spec=None, + vsoma=None): + self._opt_data = {'paramfn': paramfn, + 'params': params, + 'data': {'dpls': None, + 'avg_dpl': avg_dpl, + 'spikes': None, + 'gid_ranges': None, + 'spec': None, + 'vsoma': None}} + + def update_initial_opt_data_from_sim_data(self, paramfn): + if paramfn not in self._sim_data: + raise ValueError("Simulation not in sim_data: %s" % paramfn) + + single_sim_data = self._sim_data[paramfn]['data'] + self._opt_data['initial_dpl'] = \ + deepcopy(single_sim_data['avg_dpl']) + self._opt_data['initial_error'] = self.get_err(paramfn) + + def get_err(self, paramfn, tstop=None): + if paramfn not in self._sim_data: + raise ValueError("Simulation not in sim_data: %s" % paramfn) + + if tstop is None: + tstop = self._sim_data[paramfn]['params']['tstop'] + _, err = self.calcerr(paramfn, tstop) + return err + + def get_err_wrapper(self, queue, paramfn, tstop=None): + err = self.get_err(paramfn, tstop) + queue.put(err) + + def get_werr(self, paramfn, weights, tstop=None, tstart=None): + if paramfn not in self._sim_data: + raise ValueError("Simulation not in sim_data: %s" % paramfn) + + if tstop is None: + tstop = self._sim_data[paramfn]['params']['tstop'] + _, werr = self.calcerr(paramfn, tstop, tstart, weights) + return werr + + def update_opt_data_from_sim_data(self, paramfn): + if paramfn not in self._sim_data: + raise ValueError("Simulation not in sim_data: %s" % paramfn) + + sim_params = self._sim_data[paramfn] + single_sim = self._sim_data[paramfn]['data'] + self._opt_data = {'paramfn': paramfn, + 'params': deepcopy(sim_params), + 'data': {'dpls': deepcopy(single_sim['dpls']), + 'avg_dpl': deepcopy(single_sim['avg_dpl']), + 'spikes': deepcopy(single_sim['spikes']), + 'gid_ranges': + deepcopy(single_sim['gid_ranges']), + 'spec': deepcopy(single_sim['spec']), + 'vsoma': deepcopy(single_sim['vsoma'])}} + + def update_sim_data_from_opt_data(self, paramfn): + opt_data = self._opt_data['data'] + single_sim = {'paramfn': paramfn, + 'params': deepcopy(self._opt_data['params']), + 'data': {'dpls': deepcopy(opt_data['dpls']), + 'avg_dpl': deepcopy(opt_data['avg_dpl']), + 'spikes': deepcopy(opt_data['spikes']), + 'gid_ranges': + deepcopy(opt_data['gid_ranges']), + 'spec': deepcopy(opt_data['spec']), + 'vsoma': deepcopy(opt_data['vsoma'])}} + self._sim_data[paramfn] = single_sim def _read_dpl(self, paramfn, trial_idx, ntrial): if ntrial == 1: @@ -648,24 +722,33 @@ def plot_dipole(self, paramfn, ax, linewidth, dipole_scalefctr, N_pyr_x=0, yl[0] = min(yl[0], dpl.data['agg'].min()) yl[1] = max(yl[1], dpl.data['agg'].max()) else: - if self._opt_data['avg_dpl'] is not None: - # show optimized dipole as gray line - optdpl = self._opt_data['avg_dpl'] - ax.plot(optdpl.times, optdpl.data['agg'], 'k', color='gray', + if 'avg_dpl' not in self._opt_data or \ + 'initial_dpl' not in self._opt_data: + # if there was an exception running optimization + # still plot average dipole from sim + ax.plot(dpl.times, dpl.data['agg'], 'k', linewidth=linewidth + 1) - yl[0] = min(yl[0], optdpl.data['agg'].min()) - yl[1] = max(yl[1], optdpl.data['agg'].max()) - - if self._opt_data['initial_dpl'] is not None: - # show initial dipole in dotted black line - plot_data = self._opt_data['initial_dpl'] - times = plot_data.times - plot_dpl = plot_data.data['agg'] - ax.plot(times, plot_dpl, '--', color='black', - linewidth=linewidth) - dpl = self._opt_data['initial_dpl'].data['agg'] - yl[0] = min(yl[0], dpl.min()) - yl[1] = max(yl[1], dpl.max()) + yl[0] = min(yl[0], dpl.data['agg'].min()) + yl[1] = max(yl[1], dpl.data['agg'].max()) + else: + if self._opt_data['avg_dpl'] is not None: + # show optimized dipole as gray line + optdpl = self._opt_data['avg_dpl'] + ax.plot(optdpl.times, optdpl.data['agg'], 'k', + color='gray', linewidth=linewidth + 1) + yl[0] = min(yl[0], optdpl.data['agg'].min()) + yl[1] = max(yl[1], optdpl.data['agg'].max()) + + if self._opt_data['initial_dpl'] is not None: + # show initial dipole in dotted black line + plot_data = self._opt_data['initial_dpl'] + times = plot_data.times + plot_dpl = plot_data.data['agg'] + ax.plot(times, plot_dpl, '--', color='black', + linewidth=linewidth) + dpl = self._opt_data['initial_dpl'].data['agg'] + yl[0] = min(yl[0], dpl.min()) + yl[1] = max(yl[1], dpl.max()) # get the number of pyramidal neurons used in the simulation and # multiply by scale factor to get estimated number of pyramidal From 6eb5ad90ba05ef34b05432dfdcb34f8042b2f76a Mon Sep 17 00:00:00 2001 From: Mainak Jas Date: Sat, 20 Mar 2021 00:11:05 -0400 Subject: [PATCH 096/107] Flake8 --- hnn/qt_dialog.py | 1 - hnn/qt_main.py | 718 +++++++++++++++++++++++------------------------ hnn/run.py | 7 +- 3 files changed, 363 insertions(+), 363 deletions(-) diff --git a/hnn/qt_dialog.py b/hnn/qt_dialog.py index 6534c7da3..98d280678 100644 --- a/hnn/qt_dialog.py +++ b/hnn/qt_dialog.py @@ -586,7 +586,6 @@ def get_prng_seedcore_opt(self): return int(self.mainwin.prng_seedcore_opt) - def setfromdin(self, din): if not din: return diff --git a/hnn/qt_main.py b/hnn/qt_main.py index b6def4c3f..1b8e5116f 100644 --- a/hnn/qt_main.py +++ b/hnn/qt_main.py @@ -197,82 +197,82 @@ def changeMarkerSize(self): self.redraw() def selParamFileDialog(self): - """bring up window to select simulation parameter file""" + """bring up window to select simulation parameter file""" - relative_root_path = os.path.join(os.path.dirname(__file__), '..') - hnn_root_dir = os.path.realpath(relative_root_path) + relative_root_path = os.path.join(os.path.dirname(__file__), '..') + hnn_root_dir = os.path.realpath(relative_root_path) - qfd = QFileDialog() - qfd.setHistory([os.path.join(get_output_dir(), 'param'), - os.path.join(hnn_root_dir, 'param')]) - fn = qfd.getOpenFileName(self, 'Open param file', - os.path.join(hnn_root_dir, 'param'), - "Param files (*.param)") - if len(fn) > 0 and fn[0] == '': - # no file selected in dialog - return + qfd = QFileDialog() + qfd.setHistory([os.path.join(get_output_dir(), 'param'), + os.path.join(hnn_root_dir, 'param')]) + fn = qfd.getOpenFileName(self, 'Open param file', + os.path.join(hnn_root_dir, 'param'), + "Param files (*.param)") + if len(fn) > 0 and fn[0] == '': + # no file selected in dialog + return - tmpfn = os.path.abspath(fn[0]) + tmpfn = os.path.abspath(fn[0]) - try: - params = read_params(tmpfn) - except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not" - "retrieve parameters from %s" % - tmpfn) - return - - # check that valid number of trials was given - if 'N_trials' not in params or params['N_trials'] == 0: - print("Warning: invalid configured number of trials." - " Setting 'N_trials' to 1.") - params['N_trials'] = 1 - - # Now update GUI components - self.baseparamwin.paramfn = tmpfn - - # now update the GUI components to reflect the param file selected - self.baseparamwin.updateDispParam(params) - self.setWindowTitle(self.baseparamwin.paramfn) + try: + params = read_params(tmpfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + tmpfn) + return + + # check that valid number of trials was given + if 'N_trials' not in params or params['N_trials'] == 0: + print("Warning: invalid configured number of trials." + " Setting 'N_trials' to 1.") + params['N_trials'] = 1 - self.initSimCanvas() # recreate canvas + # Now update GUI components + self.baseparamwin.paramfn = tmpfn - # check if param file exists in combo box already - cb_index = self.cbsim.findText(self.baseparamwin.paramfn) - self.populateSimCB(cb_index) # populate the combobox + # now update the GUI components to reflect the param file selected + self.baseparamwin.updateDispParam(params) + self.setWindowTitle(self.baseparamwin.paramfn) + + self.initSimCanvas() # recreate canvas - if self.sim_data.get_exp_data_size() > 0: - self.toggleEnableOptimization(True) + # check if param file exists in combo box already + cb_index = self.cbsim.findText(self.baseparamwin.paramfn) + self.populateSimCB(cb_index) # populate the combobox + + if self.sim_data.get_exp_data_size() > 0: + self.toggleEnableOptimization(True) def loadDataFile(self, fn): - """load a dipole data file""" + """load a dipole data file""" - extdata = None - try: - extdata = np.loadtxt(fn) - except ValueError: - # possible that data file is comma delimited instead of whitespace - # delimited + extdata = None try: - extdata = np.loadtxt(fn, delimiter=',') + extdata = np.loadtxt(fn) except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not load data" - " file %s" % fn) - return False - except IsADirectoryError: - QMessageBox.information(self, "HNN", "WARNING: could not load data" - " file %s" % fn) - return False + # possible that data file is comma delimited instead of whitespace + # delimited + try: + extdata = np.loadtxt(fn, delimiter=',') + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not load" + " data file %s" % fn) + return False + except IsADirectoryError: + QMessageBox.information(self, "HNN", "WARNING: could not load data" + " file %s" % fn) + return False + + self.sim_data.update_exp_data(fn, extdata) + print('Loaded data in ', fn) - self.sim_data.update_exp_data(fn, extdata) - print('Loaded data in ', fn) - - self.sim_canvas.plot() - self.sim_canvas.draw() # make sure new lines show up in plot + self.sim_canvas.plot() + self.sim_canvas.draw() # make sure new lines show up in plot - if self.baseparamwin.paramfn: - self.toggleEnableOptimization(True) - return True + if self.baseparamwin.paramfn: + self.toggleEnableOptimization(True) + return True def loadDataFileDialog(self): """bring up window to select/load external dipole data""" @@ -364,7 +364,7 @@ def show_plot(self, plot_type): SpikeViewGUI(SpikeCanvas, self.baseparamwin.params, sim_data, 'Spike Viewer') else: - raise ValueError("Unknown plot type") + raise ValueError("Unknown plot type") def showSomaVPlot(self): # start the somatic voltage visualization process (separate window) @@ -396,336 +396,336 @@ def showDipolePlot(self): self.show_plot('dipole') def showwaitsimwin(self): - """show the wait sim window (has simulation log)""" - bringwintotop(self.waitsimwin) + """show the wait sim window (has simulation log)""" + bringwintotop(self.waitsimwin) def togAvgDpl(self): - """toggle drawing of the average (across trials) dipole""" - global drawavgdpl + """toggle drawing of the average (across trials) dipole""" + global drawavgdpl - drawavgdpl = not drawavgdpl - self.sim_canvas.plot() - self.sim_canvas.draw() + drawavgdpl = not drawavgdpl + self.sim_canvas.plot() + self.sim_canvas.draw() def hidesubwin(self): - """hide GUI's sub windows""" - self.baseparamwin.hide() - self.schemwin.hide() - self.baseparamwin.syngainparamwin.hide() - for win in self.baseparamwin.lsubwin: - win.hide() - self.activateWindow() + """hide GUI's sub windows""" + self.baseparamwin.hide() + self.schemwin.hide() + self.baseparamwin.syngainparamwin.hide() + for win in self.baseparamwin.lsubwin: + win.hide() + self.activateWindow() def distribsubwin(self): - """distribute GUI's sub-windows on screen""" - sw, sh = getscreengeom() - lwin = [win for win in self.baseparamwin.lsubwin if win.isVisible()] - if self.baseparamwin.isVisible(): - lwin.insert(0, self.baseparamwin) - if self.schemwin.isVisible(): - lwin.insert(0, self.schemwin) - if self.baseparamwin.syngainparamwin.isVisible(): - lwin.append(self.baseparamwin.syngainparamwin) - curx, cury, maxh = 0, 0, 0 - for win in lwin: - win.move(curx, cury) - curx += win.width() - maxh = max(maxh, win.height()) - if curx >= sw: - curx = 0 - cury += maxh - maxh = win.height() - if cury >= sh: - cury = cury = 0 + """distribute GUI's sub-windows on screen""" + sw, sh = getscreengeom() + lwin = [win for win in self.baseparamwin.lsubwin if win.isVisible()] + if self.baseparamwin.isVisible(): + lwin.insert(0, self.baseparamwin) + if self.schemwin.isVisible(): + lwin.insert(0, self.schemwin) + if self.baseparamwin.syngainparamwin.isVisible(): + lwin.append(self.baseparamwin.syngainparamwin) + curx, cury, maxh = 0, 0, 0 + for win in lwin: + win.move(curx, cury) + curx += win.width() + maxh = max(maxh, win.height()) + if curx >= sw: + curx = 0 + cury += maxh + maxh = win.height() + if cury >= sh: + cury = cury = 0 def updateDatCanv(self, params): - """update GUI to reflect param file selected""" - self.baseparamwin.updateDispParam(params) - self.initSimCanvas() # recreate canvas - self.setWindowTitle(self.baseparamwin.paramfn) + """update GUI to reflect param file selected""" + self.baseparamwin.updateDispParam(params) + self.initSimCanvas() # recreate canvas + self.setWindowTitle(self.baseparamwin.paramfn) def updateSelectedSim(self, sim_idx): - """Update the sim shown in the ComboBox""" + """Update the sim shown in the ComboBox""" - paramfn = self.cbsim.itemText(sim_idx) - try: - params = read_params(paramfn) - except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not" - "retrieve parameters from %s" % - paramfn) - return - self.baseparamwin.paramfn = paramfn + paramfn = self.cbsim.itemText(sim_idx) + try: + params = read_params(paramfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + paramfn) + return + self.baseparamwin.paramfn = paramfn - # update GUI - self.updateDatCanv(params) - self.cbsim.setCurrentIndex(sim_idx) + # update GUI + self.updateDatCanv(params) + self.cbsim.setCurrentIndex(sim_idx) def removeSim(self): - """Remove the currently selected simulation""" + """Remove the currently selected simulation""" - sim_idx = self.cbsim.currentIndex() - paramfn = self.cbsim.itemText(sim_idx) - if not paramfn == '': - self.sim_data.remove_sim_by_fn(paramfn) + sim_idx = self.cbsim.currentIndex() + paramfn = self.cbsim.itemText(sim_idx) + if not paramfn == '': + self.sim_data.remove_sim_by_fn(paramfn) - self.cbsim.removeItem(sim_idx) + self.cbsim.removeItem(sim_idx) - # go to last entry - new_simidx = self.cbsim.count() - 1 - if new_simidx < 0: - self.clearSimulations() - else: - self.updateSelectedSim(new_simidx) + # go to last entry + new_simidx = self.cbsim.count() - 1 + if new_simidx < 0: + self.clearSimulations() + else: + self.updateSelectedSim(new_simidx) def prevSim(self): - """Go to previous simulation""" + """Go to previous simulation""" - new_simidx = self.cbsim.currentIndex() - 1 - if new_simidx < 0: - print("There is no previous simulation") - return - else: - self.updateSelectedSim(new_simidx) + new_simidx = self.cbsim.currentIndex() - 1 + if new_simidx < 0: + print("There is no previous simulation") + return + else: + self.updateSelectedSim(new_simidx) def nextSim(self): - """go to next simulation""" + """go to next simulation""" - if self.cbsim.currentIndex() + 2 > self.cbsim.count(): - print("There is no next simulation") - return - else: - new_simidx = self.cbsim.currentIndex() + 1 - self.updateSelectedSim(new_simidx) + if self.cbsim.currentIndex() + 2 > self.cbsim.count(): + print("There is no next simulation") + return + else: + new_simidx = self.cbsim.currentIndex() + 1 + self.updateSelectedSim(new_simidx) def clearSimulationData(self): - """clear the simulation data""" - self.baseparamwin.params = None - self.baseparamwin.paramfn = None + """clear the simulation data""" + self.baseparamwin.params = None + self.baseparamwin.paramfn = None - self.sim_data.clear_sim_data() - self.cbsim.clear() # un-populate the combobox - self.toggleEnableOptimization(False) + self.sim_data.clear_sim_data() + self.cbsim.clear() # un-populate the combobox + self.toggleEnableOptimization(False) def clearSimulations(self): - """clear all simulation data + """clear all simulation data - erase simulations from canvas (does not clear external data) - """ - self.clearSimulationData() - self.initSimCanvas() # recreate canvas - self.sim_canvas.draw() - self.setWindowTitle('') + erase simulations from canvas (does not clear external data) + """ + self.clearSimulationData() + self.initSimCanvas() # recreate canvas + self.sim_canvas.draw() + self.setWindowTitle('') def clearCanvas(self): - # clear all simulation & external data and erase everything from the - # canvas - self.sim_canvas.clearlextdatobj() # clear the external data - self.clearSimulationData() - self.sim_data.clear_exp_data() - self.initSimCanvas() # recreate canvas - self.sim_canvas.draw() - self.setWindowTitle('') + # clear all simulation & external data and erase everything from the + # canvas + self.sim_canvas.clearlextdatobj() # clear the external data + self.clearSimulationData() + self.sim_data.clear_exp_data() + self.initSimCanvas() # recreate canvas + self.sim_canvas.draw() + self.setWindowTitle('') def initMenu(self): - """initialize the GUI's menu""" - exitAction = QAction(QIcon.fromTheme('exit'), 'Exit', self) - exitAction.setShortcut('Ctrl+Q') - exitAction.setStatusTip('Exit HNN application') - exitAction.triggered.connect(qApp.quit) - - selParamFile = QAction(QIcon.fromTheme('open'), 'Load parameter file', - self) - selParamFile.setShortcut('Ctrl+P') - selParamFile.setStatusTip('Load simulation parameter (.param) file') - selParamFile.triggered.connect(self.selParamFileDialog) - - clearCanv = QAction('Clear canvas', self) - clearCanv.setShortcut('Ctrl+X') - clearCanv.setStatusTip('Clear canvas (simulation+data)') - clearCanv.triggered.connect(self.clearCanvas) - - clearSims = QAction('Clear simulation(s)', self) - # clearSims.setShortcut('Ctrl+X') - clearSims.setStatusTip('Clear simulation(s)') - clearSims.triggered.connect(self.clearSimulations) - - loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data file', - self) - loadDataFile.setShortcut('Ctrl+D') - loadDataFile.setStatusTip('Load (dipole) data file') - loadDataFile.triggered.connect(self.loadDataFileDialog) - - clearDataFileAct = QAction(QIcon.fromTheme('close'), - 'Clear data file(s)', self) - clearDataFileAct.setShortcut('Ctrl+C') - clearDataFileAct.setStatusTip('Clear (dipole) data file(s)') - clearDataFileAct.triggered.connect(self.clearDataFile) - - runSimAct = QAction('Run simulation', self) - runSimAct.setShortcut('Ctrl+S') - runSimAct.setStatusTip('Run simulation') - runSimAct.triggered.connect(self.controlsim) - - self.menubar = self.menuBar() - fileMenu = self.menubar.addMenu('&File') - self.menubar.setNativeMenuBar(False) - fileMenu.addAction(selParamFile) - fileMenu.addSeparator() - fileMenu.addAction(loadDataFile) - fileMenu.addAction(clearDataFileAct) - fileMenu.addSeparator() - fileMenu.addAction(exitAction) - - # part of edit menu for changing drawing properties (line thickness, - # font size, toggle avg dipole drawing) - editMenu = self.menubar.addMenu('&Edit') - viewAvgDplAction = QAction('Toggle Average Dipole Drawing', self) - viewAvgDplAction.setStatusTip('Toggle Average Dipole Drawing') - viewAvgDplAction.triggered.connect(self.togAvgDpl) - editMenu.addAction(viewAvgDplAction) - changeFontSizeAction = QAction('Change Font Size', self) - changeFontSizeAction.setStatusTip('Change Font Size.') - changeFontSizeAction.triggered.connect(self.changeFontSize) - editMenu.addAction(changeFontSizeAction) - changeLineWidthAction = QAction('Change Line Width', self) - changeLineWidthAction.setStatusTip('Change Line Width.') - changeLineWidthAction.triggered.connect(self.changeLineWidth) - editMenu.addAction(changeLineWidthAction) - changeMarkerSizeAction = QAction('Change Marker Size', self) - changeMarkerSizeAction.setStatusTip('Change Marker Size.') - changeMarkerSizeAction.triggered.connect(self.changeMarkerSize) - editMenu.addAction(changeMarkerSizeAction) - editMenu.addSeparator() - editMenu.addAction(clearSims) - # need new act to avoid DBus warning - clearDataFileAct2 = QAction(QIcon.fromTheme('close'), - 'Clear data file(s)', self) - clearDataFileAct2.setStatusTip('Clear (dipole) data file(s)') - clearDataFileAct2.triggered.connect(self.clearDataFile) - editMenu.addAction(clearDataFileAct2) - editMenu.addAction(clearCanv) - - # view menu - to view drawing/visualizations - viewMenu = self.menubar.addMenu('&View') - self.viewDipoleAction = QAction('View Simulation Dipoles', - self) - self.viewDipoleAction.setStatusTip('View Simulation Dipoles') - self.viewDipoleAction.triggered.connect(self.showDipolePlot) - viewMenu.addAction(self.viewDipoleAction) - self.viewRasterAction = QAction('View Simulation Spiking Activity', - self) - self.viewRasterAction.setStatusTip('View Simulation Raster Plot') - self.viewRasterAction.triggered.connect(self.showRasterPlot) - viewMenu.addAction(self.viewRasterAction) - self.viewPSDAction = QAction('View PSD', self) - self.viewPSDAction.setStatusTip('View PSD') - self.viewPSDAction.triggered.connect(self.showPSDPlot) - viewMenu.addAction(self.viewPSDAction) - - self.viewSomaVAction = QAction('View Somatic Voltage', self) - self.viewSomaVAction.setStatusTip('View Somatic Voltage') - self.viewSomaVAction.triggered.connect(self.showSomaVPlot) - viewMenu.addAction(self.viewSomaVAction) - - self.viewSpecAction = QAction('View Spectrograms', self) - self.viewSpecAction.setStatusTip('View Spectrograms/Dipoles' - ' from Experimental Data') - self.viewSpecAction.triggered.connect(self.showSpecPlot) - viewMenu.addAction(self.viewSpecAction) - - viewMenu.addSeparator() - viewSchemAction = QAction('View Model Schematics', self) - viewSchemAction.setStatusTip('View Model Schematics') - viewSchemAction.triggered.connect(self.showschematics) - viewMenu.addAction(viewSchemAction) - viewSimLogAction = QAction('View Simulation Log', self) - viewSimLogAction.setStatusTip('View Detailed Simulation Log') - viewSimLogAction.triggered.connect(self.showwaitsimwin) - viewMenu.addAction(viewSimLogAction) - viewMenu.addSeparator() - distributeWindowsAction = QAction('Distribute Windows', self) - distributeWindowsAction.setStatusTip('Distribute Parameter Windows' - ' Across Screen.') - distributeWindowsAction.triggered.connect(self.distribsubwin) - viewMenu.addAction(distributeWindowsAction) - hideWindowsAction = QAction('Hide Windows', self) - hideWindowsAction.setStatusTip('Hide Parameter Windows.') - hideWindowsAction.triggered.connect(self.hidesubwin) - hideWindowsAction.setShortcut('Ctrl+H') - viewMenu.addAction(hideWindowsAction) - - simMenu = self.menubar.addMenu('&Simulation') - setParmAct = QAction('Set Parameters', self) - setParmAct.setStatusTip('Set Simulation Parameters') - setParmAct.triggered.connect(self.setparams) - simMenu.addAction(setParmAct) - simMenu.addAction(runSimAct) - setOptParamAct = QAction('Configure Optimization', self) - setOptParamAct.setShortcut('Ctrl+O') - setOptParamAct.setStatusTip('Set parameters for evoked input' - ' optimization') - setOptParamAct.triggered.connect(self.showoptparamwin) - simMenu.addAction(setOptParamAct) - self.toggleEnableOptimization(False) - prevSimAct = QAction('Go to Previous Simulation', self) - prevSimAct.setShortcut('Ctrl+Z') - prevSimAct.setStatusTip('Go Back to Previous Simulation') - prevSimAct.triggered.connect(self.prevSim) - simMenu.addAction(prevSimAct) - nextSimAct = QAction('Go to Next Simulation', self) - nextSimAct.setShortcut('Ctrl+Y') - nextSimAct.setStatusTip('Go Forward to Next Simulation') - nextSimAct.triggered.connect(self.nextSim) - simMenu.addAction(nextSimAct) - # need another QAction to avoid DBus warning - clearSims2 = QAction('Clear simulation(s)', self) - clearSims2.setStatusTip('Clear simulation(s)') - clearSims2.triggered.connect(self.clearSimulations) - simMenu.addAction(clearSims2) - - aboutMenu = self.menubar.addMenu('&About') - aboutAction = QAction('About HNN', self) - aboutAction.setStatusTip('About HNN') - aboutAction.triggered.connect(self.showAboutDialog) - aboutMenu.addAction(aboutAction) - helpAction = QAction('Help', self) - helpAction.setStatusTip('Help on how to use HNN (parameters).') - helpAction.triggered.connect(self.showHelpDialog) - # aboutMenu.addAction(helpAction) - - def toggleEnableOptimization(self, toEnable): - for menu in self.menubar.findChildren(QMenu): - if menu.title() == '&Simulation': - for item in menu.actions(): - if item.text() == 'Configure Optimization': - item.setEnabled(toEnable) - break - break - - def addButtons(self, gRow): - self.pbtn = pbtn = QPushButton('Set Parameters', self) - pbtn.setToolTip('Set Parameters') - pbtn.resize(pbtn.sizeHint()) - pbtn.clicked.connect(self.setparams) - self.grid.addWidget(self.pbtn, gRow, 0, 1, 3) - - self.pfbtn = pfbtn = QPushButton('Set Parameters From File', self) - pfbtn.setToolTip('Set Parameters From File') - pfbtn.resize(pfbtn.sizeHint()) - pfbtn.clicked.connect(self.selParamFileDialog) - self.grid.addWidget(self.pfbtn, gRow, 3, 1, 3) - - self.btnsim = btn = QPushButton('Run Simulation', self) - btn.setToolTip('Run Simulation') - btn.resize(btn.sizeHint()) - btn.clicked.connect(self.controlsim) - self.grid.addWidget(self.btnsim, gRow, 6, 1, 3) - - self.qbtn = qbtn = QPushButton('Quit', self) - qbtn.clicked.connect(QApplication.exit) - qbtn.resize(qbtn.sizeHint()) - self.grid.addWidget(self.qbtn, gRow, 9, 1, 3) + """initialize the GUI's menu""" + exitAction = QAction(QIcon.fromTheme('exit'), 'Exit', self) + exitAction.setShortcut('Ctrl+Q') + exitAction.setStatusTip('Exit HNN application') + exitAction.triggered.connect(qApp.quit) + + selParamFile = QAction(QIcon.fromTheme('open'), 'Load parameter file', + self) + selParamFile.setShortcut('Ctrl+P') + selParamFile.setStatusTip('Load simulation parameter (.param) file') + selParamFile.triggered.connect(self.selParamFileDialog) + + clearCanv = QAction('Clear canvas', self) + clearCanv.setShortcut('Ctrl+X') + clearCanv.setStatusTip('Clear canvas (simulation+data)') + clearCanv.triggered.connect(self.clearCanvas) + + clearSims = QAction('Clear simulation(s)', self) + # clearSims.setShortcut('Ctrl+X') + clearSims.setStatusTip('Clear simulation(s)') + clearSims.triggered.connect(self.clearSimulations) + + loadDataFile = QAction(QIcon.fromTheme('open'), 'Load data file', + self) + loadDataFile.setShortcut('Ctrl+D') + loadDataFile.setStatusTip('Load (dipole) data file') + loadDataFile.triggered.connect(self.loadDataFileDialog) + + clearDataFileAct = QAction(QIcon.fromTheme('close'), + 'Clear data file(s)', self) + clearDataFileAct.setShortcut('Ctrl+C') + clearDataFileAct.setStatusTip('Clear (dipole) data file(s)') + clearDataFileAct.triggered.connect(self.clearDataFile) + + runSimAct = QAction('Run simulation', self) + runSimAct.setShortcut('Ctrl+S') + runSimAct.setStatusTip('Run simulation') + runSimAct.triggered.connect(self.controlsim) + + self.menubar = self.menuBar() + fileMenu = self.menubar.addMenu('&File') + self.menubar.setNativeMenuBar(False) + fileMenu.addAction(selParamFile) + fileMenu.addSeparator() + fileMenu.addAction(loadDataFile) + fileMenu.addAction(clearDataFileAct) + fileMenu.addSeparator() + fileMenu.addAction(exitAction) + + # part of edit menu for changing drawing properties (line thickness, + # font size, toggle avg dipole drawing) + editMenu = self.menubar.addMenu('&Edit') + viewAvgDplAction = QAction('Toggle Average Dipole Drawing', self) + viewAvgDplAction.setStatusTip('Toggle Average Dipole Drawing') + viewAvgDplAction.triggered.connect(self.togAvgDpl) + editMenu.addAction(viewAvgDplAction) + changeFontSizeAction = QAction('Change Font Size', self) + changeFontSizeAction.setStatusTip('Change Font Size.') + changeFontSizeAction.triggered.connect(self.changeFontSize) + editMenu.addAction(changeFontSizeAction) + changeLineWidthAction = QAction('Change Line Width', self) + changeLineWidthAction.setStatusTip('Change Line Width.') + changeLineWidthAction.triggered.connect(self.changeLineWidth) + editMenu.addAction(changeLineWidthAction) + changeMarkerSizeAction = QAction('Change Marker Size', self) + changeMarkerSizeAction.setStatusTip('Change Marker Size.') + changeMarkerSizeAction.triggered.connect(self.changeMarkerSize) + editMenu.addAction(changeMarkerSizeAction) + editMenu.addSeparator() + editMenu.addAction(clearSims) + # need new act to avoid DBus warning + clearDataFileAct2 = QAction(QIcon.fromTheme('close'), + 'Clear data file(s)', self) + clearDataFileAct2.setStatusTip('Clear (dipole) data file(s)') + clearDataFileAct2.triggered.connect(self.clearDataFile) + editMenu.addAction(clearDataFileAct2) + editMenu.addAction(clearCanv) + + # view menu - to view drawing/visualizations + viewMenu = self.menubar.addMenu('&View') + self.viewDipoleAction = QAction('View Simulation Dipoles', + self) + self.viewDipoleAction.setStatusTip('View Simulation Dipoles') + self.viewDipoleAction.triggered.connect(self.showDipolePlot) + viewMenu.addAction(self.viewDipoleAction) + self.viewRasterAction = QAction('View Simulation Spiking Activity', + self) + self.viewRasterAction.setStatusTip('View Simulation Raster Plot') + self.viewRasterAction.triggered.connect(self.showRasterPlot) + viewMenu.addAction(self.viewRasterAction) + self.viewPSDAction = QAction('View PSD', self) + self.viewPSDAction.setStatusTip('View PSD') + self.viewPSDAction.triggered.connect(self.showPSDPlot) + viewMenu.addAction(self.viewPSDAction) + + self.viewSomaVAction = QAction('View Somatic Voltage', self) + self.viewSomaVAction.setStatusTip('View Somatic Voltage') + self.viewSomaVAction.triggered.connect(self.showSomaVPlot) + viewMenu.addAction(self.viewSomaVAction) + + self.viewSpecAction = QAction('View Spectrograms', self) + self.viewSpecAction.setStatusTip('View Spectrograms/Dipoles' + ' from Experimental Data') + self.viewSpecAction.triggered.connect(self.showSpecPlot) + viewMenu.addAction(self.viewSpecAction) + + viewMenu.addSeparator() + viewSchemAction = QAction('View Model Schematics', self) + viewSchemAction.setStatusTip('View Model Schematics') + viewSchemAction.triggered.connect(self.showschematics) + viewMenu.addAction(viewSchemAction) + viewSimLogAction = QAction('View Simulation Log', self) + viewSimLogAction.setStatusTip('View Detailed Simulation Log') + viewSimLogAction.triggered.connect(self.showwaitsimwin) + viewMenu.addAction(viewSimLogAction) + viewMenu.addSeparator() + distributeWindowsAction = QAction('Distribute Windows', self) + distributeWindowsAction.setStatusTip('Distribute Parameter Windows' + ' Across Screen.') + distributeWindowsAction.triggered.connect(self.distribsubwin) + viewMenu.addAction(distributeWindowsAction) + hideWindowsAction = QAction('Hide Windows', self) + hideWindowsAction.setStatusTip('Hide Parameter Windows.') + hideWindowsAction.triggered.connect(self.hidesubwin) + hideWindowsAction.setShortcut('Ctrl+H') + viewMenu.addAction(hideWindowsAction) + + simMenu = self.menubar.addMenu('&Simulation') + setParmAct = QAction('Set Parameters', self) + setParmAct.setStatusTip('Set Simulation Parameters') + setParmAct.triggered.connect(self.setparams) + simMenu.addAction(setParmAct) + simMenu.addAction(runSimAct) + setOptParamAct = QAction('Configure Optimization', self) + setOptParamAct.setShortcut('Ctrl+O') + setOptParamAct.setStatusTip('Set parameters for evoked input' + ' optimization') + setOptParamAct.triggered.connect(self.showoptparamwin) + simMenu.addAction(setOptParamAct) + self.toggleEnableOptimization(False) + prevSimAct = QAction('Go to Previous Simulation', self) + prevSimAct.setShortcut('Ctrl+Z') + prevSimAct.setStatusTip('Go Back to Previous Simulation') + prevSimAct.triggered.connect(self.prevSim) + simMenu.addAction(prevSimAct) + nextSimAct = QAction('Go to Next Simulation', self) + nextSimAct.setShortcut('Ctrl+Y') + nextSimAct.setStatusTip('Go Forward to Next Simulation') + nextSimAct.triggered.connect(self.nextSim) + simMenu.addAction(nextSimAct) + # need another QAction to avoid DBus warning + clearSims2 = QAction('Clear simulation(s)', self) + clearSims2.setStatusTip('Clear simulation(s)') + clearSims2.triggered.connect(self.clearSimulations) + simMenu.addAction(clearSims2) + + aboutMenu = self.menubar.addMenu('&About') + aboutAction = QAction('About HNN', self) + aboutAction.setStatusTip('About HNN') + aboutAction.triggered.connect(self.showAboutDialog) + aboutMenu.addAction(aboutAction) + helpAction = QAction('Help', self) + helpAction.setStatusTip('Help on how to use HNN (parameters).') + helpAction.triggered.connect(self.showHelpDialog) + # aboutMenu.addAction(helpAction) + + def toggleEnableOptimization(self, toEnable): + for menu in self.menubar.findChildren(QMenu): + if menu.title() == '&Simulation': + for item in menu.actions(): + if item.text() == 'Configure Optimization': + item.setEnabled(toEnable) + break + break + + def addButtons(self, gRow): + self.pbtn = pbtn = QPushButton('Set Parameters', self) + pbtn.setToolTip('Set Parameters') + pbtn.resize(pbtn.sizeHint()) + pbtn.clicked.connect(self.setparams) + self.grid.addWidget(self.pbtn, gRow, 0, 1, 3) + + self.pfbtn = pfbtn = QPushButton('Set Parameters From File', self) + pfbtn.setToolTip('Set Parameters From File') + pfbtn.resize(pfbtn.sizeHint()) + pfbtn.clicked.connect(self.selParamFileDialog) + self.grid.addWidget(self.pfbtn, gRow, 3, 1, 3) + + self.btnsim = btn = QPushButton('Run Simulation', self) + btn.setToolTip('Run Simulation') + btn.resize(btn.sizeHint()) + btn.clicked.connect(self.controlsim) + self.grid.addWidget(self.btnsim, gRow, 6, 1, 3) + + self.qbtn = qbtn = QPushButton('Quit', self) + qbtn.clicked.connect(QApplication.exit) + qbtn.resize(qbtn.sizeHint()) + self.grid.addWidget(self.qbtn, gRow, 9, 1, 3) def shownetparamwin(self): bringwintotop(self.baseparamwin.netparamwin) diff --git a/hnn/run.py b/hnn/run.py index 6c47d960d..d443a1d49 100755 --- a/hnn/run.py +++ b/hnn/run.py @@ -9,7 +9,6 @@ from math import ceil, isclose from contextlib import redirect_stdout from psutil import wait_procs, process_iter, NoSuchProcess -import threading import traceback from queue import Queue @@ -418,7 +417,8 @@ def _run(self): # check that optimization improved RMSE err_queue = Queue() - self.get_err_from_sim_data.qsig.emit(err_queue, self.paramfn, self.params['tstop']) + self.get_err_from_sim_data.qsig.emit(err_queue, self.paramfn, + self.params['tstop']) final_err = err_queue.get() if final_err > self.initial_err: txt = "Warning: optimization failed to improve RMSE below" + \ @@ -502,7 +502,8 @@ def get_initial_data(self): self.update_opt_data_from_sim_data.tsig.emit(self.paramfn) self.update_initial_opt_data_from_sim_data.tsig.emit(self.paramfn) err_queue = Queue() - self.get_err_from_sim_data.qsig.emit(err_queue, self.paramfn, self.params['tstop']) + self.get_err_from_sim_data.qsig.emit(err_queue, self.paramfn, + self.params['tstop']) self.initial_err = err_queue.get() def opt_sim(self, new_params, grad=0): From 66cf7677710e1a4ed1ff8820bb44a8daf3fcd95f Mon Sep 17 00:00:00 2001 From: Mainak Jas Date: Sat, 20 Mar 2021 00:17:55 -0400 Subject: [PATCH 097/107] More flake8 --- hnn/qt_main.py | 291 +++++++++++++++++++++++++------------------------ 1 file changed, 149 insertions(+), 142 deletions(-) diff --git a/hnn/qt_main.py b/hnn/qt_main.py index 1b8e5116f..43ed671fe 100644 --- a/hnn/qt_main.py +++ b/hnn/qt_main.py @@ -694,16 +694,16 @@ def initMenu(self): helpAction.triggered.connect(self.showHelpDialog) # aboutMenu.addAction(helpAction) - def toggleEnableOptimization(self, toEnable): + def toggleEnableOptimization(self, toEnable): for menu in self.menubar.findChildren(QMenu): - if menu.title() == '&Simulation': - for item in menu.actions(): - if item.text() == 'Configure Optimization': - item.setEnabled(toEnable) + if menu.title() == '&Simulation': + for item in menu.actions(): + if item.text() == 'Configure Optimization': + item.setEnabled(toEnable) + break break - break - def addButtons(self, gRow): + def addButtons(self, gRow): self.pbtn = pbtn = QPushButton('Set Parameters', self) pbtn.setToolTip('Set Parameters') pbtn.resize(pbtn.sizeHint()) @@ -743,172 +743,175 @@ def showschematics(self): bringwintotop(self.schemwin) def addParamImageButtons(self, gRow): - """add parameter image buttons to the GUI""" - - self.locbtn = QPushButton('Local Network' + os.linesep + 'Connections', - self) - self.locbtn.setIcon(QIcon(lookupresource('connfig'))) - self.locbtn.clicked.connect(self.shownetparamwin) - self.grid.addWidget(self.locbtn, gRow, 0, 1, 4) - - self.proxbtn = QPushButton('Proximal Drive' + os.linesep + 'Thalamus', - self) - self.proxbtn.setIcon(QIcon(lookupresource('proxfig'))) - self.proxbtn.clicked.connect(self.showproxparamwin) - self.grid.addWidget(self.proxbtn, gRow, 4, 1, 4) - - self.distbtn = QPushButton('Distal Drive NonLemniscal' + os.linesep + - 'Thal./Cortical Feedback', self) - self.distbtn.setIcon(QIcon(lookupresource('distfig'))) - self.distbtn.clicked.connect(self.showdistparamwin) - self.grid.addWidget(self.distbtn, gRow, 8, 1, 4) - - gRow += 1 + """add parameter image buttons to the GUI""" + + self.locbtn = QPushButton('Local Network' + os.linesep + + 'Connections', self) + self.locbtn.setIcon(QIcon(lookupresource('connfig'))) + self.locbtn.clicked.connect(self.shownetparamwin) + self.grid.addWidget(self.locbtn, gRow, 0, 1, 4) + + self.proxbtn = QPushButton('Proximal Drive' + os.linesep + + 'Thalamus', + self) + self.proxbtn.setIcon(QIcon(lookupresource('proxfig'))) + self.proxbtn.clicked.connect(self.showproxparamwin) + self.grid.addWidget(self.proxbtn, gRow, 4, 1, 4) + + self.distbtn = QPushButton('Distal Drive NonLemniscal' + + os.linesep + + 'Thal./Cortical Feedback', self) + self.distbtn.setIcon(QIcon(lookupresource('distfig'))) + self.distbtn.clicked.connect(self.showdistparamwin) + self.grid.addWidget(self.distbtn, gRow, 8, 1, 4) + + gRow += 1 def initUI(self): - """initialize the user interface (UI)""" + """initialize the user interface (UI)""" - self.initMenu() - self.statusBar() + self.initMenu() + self.statusBar() - # start GUI in center of screenm, scale based on screen w x h - setscalegeomcenter(self, 1500, 1300) + # start GUI in center of screenm, scale based on screen w x h + setscalegeomcenter(self, 1500, 1300) - # move param windows to be offset from main GUI - new_x = max(0, self.x() - 300) - new_y = max(0, self.y() + 100) - self.baseparamwin.move(new_x, new_y) - self.baseparamwin.evparamwin.move(new_x+50, new_y+50) - self.baseparamwin.optparamwin.move(new_x+100, new_y+100) - self.setWindowTitle(self.baseparamwin.paramfn) - QToolTip.setFont(QFont('SansSerif', 10)) + # move param windows to be offset from main GUI + new_x = max(0, self.x() - 300) + new_y = max(0, self.y() + 100) + self.baseparamwin.move(new_x, new_y) + self.baseparamwin.evparamwin.move(new_x+50, new_y+50) + self.baseparamwin.optparamwin.move(new_x+100, new_y+100) + self.setWindowTitle(self.baseparamwin.paramfn) + QToolTip.setFont(QFont('SansSerif', 10)) - self.grid = grid = QGridLayout() - # grid.setSpacing(10) + self.grid = grid = QGridLayout() + # grid.setSpacing(10) - gRow = 0 + gRow = 0 - self.addButtons(gRow) + self.addButtons(gRow) - gRow += 1 + gRow += 1 - self.initSimCanvas(gRow=gRow, reInit=False) - gRow += 2 + self.initSimCanvas(gRow=gRow, reInit=False) + gRow += 2 - self.cbsim = QComboBox(self) - try: - self.populateSimCB() - except ValueError: - # If no simulations could be loaded into combobox - # don't crash the initialization process - print("Warning: no simulations to load") - pass - self.cbsim.activated[str].connect(self.onActivateSimCB) - self.grid.addWidget(self.cbsim, gRow, 0, 1, 8) - self.btnrmsim = QPushButton('Remove Simulation', self) - self.btnrmsim.resize(self.btnrmsim.sizeHint()) - self.btnrmsim.clicked.connect(self.removeSim) - self.btnrmsim.setToolTip('Remove Currently Selected Simulation') - self.grid.addWidget(self.btnrmsim, gRow, 8, 1, 4) + self.cbsim = QComboBox(self) + try: + self.populateSimCB() + except ValueError: + # If no simulations could be loaded into combobox + # don't crash the initialization process + print("Warning: no simulations to load") + pass + self.cbsim.activated[str].connect(self.onActivateSimCB) + self.grid.addWidget(self.cbsim, gRow, 0, 1, 8) + self.btnrmsim = QPushButton('Remove Simulation', self) + self.btnrmsim.resize(self.btnrmsim.sizeHint()) + self.btnrmsim.clicked.connect(self.removeSim) + self.btnrmsim.setToolTip('Remove Currently Selected Simulation') + self.grid.addWidget(self.btnrmsim, gRow, 8, 1, 4) - gRow += 1 - self.addParamImageButtons(gRow) + gRow += 1 + self.addParamImageButtons(gRow) - # need a separate widget to put grid on - widget = QWidget(self) - widget.setLayout(grid) - self.setCentralWidget(widget) + # need a separate widget to put grid on + widget = QWidget(self) + widget.setLayout(grid) + self.setCentralWidget(widget) - self.setWindowIcon(QIcon(os.path.join('res', 'icon.png'))) + self.setWindowIcon(QIcon(os.path.join('res', 'icon.png'))) - self.schemwin.show() # so it's underneath main window + self.schemwin.show() # so it's underneath main window - self.show() + self.show() def onActivateSimCB(self, paramfn): - """load simulation when activating simulation combobox""" + """load simulation when activating simulation combobox""" - if paramfn != self.baseparamwin.paramfn: - try: - params = read_params(paramfn) - except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not" - "retrieve parameters from %s" % - paramfn) - return - self.baseparamwin.paramfn = paramfn + if paramfn != self.baseparamwin.paramfn: + try: + params = read_params(paramfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + paramfn) + return + self.baseparamwin.paramfn = paramfn - self.updateDatCanv(params) + self.updateDatCanv(params) def populateSimCB(self, index=None): - """populate the simulation combobox""" + """populate the simulation combobox""" - self.cbsim.clear() - for paramfn in self.sim_data._sim_data.keys(): - self.cbsim.addItem(paramfn) + self.cbsim.clear() + for paramfn in self.sim_data._sim_data.keys(): + self.cbsim.addItem(paramfn) - if self.cbsim.count() == 0: - raise ValueError("No simulations to add to combo box") + if self.cbsim.count() == 0: + raise ValueError("No simulations to add to combo box") - if index is None or index < 0: - # set to last entry - self.cbsim.setCurrentIndex(self.cbsim.count() - 1) - else: - self.cbsim.setCurrentIndex(index) + if index is None or index < 0: + # set to last entry + self.cbsim.setCurrentIndex(self.cbsim.count() - 1) + else: + self.cbsim.setCurrentIndex(index) def initSimCanvas(self, gRow=1, reInit=True): - """initialize the simulation canvas, loading any required data""" - gCol = 0 + """initialize the simulation canvas, loading any required data""" + gCol = 0 - if reInit: - self.grid.itemAtPosition(gRow, gCol).widget().deleteLater() - self.grid.itemAtPosition(gRow + 1, gCol).widget().deleteLater() + if reInit: + self.grid.itemAtPosition(gRow, gCol).widget().deleteLater() + self.grid.itemAtPosition(gRow + 1, gCol).widget().deleteLater() - # if just initialized or after clearSimulationData - if self.baseparamwin.paramfn and self.baseparamwin.params is None: - try: - self.baseparamwin.params = read_params(self.baseparamwin.paramfn) - except ValueError: - QMessageBox.information(self, "HNN", "WARNING: could not" - "retrieve parameters from %s" % - self.baseparamwin.paramfn) - return - - self.sim_canvas = SIMCanvas(self.baseparamwin.paramfn, - self.baseparamwin.params, - parent=self, width=10, height=1, - dpi=getmplDPI(), - is_optimization=self.is_optimization) - - # this is the Navigation widget - # it takes the Canvas widget and a parent - self.toolbar = NavigationToolbar2QT(self.sim_canvas, self) - gWidth = 12 - self.grid.addWidget(self.toolbar, gRow, gCol, 1, gWidth) - self.grid.addWidget(self.sim_canvas, gRow + 1, gCol, 1, gWidth) - - if self.sim_canvas.saved_exception is not None: - raise self.sim_canvas.saved_exception + # if just initialized or after clearSimulationData + if self.baseparamwin.paramfn and self.baseparamwin.params is None: + try: + self.baseparamwin.params = read_params( + self.baseparamwin.paramfn) + except ValueError: + QMessageBox.information(self, "HNN", "WARNING: could not" + "retrieve parameters from %s" % + self.baseparamwin.paramfn) + return + + self.sim_canvas = SIMCanvas(self.baseparamwin.paramfn, + self.baseparamwin.params, + parent=self, width=10, height=1, + dpi=getmplDPI(), + is_optimization=self.is_optimization) + + # this is the Navigation widget + # it takes the Canvas widget and a parent + self.toolbar = NavigationToolbar2QT(self.sim_canvas, self) + gWidth = 12 + self.grid.addWidget(self.toolbar, gRow, gCol, 1, gWidth) + self.grid.addWidget(self.sim_canvas, gRow + 1, gCol, 1, gWidth) + + if self.sim_canvas.saved_exception is not None: + raise self.sim_canvas.saved_exception def setcursors(self, cursor): - """set cursors of self and children""" - - self.setCursor(cursor) - self.update() - kids = self.children() - kids.append(self.sim_canvas) # matplotlib simcanvas - for k in kids: - if type(k) == QLayout or type(k) == QAction: - # These types don't have setCursor() - continue - k.setCursor(cursor) - k.update() + """set cursors of self and children""" + + self.setCursor(cursor) + self.update() + kids = self.children() + kids.append(self.sim_canvas) # matplotlib simcanvas + for k in kids: + if type(k) == QLayout or type(k) == QAction: + # These types don't have setCursor() + continue + k.setCursor(cursor) + k.update() def startoptmodel(self, num_steps): """start model optimization""" if self.runningsim: raise ValueError("Optimization already running") - + self.is_optimization = True if not self.baseparamwin.saveparams(): # user may have pressed 'cancel' @@ -923,7 +926,8 @@ def startoptmodel(self, num_steps): # write_legacy_paramf(param_out, self.params) seed = self.baseparamwin.runparamwin.get_prng_seedcore_opt() ncore = self.baseparamwin.runparamwin.getncore() - self.runthread = OptThread(ncore, self.baseparamwin.params, num_steps, + self.runthread = OptThread(ncore, self.baseparamwin.params, + num_steps, seed, self.sim_data, self.sim_result_callback, self.opt_callback, mainwin=self) @@ -934,7 +938,8 @@ def startoptmodel(self, num_steps): self.baseparamwin.optparamwin.btnreset.setEnabled(False) self.baseparamwin.optparamwin.btnrunop.setText('Stop Optimization') self.baseparamwin.optparamwin.btnrunop.clicked.disconnect() - self.baseparamwin.optparamwin.btnrunop.clicked.connect(self.stopsim) + self.baseparamwin.optparamwin.btnrunop.clicked.connect( + self.stopsim) # update GUI self.statusBar().showMessage("Optimizing model. . .") @@ -947,7 +952,8 @@ def startoptmodel(self, num_steps): def controlsim(self): """control the simulation""" if self.runningsim: - # stop sim works but leaves subproc as zombie until this main GUI + # stop sim works but leaves subproc as zombie until this main + # GUI self.stopsim() else: self.is_optimization = False @@ -986,7 +992,7 @@ def startsim(self, ncore): params = read_params(self.baseparamwin.paramfn) except ValueError: txt = "WARNING: could not retrieve parameters from %s" % \ - self.baseparamwin.paramfn + self.baseparamwin.paramfn QMessageBox.information(self, "HNN", txt) print(txt) return @@ -1005,7 +1011,7 @@ def startsim(self, ncore): params['N_trials'] = 1 self.runthread = SimThread(ncore, params, self.sim_result_callback, - mainwin=self) + mainwin=self) # We have all the events we need connected we can start the thread self.runthread.start() @@ -1170,6 +1176,7 @@ def done(self, except_msg=''): self.is_optimization = False + if __name__ == '__main__': app = QApplication(sys.argv) HNNGUI() From 829b71964f07457434efd09ee8d13ae206b86623 Mon Sep 17 00:00:00 2001 From: Mainak Jas Date: Sat, 20 Mar 2021 00:35:45 -0400 Subject: [PATCH 098/107] Don't ignore flake8 --- scripts/run-pytest.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/run-pytest.sh b/scripts/run-pytest.sh index 2440c5baf..61363c118 100755 --- a/scripts/run-pytest.sh +++ b/scripts/run-pytest.sh @@ -7,11 +7,8 @@ if [[ "${WSL_INSTALL}" -eq 1 ]]; then unset DISPLAY fi -# first check code style with flake8 (ignored currently) +# first check code style with flake8 echo "Checking code style compliance with flake8..." -flake8 --quiet --count \ - --exclude __init__.py,qt_main.py,qt_evoked.py,run.py,paramrw.py \ - hnn - +flake8 --quiet --count --exclude __init__.py echo "Running unit tests with pytest..." py.test --cov=. hnn/tests/ From be3dc06e159f6da150c32b3847b3162790779a15 Mon Sep 17 00:00:00 2001 From: Mainak Jas Date: Sat, 20 Mar 2021 00:41:21 -0400 Subject: [PATCH 099/107] Run autopep8 --- hnn/paramrw.py | 268 +++--- hnn/qt_evoked.py | 2197 ++++++++++++++++++++++++---------------------- 2 files changed, 1273 insertions(+), 1192 deletions(-) diff --git a/hnn/paramrw.py b/hnn/paramrw.py index be0478fc5..f2cf8666a 100644 --- a/hnn/paramrw.py +++ b/hnn/paramrw.py @@ -9,6 +9,7 @@ import numpy as np import itertools as it + def get_output_dir(): """Return the base directory for storing output files""" @@ -60,7 +61,7 @@ def get_fname(sim_dir, key, trial=None): fname = os.path.join(sim_dir, datatypes[key][0] + datatypes[key][1]) else: fname = os.path.join(sim_dir, datatypes[key][0] + '_' + str(trial) + - datatypes[key][1]) + datatypes[key][1]) return fname @@ -82,122 +83,145 @@ def get_inputs(params): return dinty # Cleans input files -def clean_lines (file): - with open(file) as f_in: - lines = (line.rstrip() for line in f_in) - lines = [line for line in lines if line] - return lines + + +def clean_lines(file): + with open(file) as f_in: + lines = (line.rstrip() for line in f_in) + lines = [line for line in lines if line] + return lines # check if using ongoing inputs -def usingOngoingInputs (params, lty = ['_prox', '_dist']): - if params is None: - return False - try: - tstop = float(params['tstop']) - except KeyError: - return False - dpref = {'_prox':'input_prox_A_','_dist':'input_dist_A_'} - for postfix in lty: - if float(params['t0_input'+postfix])<= tstop and \ - float(params['tstop_input'+postfix])>=float(params['t0_input'+postfix]) and \ - float(params['f_input'+postfix])>0.: - for k in ['weight_L2Pyr_ampa','weight_L2Pyr_nmda',\ - 'weight_L5Pyr_ampa','weight_L5Pyr_nmda',\ - 'weight_inh_ampa','weight_inh_nmda']: - if float(params[dpref[postfix]+k])>0.: - # print('usingOngoingInputs:',params[dpref[postfix]+k]) - return True +def usingOngoingInputs(params, lty=['_prox', '_dist']): + if params is None: + return False + + try: + tstop = float(params['tstop']) + except KeyError: + return False - return False + dpref = {'_prox': 'input_prox_A_', '_dist': 'input_dist_A_'} + for postfix in lty: + if float(params['t0_input'+postfix]) <= tstop and \ + float(params['tstop_input'+postfix]) >= float(params['t0_input'+postfix]) and \ + float(params['f_input'+postfix]) > 0.: + for k in ['weight_L2Pyr_ampa', 'weight_L2Pyr_nmda', + 'weight_L5Pyr_ampa', 'weight_L5Pyr_nmda', + 'weight_inh_ampa', 'weight_inh_nmda']: + if float(params[dpref[postfix]+k]) > 0.: + # print('usingOngoingInputs:',params[dpref[postfix]+k]) + return True + + return False # return number of evoked inputs (proximal, distal) # using dictionary d (or if d is a string, first load the dictionary from filename d) -def countEvokedInputs (params): - nprox = ndist = 0 - if params is not None: - for k,v in params.items(): - if k.startswith('t_'): - if k.count('evprox') > 0: - nprox += 1 - elif k.count('evdist') > 0: - ndist += 1 - return nprox, ndist - -# check if using any evoked inputs -def usingEvokedInputs (params, lsuffty = ['_evprox_', '_evdist_']): - nprox,ndist = countEvokedInputs(params) - if nprox == 0 and ndist == 0: - return False - try: - tstop = float(params['tstop']) - except KeyError: - return False - lsuff = [] - if '_evprox_' in lsuffty: - for i in range(1,nprox+1,1): lsuff.append('_evprox_'+str(i)) - if '_evdist_' in lsuffty: - for i in range(1,ndist+1,1): lsuff.append('_evdist_'+str(i)) - for suff in lsuff: - k = 't' + suff - if k not in params: continue - if float(params[k]) > tstop: continue - k = 'gbar' + suff - for k1 in params.keys(): - if k1.startswith(k): - if float(params[k1]) > 0.0: return True - return False - -# check if using any poisson inputs -def usingPoissonInputs (params): - if params is None: +def countEvokedInputs(params): + nprox = ndist = 0 + if params is not None: + for k, v in params.items(): + if k.startswith('t_'): + if k.count('evprox') > 0: + nprox += 1 + elif k.count('evdist') > 0: + ndist += 1 + return nprox, ndist + +# check if using any evoked inputs + + +def usingEvokedInputs(params, lsuffty=['_evprox_', '_evdist_']): + nprox, ndist = countEvokedInputs(params) + if nprox == 0 and ndist == 0: + return False + + try: + tstop = float(params['tstop']) + except KeyError: + return False + + lsuff = [] + if '_evprox_' in lsuffty: + for i in range(1, nprox+1, 1): + lsuff.append('_evprox_'+str(i)) + if '_evdist_' in lsuffty: + for i in range(1, ndist+1, 1): + lsuff.append('_evdist_'+str(i)) + for suff in lsuff: + k = 't' + suff + if k not in params: + continue + if float(params[k]) > tstop: + continue + k = 'gbar' + suff + for k1 in params.keys(): + if k1.startswith(k): + if float(params[k1]) > 0.0: + return True return False - try: - tstop = float(params['tstop']) +# check if using any poisson inputs + - if 't0_pois' in params and 'T_pois' in params: - t0_pois = float(params['t0_pois']) - if t0_pois > tstop: return False - T_pois = float(params['T_pois']) - if t0_pois > T_pois and T_pois != -1.0: +def usingPoissonInputs(params): + if params is None: return False - except KeyError: + + try: + tstop = float(params['tstop']) + + if 't0_pois' in params and 'T_pois' in params: + t0_pois = float(params['t0_pois']) + if t0_pois > tstop: + return False + T_pois = float(params['T_pois']) + if t0_pois > T_pois and T_pois != -1.0: + return False + except KeyError: + return False + + for cty in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: + for sy in ['ampa', 'nmda']: + k = cty+'_Pois_A_weight_'+sy + if k in params: + if float(params[k]) != 0.0: + return True + return False - for cty in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: - for sy in ['ampa','nmda']: - k = cty+'_Pois_A_weight_'+sy - if k in params: - if float(params[k]) != 0.0: - return True +# check if using any tonic (IClamp) inputs - return False -# check if using any tonic (IClamp) inputs -def usingTonicInputs (d): - if d is None: +def usingTonicInputs(d): + if d is None: + return False + + tstop = float(d['tstop']) + for cty in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: + k = 'Itonic_A_' + cty + '_soma' + if k in d: + amp = float(d[k]) + if amp != 0.0: + print(k, 'amp != 0.0', amp) + k = 'Itonic_t0_' + cty + t0, t1 = 0.0, -1.0 + if k in d: + t0 = float(d[k]) + k = 'Itonic_T_' + cty + if k in d: + t1 = float(d[k]) + if t0 > tstop: + continue + # print('t0:',t0,'t1:',t1) + if t0 < t1 or t1 == -1.0: + return True return False - tstop = float(d['tstop']) - for cty in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: - k = 'Itonic_A_' + cty + '_soma' - if k in d: - amp = float(d[k]) - if amp != 0.0: - print(k,'amp != 0.0',amp) - k = 'Itonic_t0_' + cty - t0,t1 = 0.0,-1.0 - if k in d: t0 = float(d[k]) - k = 'Itonic_T_' + cty - if k in d: t1 = float(d[k]) - if t0 > tstop: continue - #print('t0:',t0,'t1:',t1) - if t0 < t1 or t1 == -1.0: return True - return False def read_gids_param(fparam): lines = clean_lines(fparam) @@ -246,31 +270,31 @@ def legacy_param_str_to_dict(param_str): # write the params to a filename def write_legacy_paramf(fparam, p): - """ now sorting - """ - - p_keys = [key for key, val in p.items()] - p_sorted = [(key, p[key]) for key in p_keys] - with open(fparam, 'w') as f: - pstring = '%26s: ' - # do the params in p_sorted - for param in p_sorted: - key, val = param - f.write(pstring % key) - if key.startswith('N_'): - f.write('%i\n' % val) - else: - f.write(str(val)+'\n') + """ now sorting + """ + + p_keys = [key for key, val in p.items()] + p_sorted = [(key, p[key]) for key in p_keys] + with open(fparam, 'w') as f: + pstring = '%26s: ' + # do the params in p_sorted + for param in p_sorted: + key, val = param + f.write(pstring % key) + if key.startswith('N_'): + f.write('%i\n' % val) + else: + f.write(str(val)+'\n') def write_gids_param(fparam, gid_list): - with open(fparam, 'w') as f: - pstring = '%26s: ' - # write the gid info - for key in gid_list.keys(): - f.write(pstring % key) - if len(gid_list[key]): - f.write('[%4i, %4i] ' % (gid_list[key][0], gid_list[key][-1])) - else: - f.write('[]') - f.write('\n') + with open(fparam, 'w') as f: + pstring = '%26s: ' + # write the gid info + for key in gid_list.keys(): + f.write(pstring % key) + if len(gid_list[key]): + f.write('[%4i, %4i] ' % (gid_list[key][0], gid_list[key][-1])) + else: + f.write('[]') + f.write('\n') diff --git a/hnn/qt_evoked.py b/hnn/qt_evoked.py index 457e13b8a..e53dc1695 100644 --- a/hnn/qt_evoked.py +++ b/hnn/qt_evoked.py @@ -21,10 +21,12 @@ decay_multiplier = 1.6 + def _consolidate_chunks(input_dict): # MOVE to hnn-core # get a list of sorted chunks - sorted_inputs = sorted(input_dict.items(), key=lambda x: x[1]['user_start']) + sorted_inputs = sorted( + input_dict.items(), key=lambda x: x[1]['user_start']) consolidated_chunks = [] for one_input in sorted_inputs: @@ -41,19 +43,22 @@ def _consolidate_chunks(input_dict): } if (len(consolidated_chunks) > 0) and \ - (input_dict['chunk_start'] <= consolidated_chunks[-1]['chunk_end']): + (input_dict['chunk_start'] <= consolidated_chunks[-1]['chunk_end']): # update previous chunk consolidated_chunks[-1]['inputs'].extend(input_dict['inputs']) consolidated_chunks[-1]['chunk_end'] = input_dict['chunk_end'] - consolidated_chunks[-1]['opt_end'] = max(consolidated_chunks[-1]['opt_end'], input_dict['opt_end']) + consolidated_chunks[-1]['opt_end'] = max( + consolidated_chunks[-1]['opt_end'], input_dict['opt_end']) # average the weights - consolidated_chunks[-1]['weights'] = (consolidated_chunks[-1]['weights'] + one_input[1]['weights'])/2 + consolidated_chunks[-1]['weights'] = ( + consolidated_chunks[-1]['weights'] + one_input[1]['weights'])/2 else: # new chunk consolidated_chunks.append(input_dict) return consolidated_chunks + def _combine_chunks(input_chunks): # MOVE to hnn-core # Used for creating the opt params of the last step with all inputs @@ -75,6 +80,7 @@ def _combine_chunks(input_chunks): final_chunk['weights'] = np.ones(len(input_chunks[-1]['weights'])) return final_chunk + def _chunk_evinputs(opt_params, sim_tstop, sim_dt): # MOVE to hnn-core """ @@ -106,7 +112,6 @@ def _chunk_evinputs(opt_params, sim_tstop, sim_dt): input_dict = {} cdfs = {} - for input_name in opt_params.keys(): if opt_params[input_name]['user_start'] > sim_tstop or \ opt_params[input_name]['user_end'] < 0: @@ -140,26 +145,32 @@ def _chunk_evinputs(opt_params, sim_tstop, sim_dt): # check ordering to only use inputs after us continue else: - decay_factor = opt_params[input_name]['decay_multiplier']*(opt_params[other_input]['mean'] - \ - opt_params[input_name]['mean']) / \ - sim_tstop - input_dict[input_name]['weights'] -= cdfs[other_input] * decay_factor + decay_factor = opt_params[input_name]['decay_multiplier']*(opt_params[other_input]['mean'] - + opt_params[input_name]['mean']) / \ + sim_tstop + input_dict[input_name]['weights'] -= cdfs[other_input] * \ + decay_factor # weights should not drop below 0 - input_dict[input_name]['weights'] = np.clip(input_dict[input_name]['weights'], a_min=0, a_max=None) + input_dict[input_name]['weights'] = np.clip( + input_dict[input_name]['weights'], a_min=0, a_max=None) # start and stop optimization where the weights are insignificant - good_indices = np.where( input_dict[input_name]['weights'] > 0.01) + good_indices = np.where(input_dict[input_name]['weights'] > 0.01) if len(good_indices[0]) > 0: - input_dict[input_name]['opt_start'] = min(opt_params[input_name]['user_start'], times[good_indices][0]) - input_dict[input_name]['opt_end'] = max(opt_params[input_name]['user_end'], times[good_indices][-1]) + input_dict[input_name]['opt_start'] = min( + opt_params[input_name]['user_start'], times[good_indices][0]) + input_dict[input_name]['opt_end'] = max( + opt_params[input_name]['user_end'], times[good_indices][-1]) else: input_dict[input_name]['opt_start'] = opt_params[other_input]['user_start'] - input_dict[input_name]['opt_end'] = opt_params[other_input]['user_end'] + input_dict[input_name]['opt_end'] = opt_params[other_input]['user_end'] # convert to multiples of dt - input_dict[input_name]['opt_start'] = floor(input_dict[input_name]['opt_start']/sim_dt)*sim_dt - input_dict[input_name]['opt_end'] = ceil(input_dict[input_name]['opt_end']/sim_dt)*sim_dt + input_dict[input_name]['opt_start'] = floor( + input_dict[input_name]['opt_start']/sim_dt)*sim_dt + input_dict[input_name]['opt_end'] = ceil( + input_dict[input_name]['opt_end']/sim_dt)*sim_dt # combined chunks that have overlapping ranges # opt_params is a dict, turn into a list @@ -171,6 +182,7 @@ def _chunk_evinputs(opt_params, sim_tstop, sim_dt): return chunk_list + def _get_param_inputs(params): import re input_list = [] @@ -184,6 +196,7 @@ def _get_param_inputs(params): return input_list + def _trans_input(input_var): import re @@ -197,6 +210,7 @@ def _trans_input(input_var): return input_str + def _format_range_str(value): if value == 0: value_str = "0.000" @@ -368,435 +382,445 @@ def removeAllInputs(self): class EvokedInputParamDialog (EvokedInputBaseDialog): - """ Evoked Input Dialog - allows adding/removing arbitrary number of evoked inputs""" - - def __init__ (self, parent, din): - super(EvokedInputParamDialog, self).__init__() - self.initUI() - self.setfromdin(din) - - def transvar (self,k): - if k in self.dtransvar: return self.dtransvar[k] - return k - - def set_qline_float (self, key_str, value): - try: - new_value = float(value) - except ValueError: - print("WARN: bad value for param %s: %s. Unable to convert" - " to a floating point number" % (key_str, value)) - return - - # Enforce no sci. not. + limit field len + remove trailing 0's - self.dqline[key_str].setText(("%7f" % new_value).rstrip('0').rstrip('.')) - - def setfromdin (self,din): - if not din: return - - if 'dt' in din: - - # Optimization feature introduces the case where din just contains optimization - # relevant parameters. In that case, we don't want to remove all inputs, just - # modify existing inputs. - self.removeAllInputs() # turn off any previously set inputs - - nprox, ndist = countEvokedInputs(din) - for i in range(nprox+ndist): - if i % 2 == 0: - if self.nprox < nprox: - self.addProx() - elif self.ndist < ndist: - self.addDist() - else: - if self.ndist < ndist: - self.addDist() - elif self.nprox < nprox: - self.addProx() + """ Evoked Input Dialog + allows adding/removing arbitrary number of evoked inputs""" - for k,v in din.items(): - if k == 'sync_evinput': - try: - new_value = bool(int(v)) - except ValueError: - print("WARN: bad value for param %s: %s. Unable to convert" - " to a boolean value" % (k,v)) - continue - if new_value: - self.chksync.setChecked(True) - else: - self.chksync.setChecked(False) - elif k == 'inc_evinput': + def __init__(self, parent, din): + super(EvokedInputParamDialog, self).__init__() + self.initUI() + self.setfromdin(din) + + def transvar(self, k): + if k in self.dtransvar: + return self.dtransvar[k] + return k + + def set_qline_float(self, key_str, value): try: - new_value = float(v) + new_value = float(value) except ValueError: - print("WARN: bad value for param %s: %s. Unable to convert" - " to a floating point number" % (k,v)) - continue - self.incedit.setText(str(new_value).strip()) - elif k in self.dqline: - if k.startswith('numspikes'): - try: - new_value = int(v) - except ValueError: print("WARN: bad value for param %s: %s. Unable to convert" - " to a integer" % (k, v)) - continue - self.dqline[k].setText(str(new_value)) - else: - self.set_qline_float(k, v) - elif k.count('gbar') > 0 and \ - (k.count('evprox') > 0 or \ - k.count('evdist') > 0): - # NOTE: will be deprecated in future release - # for back-compat with old-style specification which didn't have ampa,nmda in evoked gbar - lks = k.split('_') - eloc = lks[1] - enum = lks[2] - base_key_str = 'gbar_' + eloc + '_' + enum + '_' - if eloc == 'evprox': - for ct in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: - # ORIGINAL MODEL/PARAM: only ampa for prox evoked inputs - key_str = base_key_str + ct + '_ampa' - self.set_qline_float(key_str, v) - elif eloc == 'evdist': - for ct in ['L2Pyr', 'L2Basket', 'L5Pyr']: - # ORIGINAL MODEL/PARAM: both ampa and nmda for distal evoked inputs - key_str = base_key_str + ct + '_ampa' - self.set_qline_float(key_str, v) - key_str = base_key_str + ct + '_nmda' - self.set_qline_float(key_str, v) - - def initUI (self): - self.layout = QVBoxLayout(self) - - # Add stretch to separate the form layout from the button - self.layout.addStretch(1) - - self.ltabs = [] - self.tabs = QTabWidget() - self.layout.addWidget(self.tabs) - - self.button_box = QVBoxLayout() - self.btnprox = QPushButton('Add Proximal Input',self) - self.btnprox.resize(self.btnprox.sizeHint()) - self.btnprox.clicked.connect(self.addProx) - self.btnprox.setToolTip('Add Proximal Input') - self.button_box.addWidget(self.btnprox) - - self.btndist = QPushButton('Add Distal Input',self) - self.btndist.resize(self.btndist.sizeHint()) - self.btndist.clicked.connect(self.addDist) - self.btndist.setToolTip('Add Distal Input') - self.button_box.addWidget(self.btndist) - - self.chksync = QCheckBox('Synchronous Inputs',self) - self.chksync.resize(self.chksync.sizeHint()) - self.chksync.setChecked(True) - self.button_box.addWidget(self.chksync) - - self.incbox = QHBoxLayout() - self.inclabel = QLabel(self) - self.inclabel.setText('Increment start time (ms)') - self.inclabel.adjustSize() - self.inclabel.setToolTip('Increment mean evoked input start time(s) by this amount on each trial.') - self.incedit = QLineEdit(self) - self.incedit.setText('0.0') - self.incbox.addWidget(self.inclabel) - self.incbox.addWidget(self.incedit) - - self.layout.addLayout(self.button_box) - self.layout.addLayout(self.incbox) - - self.tabs.resize(425,200) - - # Add tabs to widget - self.layout.addWidget(self.tabs) - self.setLayout(self.layout) - - self.setWindowTitle('Evoked Inputs') - - self.addRemoveInputButton() - self.addHideButton() - # self.addtips() - - def lines2val (self,ksearch,val): - for k in self.dqline.keys(): - if k.count(ksearch) > 0: - self.dqline[k].setText(str(val)) - - def __str__ (self): - s = '' - for k,v in self.dqline.items(): s += k + ': ' + v.text().strip() + os.linesep - if self.chksync.isChecked(): s += 'sync_evinput: 1' + os.linesep - else: s += 'sync_evinput: 0' + os.linesep - s += 'inc_evinput: ' + self.incedit.text().strip() + os.linesep - return s - - def addRemoveInputButton (self): - self.bbremovebox = QHBoxLayout() - self.btnremove = QPushButton('Remove Input',self) - self.btnremove.resize(self.btnremove.sizeHint()) - self.btnremove.clicked.connect(self.removeCurrentInput) - self.btnremove.setToolTip('Remove This Input') - self.bbremovebox.addWidget(self.btnremove) - self.layout.addLayout(self.bbremovebox) - - def addHideButton (self): - self.bbhidebox = QHBoxLayout() - self.btnhide = QPushButton('Hide Window',self) - self.btnhide.resize(self.btnhide.sizeHint()) - self.btnhide.clicked.connect(self.hide) - self.btnhide.setToolTip('Hide Window') - self.bbhidebox.addWidget(self.btnhide) - self.layout.addLayout(self.bbhidebox) - - def addTab (self,s): - tab = QWidget() - self.ltabs.append(tab) - self.tabs.addTab(tab,s) - tab.layout = QFormLayout() - tab.setLayout(tab.layout) - return tab - - def addFormToTab (self,d,tab): - for k,v in d.items(): - self.dqline[k] = QLineEdit(self) - self.dqline[k].setText(str(v)) - tab.layout.addRow(self.transvar(k),self.dqline[k]) # adds label,QLineEdit to the tab - - def makePixLabel (self,fn): - pix = QPixmap(fn) - pixlbl = ClickLabel(self) - pixlbl.setPixmap(pix) - return pixlbl - - def addProx (self): - self.nprox += 1 # starts at 1 - dprox = _get_prox_dict(self.nprox) - self.ld.append(dprox) - self.addtransvarfromdict(dprox) - self.addFormToTab(dprox, self.addTab('Proximal ' + str(self.nprox))) - self.ltabs[-1].layout.addRow(self.makePixLabel(lookupresource('proxfig'))) - #print('index to', len(self.ltabs)-1) - self.tabs.setCurrentIndex(len(self.ltabs)-1) - #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) - # self.addtips() - - def addDist (self): - self.ndist += 1 - ddist = _get_dist_dict(self.ndist) - self.ld.append(ddist) - self.addtransvarfromdict(ddist) - self.addFormToTab(ddist,self.addTab('Distal ' + str(self.ndist))) - self.ltabs[-1].layout.addRow(self.makePixLabel(lookupresource('distfig'))) - #print('index to', len(self.ltabs)-1) - self.tabs.setCurrentIndex(len(self.ltabs)-1) - #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) - # self.addtips() + " to a floating point number" % (key_str, value)) + return + # Enforce no sci. not. + limit field len + remove trailing 0's + self.dqline[key_str].setText( + ("%7f" % new_value).rstrip('0').rstrip('.')) -class OptEvokedInputParamDialog (EvokedInputBaseDialog): - def __init__(self, parent, mainwin): - super(OptEvokedInputParamDialog, self).__init__() - self.nprox = self.ndist = 0 # number of proximal,distal inputs - self.ld = [] # list of dictionaries for proximal/distal inputs - self.dqline = {} # not used, prevents failure in removeInput - - self.dtab_idx = {} # for translating input names to tab indices - self.dtab_names = {} # for translating tab indices to input names - self.dparams = {} # actual values - - # these store values used in grid - self.dqchkbox = {} # optimize - self.dqparam_name = {} # parameter name - self.dqinitial_label = {} # initial - self.dqopt_label = {} # optimtized - self.dqdiff_label = {} # delta - self.dqrange_multiplier = {} # user-defined multiplier - self.dqrange_mode = {} # range mode (stdev, %, absolute) - self.dqrange_slider = {} # slider - self.dqrange_label = {} # defined range - self.dqrange_max = {} - self.dqrange_min = {} - - self.chunk_list = [] - self.lqnumsim = [] - self.lqnumparams = [] - self.lqinputs = [] - self.opt_params = {} - self.initial_opt_ranges = [] - self.dtabdata = [] - self.simlength = 0.0 - self.sim_dt = 0.0 - self.default_num_step_sims = 30 - self.default_num_total_sims = 50 - self.mainwin = mainwin - self.optimization_running = False - self.initUI() - self.parent = parent - self.old_num_steps = 0 - - def initUI (self): - # start with a reasonable size - setscalegeom(self, 150, 150, 475, 300) - - self.ltabs = [] - self.ltabkeys = [] - self.tabs = QTabWidget() - self.din = {} - - self.grid = QGridLayout() - - row = 0 - self.sublayout = QGridLayout() - self.old_numsims = [] - self.grid.addLayout(self.sublayout, row, 0) - - row += 1 - self.grid.addWidget(self.tabs, row, 0) - - row += 1 - self.btnrunop = QPushButton('Run Optimization', self) - self.btnrunop.resize(self.btnrunop.sizeHint()) - self.btnrunop.setToolTip('Run Optimization') - self.btnrunop.clicked.connect(self.runOptimization) - self.grid.addWidget(self.btnrunop, row, 0) - - row += 1 - self.btnreset = QPushButton('Reset Ranges',self) - self.btnreset.resize(self.btnreset.sizeHint()) - self.btnreset.clicked.connect(self.updateOptRanges) - self.btnreset.setToolTip('Reset Ranges') - self.grid.addWidget(self.btnreset, row, 0) - - row += 1 - btnhide = QPushButton('Hide Window',self) - btnhide.resize(btnhide.sizeHint()) - btnhide.clicked.connect(self.hide) - btnhide.setToolTip('Hide Window') - self.grid.addWidget(btnhide, row, 0) - - self.setLayout(self.grid) - - self.setWindowTitle("Configure Optimization") - - # the largest horizontal component will be column 0 (headings) - self.resize(self.minimumSizeHint()) - - def toggle_enable_param(self, label): - import re + def setfromdin(self, din): + if not din: + return - widget_dict_list = [self.dqinitial_label, self.dqopt_label, - self.dqdiff_label, self.dqparam_name, - self.dqrange_mode, self.dqrange_multiplier, - self.dqrange_label, self.dqrange_slider] + if 'dt' in din: - if self.dqchkbox[label].isChecked(): - # set all other fields in the row to enabled - for widget_dict in widget_dict_list: - widget_dict[label].setEnabled(True) - toEnable = True - else: - # disable all other fields in the row - for widget_dict in widget_dict_list: - widget_dict[label].setEnabled(False) - toEnable = False + # Optimization feature introduces the case where din just contains optimization + # relevant parameters. In that case, we don't want to remove all inputs, just + # modify existing inputs. + self.removeAllInputs() # turn off any previously set inputs - self.changeParamEnabledStatus(label, toEnable) + nprox, ndist = countEvokedInputs(din) + for i in range(nprox+ndist): + if i % 2 == 0: + if self.nprox < nprox: + self.addProx() + elif self.ndist < ndist: + self.addDist() + else: + if self.ndist < ndist: + self.addDist() + elif self.nprox < nprox: + self.addProx() + + for k, v in din.items(): + if k == 'sync_evinput': + try: + new_value = bool(int(v)) + except ValueError: + print("WARN: bad value for param %s: %s. Unable to convert" + " to a boolean value" % (k, v)) + continue + if new_value: + self.chksync.setChecked(True) + else: + self.chksync.setChecked(False) + elif k == 'inc_evinput': + try: + new_value = float(v) + except ValueError: + print("WARN: bad value for param %s: %s. Unable to convert" + " to a floating point number" % (k, v)) + continue + self.incedit.setText(str(new_value).strip()) + elif k in self.dqline: + if k.startswith('numspikes'): + try: + new_value = int(v) + except ValueError: + print("WARN: bad value for param %s: %s. Unable to convert" + " to a integer" % (k, v)) + continue + self.dqline[k].setText(str(new_value)) + else: + self.set_qline_float(k, v) + elif k.count('gbar') > 0 and \ + (k.count('evprox') > 0 or + k.count('evdist') > 0): + # NOTE: will be deprecated in future release + # for back-compat with old-style specification which didn't have ampa,nmda in evoked gbar + lks = k.split('_') + eloc = lks[1] + enum = lks[2] + base_key_str = 'gbar_' + eloc + '_' + enum + '_' + if eloc == 'evprox': + for ct in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: + # ORIGINAL MODEL/PARAM: only ampa for prox evoked inputs + key_str = base_key_str + ct + '_ampa' + self.set_qline_float(key_str, v) + elif eloc == 'evdist': + for ct in ['L2Pyr', 'L2Basket', 'L5Pyr']: + # ORIGINAL MODEL/PARAM: both ampa and nmda for distal evoked inputs + key_str = base_key_str + ct + '_ampa' + self.set_qline_float(key_str, v) + key_str = base_key_str + ct + '_nmda' + self.set_qline_float(key_str, v) + + def initUI(self): + self.layout = QVBoxLayout(self) + + # Add stretch to separate the form layout from the button + self.layout.addStretch(1) + + self.ltabs = [] + self.tabs = QTabWidget() + self.layout.addWidget(self.tabs) + + self.button_box = QVBoxLayout() + self.btnprox = QPushButton('Add Proximal Input', self) + self.btnprox.resize(self.btnprox.sizeHint()) + self.btnprox.clicked.connect(self.addProx) + self.btnprox.setToolTip('Add Proximal Input') + self.button_box.addWidget(self.btnprox) + + self.btndist = QPushButton('Add Distal Input', self) + self.btndist.resize(self.btndist.sizeHint()) + self.btndist.clicked.connect(self.addDist) + self.btndist.setToolTip('Add Distal Input') + self.button_box.addWidget(self.btndist) + + self.chksync = QCheckBox('Synchronous Inputs', self) + self.chksync.resize(self.chksync.sizeHint()) + self.chksync.setChecked(True) + self.button_box.addWidget(self.chksync) + + self.incbox = QHBoxLayout() + self.inclabel = QLabel(self) + self.inclabel.setText('Increment start time (ms)') + self.inclabel.adjustSize() + self.inclabel.setToolTip( + 'Increment mean evoked input start time(s) by this amount on each trial.') + self.incedit = QLineEdit(self) + self.incedit.setText('0.0') + self.incbox.addWidget(self.inclabel) + self.incbox.addWidget(self.incedit) + + self.layout.addLayout(self.button_box) + self.layout.addLayout(self.incbox) + + self.tabs.resize(425, 200) + + # Add tabs to widget + self.layout.addWidget(self.tabs) + self.setLayout(self.layout) + + self.setWindowTitle('Evoked Inputs') + + self.addRemoveInputButton() + self.addHideButton() + # self.addtips() - def addTab (self,id_str): - tab = QWidget() - self.ltabs.append(tab) + def lines2val(self, ksearch, val): + for k in self.dqline.keys(): + if k.count(ksearch) > 0: + self.dqline[k].setText(str(val)) + + def __str__(self): + s = '' + for k, v in self.dqline.items(): + s += k + ': ' + v.text().strip() + os.linesep + if self.chksync.isChecked(): + s += 'sync_evinput: 1' + os.linesep + else: + s += 'sync_evinput: 0' + os.linesep + s += 'inc_evinput: ' + self.incedit.text().strip() + os.linesep + return s + + def addRemoveInputButton(self): + self.bbremovebox = QHBoxLayout() + self.btnremove = QPushButton('Remove Input', self) + self.btnremove.resize(self.btnremove.sizeHint()) + self.btnremove.clicked.connect(self.removeCurrentInput) + self.btnremove.setToolTip('Remove This Input') + self.bbremovebox.addWidget(self.btnremove) + self.layout.addLayout(self.bbremovebox) + + def addHideButton(self): + self.bbhidebox = QHBoxLayout() + self.btnhide = QPushButton('Hide Window', self) + self.btnhide.resize(self.btnhide.sizeHint()) + self.btnhide.clicked.connect(self.hide) + self.btnhide.setToolTip('Hide Window') + self.bbhidebox.addWidget(self.btnhide) + self.layout.addLayout(self.bbhidebox) + + def addTab(self, s): + tab = QWidget() + self.ltabs.append(tab) + self.tabs.addTab(tab, s) + tab.layout = QFormLayout() + tab.setLayout(tab.layout) + return tab + + def addFormToTab(self, d, tab): + for k, v in d.items(): + self.dqline[k] = QLineEdit(self) + self.dqline[k].setText(str(v)) + # adds label,QLineEdit to the tab + tab.layout.addRow(self.transvar(k), self.dqline[k]) + + def makePixLabel(self, fn): + pix = QPixmap(fn) + pixlbl = ClickLabel(self) + pixlbl.setPixmap(pix) + return pixlbl + + def addProx(self): + self.nprox += 1 # starts at 1 + dprox = _get_prox_dict(self.nprox) + self.ld.append(dprox) + self.addtransvarfromdict(dprox) + self.addFormToTab(dprox, self.addTab('Proximal ' + str(self.nprox))) + self.ltabs[-1].layout.addRow( + self.makePixLabel(lookupresource('proxfig'))) + #print('index to', len(self.ltabs)-1) + self.tabs.setCurrentIndex(len(self.ltabs)-1) + #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) + # self.addtips() - name_str = _trans_input(id_str) - self.tabs.addTab(tab, name_str) - - tab_index = len(self.ltabs)-1 - self.dtab_idx[id_str] = tab_index - self.dtab_names[tab_index] = id_str + def addDist(self): + self.ndist += 1 + ddist = _get_dist_dict(self.ndist) + self.ld.append(ddist) + self.addtransvarfromdict(ddist) + self.addFormToTab(ddist, self.addTab('Distal ' + str(self.ndist))) + self.ltabs[-1].layout.addRow( + self.makePixLabel(lookupresource('distfig'))) + #print('index to', len(self.ltabs)-1) + self.tabs.setCurrentIndex(len(self.ltabs)-1) + #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) + # self.addtips() - return tab - def cleanLabels(self): - """ - To avoid memory leaks we need to delete all widgets when we recreate grid. - Go through all tabs and check for each var name (k) - """ - for idx in range(len(self.ltabs)): - for k in self.ld[idx].keys(): - if k in self.dqinitial_label: - del self.dqinitial_label[k] - if k in self.dqopt_label: - del self.dqopt_label[k] - if k in self.dqdiff_label: - del self.dqdiff_label[k] - if k in self.dqparam_name: - del self.dqparam_name[k] - if not self.optimization_running: - if k in self.dqrange_mode: - del self.dqrange_mode[k] - if k in self.dqrange_multiplier: - del self.dqrange_multiplier[k] - if k in self.dqrange_label: - del self.dqrange_label[k] - if k in self.dqrange_slider: - del self.dqrange_slider[k] - if k in self.dqrange_min: - del self.dqrange_min[k] - if k in self.dqrange_max: - del self.dqrange_max[k] - - def addGridToTab (self, d, tab): - from functools import partial - import re +class OptEvokedInputParamDialog (EvokedInputBaseDialog): + def __init__(self, parent, mainwin): + super(OptEvokedInputParamDialog, self).__init__() + self.nprox = self.ndist = 0 # number of proximal,distal inputs + self.ld = [] # list of dictionaries for proximal/distal inputs + self.dqline = {} # not used, prevents failure in removeInput + + self.dtab_idx = {} # for translating input names to tab indices + self.dtab_names = {} # for translating tab indices to input names + self.dparams = {} # actual values + + # these store values used in grid + self.dqchkbox = {} # optimize + self.dqparam_name = {} # parameter name + self.dqinitial_label = {} # initial + self.dqopt_label = {} # optimtized + self.dqdiff_label = {} # delta + self.dqrange_multiplier = {} # user-defined multiplier + self.dqrange_mode = {} # range mode (stdev, %, absolute) + self.dqrange_slider = {} # slider + self.dqrange_label = {} # defined range + self.dqrange_max = {} + self.dqrange_min = {} + + self.chunk_list = [] + self.lqnumsim = [] + self.lqnumparams = [] + self.lqinputs = [] + self.opt_params = {} + self.initial_opt_ranges = [] + self.dtabdata = [] + self.simlength = 0.0 + self.sim_dt = 0.0 + self.default_num_step_sims = 30 + self.default_num_total_sims = 50 + self.mainwin = mainwin + self.optimization_running = False + self.initUI() + self.parent = parent + self.old_num_steps = 0 + + def initUI(self): + # start with a reasonable size + setscalegeom(self, 150, 150, 475, 300) + + self.ltabs = [] + self.ltabkeys = [] + self.tabs = QTabWidget() + self.din = {} + + self.grid = QGridLayout() + + row = 0 + self.sublayout = QGridLayout() + self.old_numsims = [] + self.grid.addLayout(self.sublayout, row, 0) + + row += 1 + self.grid.addWidget(self.tabs, row, 0) + + row += 1 + self.btnrunop = QPushButton('Run Optimization', self) + self.btnrunop.resize(self.btnrunop.sizeHint()) + self.btnrunop.setToolTip('Run Optimization') + self.btnrunop.clicked.connect(self.runOptimization) + self.grid.addWidget(self.btnrunop, row, 0) + + row += 1 + self.btnreset = QPushButton('Reset Ranges', self) + self.btnreset.resize(self.btnreset.sizeHint()) + self.btnreset.clicked.connect(self.updateOptRanges) + self.btnreset.setToolTip('Reset Ranges') + self.grid.addWidget(self.btnreset, row, 0) + + row += 1 + btnhide = QPushButton('Hide Window', self) + btnhide.resize(btnhide.sizeHint()) + btnhide.clicked.connect(self.hide) + btnhide.setToolTip('Hide Window') + self.grid.addWidget(btnhide, row, 0) + + self.setLayout(self.grid) + + self.setWindowTitle("Configure Optimization") + + # the largest horizontal component will be column 0 (headings) + self.resize(self.minimumSizeHint()) + + def toggle_enable_param(self, label): + import re + + widget_dict_list = [self.dqinitial_label, self.dqopt_label, + self.dqdiff_label, self.dqparam_name, + self.dqrange_mode, self.dqrange_multiplier, + self.dqrange_label, self.dqrange_slider] + + if self.dqchkbox[label].isChecked(): + # set all other fields in the row to enabled + for widget_dict in widget_dict_list: + widget_dict[label].setEnabled(True) + toEnable = True + else: + # disable all other fields in the row + for widget_dict in widget_dict_list: + widget_dict[label].setEnabled(False) + toEnable = False + + self.changeParamEnabledStatus(label, toEnable) + + def addTab(self, id_str): + tab = QWidget() + self.ltabs.append(tab) + + name_str = _trans_input(id_str) + self.tabs.addTab(tab, name_str) + + tab_index = len(self.ltabs)-1 + self.dtab_idx[id_str] = tab_index + self.dtab_names[tab_index] = id_str + + return tab + + def cleanLabels(self): + """ + To avoid memory leaks we need to delete all widgets when we recreate grid. + Go through all tabs and check for each var name (k) + """ + for idx in range(len(self.ltabs)): + for k in self.ld[idx].keys(): + if k in self.dqinitial_label: + del self.dqinitial_label[k] + if k in self.dqopt_label: + del self.dqopt_label[k] + if k in self.dqdiff_label: + del self.dqdiff_label[k] + if k in self.dqparam_name: + del self.dqparam_name[k] + if not self.optimization_running: + if k in self.dqrange_mode: + del self.dqrange_mode[k] + if k in self.dqrange_multiplier: + del self.dqrange_multiplier[k] + if k in self.dqrange_label: + del self.dqrange_label[k] + if k in self.dqrange_slider: + del self.dqrange_slider[k] + if k in self.dqrange_min: + del self.dqrange_min[k] + if k in self.dqrange_max: + del self.dqrange_max[k] + + def addGridToTab(self, d, tab): + from functools import partial + import re + + current_tab = len(self.ltabs)-1 + tab.layout = QGridLayout() + # tab.layout.setSpacing(10) + + self.ltabkeys.append([]) + + # The first row has column headings + row = 0 + self.ltabkeys[current_tab].append("") + for column_index, column_name in enumerate(["Optimize", "Parameter name", + "Initial", "Optimized", "Delta"]): + widget = QLabel(column_name) + widget.resize(widget.sizeHint()) + tab.layout.addWidget(widget, row, column_index) + + column_index += 1 + widget = QLabel("Range specifier") + widget.setMinimumWidth(100) + tab.layout.addWidget(widget, row, column_index, 1, 2) + + column_index += 2 + widget = QLabel("Range slider") + # widget.setMinimumWidth(160) + tab.layout.addWidget(widget, row, column_index) + + column_index += 1 + widget = QLabel("Defined range") + tab.layout.addWidget(widget, row, column_index) + + # The second row is a horizontal line + row = 1 + self.ltabkeys[current_tab].append("") + qthline = QFrame() + qthline.setFrameShape(QFrame.HLine) + qthline.setFrameShadow(QFrame.Sunken) + tab.layout.addWidget(qthline, row, 0, 1, 9) + + # The rest are the parameters + row = 2 + for k, v in d.items(): + self.ltabkeys[current_tab].append(k) - current_tab = len(self.ltabs)-1 - tab.layout = QGridLayout() - #tab.layout.setSpacing(10) - - self.ltabkeys.append([]) - - # The first row has column headings - row = 0 - self.ltabkeys[current_tab].append("") - for column_index, column_name in enumerate(["Optimize", "Parameter name", - "Initial", "Optimized", "Delta"]): - widget = QLabel(column_name) - widget.resize(widget.sizeHint()) - tab.layout.addWidget(widget, row, column_index) - - column_index += 1 - widget = QLabel("Range specifier") - widget.setMinimumWidth(100) - tab.layout.addWidget(widget, row, column_index, 1, 2) - - column_index += 2 - widget = QLabel("Range slider") - # widget.setMinimumWidth(160) - tab.layout.addWidget(widget, row, column_index) - - column_index += 1 - widget = QLabel("Defined range") - tab.layout.addWidget(widget, row, column_index) - - # The second row is a horizontal line - row = 1 - self.ltabkeys[current_tab].append("") - qthline = QFrame() - qthline.setFrameShape(QFrame.HLine) - qthline.setFrameShadow(QFrame.Sunken) - tab.layout.addWidget(qthline, row, 0, 1, 9) - - # The rest are the parameters - row = 2 - for k,v in d.items(): - self.ltabkeys[current_tab].append(k) - - # create and format widgets - self.dparams[k] = float(v) - self.dqchkbox[k] = QCheckBox() - self.dqchkbox[k].setStyleSheet(""" + # create and format widgets + self.dparams[k] = float(v) + self.dqchkbox[k] = QCheckBox() + self.dqchkbox[k].setStyleSheet(""" .QCheckBox { spacing: 20px; } @@ -807,653 +831,686 @@ def addGridToTab (self, d, tab): color: black; } """) - self.dqchkbox[k].setChecked(True) - # use partial instead of lamda (so args won't be evaluated ahead of time?) - self.dqchkbox[k].clicked.connect(partial(self.toggle_enable_param, k)) - self.dqparam_name[k] = QLabel(self) - self.dqparam_name[k].setText(self.transvar(k)) - self.dqinitial_label[k] = QLabel() - self.dqopt_label[k] = QLabel() - self.dqdiff_label[k] = QLabel() - - # add widgets to grid - tab.layout.addWidget(self.dqchkbox[k], row, 0, alignment = Qt.AlignBaseline | Qt.AlignCenter) - tab.layout.addWidget(self.dqparam_name[k], row, 1) - tab.layout.addWidget(self.dqinitial_label[k], row, 2) # initial value - tab.layout.addWidget(self.dqopt_label[k], row, 3) # optimized value - tab.layout.addWidget(self.dqdiff_label[k], row, 4) # delta - - if k.startswith('t'): - range_mode = "(stdev)" - range_multiplier = "3.0" - elif k.startswith('sigma'): - range_mode = "(%)" - range_multiplier = "50.0" - else: - range_mode = "(%)" - range_multiplier = "500.0" - - if not self.optimization_running: - self.dqrange_slider[k] = QRangeSlider(k,self) - self.dqrange_slider[k].setMinimumWidth(140) - self.dqrange_label[k] = QLabel() - self.dqrange_multiplier[k] = MyLineEdit(range_multiplier, k) - self.dqrange_multiplier[k].textModified.connect(self.updateRange) - self.dqrange_multiplier[k].setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred) - self.dqrange_multiplier[k].setMinimumWidth(50) - self.dqrange_multiplier[k].setMaximumWidth(50) - self.dqrange_mode[k] = QLabel(range_mode) - tab.layout.addWidget(self.dqrange_multiplier[k], row, 5) # range specifier - tab.layout.addWidget(self.dqrange_mode[k], row, 6) # range mode - tab.layout.addWidget(self.dqrange_slider[k], row, 7) # range slider - tab.layout.addWidget(self.dqrange_label[k], row, 8) # calculated range - - row += 1 - - # A spacer in the last row stretches to fill remaining space. - # For inputs with fewer parameters than the rest, this pushes parameters - # to the top with the same spacing as the other inputs. - tab.layout.addItem(QSpacerItem(0, 0), row, 0, 1, 9) - tab.layout.setRowStretch(row,1) - tab.setLayout(tab.layout) - - - def addProx (self): - self.nprox += 1 - dprox = _get_prox_dict(self.nprox) - self.ld.append(dprox) - self.addtransvarfromdict(dprox) - tab = self.addTab('evprox_' + str(self.nprox)) - self.addGridToTab(dprox, tab) - - def addDist (self): - self.ndist += 1 - ddist = _get_dist_dict(self.ndist) - self.ld.append(ddist) - self.addtransvarfromdict(ddist) - tab = self.addTab('evdist_' + str(self.ndist)) - self.addGridToTab(ddist, tab) - - def changeParamEnabledStatus(self, label, toEnable): - import re - - label_match = re.search('(evprox|evdist)_([0-9]+)', label) - if label_match: - my_input_name = label_match.group(1) + '_' + label_match.group(2) - else: - print("ERR: can't determine input name from parameter: %s" % label) - return - - # decrease the count of num params - for chunk_index in range(self.old_num_steps): - for input_name in self.chunk_list[chunk_index]['inputs']: - if input_name == my_input_name: - try: - num_params = int(self.lqnumparams[chunk_index].text()) - except ValueError: - print("ERR: could not get number of params for step %d"%chunk_index) - - if toEnable: - num_params += 1 - else: - num_params -= 1 - self.lqnumparams[chunk_index].setText(str(num_params)) - self.opt_params[input_name]['ranges'][label]['enabled'] = toEnable - - def updateRange(self, label, save_slider=True): - import re - - max_width = 0 - - label_match = re.search('(evprox|evdist)_([0-9]+)', label) - if label_match: - tab_name = label_match.group(1) + '_' + label_match.group(2) - else: - print("ERR: can't determine input name from parameter: %s" % label) - return - - if self.dqchkbox[label].isChecked(): - self.opt_params[tab_name]['ranges'][label]['enabled'] = True - else: - self.opt_params[tab_name]['ranges'][label]['enabled'] = False - return - - if tab_name not in self.initial_opt_ranges or \ - label not in self.initial_opt_ranges[tab_name]: - value = self.dparams[label] - else: - value = float(self.initial_opt_ranges[tab_name][label]['initial']) - - range_type = self.dqrange_mode[label].text() - if range_type == "(%)" and value == 0.0: - # change to range from 0 to 1 - range_type = "(max)" - self.dqrange_mode[label].setText(range_type) - self.dqrange_multiplier[label].setText("1.0") - elif range_type == "(max)" and value > 0.0: - # change back to % - range_type = "(%)" - self.dqrange_mode[label].setText(range_type) - self.dqrange_multiplier[label].setText("500.0") - - try: - range_multiplier = float(self.dqrange_multiplier[label].text()) - except ValueError: - range_multiplier = 0.0 - self.dqrange_multiplier[label].setText(str(range_multiplier)) - - if range_type == "(max)": - range_min = 0 - try: - range_max = float(self.dqrange_multiplier[label].text()) - except ValueError: - range_max = 1.0 - elif range_type == "(stdev)": # timing - timing_sigma = self.get_input_timing_sigma(tab_name) - timing_bound = timing_sigma * range_multiplier - range_min = max(0, value - timing_bound) - range_max = min(self.simlength, value + timing_bound) - else: # range_type == "(%)" - range_min = max(0, value - (value * range_multiplier / 100.0)) - range_max = value + (value * range_multiplier / 100.0) - - # set up the slider - self.dqrange_slider[label].setLine(value) - self.dqrange_slider[label].setMin(range_min) - self.dqrange_slider[label].setMax(range_max) - - if not save_slider: - self.dqrange_min.pop(label, None) - self.dqrange_max.pop(label, None) - - self.opt_params[tab_name]['ranges'][label]['initial'] = value - if label in self.dqrange_min and label in self.dqrange_max: - range_min = self.dqrange_min[label] - range_max = self.dqrange_max[label] - - self.opt_params[tab_name]['ranges'][label]['minval'] = range_min - self.opt_params[tab_name]['ranges'][label]['maxval'] = range_max - self.dqrange_slider[label].setRange(range_min, range_max) - - if range_min == range_max: - self.dqrange_label[label].setText(_format_range_str(range_min)) # use the exact value - self.dqrange_label[label].setEnabled(False) - # uncheck because invalid range - self.dqchkbox[label].setChecked(False) - # disable slider - self.dqrange_slider[label].setEnabled(False) - self.changeParamEnabledStatus(label, False) - else: - self.dqrange_label[label].setText(_format_range_str(range_min) + - " - " + - _format_range_str(range_max)) - - if self.dqrange_label[label].sizeHint().width() > max_width: - max_width = self.dqrange_label[label].sizeHint().width() + 15 - # fix the size for the defined range so that changing the slider doesn't change - # the dialog's width - self.dqrange_label[label].setMinimumWidth(max_width) - self.dqrange_label[label].setMaximumWidth(max_width) - - def prepareOptimization(self): - self.createOptParams() - self.rebuildOptStepInfo() - self.updateOptDeltas() - self.updateOptRanges(save_sliders=True) - self.btnreset.setEnabled(True) - self.btnrunop.setText('Run Optimization') - self.btnrunop.clicked.disconnect() - self.btnrunop.clicked.connect(self.runOptimization) - - def runOptimization(self): - self.current_opt_step = 0 - - # update the ranges to find which parameters have been disabled (unchecked) - self.updateOptRanges(save_sliders=True) - - # update the opt info dict to capture num_sims from GUI - self.rebuildOptStepInfo() - self.optimization_running = True - self.populate_initial_opt_ranges() - - # run the actual optimization - num_steps = self.get_num_chunks() - self.mainwin.startoptmodel(num_steps) - - def get_chunk_start(self, step): - return self.chunk_list[step]['opt_start'] - - def get_chunk_end(self, step): - return self.chunk_list[step]['opt_end'] - - def get_chunk_weights(self, step): - return self.chunk_list[step]['weights'] - - def get_num_chunks(self): - return len(self.chunk_list) - - def get_sims_for_chunk(self, step): - try: - num_sims = int(self.lqnumsim[step].text()) - except KeyError: - print("ERR: number of sims not found for step %d"%step) - num_sims = 0 - except ValueError: - if step == self.old_num_steps - 1: - num_sims = self.default_num_total_sims - else: - num_sims = self.default_num_step_sims - - return num_sims - - def get_chunk_ranges(self, step): - ranges = {} - for input_name in self.chunk_list[step]['inputs']: - # make sure initial value is between minval or maxval before returning - # ranges to the optimization - for label in self.opt_params[input_name]['ranges'].keys(): - if not self.opt_params[input_name]['ranges'][label]['enabled']: - continue - range_min = self.opt_params[input_name]['ranges'][label]['minval'] - range_max = self.opt_params[input_name]['ranges'][label]['maxval'] - if range_min > self.opt_params[input_name]['ranges'][label]['initial']: - self.opt_params[input_name]['ranges'][label]['initial'] = range_min - if range_max < self.opt_params[input_name]['ranges'][label]['initial']: - self.opt_params[input_name]['ranges'][label]['initial'] = range_max - - # copy the values to the ranges dict to be returned - # to optimization - ranges[label] = self.opt_params[input_name]['ranges'][label].copy() - - return ranges - - def get_initial_params(self): - initial_params = {} - for input_name in self.opt_params.keys(): - for label in self.opt_params[input_name]['ranges'].keys(): - initial_params[label] = \ - self.opt_params[input_name]['ranges'][label]['initial'] - - return initial_params - - def get_num_params(self, step): - num_params = 0 - - for input_name in self.chunk_list[step]['inputs']: - for label in self.opt_params[input_name]['ranges'].keys(): - if not self.opt_params[input_name]['ranges'][label]['enabled']: - continue + self.dqchkbox[k].setChecked(True) + # use partial instead of lamda (so args won't be evaluated ahead of time?) + self.dqchkbox[k].clicked.connect( + partial(self.toggle_enable_param, k)) + self.dqparam_name[k] = QLabel(self) + self.dqparam_name[k].setText(self.transvar(k)) + self.dqinitial_label[k] = QLabel() + self.dqopt_label[k] = QLabel() + self.dqdiff_label[k] = QLabel() + + # add widgets to grid + tab.layout.addWidget( + self.dqchkbox[k], row, 0, alignment=Qt.AlignBaseline | Qt.AlignCenter) + tab.layout.addWidget(self.dqparam_name[k], row, 1) + tab.layout.addWidget( + self.dqinitial_label[k], row, 2) # initial value + tab.layout.addWidget( + self.dqopt_label[k], row, 3) # optimized value + tab.layout.addWidget(self.dqdiff_label[k], row, 4) # delta + + if k.startswith('t'): + range_mode = "(stdev)" + range_multiplier = "3.0" + elif k.startswith('sigma'): + range_mode = "(%)" + range_multiplier = "50.0" + else: + range_mode = "(%)" + range_multiplier = "500.0" + + if not self.optimization_running: + self.dqrange_slider[k] = QRangeSlider(k, self) + self.dqrange_slider[k].setMinimumWidth(140) + self.dqrange_label[k] = QLabel() + self.dqrange_multiplier[k] = MyLineEdit(range_multiplier, k) + self.dqrange_multiplier[k].textModified.connect( + self.updateRange) + self.dqrange_multiplier[k].setSizePolicy( + QSizePolicy.Ignored, QSizePolicy.Preferred) + self.dqrange_multiplier[k].setMinimumWidth(50) + self.dqrange_multiplier[k].setMaximumWidth(50) + self.dqrange_mode[k] = QLabel(range_mode) + tab.layout.addWidget( + self.dqrange_multiplier[k], row, 5) # range specifier + tab.layout.addWidget( + self.dqrange_mode[k], row, 6) # range mode + tab.layout.addWidget( + self.dqrange_slider[k], row, 7) # range slider + # calculated range + tab.layout.addWidget(self.dqrange_label[k], row, 8) + + row += 1 + + # A spacer in the last row stretches to fill remaining space. + # For inputs with fewer parameters than the rest, this pushes parameters + # to the top with the same spacing as the other inputs. + tab.layout.addItem(QSpacerItem(0, 0), row, 0, 1, 9) + tab.layout.setRowStretch(row, 1) + tab.setLayout(tab.layout) + + def addProx(self): + self.nprox += 1 + dprox = _get_prox_dict(self.nprox) + self.ld.append(dprox) + self.addtransvarfromdict(dprox) + tab = self.addTab('evprox_' + str(self.nprox)) + self.addGridToTab(dprox, tab) + + def addDist(self): + self.ndist += 1 + ddist = _get_dist_dict(self.ndist) + self.ld.append(ddist) + self.addtransvarfromdict(ddist) + tab = self.addTab('evdist_' + str(self.ndist)) + self.addGridToTab(ddist, tab) + + def changeParamEnabledStatus(self, label, toEnable): + import re + + label_match = re.search('(evprox|evdist)_([0-9]+)', label) + if label_match: + my_input_name = label_match.group(1) + '_' + label_match.group(2) else: - num_params += 1 - - return num_params - - def push_chunk_ranges(self, ranges): - for label, value in ranges.items(): - for tab_name in self.opt_params.keys(): - if label in self.opt_params[tab_name]['ranges']: - self.opt_params[tab_name]['ranges'][label]['initial'] = float(value) - - def clean_opt_grid(self): - # This is the top part of the Configure Optimization dialog. + print("ERR: can't determine input name from parameter: %s" % label) + return - column_count = self.sublayout.columnCount() - row = 0 - while True: - try: - self.sublayout.itemAtPosition(row,0).widget() - except AttributeError: - # no more rows - break + # decrease the count of num params + for chunk_index in range(self.old_num_steps): + for input_name in self.chunk_list[chunk_index]['inputs']: + if input_name == my_input_name: + try: + num_params = int(self.lqnumparams[chunk_index].text()) + except ValueError: + print( + "ERR: could not get number of params for step %d" % chunk_index) + + if toEnable: + num_params += 1 + else: + num_params -= 1 + self.lqnumparams[chunk_index].setText(str(num_params)) + self.opt_params[input_name]['ranges'][label]['enabled'] = toEnable + + def updateRange(self, label, save_slider=True): + import re + + max_width = 0 + + label_match = re.search('(evprox|evdist)_([0-9]+)', label) + if label_match: + tab_name = label_match.group(1) + '_' + label_match.group(2) + else: + print("ERR: can't determine input name from parameter: %s" % label) + return - for column in range(column_count): - try: - # Use deleteLater() to avoid memory leaks. - self.sublayout.itemAtPosition(row, column).widget().deleteLater() - except AttributeError: - # if item wasn't found - pass - row += 1 - - # reset data for number of sims per chunk (step) - self.lqnumsim = [] - self.lqnumparams = [] - self.lqinputs = [] - self.old_num_steps = 0 - - def rebuildOptStepInfo(self): - # split chunks from paramter file - self.chunk_list = _chunk_evinputs(self.opt_params, self.simlength, self.sim_dt) - - if len(self.chunk_list) == 0: - self.clean_opt_grid() - - qlabel = QLabel("No valid evoked inputs to optimize!") - qlabel.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - qlabel.resize(qlabel.minimumSizeHint()) - self.sublayout.addWidget(qlabel, 0, 0) - self.btnrunop.setEnabled(False) - self.btnreset.setEnabled(False) - else: - self.btnrunop.setEnabled(True) - self.btnreset.setEnabled(True) - - if len(self.chunk_list) < self.old_num_steps or \ - self.old_num_steps == 0: - # clean up the old grid sublayout - self.clean_opt_grid() - - # keep track of inputs to optimize over (check against self.opt_params later) - all_inputs = [] - - # create a new grid sublayout with a row for each optimization step - for chunk_index, chunk in enumerate(self.chunk_list): - chunk['num_params'] = self.get_num_params(chunk_index) - - inputs = [] - for input_name in chunk['inputs']: - all_inputs.append(input_name) - inputs.append(_trans_input(input_name)) - - if chunk_index >= self.old_num_steps: - qlabel = QLabel("Optimization step %d:"%(chunk_index+1)) - qlabel.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - qlabel.resize(qlabel.minimumSizeHint()) - self.sublayout.addWidget(qlabel,chunk_index, 0) - - self.lqinputs.append(QLabel("Inputs: %s"%', '.join(inputs))) - self.lqinputs[chunk_index].setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - self.lqinputs[chunk_index].resize(self.lqinputs[chunk_index].minimumSizeHint()) - self.sublayout.addWidget(self.lqinputs[chunk_index], chunk_index, 1) - - # spacer here for readability of input names and reduce size - # of "Num simulations:" - self.sublayout.addItem(QSpacerItem(0, 0, hPolicy = QSizePolicy.MinimumExpanding), chunk_index, 2) - - qlabel_params = QLabel("Num params:") - qlabel_params.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - qlabel_params.resize(qlabel_params.minimumSizeHint()) - self.sublayout.addWidget(qlabel_params,chunk_index, 3) - - self.lqnumparams.append(QLabel(str(chunk['num_params']))) - self.lqnumparams[chunk_index].setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - self.lqnumparams[chunk_index].resize(self.lqnumparams[chunk_index].minimumSizeHint()) - self.sublayout.addWidget(self.lqnumparams[chunk_index],chunk_index, 4) - - qlabel_sims = QLabel("Num simulations:") - qlabel_sims.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) - qlabel_sims.resize(qlabel_sims.minimumSizeHint()) - self.sublayout.addWidget(qlabel_sims,chunk_index, 5) - - if chunk_index == len(self.chunk_list) - 1: - chunk['num_sims'] = self.default_num_total_sims + if self.dqchkbox[label].isChecked(): + self.opt_params[tab_name]['ranges'][label]['enabled'] = True else: - chunk['num_sims'] = self.default_num_step_sims - self.lqnumsim.append(QLineEdit(str(chunk['num_sims']))) - self.lqnumsim[chunk_index].resize( - self.lqnumsim[chunk_index].minimumSizeHint()) - self.sublayout.addWidget(self.lqnumsim[chunk_index], - chunk_index, 6) - else: - self.lqinputs[chunk_index].setText("Inputs: %s"%', '.join(inputs)) - self.lqnumparams[chunk_index].setText(str(chunk['num_params'])) - - self.old_num_steps = len(self.chunk_list) - - remove_list = [] - # remove a tab if necessary - for input_name in self.opt_params.keys(): - if input_name not in all_inputs and input_name in self.dtab_idx: - remove_list.append(input_name) - - while len(remove_list) > 0: - tab_name = remove_list.pop() - tab_index = self.dtab_idx[tab_name] - - self.removeInput(tab_index) - del self.dtab_idx[tab_name] - del self.dtab_names[tab_index] - self.ltabkeys.pop(tab_index) - - # rebuild dtab_idx and dtab_names - temp_dtab_names = {} - temp_dtab_idx = {} - for new_tab_index, old_tab_index in enumerate(self.dtab_idx.values()): - # self.dtab_idx[id_str] = tab_index - id_str = self.dtab_names[old_tab_index] - temp_dtab_names[new_tab_index] = id_str - temp_dtab_idx[id_str] = new_tab_index - self.dtab_names = temp_dtab_names - self.dtab_idx = temp_dtab_idx - - def toggle_enable_user_fields(self, step, enable=True): - if not enable: - # the optimization called this to disable parameters on - # for the step passed in to this function - self.current_opt_step = step - - for input_name in self.chunk_list[step]['inputs']: - tab_index = self.dtab_idx[input_name] - tab = self.ltabs[tab_index] - - for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer - label = self.ltabkeys[tab_index][row_index] - self.dqchkbox[label].setEnabled(enable) - self.dqrange_slider[label].setEnabled(enable) - self.dqrange_multiplier[label].setEnabled(enable) - - def get_input_timing_sigma(self, tab_name): - """ get timing_sigma from already loaded values """ - - label = 'sigma_t_' + tab_name - try: - timing_sigma = self.dparams[label] - except KeyError: - timing_sigma = 3.0 - print("ERR: Couldn't fing %s. Using default %f" % - (label,timing_sigma)) - - if timing_sigma == 0.0: - # sigma of 0 will not produce a CDF - timing_sigma = 0.01 - - return timing_sigma - - def createOptParams(self): - global decay_multiplier - - self.opt_params = {} - - # iterate through tabs. data is contained in grid layout - for tab_index, tab in enumerate(self.ltabs): - tab_name = self.dtab_names[tab_index] - - # before optimization has started update 'mean', 'sigma', - # 'start', and 'user_end' - start_time_label = 't_' + tab_name - try: - try: - range_multiplier = float(self.dqrange_multiplier[start_time_label].text()) - except ValueError: - range_multiplier = 0.0 - value = self.dparams[start_time_label] - except KeyError: - print("ERR: could not find start time parameter: %s" % start_time_label) - continue - - timing_sigma = self.get_input_timing_sigma(tab_name) - self.opt_params[tab_name] = {'ranges': {}, - 'mean' : value, - 'sigma': timing_sigma, - 'decay_multiplier': decay_multiplier} - - timing_bound = timing_sigma * range_multiplier - self.opt_params[tab_name]['user_start'] = max(0, value - timing_bound) - self.opt_params[tab_name]['user_end'] = min(self.simlength, value + timing_bound) - - # add an empty dictionary so that rebuildOptStepInfo() can determine - # how many parameters - for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer - label = self.ltabkeys[tab_index][row_index] - self.opt_params[tab_name]['ranges'][label] = {'enabled': True} - - def clear_initial_opt_ranges(self): - self.initial_opt_ranges = {} - - def populate_initial_opt_ranges(self): - self.initial_opt_ranges = {} - - for input_name in self.opt_params.keys(): - self.initial_opt_ranges[input_name] = deepcopy(self.opt_params[input_name]['ranges']) - - def updateOptDeltas(self): - # iterate through tabs. data is contained in grid layout - for tab_index, tab in enumerate(self.ltabs): - tab_name = self.dtab_names[tab_index] - - # update the initial value - for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer - label = self.ltabkeys[tab_index][row_index] - value = self.dparams[label] - - # Calculate value to put in "Delta" column. When possible, use - # percentages, but when initial value is 0, use absolute changes + self.opt_params[tab_name]['ranges'][label]['enabled'] = False + return + if tab_name not in self.initial_opt_ranges or \ - not self.dqchkbox[label].isChecked(): - self.dqdiff_label[label].setEnabled(False) - self.dqinitial_label[label].setText(("%6f"%self.dparams[label]).rstrip('0').rstrip('.')) - text = '--' - color_fmt = "QLabel { color : black; }" - self.dqopt_label[label].setText(text) - self.dqopt_label[label].setStyleSheet(color_fmt) - self.dqopt_label[label].setAlignment(Qt.AlignHCenter) - self.dqdiff_label[label].setAlignment(Qt.AlignHCenter) + label not in self.initial_opt_ranges[tab_name]: + value = self.dparams[label] else: - initial_value = float(self.initial_opt_ranges[tab_name][label]['initial']) - self.dqinitial_label[label].setText(("%6f"%initial_value).rstrip('0').rstrip('.')) - self.dqopt_label[label].setText(("%6f"%self.dparams[label]).rstrip('0').rstrip('.')) - self.dqopt_label[label].setAlignment(Qt.AlignVCenter|Qt.AlignLeft) - self.dqdiff_label[label].setAlignment(Qt.AlignVCenter|Qt.AlignLeft) - - if isclose(value, initial_value, abs_tol=1e-7): - diff = 0 - text = "0.0" - color_fmt = "QLabel { color : black; }" - else: - diff = value - initial_value - - if initial_value == 0: - # can't calculate % - if diff < 0: - text = ("%6f"%diff).rstrip('0').rstrip('.') - color_fmt = "QLabel { color : red; }" - elif diff > 0: - text = ("+%6f"%diff).rstrip('0').rstrip('.') - color_fmt = "QLabel { color : green; }" - else: - # calculate percent difference - percent_diff = 100 * diff/abs(initial_value) - if percent_diff < 0: - text = ("%2.2f %%"%percent_diff) - color_fmt = "QLabel { color : red; }" - elif percent_diff > 0: - text = ("+%2.2f %%"%percent_diff) - color_fmt = "QLabel { color : green; }" - - self.dqdiff_label[label].setStyleSheet(color_fmt) - self.dqdiff_label[label].setText(text) - - def updateRangeFromSlider(self, label, range_min, range_max): - import re + value = float(self.initial_opt_ranges[tab_name][label]['initial']) + + range_type = self.dqrange_mode[label].text() + if range_type == "(%)" and value == 0.0: + # change to range from 0 to 1 + range_type = "(max)" + self.dqrange_mode[label].setText(range_type) + self.dqrange_multiplier[label].setText("1.0") + elif range_type == "(max)" and value > 0.0: + # change back to % + range_type = "(%)" + self.dqrange_mode[label].setText(range_type) + self.dqrange_multiplier[label].setText("500.0") - label_match = re.search('(evprox|evdist)_([0-9]+)', label) - if label_match: - tab_name = label_match.group(1) + '_' + label_match.group(2) - else: - print("ERR: can't determine input name from parameter: %s" % label) - return - - self.dqrange_min[label] = range_min - self.dqrange_max[label] = range_max - self.dqrange_label[label].setText(_format_range_str(range_min) + " - " + - _format_range_str(range_max)) - self.opt_params[tab_name]['ranges'][label]['minval'] = range_min - self.opt_params[tab_name]['ranges'][label]['maxval'] = range_max - - def updateOptRanges(self, save_sliders=False): - # iterate through tabs. data is contained in grid layout - for tab_index, tab in enumerate(self.ltabs): - # now update the ranges - for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer - label = self.ltabkeys[tab_index][row_index] - self.updateRange(label, save_sliders) - - def setfromdin (self,din): - if not din: - return - - if 'dt' in din: - # din proivdes a complete parameter set - self.din = din - self.simlength = float(din['tstop']) - self.sim_dt = float(din['dt']) - - self.cleanLabels() - self.removeAllInputs() # turn off any previously set inputs - self.ltabkeys = [] - self.dtab_idx = {} - self.dtab_names = {} - - for evinput in _get_param_inputs(din): - if 'evprox_' in evinput: - self.addProx() - elif 'evdist_' in evinput: - self.addDist() - - for k,v in din.items(): - if k in self.dparams: try: - new_value = float(v) + range_multiplier = float(self.dqrange_multiplier[label].text()) except ValueError: - print("WARN: bad value for param %s: %s. Unable to convert" - " to a floating point number" % (k,v)) - continue - self.dparams[k] = new_value - elif k.count('gbar') > 0 and \ - (k.count('evprox') > 0 or \ - k.count('evdist') > 0): - # NOTE: will be deprecated in future release - # for back-compat with old-style specification which didn't have ampa,nmda in evoked gbar + range_multiplier = 0.0 + self.dqrange_multiplier[label].setText(str(range_multiplier)) + + if range_type == "(max)": + range_min = 0 + try: + range_max = float(self.dqrange_multiplier[label].text()) + except ValueError: + range_max = 1.0 + elif range_type == "(stdev)": # timing + timing_sigma = self.get_input_timing_sigma(tab_name) + timing_bound = timing_sigma * range_multiplier + range_min = max(0, value - timing_bound) + range_max = min(self.simlength, value + timing_bound) + else: # range_type == "(%)" + range_min = max(0, value - (value * range_multiplier / 100.0)) + range_max = value + (value * range_multiplier / 100.0) + + # set up the slider + self.dqrange_slider[label].setLine(value) + self.dqrange_slider[label].setMin(range_min) + self.dqrange_slider[label].setMax(range_max) + + if not save_slider: + self.dqrange_min.pop(label, None) + self.dqrange_max.pop(label, None) + + self.opt_params[tab_name]['ranges'][label]['initial'] = value + if label in self.dqrange_min and label in self.dqrange_max: + range_min = self.dqrange_min[label] + range_max = self.dqrange_max[label] + + self.opt_params[tab_name]['ranges'][label]['minval'] = range_min + self.opt_params[tab_name]['ranges'][label]['maxval'] = range_max + self.dqrange_slider[label].setRange(range_min, range_max) + + if range_min == range_max: + self.dqrange_label[label].setText( + _format_range_str(range_min)) # use the exact value + self.dqrange_label[label].setEnabled(False) + # uncheck because invalid range + self.dqchkbox[label].setChecked(False) + # disable slider + self.dqrange_slider[label].setEnabled(False) + self.changeParamEnabledStatus(label, False) + else: + self.dqrange_label[label].setText(_format_range_str(range_min) + + " - " + + _format_range_str(range_max)) + + if self.dqrange_label[label].sizeHint().width() > max_width: + max_width = self.dqrange_label[label].sizeHint().width() + 15 + # fix the size for the defined range so that changing the slider doesn't change + # the dialog's width + self.dqrange_label[label].setMinimumWidth(max_width) + self.dqrange_label[label].setMaximumWidth(max_width) + + def prepareOptimization(self): + self.createOptParams() + self.rebuildOptStepInfo() + self.updateOptDeltas() + self.updateOptRanges(save_sliders=True) + self.btnreset.setEnabled(True) + self.btnrunop.setText('Run Optimization') + self.btnrunop.clicked.disconnect() + self.btnrunop.clicked.connect(self.runOptimization) + + def runOptimization(self): + self.current_opt_step = 0 + + # update the ranges to find which parameters have been disabled (unchecked) + self.updateOptRanges(save_sliders=True) + + # update the opt info dict to capture num_sims from GUI + self.rebuildOptStepInfo() + self.optimization_running = True + self.populate_initial_opt_ranges() + + # run the actual optimization + num_steps = self.get_num_chunks() + self.mainwin.startoptmodel(num_steps) + + def get_chunk_start(self, step): + return self.chunk_list[step]['opt_start'] + + def get_chunk_end(self, step): + return self.chunk_list[step]['opt_end'] + + def get_chunk_weights(self, step): + return self.chunk_list[step]['weights'] + + def get_num_chunks(self): + return len(self.chunk_list) + + def get_sims_for_chunk(self, step): try: - new_value = float(v) + num_sims = int(self.lqnumsim[step].text()) + except KeyError: + print("ERR: number of sims not found for step %d" % step) + num_sims = 0 except ValueError: - print("WARN: bad value for param %s: %s. Unable to convert" - " to a floating point number" % (k,v)) - continue - lks = k.split('_') - eloc = lks[1] - enum = lks[2] - base_key_str = 'gbar_' + eloc + '_' + enum + '_' - if eloc == 'evprox': - for ct in ['L2Pyr','L2Basket','L5Pyr','L5Basket']: - # ORIGINAL MODEL/PARAM: only ampa for prox evoked inputs - key_str = base_key_str + ct + '_ampa' - self.dparams[key_str] = new_value - elif eloc == 'evdist': - for ct in ['L2Pyr','L2Basket','L5Pyr']: - # ORIGINAL MODEL/PARAM: both ampa and nmda for distal evoked inputs - key_str = base_key_str + ct + '_ampa' - self.dparams[key_str] = new_value - key_str = base_key_str + ct + '_nmda' - self.dparams[key_str] = new_value - - if not self.optimization_running: - self.createOptParams() - self.rebuildOptStepInfo() - self.updateOptRanges(save_sliders=True) - - self.updateOptDeltas() - - def __str__ (self): - # don't write any values to param file - return '' + if step == self.old_num_steps - 1: + num_sims = self.default_num_total_sims + else: + num_sims = self.default_num_step_sims + + return num_sims + + def get_chunk_ranges(self, step): + ranges = {} + for input_name in self.chunk_list[step]['inputs']: + # make sure initial value is between minval or maxval before returning + # ranges to the optimization + for label in self.opt_params[input_name]['ranges'].keys(): + if not self.opt_params[input_name]['ranges'][label]['enabled']: + continue + range_min = self.opt_params[input_name]['ranges'][label]['minval'] + range_max = self.opt_params[input_name]['ranges'][label]['maxval'] + if range_min > self.opt_params[input_name]['ranges'][label]['initial']: + self.opt_params[input_name]['ranges'][label]['initial'] = range_min + if range_max < self.opt_params[input_name]['ranges'][label]['initial']: + self.opt_params[input_name]['ranges'][label]['initial'] = range_max + + # copy the values to the ranges dict to be returned + # to optimization + ranges[label] = self.opt_params[input_name]['ranges'][label].copy() + + return ranges + + def get_initial_params(self): + initial_params = {} + for input_name in self.opt_params.keys(): + for label in self.opt_params[input_name]['ranges'].keys(): + initial_params[label] = \ + self.opt_params[input_name]['ranges'][label]['initial'] + + return initial_params + + def get_num_params(self, step): + num_params = 0 + + for input_name in self.chunk_list[step]['inputs']: + for label in self.opt_params[input_name]['ranges'].keys(): + if not self.opt_params[input_name]['ranges'][label]['enabled']: + continue + else: + num_params += 1 + + return num_params + + def push_chunk_ranges(self, ranges): + for label, value in ranges.items(): + for tab_name in self.opt_params.keys(): + if label in self.opt_params[tab_name]['ranges']: + self.opt_params[tab_name]['ranges'][label]['initial'] = float( + value) + + def clean_opt_grid(self): + # This is the top part of the Configure Optimization dialog. + + column_count = self.sublayout.columnCount() + row = 0 + while True: + try: + self.sublayout.itemAtPosition(row, 0).widget() + except AttributeError: + # no more rows + break + + for column in range(column_count): + try: + # Use deleteLater() to avoid memory leaks. + self.sublayout.itemAtPosition( + row, column).widget().deleteLater() + except AttributeError: + # if item wasn't found + pass + row += 1 + + # reset data for number of sims per chunk (step) + self.lqnumsim = [] + self.lqnumparams = [] + self.lqinputs = [] + self.old_num_steps = 0 + + def rebuildOptStepInfo(self): + # split chunks from paramter file + self.chunk_list = _chunk_evinputs( + self.opt_params, self.simlength, self.sim_dt) + + if len(self.chunk_list) == 0: + self.clean_opt_grid() + + qlabel = QLabel("No valid evoked inputs to optimize!") + qlabel.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) + qlabel.resize(qlabel.minimumSizeHint()) + self.sublayout.addWidget(qlabel, 0, 0) + self.btnrunop.setEnabled(False) + self.btnreset.setEnabled(False) + else: + self.btnrunop.setEnabled(True) + self.btnreset.setEnabled(True) + + if len(self.chunk_list) < self.old_num_steps or \ + self.old_num_steps == 0: + # clean up the old grid sublayout + self.clean_opt_grid() + + # keep track of inputs to optimize over (check against self.opt_params later) + all_inputs = [] + + # create a new grid sublayout with a row for each optimization step + for chunk_index, chunk in enumerate(self.chunk_list): + chunk['num_params'] = self.get_num_params(chunk_index) + + inputs = [] + for input_name in chunk['inputs']: + all_inputs.append(input_name) + inputs.append(_trans_input(input_name)) + + if chunk_index >= self.old_num_steps: + qlabel = QLabel("Optimization step %d:" % (chunk_index+1)) + qlabel.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) + qlabel.resize(qlabel.minimumSizeHint()) + self.sublayout.addWidget(qlabel, chunk_index, 0) + + self.lqinputs.append(QLabel("Inputs: %s" % ', '.join(inputs))) + self.lqinputs[chunk_index].setAlignment( + Qt.AlignBaseline | Qt.AlignLeft) + self.lqinputs[chunk_index].resize( + self.lqinputs[chunk_index].minimumSizeHint()) + self.sublayout.addWidget( + self.lqinputs[chunk_index], chunk_index, 1) + + # spacer here for readability of input names and reduce size + # of "Num simulations:" + self.sublayout.addItem(QSpacerItem( + 0, 0, hPolicy=QSizePolicy.MinimumExpanding), chunk_index, 2) + + qlabel_params = QLabel("Num params:") + qlabel_params.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) + qlabel_params.resize(qlabel_params.minimumSizeHint()) + self.sublayout.addWidget(qlabel_params, chunk_index, 3) + + self.lqnumparams.append(QLabel(str(chunk['num_params']))) + self.lqnumparams[chunk_index].setAlignment( + Qt.AlignBaseline | Qt.AlignLeft) + self.lqnumparams[chunk_index].resize( + self.lqnumparams[chunk_index].minimumSizeHint()) + self.sublayout.addWidget( + self.lqnumparams[chunk_index], chunk_index, 4) + + qlabel_sims = QLabel("Num simulations:") + qlabel_sims.setAlignment(Qt.AlignBaseline | Qt.AlignLeft) + qlabel_sims.resize(qlabel_sims.minimumSizeHint()) + self.sublayout.addWidget(qlabel_sims, chunk_index, 5) + + if chunk_index == len(self.chunk_list) - 1: + chunk['num_sims'] = self.default_num_total_sims + else: + chunk['num_sims'] = self.default_num_step_sims + self.lqnumsim.append(QLineEdit(str(chunk['num_sims']))) + self.lqnumsim[chunk_index].resize( + self.lqnumsim[chunk_index].minimumSizeHint()) + self.sublayout.addWidget(self.lqnumsim[chunk_index], + chunk_index, 6) + else: + self.lqinputs[chunk_index].setText( + "Inputs: %s" % ', '.join(inputs)) + self.lqnumparams[chunk_index].setText(str(chunk['num_params'])) + + self.old_num_steps = len(self.chunk_list) + + remove_list = [] + # remove a tab if necessary + for input_name in self.opt_params.keys(): + if input_name not in all_inputs and input_name in self.dtab_idx: + remove_list.append(input_name) + + while len(remove_list) > 0: + tab_name = remove_list.pop() + tab_index = self.dtab_idx[tab_name] + + self.removeInput(tab_index) + del self.dtab_idx[tab_name] + del self.dtab_names[tab_index] + self.ltabkeys.pop(tab_index) + + # rebuild dtab_idx and dtab_names + temp_dtab_names = {} + temp_dtab_idx = {} + for new_tab_index, old_tab_index in enumerate(self.dtab_idx.values()): + # self.dtab_idx[id_str] = tab_index + id_str = self.dtab_names[old_tab_index] + temp_dtab_names[new_tab_index] = id_str + temp_dtab_idx[id_str] = new_tab_index + self.dtab_names = temp_dtab_names + self.dtab_idx = temp_dtab_idx + + def toggle_enable_user_fields(self, step, enable=True): + if not enable: + # the optimization called this to disable parameters on + # for the step passed in to this function + self.current_opt_step = step + + for input_name in self.chunk_list[step]['inputs']: + tab_index = self.dtab_idx[input_name] + tab = self.ltabs[tab_index] + + for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer + label = self.ltabkeys[tab_index][row_index] + self.dqchkbox[label].setEnabled(enable) + self.dqrange_slider[label].setEnabled(enable) + self.dqrange_multiplier[label].setEnabled(enable) + + def get_input_timing_sigma(self, tab_name): + """ get timing_sigma from already loaded values """ + + label = 'sigma_t_' + tab_name + try: + timing_sigma = self.dparams[label] + except KeyError: + timing_sigma = 3.0 + print("ERR: Couldn't fing %s. Using default %f" % + (label, timing_sigma)) + + if timing_sigma == 0.0: + # sigma of 0 will not produce a CDF + timing_sigma = 0.01 + + return timing_sigma + + def createOptParams(self): + global decay_multiplier + + self.opt_params = {} + + # iterate through tabs. data is contained in grid layout + for tab_index, tab in enumerate(self.ltabs): + tab_name = self.dtab_names[tab_index] + + # before optimization has started update 'mean', 'sigma', + # 'start', and 'user_end' + start_time_label = 't_' + tab_name + try: + try: + range_multiplier = float( + self.dqrange_multiplier[start_time_label].text()) + except ValueError: + range_multiplier = 0.0 + value = self.dparams[start_time_label] + except KeyError: + print("ERR: could not find start time parameter: %s" % + start_time_label) + continue + + timing_sigma = self.get_input_timing_sigma(tab_name) + self.opt_params[tab_name] = {'ranges': {}, + 'mean': value, + 'sigma': timing_sigma, + 'decay_multiplier': decay_multiplier} + + timing_bound = timing_sigma * range_multiplier + self.opt_params[tab_name]['user_start'] = max( + 0, value - timing_bound) + self.opt_params[tab_name]['user_end'] = min( + self.simlength, value + timing_bound) + + # add an empty dictionary so that rebuildOptStepInfo() can determine + # how many parameters + for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer + label = self.ltabkeys[tab_index][row_index] + self.opt_params[tab_name]['ranges'][label] = {'enabled': True} + + def clear_initial_opt_ranges(self): + self.initial_opt_ranges = {} + + def populate_initial_opt_ranges(self): + self.initial_opt_ranges = {} + + for input_name in self.opt_params.keys(): + self.initial_opt_ranges[input_name] = deepcopy( + self.opt_params[input_name]['ranges']) + + def updateOptDeltas(self): + # iterate through tabs. data is contained in grid layout + for tab_index, tab in enumerate(self.ltabs): + tab_name = self.dtab_names[tab_index] + + # update the initial value + for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer + label = self.ltabkeys[tab_index][row_index] + value = self.dparams[label] + + # Calculate value to put in "Delta" column. When possible, use + # percentages, but when initial value is 0, use absolute changes + if tab_name not in self.initial_opt_ranges or \ + not self.dqchkbox[label].isChecked(): + self.dqdiff_label[label].setEnabled(False) + self.dqinitial_label[label].setText( + ("%6f" % self.dparams[label]).rstrip('0').rstrip('.')) + text = '--' + color_fmt = "QLabel { color : black; }" + self.dqopt_label[label].setText(text) + self.dqopt_label[label].setStyleSheet(color_fmt) + self.dqopt_label[label].setAlignment(Qt.AlignHCenter) + self.dqdiff_label[label].setAlignment(Qt.AlignHCenter) + else: + initial_value = float( + self.initial_opt_ranges[tab_name][label]['initial']) + self.dqinitial_label[label].setText( + ("%6f" % initial_value).rstrip('0').rstrip('.')) + self.dqopt_label[label].setText( + ("%6f" % self.dparams[label]).rstrip('0').rstrip('.')) + self.dqopt_label[label].setAlignment( + Qt.AlignVCenter | Qt.AlignLeft) + self.dqdiff_label[label].setAlignment( + Qt.AlignVCenter | Qt.AlignLeft) + + if isclose(value, initial_value, abs_tol=1e-7): + diff = 0 + text = "0.0" + color_fmt = "QLabel { color : black; }" + else: + diff = value - initial_value + + if initial_value == 0: + # can't calculate % + if diff < 0: + text = ("%6f" % diff).rstrip('0').rstrip('.') + color_fmt = "QLabel { color : red; }" + elif diff > 0: + text = ("+%6f" % diff).rstrip('0').rstrip('.') + color_fmt = "QLabel { color : green; }" + else: + # calculate percent difference + percent_diff = 100 * diff/abs(initial_value) + if percent_diff < 0: + text = ("%2.2f %%" % percent_diff) + color_fmt = "QLabel { color : red; }" + elif percent_diff > 0: + text = ("+%2.2f %%" % percent_diff) + color_fmt = "QLabel { color : green; }" + + self.dqdiff_label[label].setStyleSheet(color_fmt) + self.dqdiff_label[label].setText(text) + + def updateRangeFromSlider(self, label, range_min, range_max): + import re + + label_match = re.search('(evprox|evdist)_([0-9]+)', label) + if label_match: + tab_name = label_match.group(1) + '_' + label_match.group(2) + else: + print("ERR: can't determine input name from parameter: %s" % label) + return + + self.dqrange_min[label] = range_min + self.dqrange_max[label] = range_max + self.dqrange_label[label].setText(_format_range_str(range_min) + " - " + + _format_range_str(range_max)) + self.opt_params[tab_name]['ranges'][label]['minval'] = range_min + self.opt_params[tab_name]['ranges'][label]['maxval'] = range_max + + def updateOptRanges(self, save_sliders=False): + # iterate through tabs. data is contained in grid layout + for tab_index, tab in enumerate(self.ltabs): + # now update the ranges + for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer + label = self.ltabkeys[tab_index][row_index] + self.updateRange(label, save_sliders) + + def setfromdin(self, din): + if not din: + return + + if 'dt' in din: + # din proivdes a complete parameter set + self.din = din + self.simlength = float(din['tstop']) + self.sim_dt = float(din['dt']) + + self.cleanLabels() + self.removeAllInputs() # turn off any previously set inputs + self.ltabkeys = [] + self.dtab_idx = {} + self.dtab_names = {} + + for evinput in _get_param_inputs(din): + if 'evprox_' in evinput: + self.addProx() + elif 'evdist_' in evinput: + self.addDist() + + for k, v in din.items(): + if k in self.dparams: + try: + new_value = float(v) + except ValueError: + print("WARN: bad value for param %s: %s. Unable to convert" + " to a floating point number" % (k, v)) + continue + self.dparams[k] = new_value + elif k.count('gbar') > 0 and \ + (k.count('evprox') > 0 or + k.count('evdist') > 0): + # NOTE: will be deprecated in future release + # for back-compat with old-style specification which didn't have ampa,nmda in evoked gbar + try: + new_value = float(v) + except ValueError: + print("WARN: bad value for param %s: %s. Unable to convert" + " to a floating point number" % (k, v)) + continue + lks = k.split('_') + eloc = lks[1] + enum = lks[2] + base_key_str = 'gbar_' + eloc + '_' + enum + '_' + if eloc == 'evprox': + for ct in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: + # ORIGINAL MODEL/PARAM: only ampa for prox evoked inputs + key_str = base_key_str + ct + '_ampa' + self.dparams[key_str] = new_value + elif eloc == 'evdist': + for ct in ['L2Pyr', 'L2Basket', 'L5Pyr']: + # ORIGINAL MODEL/PARAM: both ampa and nmda for distal evoked inputs + key_str = base_key_str + ct + '_ampa' + self.dparams[key_str] = new_value + key_str = base_key_str + ct + '_nmda' + self.dparams[key_str] = new_value + + if not self.optimization_running: + self.createOptParams() + self.rebuildOptStepInfo() + self.updateOptRanges(save_sliders=True) + + self.updateOptDeltas() + + def __str__(self): + # don't write any values to param file + return '' From 6476f123c03fcf2cc21412586c961b7715df7093 Mon Sep 17 00:00:00 2001 From: Mainak Jas Date: Sat, 20 Mar 2021 00:48:23 -0400 Subject: [PATCH 100/107] More pep8 --- hnn/paramrw.py | 9 ++++----- hnn/qt_evoked.py | 48 ++++++++++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/hnn/paramrw.py b/hnn/paramrw.py index f2cf8666a..657bb54b3 100644 --- a/hnn/paramrw.py +++ b/hnn/paramrw.py @@ -4,10 +4,8 @@ # rev 2016-05-01 (SL: removed dependence on cartesian, updated for python3) # last major: (SL: cleanup of self.p_all) -import re import os import numpy as np -import itertools as it def get_output_dir(): @@ -56,7 +54,7 @@ def get_fname(sim_dir, key, trial=None): 'param': ('param', '.txt'), 'vsoma': ('vsoma', '.pkl')} - if trial == None or key == 'param': + if trial is None or key == 'param': # param file currently identical for all trials fname = os.path.join(sim_dir, datatypes[key][0] + datatypes[key][1]) else: @@ -106,7 +104,7 @@ def usingOngoingInputs(params, lty=['_prox', '_dist']): dpref = {'_prox': 'input_prox_A_', '_dist': 'input_dist_A_'} for postfix in lty: if float(params['t0_input'+postfix]) <= tstop and \ - float(params['tstop_input'+postfix]) >= float(params['t0_input'+postfix]) and \ + float(params['tstop_input'+postfix]) >= float(params['t0_input' + postfix]) and \ float(params['f_input'+postfix]) > 0.: for k in ['weight_L2Pyr_ampa', 'weight_L2Pyr_nmda', 'weight_L5Pyr_ampa', 'weight_L5Pyr_nmda', @@ -118,7 +116,8 @@ def usingOngoingInputs(params, lty=['_prox', '_dist']): return False # return number of evoked inputs (proximal, distal) -# using dictionary d (or if d is a string, first load the dictionary from filename d) +# using dictionary d (or if d is a string, first load the dictionary from +# filename d) def countEvokedInputs(params): diff --git a/hnn/qt_evoked.py b/hnn/qt_evoked.py index e53dc1695..7f9684285 100644 --- a/hnn/qt_evoked.py +++ b/hnn/qt_evoked.py @@ -30,7 +30,7 @@ def _consolidate_chunks(input_dict): consolidated_chunks = [] for one_input in sorted_inputs: - if not 'opt_start' in one_input[1]: + if 'opt_start' not in one_input[1]: continue # extract info from sorted list @@ -98,7 +98,6 @@ def _chunk_evinputs(opt_params, sim_tstop, sim_dt): 'opt_end' """ - import re import scipy.stats as stats from math import ceil, floor @@ -413,9 +412,9 @@ def setfromdin(self, din): if 'dt' in din: - # Optimization feature introduces the case where din just contains optimization - # relevant parameters. In that case, we don't want to remove all inputs, just - # modify existing inputs. + # Optimization feature introduces the case where din just contains + # optimization relevant parameters. In that case, we don't want to + # remove all inputs, just modify existing inputs. self.removeAllInputs() # turn off any previously set inputs nprox, ndist = countEvokedInputs(din) @@ -473,12 +472,14 @@ def setfromdin(self, din): base_key_str = 'gbar_' + eloc + '_' + enum + '_' if eloc == 'evprox': for ct in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: - # ORIGINAL MODEL/PARAM: only ampa for prox evoked inputs + # ORIGINAL MODEL/PARAM: only ampa for prox evoked + # inputs key_str = base_key_str + ct + '_ampa' self.set_qline_float(key_str, v) elif eloc == 'evdist': for ct in ['L2Pyr', 'L2Basket', 'L5Pyr']: - # ORIGINAL MODEL/PARAM: both ampa and nmda for distal evoked inputs + # ORIGINAL MODEL/PARAM: both ampa and nmda for distal + # evoked inputs key_str = base_key_str + ct + '_ampa' self.set_qline_float(key_str, v) key_str = base_key_str + ct + '_nmda' @@ -601,9 +602,9 @@ def addProx(self): self.addFormToTab(dprox, self.addTab('Proximal ' + str(self.nprox))) self.ltabs[-1].layout.addRow( self.makePixLabel(lookupresource('proxfig'))) - #print('index to', len(self.ltabs)-1) + # print('index to', len(self.ltabs)-1) self.tabs.setCurrentIndex(len(self.ltabs)-1) - #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) + # print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) # self.addtips() def addDist(self): @@ -614,9 +615,9 @@ def addDist(self): self.addFormToTab(ddist, self.addTab('Distal ' + str(self.ndist))) self.ltabs[-1].layout.addRow( self.makePixLabel(lookupresource('distfig'))) - #print('index to', len(self.ltabs)-1) + # print('index to', len(self.ltabs)-1) self.tabs.setCurrentIndex(len(self.ltabs)-1) - #print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) + # print('index now', self.tabs.currentIndex(), ' of ', self.tabs.count()) # self.addtips() @@ -709,7 +710,6 @@ def initUI(self): self.resize(self.minimumSizeHint()) def toggle_enable_param(self, label): - import re widget_dict_list = [self.dqinitial_label, self.dqopt_label, self.dqdiff_label, self.dqparam_name, @@ -773,7 +773,6 @@ def cleanLabels(self): def addGridToTab(self, d, tab): from functools import partial - import re current_tab = len(self.ltabs)-1 tab.layout = QGridLayout() @@ -885,8 +884,8 @@ def addGridToTab(self, d, tab): row += 1 # A spacer in the last row stretches to fill remaining space. - # For inputs with fewer parameters than the rest, this pushes parameters - # to the top with the same spacing as the other inputs. + # For inputs with fewer parameters than the rest, this pushes + # parameters to the top with the same spacing as the other inputs. tab.layout.addItem(QSpacerItem(0, 0), row, 0, 1, 9) tab.layout.setRowStretch(row, 1) tab.setLayout(tab.layout) @@ -1043,7 +1042,8 @@ def prepareOptimization(self): def runOptimization(self): self.current_opt_step = 0 - # update the ranges to find which parameters have been disabled (unchecked) + # update the ranges to find which parameters have been disabled + # (unchecked) self.updateOptRanges(save_sliders=True) # update the opt info dict to capture num_sims from GUI @@ -1341,8 +1341,8 @@ def createOptParams(self): self.opt_params[tab_name]['user_end'] = min( self.simlength, value + timing_bound) - # add an empty dictionary so that rebuildOptStepInfo() can determine - # how many parameters + # add an empty dictionary so that rebuildOptStepInfo() can + # determine how many parameters for row_index in range(2, tab.layout.rowCount()-1): # last row is a spacer label = self.ltabkeys[tab_index][row_index] self.opt_params[tab_name]['ranges'][label] = {'enabled': True} @@ -1368,7 +1368,8 @@ def updateOptDeltas(self): value = self.dparams[label] # Calculate value to put in "Delta" column. When possible, use - # percentages, but when initial value is 0, use absolute changes + # percentages, but when initial value is 0, use absolute + # changes if tab_name not in self.initial_opt_ranges or \ not self.dqchkbox[label].isChecked(): self.dqdiff_label[label].setEnabled(False) @@ -1480,7 +1481,8 @@ def setfromdin(self, din): (k.count('evprox') > 0 or k.count('evdist') > 0): # NOTE: will be deprecated in future release - # for back-compat with old-style specification which didn't have ampa,nmda in evoked gbar + # for back-compat with old-style specification which didn't + # have ampa,nmda in evoked gbar try: new_value = float(v) except ValueError: @@ -1493,12 +1495,14 @@ def setfromdin(self, din): base_key_str = 'gbar_' + eloc + '_' + enum + '_' if eloc == 'evprox': for ct in ['L2Pyr', 'L2Basket', 'L5Pyr', 'L5Basket']: - # ORIGINAL MODEL/PARAM: only ampa for prox evoked inputs + # ORIGINAL MODEL/PARAM: only ampa for prox evoked + # inputs key_str = base_key_str + ct + '_ampa' self.dparams[key_str] = new_value elif eloc == 'evdist': for ct in ['L2Pyr', 'L2Basket', 'L5Pyr']: - # ORIGINAL MODEL/PARAM: both ampa and nmda for distal evoked inputs + # ORIGINAL MODEL/PARAM: both ampa and nmda for distal + # evoked inputs key_str = base_key_str + ct + '_ampa' self.dparams[key_str] = new_value key_str = base_key_str + ct + '_nmda' From 0dae3441dcb18e429ba499c1ca33e7e7ecf17e13 Mon Sep 17 00:00:00 2001 From: Mainak Jas Date: Sat, 20 Mar 2021 00:58:30 -0400 Subject: [PATCH 101/107] Exclude qt_evoked.py for now --- hnn/paramrw.py | 3 +-- scripts/run-pytest.sh | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/hnn/paramrw.py b/hnn/paramrw.py index 657bb54b3..3e34f3e39 100644 --- a/hnn/paramrw.py +++ b/hnn/paramrw.py @@ -104,8 +104,7 @@ def usingOngoingInputs(params, lty=['_prox', '_dist']): dpref = {'_prox': 'input_prox_A_', '_dist': 'input_dist_A_'} for postfix in lty: if float(params['t0_input'+postfix]) <= tstop and \ - float(params['tstop_input'+postfix]) >= float(params['t0_input' + postfix]) and \ - float(params['f_input'+postfix]) > 0.: + float(params['tstop_input'+postfix]) >= float(params['t0_input' + postfix]) and float(params['f_input'+postfix]) > 0.: # noqa: E501 for k in ['weight_L2Pyr_ampa', 'weight_L2Pyr_nmda', 'weight_L5Pyr_ampa', 'weight_L5Pyr_nmda', 'weight_inh_ampa', 'weight_inh_nmda']: diff --git a/scripts/run-pytest.sh b/scripts/run-pytest.sh index 61363c118..85c738c16 100755 --- a/scripts/run-pytest.sh +++ b/scripts/run-pytest.sh @@ -9,6 +9,6 @@ fi # first check code style with flake8 echo "Checking code style compliance with flake8..." -flake8 --quiet --count --exclude __init__.py +flake8 --count --exclude __init__.py,qt_evoked.py echo "Running unit tests with pytest..." py.test --cov=. hnn/tests/ From 17581d756c493c1d1c20e65a7edbdece245daaed Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 20 Mar 2021 10:57:28 -0400 Subject: [PATCH 102/107] MAINT: update install scripts --- .travis.yml | 2 -- Makefile | 16 ---------------- installer/aws/aws-build.sh | 5 ++--- installer/brown_ccv/oscar_setup.sh | 2 +- installer/centos/README.md | 2 +- installer/centos/hnn-centos6.sh | 4 +--- installer/centos/hnn-centos7.sh | 2 +- installer/docker/Dockerfile | 7 ++++--- installer/mac/check-post.sh | 11 +---------- installer/mac/check-pre.sh | 12 ------------ installer/ubuntu/hnn-ubuntu.sh | 19 ++++++------------- installer/windows/hnn-windows.ps1 | 4 ++-- scripts/setup-travis-osx.sh | 5 +---- 13 files changed, 20 insertions(+), 71 deletions(-) delete mode 100644 Makefile diff --git a/.travis.yml b/.travis.yml index 5db4ac2fd..e470e8e28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,8 +35,6 @@ matrix: - os: linux dist: focal name: "Ubuntu focal" - env: - - NEURON_VERSION=7.7 apt: sources: - ubuntu-toolchain-r-test diff --git a/Makefile b/Makefile deleted file mode 100644 index 5023c771c..000000000 --- a/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -# Makefile for model - compiles mod files for use by NEURON -# first rev: (SL: created) - -# macros -UNAME := $(shell uname) - -vpath %.mod mod/ - -# make rules -x86_64/special : mod - nrnivmodl $< - -# clean -.PHONY: clean -clean : - rm -f x86_64/* diff --git a/installer/aws/aws-build.sh b/installer/aws/aws-build.sh index b8e2015c5..24291af5d 100644 --- a/installer/aws/aws-build.sh +++ b/installer/aws/aws-build.sh @@ -15,7 +15,7 @@ sudo apt-get install -y git python3-dev python3-pip python3-psutil \ git vim iputils-ping net-tools iproute2 nano sudo \ telnet language-pack-en-base sudo pip3 install pip --upgrade -sudo pip install matplotlib pyqt5 scipy numpy nlopt NEURON +sudo pip install matplotlib pyqt5 nlopt hnn-core echo '# these lines define global session variables for HNN' >> ~/.bashrc echo 'export OMPI_MCA_btl_base_warn_component_unused=0' >> ~/.bashrc @@ -24,8 +24,7 @@ export OMPI_MCA_btl_base_warn_component_unused=0 cd $HOME && \ git clone https://github.com/jonescompneurolab/hnn && \ - cd hnn && \ - make + cd hnn echo '#!/bin/bash' | sudo tee /usr/local/bin/hnn echo 'cd $HOME/hnn' | sudo tee -a /usr/local/bin/hnn diff --git a/installer/brown_ccv/oscar_setup.sh b/installer/brown_ccv/oscar_setup.sh index 685e1423e..1464563b7 100644 --- a/installer/brown_ccv/oscar_setup.sh +++ b/installer/brown_ccv/oscar_setup.sh @@ -9,7 +9,7 @@ cd $HOME/HNN git clone https://github.com/jonescompneurolab/hnn # Install python modules. Ignore the errors -pip3 install --user psutil nlopt NEURON >/dev/null 2>&1 +pip3 install --user psutil nlopt hnn-core >/dev/null 2>&1 # Build HNN cd $HOME/HNN/hnn && \ diff --git a/installer/centos/README.md b/installer/centos/README.md index ce1dbf468..7f452d5d0 100644 --- a/installer/centos/README.md +++ b/installer/centos/README.md @@ -1,6 +1,6 @@ # HNN "Python" install (CentOS) -The script below assumes that it can update OS packages for python and prerequisites for NEURON and HNN. +The script below assumes that it can update OS packages for python and prerequisites for HNN. * CentOS 7: [centos7-installer.sh](centos7-installer.sh) diff --git a/installer/centos/hnn-centos6.sh b/installer/centos/hnn-centos6.sh index 81c2fd827..16ad5867d 100644 --- a/installer/centos/hnn-centos6.sh +++ b/installer/centos/hnn-centos6.sh @@ -16,7 +16,7 @@ sudo yum -y install python34-setuptools sudo easy_install-3.4 pip pip3 install --upgrade matplotlib --user pip3 install --upgrade nlopt scipy --user -pip3 install NEURON +pip3 install hnn-core sudo yum -y install ncurses-devel sudo yum -y install openmpi openmpi-devel sudo yum -y install libXext libXext-devel @@ -27,8 +27,6 @@ sudo PATH=$PATH:/usr/lib64/openmpi/bin pip3 install mpi4py startdir=$(pwd) echo $startdir -pip install NEURON - # move outside of nrn directories cd $startdir diff --git a/installer/centos/hnn-centos7.sh b/installer/centos/hnn-centos7.sh index 2880d5afe..0cbae2bdd 100755 --- a/installer/centos/hnn-centos7.sh +++ b/installer/centos/hnn-centos7.sh @@ -9,7 +9,7 @@ sudo yum -y install automake gcc gcc-c++ flex bison libtool git \ # the system version of pip installs nlopt in the wrong directory sudo pip3 install --upgrade pip -sudo /usr/local/bin/pip3 install NEURON PyOpenGL matplotlib pyqt5 pyqtgraph scipy numpy nlopt +sudo /usr/local/bin/pip3 install hnn-core matplotlib pyqt5 nlopt export PATH=$PATH:/usr/lib64/openmpi/bin diff --git a/installer/docker/Dockerfile b/installer/docker/Dockerfile index 7dbb90f61..f417f381e 100644 --- a/installer/docker/Dockerfile +++ b/installer/docker/Dockerfile @@ -83,15 +83,16 @@ LABEL org.label-schema.build-date=$BUILD_DATE \ org.label-schema.vcs-ref=$VCS_REF \ org.label-schema.schema-version=$VCS_TAG -# install NEURON -RUN sudo pip install NEURON +# install hnn-core +RUN sudo pip install hnn-core # install HNN RUN sudo apt-get update && \ sudo apt-get install --no-install-recommends -y \ make gcc libc6-dev libtinfo-dev libncurses-dev \ libx11-dev libreadline-dev g++ && \ - git clone ${SOURCE_REPO} \ + git clone --single-branch --branch maint/pre-hnn-core \ + ${SOURCE_REPO} \ --depth 1 --single-branch --branch $SOURCE_BRANCH \ $HOME/hnn_source_code && \ cd $HOME/hnn_source_code && \ diff --git a/installer/mac/check-post.sh b/installer/mac/check-post.sh index 6b2f02693..df71702d3 100755 --- a/installer/mac/check-post.sh +++ b/installer/mac/check-post.sh @@ -26,15 +26,6 @@ check_python_version () { echo "Performing post-install checks for HNN" echo "--------------------------------------" - -echo -n "Checking for XQuartz..." -XQUARTZ_VERSION=$(mdls -name kMDItemVersion /Applications/Utilities/XQuartz.app) -if [[ "$?" -eq "0" ]]; then - echo "ok" -else - echo "failed" -fi - CUR_DIR=$(pwd) echo -n "Checking if HNN is compiled..." @@ -211,7 +202,7 @@ else fi PREREQS_INSTALLED=1 -for prereq in "pyqtgraph" "matplotlib" "scipy" "psutil" "numpy" "nlopt" "neuron"; do +for prereq in "matplotlib" "scipy" "psutil" "numpy" "nlopt" "neuron"; do echo -n "Checking Python can import $prereq module..." $PYTHON -c "import $prereq" > /dev/null 2>&1 if [[ "$?" -eq "0" ]]; then diff --git a/installer/mac/check-pre.sh b/installer/mac/check-pre.sh index 327879b9e..1658122a3 100755 --- a/installer/mac/check-pre.sh +++ b/installer/mac/check-pre.sh @@ -30,18 +30,6 @@ check_python_version () { echo "Performing pre-install checks for HNN" echo "--------------------------------------" -echo -n "Checking if XQuartz is installed..." -XQUARTZ_VERSION=$(mdls -name kMDItemVersion /Applications/Utilities/XQuartz.app) -if [[ "$?" -eq "0" ]]; then - echo "ok" - echo "Xquartz version $(echo ${XQUARTZ_VERSION}|cut -d ' ' -f 3) is already installed" - echo "You can skip the XQuartz installation step" - echo -else - echo "failed" - return=2 -fi - echo -n "Checking for existing python..." PYTHON_VERSION= FOUND= diff --git a/installer/ubuntu/hnn-ubuntu.sh b/installer/ubuntu/hnn-ubuntu.sh index 51c6325a1..46b1bb888 100755 --- a/installer/ubuntu/hnn-ubuntu.sh +++ b/installer/ubuntu/hnn-ubuntu.sh @@ -90,8 +90,7 @@ echo "Using python: $PYTHON with pip: $PIP" | tee -a "$LOGFILE" if [[ "$USE_CONDA" -eq 0 ]]; then echo "Downloading python packages for HNN with pip..." | tee -a "$LOGFILE" - $PIP download matplotlib PyOpenGL \ - pyqt5 pyqtgraph scipy numpy nlopt psutil &> "$LOGFILE" & + $PIP download pyqt5 nlopt psutil hnn-core &> "$LOGFILE" & PIP_PID=$! fi @@ -105,20 +104,15 @@ if [[ "$USE_CONDA" -eq 0 ]]; then NAME="downloading python packages for HNN " wait_for_pid "${PIP_PID}" "$NAME" - $PIP install --no-cache-dir NEURON + # install hnn-core and prerequisites (e.g. NEURON) + echo "Installing python prequisites for HNN with pip..." | tee -a "$LOGFILE" + $PIP install --no-cache-dir pyqt5 nlopt psutil hnn-core &> "$LOGFILE" # WSL may not have nrnivmodl in PATH if ! which nrnivmodl &> /dev/null; then export PATH="$PATH:$HOME/.local/bin" echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc fi - - echo "Installing python packages for HNN with pip..." | tee -a "$LOGFILE" - $PIP install --no-cache-dir --user matplotlib PyOpenGL \ - pyqt5 pyqtgraph scipy numpy nlopt psutil &> "$LOGFILE" - - pip install \ - https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master else URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh" FILENAME="$HOME/miniconda.sh" @@ -142,9 +136,8 @@ else sudo -E apt-get install --no-install-recommends -y \ libopenmpi-dev &> "$LOGFILE" - pip install mpi4py NEURON - pip install \ - https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master + # install hnn-core and prerequisites (e.g. NEURON) + pip install mpi4py pyqt5 hnn-core fi echo "Downloading runtime prerequisite packages..." | tee -a "$LOGFILE" diff --git a/installer/windows/hnn-windows.ps1 b/installer/windows/hnn-windows.ps1 index c4a6bea6f..c8759ffe8 100644 --- a/installer/windows/hnn-windows.ps1 +++ b/installer/windows/hnn-windows.ps1 @@ -399,7 +399,7 @@ if ($null -ne $script:VIRTUALENV) { # use pip3 for good measure Start-Process "$HOME\venv\hnn\Scripts\pip3" "install matplotlib scipy PyQt5 psutil nlopt" -Wait # get hnn-core, but skip NEURON dependency - Start-Process "$HOME\venv\hnn\Scripts\pip3" "install --no-deps mpi4py https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master" -Wait + Start-Process "$HOME\venv\hnn\Scripts\pip3" "install --no-deps mpi4py hnn-core" -Wait } else { Write-Warning "Virtualenv failed to create a valid python3 environment" @@ -430,7 +430,7 @@ elseif ($null -ne $script:CONDA_PATH) { # need to call the right pip to install in miniconda environment # get hnn-core, but skip NEURON dependency Set-Location $HOME - Miniconda3\envs\hnn\Scripts\pip install --no-deps mpi4py https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master + Miniconda3\envs\hnn\Scripts\pip install --no-deps mpi4py hnn-core Set-Location $CONDA_ENV mkdir .\etc\conda\activate.d 2>&1>$null diff --git a/scripts/setup-travis-osx.sh b/scripts/setup-travis-osx.sh index fe376002a..fc3b123b5 100755 --- a/scripts/setup-travis-osx.sh +++ b/scripts/setup-travis-osx.sh @@ -21,7 +21,4 @@ conda install -y -n hnn openmpi mpi4py conda install -y -n hnn -c conda-forge nlopt conda activate hnn -# NEURON needs to be installed first (not in the same command) or the wheel -# build in hnn-core will fail -pip install NEURON -pip install https://api.github.com/repos/jonescompneurolab/hnn-core/zipball/master \ No newline at end of file +pip install hnn-core pyqt5 \ No newline at end of file From bd37ef2a4fdecff737121abb5e30edaf1caf031a Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 20 Mar 2021 12:57:12 -0400 Subject: [PATCH 103/107] TST: udpate tests after integration --- hnn/tests/test_compare_hnn.py | 21 +++++++++------------ hnn/tests/test_gui.py | 7 ------- scripts/run-pytest.sh | 2 +- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/hnn/tests/test_compare_hnn.py b/hnn/tests/test_compare_hnn.py index 99eb07d5c..59418b38f 100644 --- a/hnn/tests/test_compare_hnn.py +++ b/hnn/tests/test_compare_hnn.py @@ -8,7 +8,7 @@ import pytest from hnn import HNNGUI -from hnn.paramrw import get_output_dir +from hnn.paramrw import get_output_dir, get_fname def run_hnn(qtbot, monkeypatch): @@ -43,9 +43,8 @@ def test_hnn(qtbot, monkeypatch): run_hnn(qtbot, monkeypatch) dirname = op.join(get_output_dir(), 'data', 'default') - - fname = "dpl.txt" - pr = loadtxt(op.join(dirname, fname)) + dipole_fn = get_fname(dirname, 'normdpl', 0) + pr = loadtxt(op.join(dirname, dipole_fn)) assert len(pr) > 0 @@ -56,8 +55,8 @@ def test_compare_hnn(qtbot, monkeypatch): # do we need to run a simulation? run_sim = False dirname = op.join(get_output_dir(), 'data', 'default') - for data_type in ['dpl', 'rawdpl', 'i']: - fname = "%s.txt" % (data_type) + for data_type in ['normdpl', 'rawspk']: + fname = get_fname(dirname, data_type, 0) if not op.exists(fname): run_sim = True break @@ -67,8 +66,8 @@ def test_compare_hnn(qtbot, monkeypatch): data_dir = ('https://raw.githubusercontent.com/jonescompneurolab/' 'hnn/test_data/') - for data_type in ['dpl', 'rawdpl', 'i']: - fname = "%s.txt" % (data_type) + for data_type in ['normdpl', 'rawspk']: + fname = get_fname(dirname, data_type, 0) data_url = op.join(data_dir, fname) if not op.exists(fname): _fetch_file(data_url, fname) @@ -77,7 +76,5 @@ def test_compare_hnn(qtbot, monkeypatch): master = loadtxt(fname) assert_allclose(pr[:, 1], master[:, 1], rtol=1e-4, atol=0) - if data_type in ['dpl', 'rawdpl', 'i']: - assert_allclose(pr[:, 2], master[:, 2], rtol=1e-4, atol=0) - if data_type in ['dpl', 'rawdpl']: - assert_allclose(pr[:, 3], master[:, 3], rtol=1e-4, atol=0) + assert_allclose(pr[:, 2], master[:, 2], rtol=1e-4, atol=0) + assert_allclose(pr[:, 3], master[:, 3], rtol=1e-4, atol=0) diff --git a/hnn/tests/test_gui.py b/hnn/tests/test_gui.py index 021ecf952..11e201f0a 100644 --- a/hnn/tests/test_gui.py +++ b/hnn/tests/test_gui.py @@ -6,13 +6,6 @@ from hnn import HNNGUI -@pytest.mark.skipif(sys.platform == 'win32', - reason="does not run on windows") -def test_HNNGUI(qtbot): - main = HNNGUI() - qtbot.addWidget(main) - - @pytest.mark.skipif(sys.platform == 'win32', reason="does not run on windows") def test_exit_button(qtbot, monkeypatch): diff --git a/scripts/run-pytest.sh b/scripts/run-pytest.sh index 85c738c16..bc49d6fea 100755 --- a/scripts/run-pytest.sh +++ b/scripts/run-pytest.sh @@ -11,4 +11,4 @@ fi echo "Checking code style compliance with flake8..." flake8 --count --exclude __init__.py,qt_evoked.py echo "Running unit tests with pytest..." -py.test --cov=. hnn/tests/ +py.test . --cov=hnn hnn/tests/ # --cov-report=xml \ No newline at end of file From 99fdc8fd5a91a6df62edc7ab52a13cd7b82da8bb Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 20 Mar 2021 14:04:52 -0400 Subject: [PATCH 104/107] MAINT: rename vis*.py files to qt_*.py --- hnn/{visdipole.py => qt_dipole.py} | 0 hnn/qt_main.py | 22 +++++++++++----------- hnn/{vispsd.py => qt_psd.py} | 0 hnn/{qt_canvas.py => qt_sim.py} | 0 hnn/{visspec.py => qt_spec.py} | 0 hnn/{visrast.py => qt_spike.py} | 0 hnn/{run.py => qt_thread.py} | 0 hnn/{visvolt.py => qt_vsoma.py} | 8 ++++---- 8 files changed, 15 insertions(+), 15 deletions(-) rename hnn/{visdipole.py => qt_dipole.py} (100%) rename hnn/{vispsd.py => qt_psd.py} (100%) rename hnn/{qt_canvas.py => qt_sim.py} (100%) rename hnn/{visspec.py => qt_spec.py} (100%) rename hnn/{visrast.py => qt_spike.py} (100%) rename hnn/{run.py => qt_thread.py} (100%) rename hnn/{visvolt.py => qt_vsoma.py} (95%) diff --git a/hnn/visdipole.py b/hnn/qt_dipole.py similarity index 100% rename from hnn/visdipole.py rename to hnn/qt_dipole.py diff --git a/hnn/qt_main.py b/hnn/qt_main.py index 43ed671fe..56407a86a 100644 --- a/hnn/qt_main.py +++ b/hnn/qt_main.py @@ -35,17 +35,17 @@ from .paramrw import (usingOngoingInputs, get_output_dir, write_gids_param, get_fname) from .simdata import SimData -from .qt_canvas import SIMCanvas -from .run import SimThread, OptThread +from .qt_sim import SIMCanvas +from .qt_thread import SimThread, OptThread from .qt_lib import (getmplDPI, getscreengeom, lookupresource, setscalegeomcenter) from .specfn import spec_dpl_kernel, save_spec_data from .DataViewGUI import DataViewGUI -from .visdipole import DipoleCanvas -from .visvolt import VoltViewGUI, VoltCanvas -from .visspec import SpecViewGUI, SpecCanvas -from .visrast import SpikeViewGUI, SpikeCanvas -from .vispsd import PSDViewGUI, PSDCanvas +from .qt_dipole import DipoleCanvas +from .qt_vsoma import VSomaViewGUI, VSomaCanvas +from .qt_spec import SpecViewGUI, SpecCanvas +from .qt_spike import SpikeViewGUI, SpikeCanvas +from .qt_psd import PSDViewGUI, PSDCanvas # TODO: These globals should be made configurable via the GUI drawavgdpl = 0 @@ -351,9 +351,9 @@ def show_plot(self, plot_type): if plot_type == 'dipole': DataViewGUI(DipoleCanvas, self.baseparamwin.params, sim_data, 'Dipole Viewer') - elif plot_type == 'volt': - VoltViewGUI(VoltCanvas, self.baseparamwin.params, sim_data, - 'Dipole Viewer') + elif plot_type == 'vsoma': + VSomaViewGUI(VSomaCanvas, self.baseparamwin.params, sim_data, + 'Somatic Voltages Viewer') elif plot_type == 'PSD': PSDViewGUI(PSDCanvas, self.baseparamwin.params, sim_data, 'PSD Viewer') @@ -381,7 +381,7 @@ def showSomaVPlot(self): msg.setStandardButtons(QMessageBox.Ok) msg.exec_() else: - self.show_plot('volt') + self.show_plot('vsoma') def showPSDPlot(self): self.show_plot('PSD') diff --git a/hnn/vispsd.py b/hnn/qt_psd.py similarity index 100% rename from hnn/vispsd.py rename to hnn/qt_psd.py diff --git a/hnn/qt_canvas.py b/hnn/qt_sim.py similarity index 100% rename from hnn/qt_canvas.py rename to hnn/qt_sim.py diff --git a/hnn/visspec.py b/hnn/qt_spec.py similarity index 100% rename from hnn/visspec.py rename to hnn/qt_spec.py diff --git a/hnn/visrast.py b/hnn/qt_spike.py similarity index 100% rename from hnn/visrast.py rename to hnn/qt_spike.py diff --git a/hnn/run.py b/hnn/qt_thread.py similarity index 100% rename from hnn/run.py rename to hnn/qt_thread.py diff --git a/hnn/visvolt.py b/hnn/qt_vsoma.py similarity index 95% rename from hnn/visvolt.py rename to hnn/qt_vsoma.py index 991f42e0c..86867dfcd 100644 --- a/hnn/visvolt.py +++ b/hnn/qt_vsoma.py @@ -19,15 +19,15 @@ random_label = np.random.rand(100) -class VoltCanvas(FigureCanvasQTAgg): +class VSomaCanvas(FigureCanvasQTAgg): """Class for the somatic voltages viewer - This is designed to be called from VoltViewGUI class to add functionality + This is designed to be called from VSomaViewGUI class to add functionality for loading and clearing data """ def __init__(self, params, sim_data, index, parent=None, width=12, - height=10, dpi=120, title='Voltage Viewer'): + height=10, dpi=120, title='Somatic Voltage Viewer'): FigureCanvasQTAgg.__init__(self, Figure(figsize=(width, height), dpi=dpi)) self.title = title @@ -130,7 +130,7 @@ def plot(self): self.draw() -class VoltViewGUI(DataViewGUI): +class VSomaViewGUI(DataViewGUI): """Class for displaying somatic voltages viewer Required parameters in params dict: N_trials, tstop From 1d8fdfa4012ea20c0be46015c85644bed73a569e Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sat, 20 Mar 2021 20:30:51 -0400 Subject: [PATCH 105/107] MAINT: synchronization improvements with optim. --- hnn/paramrw.py | 29 ++++++++++++++- hnn/qt_main.py | 33 +++++++++------- hnn/qt_thread.py | 97 +++++++++++++++++++++++++++++++++--------------- hnn/simdata.py | 17 +++++++-- 4 files changed, 127 insertions(+), 49 deletions(-) diff --git a/hnn/paramrw.py b/hnn/paramrw.py index 3e34f3e39..6323f426d 100644 --- a/hnn/paramrw.py +++ b/hnn/paramrw.py @@ -244,14 +244,15 @@ def read_gids_param(fparam): def legacy_param_str_to_dict(param_str): boolean_params = ['sync_evinput', 'record_vsoma', 'save_spec_data', 'save_figs'] - + string_params = ['sim_prefix', 'spec_cmap', 'distribution_prox', + 'distribution_dist'] param_dict = {} for line in param_str.splitlines(): keystring, val = line.split(': ') key = keystring.strip() if key == 'expmt_groups': continue - elif key == 'sim_prefix' or key == 'spec_cmap': + elif key in string_params: param_dict[key] = val elif key.startswith('N_') or key.startswith('numspikes_') or \ key.startswith('events_per_cycle_') or \ @@ -296,3 +297,27 @@ def write_gids_param(fparam, gid_list): else: f.write('[]') f.write('\n') + +def hnn_core_compat_params(params): + boolean_params = ['sync_evinput', 'record_vsoma', 'save_spec_data', + 'save_figs'] + string_params = ['sim_prefix', 'spec_cmap', 'distribution_prox', + 'distribution_dist'] + + param_dict = {} + for key, val in params.items(): + if key == 'expmt_groups': + continue + elif key in string_params: + param_dict[key] = val + elif key.startswith('N_') or key.startswith('numspikes_') or \ + key.startswith('events_per_cycle_') or \ + key.startswith('repeats_') or \ + key.startswith('prng_seedcore_'): + param_dict[key] = int(val) + elif key in boolean_params: + param_dict[key] = bool(val) + else: + param_dict[key] = float(val) + + return param_dict \ No newline at end of file diff --git a/hnn/qt_main.py b/hnn/qt_main.py index 56407a86a..16505acad 100644 --- a/hnn/qt_main.py +++ b/hnn/qt_main.py @@ -984,19 +984,11 @@ def stopsim(self): def startsim(self, ncore): """start the simulation""" + # update self.self.baseparamwin.params with values from GUI + # and save to file if not self.baseparamwin.saveparams(): return # make sure params saved and ok to run - # reread the params to get anything new - try: - params = read_params(self.baseparamwin.paramfn) - except ValueError: - txt = "WARNING: could not retrieve parameters from %s" % \ - self.baseparamwin.paramfn - QMessageBox.information(self, "HNN", txt) - print(txt) - return - self.setcursors(Qt.WaitCursor) print('Starting simulation (%d cores). . .' % ncore) @@ -1005,13 +997,26 @@ def startsim(self, ncore): self.statusBar().showMessage("Running simulation. . .") # check that valid number of trials was given - if 'N_trials' not in params or params['N_trials'] == 0: + if 'N_trials' not in self.baseparamwin.params or \ + self.baseparamwin.params['N_trials'] == 0: print("Warning: invalid configured number of trials." " Setting to 1.") - params['N_trials'] = 1 + self.baseparamwin.params['N_trials'] = 1 + + if self.baseparamwin.params['record_vsoma'] and ncore > 1: + txt = 'A bug currently prevents recording somatic voltages' + \ + ' for simulatioins run on more than one core. This' + \ + ' simulation will proceed using only a single core.' + msg = QMessageBox() + msg.setIcon(QMessageBox.Information) + msg.setText(txt) + msg.setWindowTitle('Rerun simulation') + msg.setStandardButtons(QMessageBox.Ok) + msg.exec_() + ncore = 1 - self.runthread = SimThread(ncore, params, self.sim_result_callback, - mainwin=self) + self.runthread = SimThread(ncore, self.baseparamwin.params, + self.sim_result_callback, mainwin=self) # We have all the events we need connected we can start the thread self.runthread.start() diff --git a/hnn/qt_thread.py b/hnn/qt_thread.py index d443a1d49..3c579d796 100755 --- a/hnn/qt_thread.py +++ b/hnn/qt_thread.py @@ -11,13 +11,14 @@ from psutil import wait_procs, process_iter, NoSuchProcess import traceback from queue import Queue +from threading import Event +import numpy as np import nlopt from PyQt5 import QtCore from hnn_core import simulate_dipole, Network, MPIBackend -from hnn_core.dipole import Dipole -from .paramrw import get_output_dir +from .paramrw import get_output_dir, hnn_core_compat_params class BasicSignal(QtCore.QObject): @@ -31,10 +32,20 @@ class ObjectSignal(QtCore.QObject): class QueueSignal(QtCore.QObject): - """for synchronization""" + """for returning data""" qsig = QtCore.pyqtSignal(Queue, str, float) +class QueueDataSignal(QtCore.QObject): + """for returning data""" + qsig = QtCore.pyqtSignal(Queue, str, np.ndarray, float, float) + + +class EventSignal(QtCore.QObject): + """for synchronization""" + esig = QtCore.pyqtSignal(Event, str) + + class TextSignal(QtCore.QObject): """for passing text""" tsig = QtCore.pyqtSignal(str) @@ -45,11 +56,6 @@ class DataSignal(QtCore.QObject): dsig = QtCore.pyqtSignal(str, dict) -class OptDataSignal(QtCore.QObject): - """for signalling update to opt_data""" - odsig = QtCore.pyqtSignal(str, dict, Dipole) - - class ParamSignal(QtCore.QObject): """for updating GUI & param file during optimization""" psig = QtCore.pyqtSignal(dict) @@ -222,13 +228,13 @@ def stop(self): _kill_and_check_nrniv_procs() self.killed = True - def run(self, simlength=None): + def run(self, sim_length=None): """Start simulation""" msg = '' banner = not self.is_optimization try: - self._run(banner=banner, simlength=simlength) # run simulation + self._run(banner=banner, sim_length=sim_length) # run simulation # update params in all windows (optimization) except RuntimeError as e: msg = str(e) @@ -239,9 +245,16 @@ def run(self, simlength=None): self.param_signal.psig.emit(self.params) self.done_signal.tsig.emit(msg) - def _run(self, banner=True, simlength=None): + # gracefully stop this thread + self.quit() + + def _run(self, banner=True, sim_length=None): self.killed = False + sim_params = hnn_core_compat_params(self.params) + if sim_length is not None: + sim_params['tstop'] = sim_length + while True: if self.ncore == 0: raise RuntimeError("No cores available for simulation") @@ -249,7 +262,7 @@ def _run(self, banner=True, simlength=None): try: sim_log = self._log_sim_status(parent=self) with redirect_stdout(sim_log): - sim_data = simulate(self.params, self.ncore) + sim_data = simulate(sim_params, self.ncore) break except RuntimeError as e: if self.ncore == 1: @@ -269,6 +282,7 @@ def _run(self, banner=True, simlength=None): % self.ncore print(txt) self._updatewaitsimwin(txt) + _kill_and_check_nrniv_procs() # put sim_data into the val attribute of a ResultObj self.result_signal.sig.emit(ResultObj(sim_data, self.params)) @@ -323,24 +337,24 @@ def __init__(self, ncore, params, num_steps, seed, sim_data, self.refresh_signal = BasicSignal() self.refresh_signal.sig.connect(self.mainwin.initSimCanvas) - self.update_opt_data = OptDataSignal() - self.update_opt_data.odsig.connect(sim_data.update_opt_data) - - self.update_sim_data_from_opt_data = TextSignal() - self.update_sim_data_from_opt_data.tsig.connect( + self.update_sim_data_from_opt_data = EventSignal() + self.update_sim_data_from_opt_data.esig.connect( sim_data.update_sim_data_from_opt_data) - self.update_opt_data_from_sim_data = TextSignal() - self.update_opt_data_from_sim_data.tsig.connect( + self.update_opt_data_from_sim_data = EventSignal() + self.update_opt_data_from_sim_data.esig.connect( sim_data.update_opt_data_from_sim_data) - self.update_initial_opt_data_from_sim_data = TextSignal() - self.update_initial_opt_data_from_sim_data.tsig.connect( + self.update_initial_opt_data_from_sim_data = EventSignal() + self.update_initial_opt_data_from_sim_data.esig.connect( sim_data.update_initial_opt_data_from_sim_data) self.get_err_from_sim_data = QueueSignal() self.get_err_from_sim_data.qsig.connect(sim_data.get_err_wrapper) + self.get_werr_from_sim_data = QueueDataSignal() + self.get_werr_from_sim_data.qsig.connect(sim_data.get_werr_wrapper) + def run(self): msg = '' try: @@ -413,7 +427,10 @@ def _run(self): self.optparamwin.push_chunk_ranges(push_values) # update opt_data with the final best - self.update_sim_data_from_opt_data.tsig.emit(self.paramfn) + update_event = Event() + self.update_sim_data_from_opt_data.esig.emit(update_event, + self.paramfn) + update_event.wait() # check that optimization improved RMSE err_queue = Queue() @@ -457,6 +474,7 @@ def run_opt_step(self): txt = 'Optimizing from [%3.3f-%3.3f] ms' % (self.opt_start, self.opt_end) self._updatewaitsimwin(txt) + print(txt) # weights calculated once per step self.opt_weights = \ @@ -499,8 +517,15 @@ def get_initial_data(self): # store the initial fit for display in final dipole plot as # black dashed line. - self.update_opt_data_from_sim_data.tsig.emit(self.paramfn) - self.update_initial_opt_data_from_sim_data.tsig.emit(self.paramfn) + update_event = Event() + self.update_opt_data_from_sim_data.esig.emit(update_event, + self.paramfn) + update_event.wait() + update_event.clear() + self.update_initial_opt_data_from_sim_data.esig.emit(update_event, + self.paramfn) + update_event.wait() + err_queue = Queue() self.get_err_from_sim_data.qsig.emit(err_queue, self.paramfn, self.params['tstop']) @@ -529,17 +554,22 @@ def opt_sim(self, new_params, grad=0): self.step_ranges[param_name]['maxval'])) return 1e9 # invalid param value -> large error - # populate param values into GUI and save params to file + # populate param values into GUI self.baseparamwin.update_gui_params(opt_params) + sim_params = hnn_core_compat_params(self.params) + for param_name, param_value in opt_params.items(): + sim_params[param_name] = param_value + # run the simulation, but stop at self.opt_end - self.sim_thread = SimThread(self.ncore, self.params, + self.sim_thread = SimThread(self.ncore, sim_params, self.result_callback, mainwin=self.mainwin) self.sim_running = True try: - self.sim_thread.run(simlength=self.opt_end) + # may not need to run the entire simulation + self.sim_thread.run(sim_length=self.opt_end) self.sim_thread.wait() if self.killed: self.quit() @@ -550,8 +580,12 @@ def opt_sim(self, new_params, grad=0): "See previous traceback.") # calculate wRMSE for all steps - werr = self.sim_data.get_werr(self.paramfn, self.opt_weights, - self.opt_end, tstart=self.opt_start) + err_queue = Queue() + self.get_werr_from_sim_data.qsig.emit(err_queue, self.paramfn, + self.opt_weights, self.opt_end, + self.opt_start) + werr = err_queue.get() + txt = "Weighted RMSE = %f" % werr print(txt) self._updatewaitsimwin(os.linesep + 'Simulation finished: ' + txt + @@ -567,7 +601,10 @@ def opt_sim(self, new_params, grad=0): if werr < self.best_step_werr: self._updatewaitsimwin("new best with RMSE %f" % werr) - self.update_opt_data_from_sim_data.tsig.emit(self.paramfn) + update_event = Event() + self.update_opt_data_from_sim_data.esig.emit(update_event, + self.paramfn) + update_event.wait() self.best_step_werr = werr # save best param file diff --git a/hnn/simdata.py b/hnn/simdata.py index 2271d7efc..d3e16fc26 100644 --- a/hnn/simdata.py +++ b/hnn/simdata.py @@ -468,7 +468,7 @@ def update_opt_data(self, paramfn, params, avg_dpl, dpls=None, 'spec': None, 'vsoma': None}} - def update_initial_opt_data_from_sim_data(self, paramfn): + def update_initial_opt_data_from_sim_data(self, event, paramfn): if paramfn not in self._sim_data: raise ValueError("Simulation not in sim_data: %s" % paramfn) @@ -477,6 +477,8 @@ def update_initial_opt_data_from_sim_data(self, paramfn): deepcopy(single_sim_data['avg_dpl']) self._opt_data['initial_error'] = self.get_err(paramfn) + event.set() + def get_err(self, paramfn, tstop=None): if paramfn not in self._sim_data: raise ValueError("Simulation not in sim_data: %s" % paramfn) @@ -499,7 +501,12 @@ def get_werr(self, paramfn, weights, tstop=None, tstart=None): _, werr = self.calcerr(paramfn, tstop, tstart, weights) return werr - def update_opt_data_from_sim_data(self, paramfn): + def get_werr_wrapper(self, queue, paramfn, weights, tstop=None, + tstart=None): + err = self.get_werr(paramfn, weights, tstop, tstart) + queue.put(err) + + def update_opt_data_from_sim_data(self, event, paramfn): if paramfn not in self._sim_data: raise ValueError("Simulation not in sim_data: %s" % paramfn) @@ -515,7 +522,9 @@ def update_opt_data_from_sim_data(self, paramfn): 'spec': deepcopy(single_sim['spec']), 'vsoma': deepcopy(single_sim['vsoma'])}} - def update_sim_data_from_opt_data(self, paramfn): + event.set() + + def update_sim_data_from_opt_data(self, event, paramfn): opt_data = self._opt_data['data'] single_sim = {'paramfn': paramfn, 'params': deepcopy(self._opt_data['params']), @@ -528,6 +537,8 @@ def update_sim_data_from_opt_data(self, paramfn): 'vsoma': deepcopy(opt_data['vsoma'])}} self._sim_data[paramfn] = single_sim + event.set() + def _read_dpl(self, paramfn, trial_idx, ntrial): if ntrial == 1: dpltrial = self._sim_data[paramfn]['data']['avg_dpl'] From 05b954f7d653dc8befd0633f21946b19a8751c85 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 21 Mar 2021 04:41:58 -0400 Subject: [PATCH 106/107] BUG: missing key in updating opt_data --- hnn/simdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hnn/simdata.py b/hnn/simdata.py index d3e16fc26..966c5e960 100644 --- a/hnn/simdata.py +++ b/hnn/simdata.py @@ -510,7 +510,7 @@ def update_opt_data_from_sim_data(self, event, paramfn): if paramfn not in self._sim_data: raise ValueError("Simulation not in sim_data: %s" % paramfn) - sim_params = self._sim_data[paramfn] + sim_params = self._sim_data[paramfn]['params'] single_sim = self._sim_data[paramfn]['data'] self._opt_data = {'paramfn': paramfn, 'params': deepcopy(sim_params), From d95319eab37596fdbb8b4dbed2efbff3541aa322 Mon Sep 17 00:00:00 2001 From: Blake Caldwell Date: Sun, 21 Mar 2021 04:47:09 -0400 Subject: [PATCH 107/107] MAINT: wait to update installation scripts --- installer/ubuntu/hnn-ubuntu.sh | 95 ++++++++++--------------------- installer/windows/hnn-windows.ps1 | 51 +++++++++-------- 2 files changed, 57 insertions(+), 89 deletions(-) diff --git a/installer/ubuntu/hnn-ubuntu.sh b/installer/ubuntu/hnn-ubuntu.sh index 46b1bb888..4af9990d8 100755 --- a/installer/ubuntu/hnn-ubuntu.sh +++ b/installer/ubuntu/hnn-ubuntu.sh @@ -3,7 +3,6 @@ set -e [[ "$LOGFILE" ]] || LOGFILE="ubuntu_install.log" -[[ "$USE_CONDA" ]] || USE_CONDA=0 function start_download { echo "Downloading $2" @@ -62,17 +61,17 @@ echo "Using python version $PYTHON_VERSION" | tee -a "$LOGFILE" export DEBIAN_FRONTEND=noninteractive echo "Updating package repository..." | tee -a "$LOGFILE" -sudo -E apt-get update &> "$LOGFILE" +sudo -E apt-get update &>> "$LOGFILE" echo "Updating OS python packages..." | tee -a "$LOGFILE" if [[ "${PYTHON_VERSION}" =~ "3.7" ]] && [[ "$DISTRIB" =~ "bionic" ]]; then - sudo -E apt-get install --no-install-recommends -y python3.7 python3-pip python3-tk python3.7-dev &> "$LOGFILE" && \ - sudo python3.7 -m pip install --upgrade pip setuptools &> "$LOGFILE" + sudo -E apt-get install --no-install-recommends -y python3.7 python3-pip python3.7-tk python3.7-dev &>> "$LOGFILE" && \ + sudo python3.7 -m pip install --upgrade pip setuptools &>> "$LOGFILE" sudo ln -s /usr/lib/python3/dist-packages/apt_pkg.cpython-36m-x86_64-linux-gnu.so \ /usr/lib/python3/dist-packages/apt_pkg.so else - sudo -E apt-get install --no-install-recommends -y python3 python3-pip python3-tk python3-setuptools &> "$LOGFILE" && \ - sudo pip3 install --upgrade pip &> "$LOGFILE" + sudo -E apt-get install --no-install-recommends -y python3 python3-pip python3-tk python3-setuptools &>> "$LOGFILE" && \ + sudo pip3 install --upgrade pip &>> "$LOGFILE" fi if which python3 &> /dev/null; then @@ -88,68 +87,22 @@ elif which python &> /dev/null; then fi echo "Using python: $PYTHON with pip: $PIP" | tee -a "$LOGFILE" -if [[ "$USE_CONDA" -eq 0 ]]; then - echo "Downloading python packages for HNN with pip..." | tee -a "$LOGFILE" - $PIP download pyqt5 nlopt psutil hnn-core &> "$LOGFILE" & - PIP_PID=$! -fi - echo "Installing OS compilation toolchain..." | tee -a "$LOGFILE" # get prerequisites from pip. requires gcc to build psutil sudo -E apt-get install --no-install-recommends -y \ - make gcc g++ python3-dev &> "$LOGFILE" - -if [[ "$USE_CONDA" -eq 0 ]]; then - echo "Waiting for python packages for HNN downloads to finish..." - NAME="downloading python packages for HNN " - wait_for_pid "${PIP_PID}" "$NAME" - - # install hnn-core and prerequisites (e.g. NEURON) - echo "Installing python prequisites for HNN with pip..." | tee -a "$LOGFILE" - $PIP install --no-cache-dir pyqt5 nlopt psutil hnn-core &> "$LOGFILE" - - # WSL may not have nrnivmodl in PATH - if ! which nrnivmodl &> /dev/null; then - export PATH="$PATH:$HOME/.local/bin" - echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc - fi -else - URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh" - FILENAME="$HOME/miniconda.sh" - start_download "$FILENAME" "$URL" - - echo "Installing miniconda..." - chmod +x "$HOME/miniconda.sh" - "$HOME/miniconda.sh" -b -p "${HOME}/Miniconda3" - export PATH=${HOME}/Miniconda3/bin:$PATH + make gcc g++ python3-dev &>> "$LOGFILE" - # create conda environment - conda env create -f environment.yml +$PIP install --no-cache-dir NEURON - # conda is faster to install nlopt - conda install -y -n hnn -c conda-forge nlopt - - source activate hnn && echo "activated conda HNN environment" - - echo "Installing MPI compilation toolchain..." | tee -a "$LOGFILE" - # get prerequisites to build mpi4py - sudo -E apt-get install --no-install-recommends -y \ - libopenmpi-dev &> "$LOGFILE" - - # install hnn-core and prerequisites (e.g. NEURON) - pip install mpi4py pyqt5 hnn-core +# WSL may not have nrnivmodl in PATH +if ! which nrnivmodl &> /dev/null; then + export PATH="$PATH:$HOME/.local/bin" + echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc fi -echo "Downloading runtime prerequisite packages..." | tee -a "$LOGFILE" -apt-get download \ - openmpi-bin lsof libfontconfig1 libxext6 libx11-xcb1 libxcb-glx0 \ - libxkbcommon-x11-0 libgl1-mesa-glx \ - libc6-dev libtinfo-dev libncurses5-dev \ - libx11-dev libreadline-dev \ - libxcb-icccm4 libxcb-util1 libxcb-image0 libxcb-keysyms1 \ - libxcb-render0 libxcb-shape0 libxcb-randr0 libxcb-render-util0 \ - libxcb-xinerama0 &> "$LOGFILE" & -APT_DOWNLOAD=$! +echo "Installing python packages for HNN with pip..." | tee -a "$LOGFILE" +$PIP install --no-cache-dir --user matplotlib PyOpenGL \ + pyqt5 pyqtgraph scipy numpy nlopt psutil &>> "$LOGFILE" # save dir installing hnn to startdir=$(pwd) @@ -162,11 +115,11 @@ if [[ $TRAVIS_TESTING -ne 1 ]]; then cd hnn_source_code if [ -d "$source_code_dir/.git" ]; then - git pull origin master &> "$LOGFILE" + git pull origin master &>> "$LOGFILE" fi else echo "Downloading and extracting HNN..." | tee -a "$LOGFILE" - wget -O hnn.tar.gz https://github.com/jonescompneurolab/hnn/releases/latest/download/hnn.tar.gz | tee -a "$LOGFILE" + wget --no-hsts --no-check-certificate -O hnn.tar.gz https://github.com/jonescompneurolab/hnn/releases/latest/download/hnn.tar.gz | tee -a "$LOGFILE" mkdir hnn_source_code tar -x --strip-components 1 -f hnn.tar.gz -C hnn_source_code &>> "$LOGFILE" && \ cd hnn_source_code &>> "$LOGFILE" @@ -179,6 +132,10 @@ else fi fi +echo "Building HNN..." | tee -a "$LOGFILE" +make -j4 &>> "$LOGFILE" +MAKE_PID=$! + # create the global session variables echo '# these lines define global session variables for HNN' >> ~/.bashrc echo "export PATH=\$PATH:\"$source_code_dir\"" >> ~/.bashrc @@ -190,7 +147,7 @@ if [[ -d "$HOME/Desktop" ]]; then cp -f hnn.desktop "$HOME/Desktop" && \ sed -i "s~/home/hnn_user\(.*\)$~\"$startdir\"\1~g" "$HOME/Desktop/hnn.desktop" && \ chmod +x "$HOME/Desktop/hnn.desktop" - } &> "$LOGFILE" + } &>> "$LOGFILE" fi echo "Installing prerequisites..." | tee -a "$LOGFILE" @@ -203,11 +160,17 @@ sudo -E apt-get install --no-install-recommends -y \ libx11-dev libreadline-dev \ libxcb-icccm4 libxcb-util1 libxcb-image0 libxcb-keysyms1 \ libxcb-render0 libxcb-shape0 libxcb-randr0 libxcb-render-util0 \ - libxcb-xinerama0 libxcb-xfixes0 &> "$LOGFILE" + libxcb-xinerama0 libxcb-xfixes0 &>> "$LOGFILE" # Clean up a little echo "Cleaning up..." | tee -a "$LOGFILE" -sudo -E apt-get clean &> "$LOGFILE" +sudo -E apt-get clean &>> "$LOGFILE" + +if [[ $TRAVIS_TESTING -ne 1 ]]; then + echo "Waiting for HNN module build to finish..." + NAME="building HNN modules" + wait_for_pid "${MAKE_PID}" "$NAME" +fi echo "HNN installation successful" | tee -a "$LOGFILE" echo "Source code is at $source_code_dir" | tee -a "$LOGFILE" diff --git a/installer/windows/hnn-windows.ps1 b/installer/windows/hnn-windows.ps1 index c8759ffe8..ccb092abf 100644 --- a/installer/windows/hnn-windows.ps1 +++ b/installer/windows/hnn-windows.ps1 @@ -6,7 +6,7 @@ install Miniconda: - Miniconda3-latest-Windows-x86_64.exe Additionally the following will be installed if they are not found: - - nrn-7.8.w64-mingwsetup.exe + - nrn-7.7.w64-mingwsetup.exe Other requirements: - Only 64-bit installs are supported due to NEURON compatibility @@ -310,8 +310,8 @@ if ($script:installMiniconda) { $program = "NEURON" if (!(Test-Installed($program))) { - $file = "nrn-7.8.w64-mingwsetup.exe" - $url = "https://neuron.yale.edu/ftp/neuron/versions/v7.8/$file" + $file = "nrn-7.7.w64-mingwsetup.exe" + $url = "https://neuron.yale.edu/ftp/neuron/versions/v7.7/$file" Download-Program $program $file $url $dirpath = $script:NEURON_PATH Write-Host "Installing $program to $dirpath..." @@ -379,13 +379,6 @@ if ($proc1) { Write-Host "Miniconda is finished" } -if ($proc2) { - Write-Host "Waiting for NEURON install to finish..." - $proc2.WaitForExit() 2>$null - Update-User-Paths("$script:NEURON_PATH\bin") - Write-Host "NEURON is finished" -} - # setup python with virtualenv or 'conda if ($null -ne $script:VIRTUALENV) { Write-Host "Creating Python virtualenv at $HOME\venv\hnn..." @@ -397,9 +390,7 @@ if ($null -ne $script:VIRTUALENV) { $script:PYTHON = "$HOME\venv\hnn\Scripts\python.exe" if (Test-Python-3($script:PYTHON)) { # use pip3 for good measure - Start-Process "$HOME\venv\hnn\Scripts\pip3" "install matplotlib scipy PyQt5 psutil nlopt" -Wait - # get hnn-core, but skip NEURON dependency - Start-Process "$HOME\venv\hnn\Scripts\pip3" "install --no-deps mpi4py hnn-core" -Wait + Start-Process "$HOME\venv\hnn\Scripts\pip3" "install PyOpenGL pyqtgraph matplotlib scipy PyQt5 psutil nlopt" -Wait } else { Write-Warning "Virtualenv failed to create a valid python3 environment" @@ -424,21 +415,14 @@ elseif ($null -ne $script:CONDA_PATH) { if (!$script:env_exists) { Write-Host "Setting up anaconda hnn environment..." - conda env create -f environment.yml + conda create -y -n hnn python=3.7 PyOpenGL pyqtgraph matplotlib scipy conda psutil conda install -y -n hnn -c conda-forge nlopt - - # need to call the right pip to install in miniconda environment - # get hnn-core, but skip NEURON dependency - Set-Location $HOME - Miniconda3\envs\hnn\Scripts\pip install --no-deps mpi4py hnn-core - Set-Location $CONDA_ENV mkdir .\etc\conda\activate.d 2>&1>$null mkdir .\etc\conda\deactivate.d 2>&1>$null - # "set NRN_PYLIB=$script:PYTHON_DLL" | Set-Content "$CONDA_ENV\etc\conda\activate.d\env_vars.bat" - "set PYTHONPATH=$script:NEURON_PATH\lib\python" | Add-Content "$CONDA_ENV\etc\conda\activate.d\env_vars.bat" - "export PYTHONPATH=/c/nrn/lib/python" | Add-Content "$CONDA_ENV\etc\conda\activate.d\env_vars.sh" + #"set NRN_PYLIB=$script:PYTHON_DLL" | Set-Content "$CONDA_ENV\etc\conda\activate.d\env_vars.bat" + "set PYTHONHOME=$CONDA_ENV" | Add-Content "$CONDA_ENV\etc\conda\activate.d\env_vars.bat" } else { Write-Host "Miniconda hnn environment already exists" @@ -449,6 +433,27 @@ else { } + +if ($proc2) { + Write-Host "Waiting for NEURON install to finish..." + $proc2.WaitForExit() 2>$null + Update-User-Paths("$script:NEURON_PATH\bin") + Write-Host "NEURON is finished" +} + +if (!(Test-Path "$HNN_PATH\nrnmech.dll" -PathType Leaf)) { + Write-Host "Creating nrnmech.dll" + Set-Location $HNN_PATH\mod + Start-Process "$script:NEURON_PATH\mingw\usr\bin\sh.exe" "$script:NEURON_ESC_PATH/lib/mknrndll.sh C:\nrn\" + $obj = New-Object -com Wscript.Shell + sleep -s 10 + $obj.SendKeys("{ENTER}") + Copy-Item $HNN_PATH\mod\nrnmech.dll -Destination $HNN_PATH +} +else { + Write-Host "nrnmech.dll already exists $HNN_PATH\nrnmech.dll" +} + Write-Host "" Write-Host "Finished installing HNN and prerequisites." Write-Host "Activate the environment from cmd.exe (not Powershell):"

pmbeR%+7SKbw#M2cLe; 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

pWS@NWe*IPR|^5E8^@^V7Hz*2O=z8Y~LV<3+H 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�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 z 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%0A-yvG{CBCZ$@WEMSQ)3GFFCKnab9# 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 uf12wmU)z*G*9@Q_x}Z=)Ja1C diff --git a/installer/nsg/install_pngs/005.png b/installer/nsg/install_pngs/005.png deleted file mode 100644 index b02108b3c5e6b42bc635daf3e5c681834b20de2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115031 zcmeFZc{rAB`vs~}Nrj|ILNaD*FlTtlJVr84WgbH2A(WyJN+EL@l1!OrDoF^*lsR)0 z$~@ca@xH%f??3lG_Fwzhzvnohb*}RcxS}j`kd&H~h=}N*oUEi8 z5z(H1h=_Ksk?h3ZoaN}hhX3qxl#o*=AtC7*RQW+fL{B6qDX#7o`@7Hew)(Zrt-qc# z^HGdXl|3XpNEoSh+#`7{M)7{nLy{~mhVK;XH?%c#GD}^e0|R|;$o!*ae~2pOp8Dze zue7{a@FhyMy-Mg^4_(9dU~6LC=&+Szo5e(I5N|6ItcLq?67c zKaj{;xM&|Ob!Ru>o$sVtzkk=#*4Dmut^MoQANB$!+KZ!Ch_;5Qq*RmG@4cs>qN1Xq z$!ML>@w{Trd^Ye~xAFD`emGpYdiAQBnwqNW^&GAB_4P;6NsOwcdqtHZOw0NVu3p8j zJx)zcedy5j3sHp6zB|CN{Xs6@PoF*|CMHf!PJaIU`Rms!6$aVuvq>i-iBem;a4Q-b z8U_YjBoc~>kI0*DtORJwd3ga zTaU@>-#0DL@v@&fVjqF;QB~!0`SLw-+ZS^Gt&yKYi5}U5>Ki#%3Qk3GWolUSP@g<` za^KtSPZu9x3ru7sojAhfOdb4b%7q8_gWGw6yf}dj0eaB1Xf) zC{?Mrlb;4jWOpu2^%Ujjw+-4audNj=+HZeP3Sw~!3kw17^<|cX+q2!)+53Er3UnIH z@)}0Zva=s1etEN}FjQ=F`IKw#Aqt8Y?N&v01EOYMo`;IAzAlhCsAn=xXH?QRott~% z-h2B`no7)9|M}<6s$FD*XX^7urf9+&2=TYud{cWi&1hH#DsKR>^@@n)Zu(MYVh zi;Ih~v9Xtzm$7k@bdsE+B2Sbeb?{ipc)aISL1(|u*2d4DKh5La&1yqVg-KS>>)B$_ z2>ys%E@?z5F|`dFRfZxYX_2x3di@zL@1DvZnHDP>nu{+y0h+#2O}? zru)iV$G$`j+@4div9WP?FX^(W9E-N5JbZX#eVs~*jh(#*U#`^o*N5ur1&@uNElJhY z)%skKEfzLYIlGPxtgy)7H_Uv`89t%86_DsQ^7)B*f>$B-3>%b|mR9|MoSc+Y$oLLZ zAj1IH_H~7+q`Innww|$F)yUF2apHuwj*igv5BK{kJRe{c{QT0y64TSu73AfoOMhj} zRIW{@CU^%12Hx-`;W8fIs_U>S;Jkfak@@K=xpo9Eb#Rx>BF*+X5^=I}a>j{xF4l*$ zx7TarnKw^=OHq)OedrqVc>Cot)qP6zw%gyK8tEies+x<+G=hDff=})zC5?^=OKi8o zH|{R7qobwu?l1b`Bt%Xt?6&;pdTJ|M;0=3wdmN|cvLeUX2JR{(3gW$c4PE=3$33RA z^vf2fdQw{~bZlR-1|p!8l|xcJzU@ZzcITQZ7#j~SbQ=2Ptd@USH)yeWI`c)_+i!av zmgzdbe=oM_%8j~c&dkUt5Z7ec`f7A!gy&cq{?ZgD7?D2i6mak)pYY;$UJaZ@dbethjPrSj&L#OFZ}APTAyubh~RAfwO}>qQq(i$BxI^>yFw`x z$HmLb%gj8sGM?ZnKr<<>N@c2@m6g?>Z`rDAR}r3&V5q8kh>Ggjvu93OMOUs}Tc{=% zyZyK3SW&;nFUMvP^Bk>j-@Y{!Je_;hI+2)^)SRuOs-e*_Khf$e6t|ixoz#WH_(~~e z^4BlKrd;!)Q!a;$lvJFx?f0E>VLNy2LRRqnWO<9?_QiKXw}vFKhR)8;H*aQ2C+$0M zKymveyhi)mWi4LBQ#oGPvZ!a*&Ycnx5=ijAzP{d7=lKU+%CPsZ`~6xh4v>+B1QA|q z)qA!#t(XddJyW$+SzIi@%KCG2ZKk=t-YUyO1!CXY4=+8{XRlws9w>j@ zFnS?MvBG0P$ZL1|UMNHpop?&<2kzibtukTKAig>TZuBNz44Y{|| zMlMWs7bvKxEUl-tSj>BDa96G8`fP1JIU`frXWOih$eP6OXKtN#?!qe zrlzLSNiEIIDulzTW@u=a*Y(A0Za;VVx!cQso77hDWX=ly_Cicr-GOXo!H`5&+oGOPhqT@0!hQ9* z8LWYfa+6gewH14R^f^9_EMHq9>x;H_`ShvkgkV78cazE(M!9&}CV?Yab*&b*!scDS z=kwaqJq=gaOH12UB(=1zUY+}RSjEKDl+&*(kp9rqMi^COup4fqUPsz5D8l`zIH8gEWWh>MGh ziaJ3}ZN)>qv9U2cJe-%Co1yXR(oA{l%gYZ^6e2xdSrDKo(T_oU0mLFVi3FB1Q{&f+ zM`lFBH6+J@btWLJ4w_jJkcFhaNb=O`p_0W?C`~wcN(29AN z*U_`HvsbN7rdwxMbX>esOY3Qa80U&?D2t+2M~1PQY)cat7q@EIOLyCTYja~{^{e#=Z$9K6iB2?GXp4Y zo47(`1FzA$J4!Kp&QskBKBpPBL1?T|q2D13n%*!CIGDr>_IIwTu zUk)jUvOXM};X6Ba-MDe%Q^={4Cr%(sx&Hp%l5JRp@LjpPmzHGTz9)mRkX>4q)uP^^?ifKXBXe z$Gf}D!gG)6Oy0bCgK|6YG2n0{hxP^Q4h|Lj9Dq3dHdLy$oLbub2M*--Okrn{waW0G zS(LT(lJ^sB&$AuJn~V`{7fc>To7oOjR^SW%{ri_(Y~9|?t+;2ZAHVnhzQdv`BTskl zKg!6+sJHC3lA%HNmP#s0%x4pk-konb>?DK;oGu;?85tSDeg)MqG~_m#UAi^&5vWLP z{ZDmisgRghl~mBtgLI<8qM`;HJ73Sv%_VqDreV)JIyzAIm;6uK0^C1*`0(8E1HjtJ z$tDPkt@Uv+%`8333sG1yv!>XU&Z^A?j=kFI>O7|P&v>mnmRDA~rWR2T9?^=F*42I( zv_C-2!^+MML}q4Y*6-A+bn24JqmYorxw)%UDT#?gKi=Q7?#$X)9u5UQw6dD~5_R$7 zg$v#@TPL=$4;QCdp8MkOE}cw1Vc~UQGovn>LX`dezVvcx4i32n*>+hejB>dgogEz> z*%V=>T@y}xJUkp)Z;gVIFz7*PWzR0BJa&v9n}t&0^*ajOf z@3n((g z(l;->=NohqisaN?9E-8*M`}Vo_Cd~QC$h$+aZ+%!4U25!sP)=9I>>ES^*UR z8pG>mcYaQn4YqGT8QCVj=BH4)0lhC1Q;20mr&hiN5|^QzT-|Hcq-i9s@87>OH)5#( zrj8yyoYXpDTd~lA1L(HMK|y?)Vf!U0*kxPfgYDBuUq}L(X)xTB_?H*{IeP5a(zw{x zO1X*{K-B}XQ^evw`^uJlcklPTcQ3{|t=;M-Uk}*QkoN6V*1*;YVdu#ku0E$7zf$x zXm3X*1~9qlwYIoG)7;qTwKi2`S2k6+m)7mwQx_&i#*Z~Mrv&VcR_7J%esKuAEdCoyTy<5**yPo?Swp44d@sk3b$UdJ;M3&1G1ZgiAW0dU%n_RD9ni# zJ57B1@x!Qj(I-BhNp2En?WA4#EyO9*6cq7y@BR^|=*ZMPMoSwX9sPzYI9Dgrb-=4g zI_YnG@{qm2(9lq+>&&#*OW^c^x0+aQ!SEwOaqU*98L|VFUNKYxKxdFuEO?ON()lSV zDH%vUAVEcP8+rqxeyXk2*8c*tdDt((u+e{kI6!oqG`h~k>L|1x}62I5YJ);;;f3nO420#4k|$xE$- zTm;}@oTG&z7)_;mm47!vK_-OeFBf-bawCWWuAAL%RiKl(dcsQF-F*f59?I0)h1;sr~Ng2VRjpd?}LN58$k+}wXdy_Aw#iD$!=~8<}Z4%y$6})g^ zmTqhL!P2MOzVxzT`E~=9U^ige`7}oRd?{#Y`;*7d$b{VY_n!v%0ebGgSjZ?ROMt(L(k=-mzyDe^?7BcsvmIOZvXKNnTe3w_&CC#wxV8SbF*8>JJ41~*N_-kG5fKqKwlpjVAzD*&SOX`88V{Y3w)y^c5IPU2NoTmG z!Gu>!rPrEmZ}DHqerx3f>Kb#5nz|b?2dviC)`o&sRwBqB`2f|`NvQ9HW{>+-DpMdr z-9j7foEv`8n><~oT8+-(-QR8aaJ()=MR4jCI*uzlM;V0DiZnSp?nr9U($QIky4M(W zv36j|aR8Z}VPNN}vpq#n}h!%4RIF3u}Ej>4P^T2>l+iO+xJW1sU(;2Z~>EU;GcZJf4 zi2`8NJ!RRudv_m1k9bWfRfJ%EzPkS6NZgLN*2%=ylL}!Y4kZTQ%UDJC&i%8t8BMWa zjA_sw4>K#eEe}3GBJ#n1vMX1unqmc2RaJpHqQKEyTozY?C-vIf+X=NYyJE5X?ykMI z#e@FWtNkmMhk_<1Ch+E8o!NBfjQ%OI?Gp}$0%BYK_Y<};PT1W6G7|K=IDWf$4{icX z8wy|%=7dVNxw%w@Tla=kfNfr@72A9gA3rV9hw25f)e8q6m&DoBue`kiQ5MKB>ha^Z zxF|@2;U}qHujx-#R#pykB4cmQ9J7bQ76L~*FV3-Yk|C1&mlqc=1D}aB9Fa}M$y*~N=hg`Vy8yd$4_RAT*}Z!=ifbBg zyrRJ)gQnp?2=pLz5bJd^JLU(3`#e&22*owo@Qsg*l>hm#e=ix$_aZw(W#y(f*U~}t z2PEVYf(QZ~-R(c`v02`rSP+@Kk=`~|7a%{iS{2|sR#sNZ-DZ{xYq3C{D~B39))c3! zskt;1#9gYF)t;_7K02Cq(?pwW-@X%q4$1G0o?|xwWPn$yK%rW*ZO({SCcff{@ zgV3R>uP#m@Vf7*(F$?k&_K=7+k@d~)n6`hC3kzMU{SP*ps&PgB>anXLfI=K!D{E^A zg?0EYbCvnhN$!Z4UAwkJ&~;ibrtuZ63Kmmdo|1SFvW1m{ytnse!sa}Sv(RUEz4I(A zsjpvKK>D5Z_v$xmjAjZPg31BK=NeK6NO54|@~`YFk0LoGrBlF@^dy7}Uby>f3F6pW ztS%JK0szr`Bbt<_rAS;8E{QXb>4}K#8)}>7c_YwO=58f-Wz?>#R~=AchFi8UHzG~yu6Hd z2TA3JSk|{c;x4hr|j51X&-FgbN`rY3GfRuNnMHUsVD111W*! zVq$aw53XKKI@Zm7^6C4MRpL+q^iHx;@ ztpuHoRZbP!%gN1c_uh9mR4(YN2M-+Jp$?uT&PNIZR94IkfzFLohyXn%P5~*m;+Xzs z77w+OiOC4yZBSrf+jtVIJg_h%>Q{26DIFocj?~DmnFUQFm!quDwyY0z z(?d!X1ggD)O2xJ3W)b9Lub^Yx1_f5vq@?`lNw9O!uQxY0q0XQ}Uv13-1Ho;jvB_XB zHlO#{9m&+&g~B~a_~cB*5*CP&AYwECMJp+RP$Culotw+k$vkxE5V%AlD&wfy=FuQ%l3Y*?kxNEE1k!9ze7(=nu;<=jv(M%n zdS5SD<&j*FTPdli)Ya5xArgxrCvpy`g021;tUgIiO$Lbx)C!6|L?Lu3rn?IoEO?L< zfnNaE)~mLbwg?IWK{EI{yp4bKUQtO|+Sp)~fCg2c0UJ2|>bMEM6%`c)D5jj6S6u9d zlL}1WC=>@hmM`Y!yE{9eB_s%`Oz!M+ACE(I>OtK>i|+Zh=nB0X)k24^&sxtwy!YyaR@KbH|9Am_VEY{! zIB_@t)}w%c2_(z5SHRi48krKOZUC1j6l4(ZM*aaBbhk7#3Q-_!g$FvU^n_ zU0@ab@>`6~3FGh+3Z$r)2XrIk2rSGcr=MR@3$_-reHxpao9pU;VR2e?-Wt6hstF?G z^+%5$iH$AlO0nTGuykeE;+DSh;{xU-Dq=5YoP-vH#{!>Se z@LGOL7WG=y8t6<-W=En0|0V6dCl|DGdc z_#c#E2?<-!^7U}@_`2)!ttwBSKE(|p-9{%QWb^%5S_*{(7o|vOlsk#eem(*?0}YoF`u~s{3~^0a3`+c~xl& zPux`bLWTQ4hOx*{s?@Gty=d|E1)&!{&n+!3{vH_UlF#Pt^mZLRz4gHdbc?`}B})6C z06~MUxKTq2Xbo6`Kg1c70ryI&@=!NUq?YwfKz}UdZ_kYaoPGph8rl((wjIp$FA1y)-#EJ}6jTs5NU# zGo8N{aPrsCX}tvDNl%k-YVIgVAwA(+;6S0zvfJgFrY1ik98BdA064^pw{Pu{lJ_6u ziWPKxC7l!y7^vnW0F?@NnV+8zvH?NrH7>$Qtfn+FI*JM+pVZ=n?pz69E{9!T>9q*c zOpTc5&yT}4LhC9NhbU8vd>fGXx^)6g1mhbw${Qj&J3E2OczJk|Q&QZOh28!bpK55v%R8-7|N1N!*Cym(cx52Dq11SOvh&5V2LzlzG6wXHF&;`7ab16f#5*{k!eX8qbsHXb&{clT%ZJ5|6{!a^gf(c=*D1<4}M_dNDD>zGw~( z4#l{WM~)wN%aY1Q-SW;0W|V_Kb!np*Mw> z-qV${bdiP>6~9c>#;Cg-BYqDyiXz~b4g-N_n5Z3Ad*)0S zV_aj8>xyL~Q96=CVxmMCV`M}4^XK0sJeWv65RN?xGubJj&$$^9R&(VePC|RhX=7t! zZxgEGMRRnY0Qll^4*U4{q_L-|j{W}qTRYF}nuZ2nXESy;giZ|H1mL&bO2q5X4acm?)k$PO1dnw=H)Kkv zgkSR}wNCj+Ncd``Pqn4B!sG!3tyND9SRA0^H;XrNPonX!x)2V96qrf z^Qh==ZsrClLFe~I&SQf?9~tXCTJPV#M@554C5#cl=|=jN_eo@ACvqkfP50dK?rxo# zJkTH!QYhBdf3zf^o`0 zHM2l8nc8`u2JPJpP)a=KW@jZ;ol5iw28z{%Un(>2C!7e?5wr?He(>NyO#^q+hNmq5 zT3P7xZSRCfM|bC$tCaO+vC7-q|3NqZ57Ibx>NDEpQ!X)$g(wJwLL(xgpWPYLI0m6E zvSBpMXKbR@=-m9T&KY3(f-W2M_s}{xfBrmv!H0kSE|9;`r{M**LkEB$VgY3$$PtO? zf(WIyK){uI%1p2!P~3-TsA=fs4BQ;Ai4O?E=QKO(0A(AkU7){rM9@$*eSOXb2Q97dfR|BF zP5ENLklL-L`YYBTv5)a zq5yEQu{0`~WvEoS$;oK@CFke!purF0ly#Qkwf$KoW6`Pc9&b=VAqB({3d$-p>v&D{ zR7W>A)-*LV>T`R6h>x&q9H0?62cZhVfH-&Uy;Tdbx&08pQ3cd2AlXWR=0n?G=|}FO z=aE~QK^fy?W2JgrWqtj6dCAGiElD!?{{M)yeS4<3&_rx(JVN~b61DXWsD1>S4nB(ljH=y(?{;ar_;TOi z1+)p_(1DV>fqyNu>0)SbK%-D2{fLyRDpkP^F8~oxxqeVBf)duZM78~8i8v2e6wQ@A z1X*ls%INs`u_H&ird$e&aPT0xL1=~seQ{b9UaZ0L za^a$$Ojdal6B88fW&w5Wh$5RVH9ftIh*tDTA?xngu_JyDq6sIJjg{~Mx-@}w3^fRD z9&&OO_`w1K;ftvr zgjEq4@$Tv2J{pA>ky&_V16Rfex`AoFBDr8kb>H8|EJ$HyL0c}y$WGYvmimy6oss8i;zr0CLXFq!uM%1LFq%#Z*lBzaS z;=KRk>aBrY3#tNK4oq_2fc*u6cUwsO7ivs$fdK*g4jx>1PddHxe=M+?FjnGF>g(&H z(aX`0DMIst324ZM4>}&W&&q79_{a5twB-$wha4eAvwcK~h`6L+tCNwzl58k%1O+C>4nzTH zefbwg5#32-JsLE+6m!>k^S@Ok`m^`N!%P4E;@i~!^Oyho6A{t>t2KyTEN_MqlE}K5 zdL~mN>|yo4E4+CuAn|7$mA)Q~#_gS*p5zTC7c2|L2TuO`&s_RQImdQ!%;+hpJ`3Xc z&nN$*>g`YRf8QV)Otc+#L`47l)Bg|GK&hvDS22mC>N7?;Dyf-!fr%g%tn${ulddWK zxecRM4xOA&!TmPbbA^Ym0%|0+GN~!EaFi zM_KQzU-?PBe!A3GlB!=Vh?`{;REdjpm+4Ae31{w(O6kZh4z$0VgwW>Dg}#Cj43{z{ zIpiQfamaY+M$VrbRfB$mG7I4)J|g1isZ)Ip$CJYt<&s)xK%L?3K%0A#4JGs3xpN{S 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