| 1 | #!/usr/bin/env python |
|---|
| 2 | # -*- coding: utf8 -*- |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | retina.py |
|---|
| 6 | ========= |
|---|
| 7 | |
|---|
| 8 | The retina for 'benchmark one'. This should feed the 'Hyper-Column' (see |
|---|
| 9 | https://facets.kip.uni-heidelberg.de/private/wiki/index.php/V1_hypercolumn ) |
|---|
| 10 | |
|---|
| 11 | |
|---|
| 12 | For this retina, it consists of 2 layers of neurons on a rectangular |
|---|
| 13 | grid connected in a one to one fashion. Here, we more use primate 'Phasic' |
|---|
| 14 | responses originate with morphologically larger ganglion cell types with fast |
|---|
| 15 | optic nerve fiber conduction velocities (~4 m/s, Gouras, 1969). Microelectrodes |
|---|
| 16 | staining of such cells shows that they are 'parasol' types (Dacey and Lee, 1994). |
|---|
| 17 | ON types branch low in the inner plexiform layer (sublamina b), while OFF types |
|---|
| 18 | branch high in the inner plexiform layer (sublamina a) following the classic |
|---|
| 19 | branching pattern for ON and OFF center cells (Nelson et al, 1978; Dacey and Lee, |
|---|
| 20 | 1994). Phasic cells are often referred to as the 'magnocellular' or 'M-cell' |
|---|
| 21 | pathway because their fibers terminate in the magnocellular layer of the lateral |
|---|
| 22 | geniculate nucleus of the thalamus. Near the fovea receptive fields of phasic |
|---|
| 23 | cells are 2-3 times larger than those of tonic cells and may be 10 times larger |
|---|
| 24 | in peripheral retina. |
|---|
| 25 | |
|---|
| 26 | The data times outputted by the model are the input to the LGN ( |
|---|
| 27 | http://en.wikipedia.org/wiki/Lateral_geniculate_nucleus ). Time for travelling |
|---|
| 28 | through the retina being compensated : |
|---|
| 29 | |
|---|
| 30 | |
|---|
| 31 | @article{Stanford87, |
|---|
| 32 | Author = {Stanford, L R}, |
|---|
| 33 | Journal = {Science}, |
|---|
| 34 | Month = {Oct}, |
|---|
| 35 | Number = {4825}, |
|---|
| 36 | Pages = {358-360}, |
|---|
| 37 | Title = {Conduction velocity variations minimize conduction time differences |
|---|
| 38 | among retinal ganglion cell axons}, |
|---|
| 39 | Volume = {238}, |
|---|
| 40 | Year = {1987}} |
|---|
| 41 | |
|---|
| 42 | To remember (in the cat) 5° of visual angle ~ 1 mm of retina |
|---|
| 43 | |
|---|
| 44 | See : |
|---|
| 45 | Data for parameters http://webvision.med.utah.edu/GCPHYS1.HTM (TODO LUP: define |
|---|
| 46 | different sets for different animals (cat, monkey, human)) |
|---|
| 47 | Morphology : http://webvision.med.utah.edu/GC1.html |
|---|
| 48 | Topics on CVonline http://homepages.inf.ed.ac.uk/cgi/rbf/CVONLINE/phase3entries.pl?TAG59 |
|---|
| 49 | |
|---|
| 50 | TODO (LuP) : use Ringach 07 for setting the neurons centers |
|---|
| 51 | TODO (LuP) : the grid is rectangular, but should be hexagonal / log-polar conformation |
|---|
| 52 | / define boudaries in biological coordinates |
|---|
| 53 | |
|---|
| 54 | from scipy.interpolate import interpolate |
|---|
| 55 | from numpy.random import randn |
|---|
| 56 | from numpy import * |
|---|
| 57 | |
|---|
| 58 | data = arange(16) |
|---|
| 59 | data = data+1 |
|---|
| 60 | data = data.reshape(4,4) |
|---|
| 61 | xrange = arange(4) |
|---|
| 62 | yrange = arange(4) |
|---|
| 63 | X,Y = meshgrid(xrange,yrange) |
|---|
| 64 | |
|---|
| 65 | outgrid = interpolate.interp2d(X,Y,data,kind='linear') |
|---|
| 66 | xi = array([0,0.5,1,1.5,2,2.5,3]) |
|---|
| 67 | yi = xi |
|---|
| 68 | |
|---|
| 69 | z = outgrid(xi,yi) |
|---|
| 70 | |
|---|
| 71 | |
|---|
| 72 | |
|---|
| 73 | TODO (LuP) : allow the use of moving images / |
|---|
| 74 | TODO (LuP) : justify or not SFA (since we are interested in synchrony effect, we should |
|---|
| 75 | rather use a simpler model) / noise model |
|---|
| 76 | TODO (LuP) : use real delays |
|---|
| 77 | TODO (LuP) : get significative statistics to the retina into that file |
|---|
| 78 | TODO (Lup) : use another cell type? |
|---|
| 79 | |
|---|
| 80 | TODO (Lup) : use nest2 definitively |
|---|
| 81 | |
|---|
| 82 | $ Id $ |
|---|
| 83 | |
|---|
| 84 | """ |
|---|
| 85 | import datetime |
|---|
| 86 | |
|---|
| 87 | import pyNN.nest2 as sim |
|---|
| 88 | from pyNN.utility import Timer |
|---|
| 89 | from NeuroTools.signals import load_spikelist |
|---|
| 90 | import os, tempfile |
|---|
| 91 | import numpy |
|---|
| 92 | from NeuroTools.parameters import ParameterSet |
|---|
| 93 | |
|---|
| 94 | |
|---|
| 95 | class Retina(object): |
|---|
| 96 | """ |
|---|
| 97 | Model class for the retina. |
|---|
| 98 | |
|---|
| 99 | """ |
|---|
| 100 | def __init__(self,N=1000): |
|---|
| 101 | """ |
|---|
| 102 | Default parameters for the retina of size NxN. |
|---|
| 103 | |
|---|
| 104 | Sets up parameters and the whole structure of the dictionary / HDF file in url. |
|---|
| 105 | It contains all relevant parameters and stores them to a dictionary for |
|---|
| 106 | clarity &future compatibility with XML exports. |
|---|
| 107 | |
|---|
| 108 | """ |
|---|
| 109 | #try : |
|---|
| 110 | # url = "https://neuralensemble.org/svn/NeuroTools/trunk/examples/retina/retina.param" |
|---|
| 111 | # self.params = ParameterSet(url) |
|---|
| 112 | # print "Loaded parameters from SVN" |
|---|
| 113 | #except : |
|---|
| 114 | params = {} # a dictionary containing all parameters |
|---|
| 115 | # === Define parameters ======================================================== |
|---|
| 116 | # LUP: get running file name and include script in HDF5? |
|---|
| 117 | |
|---|
| 118 | params = {'description' : 'default retina', |
|---|
| 119 | 'N': N, # integer; square of total number of Ganglion Cells LUP: how do we include types and units in parameters? (or by default it is a float in ISO standards) TODO: go rectangular |
|---|
| 120 | 'N_ret': .2, # float; diameter of Ganglion Cell's RF (max: 1) |
|---|
| 121 | 'K_ret': 4.0, # float; ratio of center vs. surround in DOG |
|---|
| 122 | 'dt' : 0.1,# discretization step in simulations (ms) |
|---|
| 123 | 'simtime' : 40000*0.1, # float; (ms) |
|---|
| 124 | 'syn_delay' : 1.0, # float; (ms) |
|---|
| 125 | 'noise_std' : 5.0, # (nA) standard deviation of the internal noise |
|---|
| 126 | 'snr' : 2.0, # (nA) maximum signal |
|---|
| 127 | 'weight' : 1.0, # |
|---|
| 128 | #'threads' : 2, 'kernelseeds' : [43210987, 394780234], # array with one element per thread |
|---|
| 129 | #'threads' : 1, |
|---|
| 130 | 'kernelseed' : 4321097, # array with one element per thread |
|---|
| 131 | # seed for random generator used when building connections |
|---|
| 132 | 'connectseed' : 12345789, # seed for random generator(s) used during simulation |
|---|
| 133 | 'initialized' : datetime.datetime.now().isoformat() # the date in ISO 8601 format to avoid overriding old simulations |
|---|
| 134 | } |
|---|
| 135 | |
|---|
| 136 | # retinal neurons parameters |
|---|
| 137 | params['parameters_gc'] = {'Theta':-57.0, 'Vreset': -70.0,'Vinit': -70.0, |
|---|
| 138 | 'TauR': 0.5, 'gL':28.95, |
|---|
| 139 | 'C':289.5, 'V_reversal_E': 0.0, 'V_reversal_I': -75.0, 'TauSyn_E':1.5, |
|---|
| 140 | 'TauSyn_I': 10.0, 'V_reversal_sfa': -70.0, 'q_sfa': 0., #14.48, |
|---|
| 141 | 'Tau_sfa':110.0, 'V_reversal_relref': -70.0, 'q_relref': 3214.0, |
|---|
| 142 | 'Tau_relref': 1.97}#,'python':True} |
|---|
| 143 | |
|---|
| 144 | ## default input image # TODO add start and stop time |
|---|
| 145 | # define the center of every neuron in normalized visual angle / retinal space |
|---|
| 146 | |
|---|
| 147 | #X,Y = numpy.meshgrid(numpy.linspace(-N/2, N/2,N),numpy.linspace(-N/2,N/2,N)) |
|---|
| 148 | X,Y = numpy.random.rand(N)*2-1, numpy.random.rand(N)*2 -1 |
|---|
| 149 | |
|---|
| 150 | # Generate a DOG excitation on the input layer of the GC |
|---|
| 151 | # Based on the assumptions of the DOG model (Enroth-Cugell and Robson, 1966) that ganglion cells linearly add signals from both center and surround mechanisms for all points in space |
|---|
| 152 | # this is the impulse response to a discrete dirac in the center to some specific luminance / excentricity value |
|---|
| 153 | # easy : extend to input images (simply by convoluting the image with this kernel) |
|---|
| 154 | # hard : extend to time varrying movies (not only a step) |
|---|
| 155 | params['position'] = [X,Y] |
|---|
| 156 | R2= X**2 + Y**2 |
|---|
| 157 | N, N_ret, K_ret = params['N'], params['N_ret'], params['K_ret'] |
|---|
| 158 | amplitude = (numpy.exp(-R2 / ( 2 * N_ret**2) ) - 1/K_ret**2 * numpy.exp(-R2 / ( 2 * N_ret**2 * K_ret**2) )) / (1 - 1/K_ret**2) |
|---|
| 159 | #amplitude *= params['noise_std'] #Â scaled by the noise variance |
|---|
| 160 | #file.setStructure({'amplitude' : amplitude }, "/build", createparents=True) |
|---|
| 161 | params['amplitude'] = amplitude |
|---|
| 162 | |
|---|
| 163 | self.params = ParameterSet(params) |
|---|
| 164 | #self.params.save('file://' + os.getcwd() + '/retina.param') |
|---|
| 165 | |
|---|
| 166 | |
|---|
| 167 | def run(self,params,verbose=True): |
|---|
| 168 | """ |
|---|
| 169 | params are the parameters to use |
|---|
| 170 | |
|---|
| 171 | """ |
|---|
| 172 | tmpdir = tempfile.mkdtemp() |
|---|
| 173 | myTimer = Timer() |
|---|
| 174 | # === Build the network ======================================================== |
|---|
| 175 | if verbose: print "Setting up simulation" |
|---|
| 176 | myTimer.start() # start timer on construction |
|---|
| 177 | sim.setup(timestep=params['dt'],max_delay=params['syn_delay']) |
|---|
| 178 | N = params['N'] |
|---|
| 179 | #dc_generator |
|---|
| 180 | phr_ON = sim.Population((N,),'dc_generator') |
|---|
| 181 | phr_OFF = sim.Population((N,),'dc_generator') |
|---|
| 182 | |
|---|
| 183 | |
|---|
| 184 | for factor, phr in [(-params['snr'],phr_OFF),(params['snr'],phr_ON)]: |
|---|
| 185 | phr.tset('amplitude', params['amplitude'] * factor ) |
|---|
| 186 | phr.set({ 'start' : params['simtime']/4, 'stop' : params['simtime']/4*3}) |
|---|
| 187 | |
|---|
| 188 | # internal noise model (see benchmark_noise) |
|---|
| 189 | noise_ON = sim.Population((N,),'noise_generator',{'mean':0.,'std':params['noise_std']}) |
|---|
| 190 | noise_OFF = sim.Population((N,),'noise_generator',{'mean':0.,'std':params['noise_std']}) |
|---|
| 191 | |
|---|
| 192 | |
|---|
| 193 | # target ON and OFF populations (what about a tridimensional Population?) |
|---|
| 194 | out_ON = sim.Population((N,), sim.IF_curr_alpha)#'IF_cond_alpha) #iaf_sfa_neuron')# EIF_cond_alpha_isfa_ista, IF_cond_exp_gsfa_grr,sim.IF_cond_alpha)#'iaf_sfa_neuron',params['parameters_gc'])#'iaf_cond_neuron')# IF_cond_alpha) # |
|---|
| 195 | out_OFF = sim.Population((N,), sim.IF_curr_alpha)#'IF_cond_alpha) #IF_curr_alpha)#'iaf_sfa_neuron')#sim.IF_curr_alpha)#,params['parameters_gc']) |
|---|
| 196 | |
|---|
| 197 | |
|---|
| 198 | # initialize membrane potential TODO: and conductances? |
|---|
| 199 | from pyNN.random import RandomDistribution, NumpyRNG |
|---|
| 200 | rng = NumpyRNG(seed=params['kernelseed']) |
|---|
| 201 | vinit_distr = RandomDistribution(distribution='uniform',parameters=[-70,-55], rng=rng) |
|---|
| 202 | for out_ in [out_ON, out_OFF]: |
|---|
| 203 | out_.randomInit(vinit_distr) |
|---|
| 204 | |
|---|
| 205 | |
|---|
| 206 | retina_proj_ON = sim.Projection(phr_ON, out_ON, sim.OneToOneConnector()) |
|---|
| 207 | retina_proj_ON.setWeights(params['weight']) |
|---|
| 208 | # TODO fix setWeight, add setDelays to 10 ms (relative to stimulus onset) |
|---|
| 209 | retina_proj_OFF = sim.Projection(phr_OFF, out_OFF, sim.OneToOneConnector()) |
|---|
| 210 | retina_proj_OFF.setWeights(params['weight']) |
|---|
| 211 | |
|---|
| 212 | noise_proj_ON = sim.Projection(noise_ON, out_ON, sim.OneToOneConnector()) |
|---|
| 213 | noise_proj_ON.setWeights(params['weight']) |
|---|
| 214 | noise_proj_OFF = sim.Projection(noise_OFF, out_OFF, sim.OneToOneConnector()) # implication if ON and OFF have the same noise input? |
|---|
| 215 | noise_proj_OFF.setWeights(params['weight']) |
|---|
| 216 | |
|---|
| 217 | |
|---|
| 218 | out_ON.record() |
|---|
| 219 | out_OFF.record() |
|---|
| 220 | |
|---|
| 221 | # reads out time used for building |
|---|
| 222 | buildCPUTime= myTimer.elapsedTime() |
|---|
| 223 | |
|---|
| 224 | # === Run simulation =========================================================== |
|---|
| 225 | if verbose: print "Running simulation" |
|---|
| 226 | |
|---|
| 227 | myTimer.reset() # start timer on construction |
|---|
| 228 | sim.run(params['simtime']) |
|---|
| 229 | simCPUTime = myTimer.elapsedTime() |
|---|
| 230 | |
|---|
| 231 | myTimer.reset() # start timer on construction |
|---|
| 232 | # TODO LUP use something like "for pop in [phr, out]" ? |
|---|
| 233 | out_ON_filename=os.path.join(tmpdir,'out_on.gdf') |
|---|
| 234 | out_OFF_filename=os.path.join(tmpdir,'out_off.gdf') |
|---|
| 235 | out_ON.printSpikes(out_ON_filename)# |
|---|
| 236 | out_OFF.printSpikes(out_OFF_filename)# |
|---|
| 237 | |
|---|
| 238 | # TODO LUP get out_ON_DATA on a 2D grid independantly of out_ON.cell.astype(int) |
|---|
| 239 | out_ON_DATA = load_spikelist(out_ON_filename,range(N), |
|---|
| 240 | t_start=0.0, t_stop=params['simtime']) |
|---|
| 241 | out_OFF_DATA = load_spikelist(out_OFF_filename,range(N), |
|---|
| 242 | t_start=0.0, t_stop=params['simtime']) |
|---|
| 243 | |
|---|
| 244 | out = {'out_ON_DATA':out_ON_DATA, |
|---|
| 245 | 'out_OFF_DATA':out_OFF_DATA}#,'out_ON_pos':out_ON} |
|---|
| 246 | # cleans up |
|---|
| 247 | os.remove(out_ON_filename) |
|---|
| 248 | os.remove(out_OFF_filename) |
|---|
| 249 | os.rmdir(tmpdir) |
|---|
| 250 | writeCPUTime = myTimer.elapsedTime() |
|---|
| 251 | |
|---|
| 252 | if verbose: |
|---|
| 253 | print "\nRetina Network Simulation:" |
|---|
| 254 | print(params['description']) |
|---|
| 255 | print "Number of Neurons : ", N |
|---|
| 256 | print "Output rate (ON) : ", out_ON_DATA.mean_rate(), "Hz/neuron in ", params['simtime'], "ms" |
|---|
| 257 | print "Output rate (OFF) : ", out_OFF_DATA.mean_rate(), "Hz/neuron in ",params['simtime'], "ms" |
|---|
| 258 | print("Build time : %g s" % buildCPUTime) |
|---|
| 259 | print("Simulation time : %g s" % simCPUTime) |
|---|
| 260 | print("Writing time : %g s" % writeCPUTime) |
|---|
| 261 | |
|---|
| 262 | return out |
|---|
| 263 | |
|---|
| 264 | |
|---|
| 265 | if __name__ == '__main__': |
|---|
| 266 | |
|---|
| 267 | ret = Retina(100) |
|---|
| 268 | out = ret.run(ret.params) |
|---|
| 269 | # plotting |
|---|
| 270 | import pylab |
|---|
| 271 | fig = pylab.figure(1) |
|---|
| 272 | z = pylab.subplot(121) |
|---|
| 273 | out['out_ON_DATA'].raster_plot(display=z, id_list=range(20), kwargs={'color':'r'}) |
|---|
| 274 | z = pylab.subplot(122) |
|---|
| 275 | out['out_OFF_DATA'].raster_plot(display=z, id_list=range(20), kwargs={'color':'b'}) |
|---|
| 276 | fig = pylab.figure(2) |
|---|
| 277 | z = pylab.subplot(111) |
|---|
| 278 | out['out_ON_DATA'].firing_rate(ret.params.simtime/100, display=z, kwargs={'label':'ON','color':'r'}) |
|---|
| 279 | out['out_OFF_DATA'].firing_rate(ret.params.simtime/100, display=z, kwargs={'label':'OFF','color':'b'}) |
|---|
| 280 | pylab.legend() |
|---|
| 281 | |
|---|