root/trunk/src/signals.py

Revision 362, 121.8 kB (checked in by LaurentPerrinet, 1 month ago)

Small fixes on example/single _neuron and sfn2008 / corrected ylabel in isi plot

Line 
1 """
2 NeuroTools.signals
3 ==================
4
5 A collection of functions to create, manipulate and play with spikes and analog
6 signals.
7
8 Classes
9 -------
10
11 SpikeTrain       - object representing a spike train, for one cell. Useful for plots,
12                    calculations such as ISI, CV, mean rate(), ...
13 SpikeList        - object representing the activity of a population of neurons. Functions as a
14                    dictionary of SpikeTrain objects, with methods to compute firing rate,
15                    ISI, CV, cross-correlations, and so on.
16 AnalogSignal     - object representing an analog signal, with its data. Can be used to do
17                    threshold detection, event triggered averages, ...
18 AnalogSignalList - list of AnalogSignal objects, again with methods such as mean, std, plot,
19                    and so on
20 VmList           - AnalogSignalList object used for Vm traces
21 ConductanceList  - AnalogSignalList object used for conductance traces
22 CurrentList      - AnalogSignalList object used for current traces
23
24 Functions
25 ---------
26
27 load_spikelist       - load a SpikeList object from a file. Expects a particular format.
28                        Can also load data in a different format, but then you have
29                        to write your own File object that will know how to read the data (see io.py)
30 load_vmlist          - function to load a VmList object (inherits from AnalogSignalList) from a file.
31                        Same comments on format as previously.
32 load_currentlist     - function to load a CurrentList object (inherits from AnalogSignalList) from a file.
33                        Same comments on format as previously.
34 load_conductancelist - function to load a ConductanceList object (inherits from AnalogSignalList) from a file.
35                        Same comments on format as previously. load_conductancelist returns two
36                        ConductanceLists, one for the excitatory conductance and one for the inhibitory conductance
37 load                 - a generic loader for all the previous load methods.
38 """
39
40 import os, re
41 from NeuroTools import check_dependency, check_numpy_version
42 from NeuroTools import analysis
43 from NeuroTools.io import *
44 from NeuroTools.plotting import get_display, set_axis_limits, set_labels, SimpleMultiplot
45
46 if check_dependency('psyco'):
47     import psyco
48     psyco.full()
49
50 import numpy
51 newnum = check_numpy_version()
52
53 HAVE_PYLAB = check_dependency('pylab')
54 HAVE_MATPLOTLIB = check_dependency('matplotlib')
55 if HAVE_PYLAB:
56     import pylab
57 else:
58     PYLAB_ERROR = "The pylab package was not detected"
59 if not HAVE_MATPLOTLIB:
60     MATPLOTLIB_ERROR = "The matplotlib package was not detected"
61
62 class SpikeTrain(object):
63     """
64     SpikeTrain(spikes_times, t_start=None, t_stop=None)
65     This class defines a spike train as a list of times events.
66
67     Event times are given in a list (sparse representation) in milliseconds.
68
69     Inputs:
70         spike_times - a list/numpy array of spike times (in milliseconds)
71         t_start     - beginning of the SpikeTrain (if not, this is infered)
72         t_stop      - end of the SpikeTrain (if not, this is infered)
73
74     Examples:
75         >> s1 = SpikeTrain([0.0, 0.1, 0.2, 0.5])
76         >> s1.isi()
77             array([ 0.1,  0.1,  0.3])
78         >> s1.mean_rate()
79             8.0
80         >> s1.cv_isi()
81             0.565685424949
82     """
83
84     #######################################################################
85     ## Constructor and key methods to manipulate the SpikeTrain objects  ##
86     #######################################################################
87     def __init__(self, spike_times, t_start=None, t_stop=None):
88         """
89         Constructor of the SpikeTrain object
90         
91         See also
92             SpikeTrain
93         """
94
95         self.t_start     = t_start
96         self.t_stop      = t_stop
97         self.spike_times = numpy.array(spike_times, float)
98
99         # If t_start is not None, we resize the spike_train keeping only
100         # the spikes with t >= t_start
101         if self.t_start is not None:
102             self.spike_times = numpy.extract((self.spike_times >= self.t_start), self.spike_times)
103
104         # If t_stop is not None, we resize the spike_train keeping only
105         # the spikes with t <= t_stop
106         if self.t_stop is not None:
107             self.spike_times = numpy.extract((self.spike_times <= self.t_stop), self.spike_times)
108
109         # We sort the spike_times. May be slower, but is necessary by the way for quite a
110         # lot of methods...
111         self.spike_times = numpy.sort(self.spike_times, kind="quicksort")
112         # Here we deal with the t_start and t_stop values if the SpikeTrain
113         # is empty, with only one element or several elements, if we
114         # need to guess t_start and t_stop
115         # no element : t_start = 0, t_stop = 0.1
116         # 1 element  : t_start = time, t_stop = time + 0.1
117         # several    : t_start = min(time), t_stop = max(time)
118         
119         size = len(self.spike_times)
120         if size == 0:
121             if self.t_start is None:
122                 self.t_start = 0
123             if self.t_stop is None:
124                 self.t_stop  = 0.1
125         elif size == 1: # spike list may be empty
126             if self.t_start is None:
127                 self.t_start = self.spike_times[0]
128             if self.t_stop is None:
129                 self.t_stop = self.spike_times[0] + 0.1
130         elif size > 1:
131             if self.t_start is None:
132                 self.t_start = numpy.min(self.spike_times)
133             if numpy.any(self.spike_times < self.t_start):
134                 raise ValueError("Spike times must not be less than t_start")
135             if self.t_stop is None:
136                 self.t_stop = numpy.max(self.spike_times)
137             if numpy.any(self.spike_times > self.t_stop):
138                 raise ValueError("Spike times must not be greater than t_stop")
139
140         if self.t_start >= self.t_stop :
141             raise Exception("Incompatible time interval : t_start = %s, t_stop = %s" % (self.t_start, self.t_stop))
142         if self.t_start < 0:
143             raise ValueError("t_start must not be negative")
144         if numpy.any(self.spike_times < 0):
145             raise ValueError("Spike times must not be negative")
146
147     def __str__(self):
148         return str(self.spike_times)
149
150     def __len__(self):
151         return len(self.spike_times)
152    
153     def __getslice__(self, i, j):
154         """
155         Return a sublist of the spike_times vector of the SpikeTrain
156         """
157         return self.spike_times[i:j]
158    
159     def time_parameters(self):
160         """
161         Return the time parameters of the SpikeTrain (t_start, t_stop)
162         """
163         return (self.t_start, self.t_stop)
164    
165     def is_equal(self, spktrain):
166         """
167         Return True if the SpikeTrain object is equal to one other SpikeTrain, i.e
168         if they have same time parameters and same spikes_times
169         
170         Inputs:
171             spktrain - A SpikeTrain object
172         
173         See also:
174             time_parameters()
175         """
176         test = (self.time_parameters() == spktrain.time_parameters())
177         return numpy.all(self.spike_times == spktrain.spike_times) and test
178    
179     def copy(self):
180         """
181         Return a copy of the SpikeTrain object
182         """
183         return SpikeTrain(self.spike_times, self.t_start, self.t_stop)
184
185
186     def duration(self):
187         """
188         Return the duration of the SpikeTrain
189         """
190         return self.t_stop - self.t_start
191    
192    
193     def merge(self, spiketrain):
194         """
195         Add the spike times from a spiketrain to the current SpikeTrain
196         
197         Inputs:
198             spiketrain - The SpikeTrain that should be added
199         
200         Examples:
201             >> a = SpikeTrain(range(0,100,10),0.1,0,100)
202             >> b = SpikeTrain(range(400,500,10),0.1,400,500)
203             >> a.merge(b)
204             >> a.spike_times
205                 [   0.,   10.,   20.,   30.,   40.,   50.,   60.,   70.,   80.,
206                 90.,  400.,  410.,  420.,  430.,  440.,  450.,  460.,  470.,
207                 480.,  490.]
208             >> a.t_stop
209                 500
210         """
211         self.spike_times = numpy.insert(self.spike_times, self.spike_times.searchsorted(spiketrain.spike_times), \
212                                         spiketrain.spike_times)
213         self.t_start     = min(self.t_start, spiketrain.t_start)
214         self.t_stop      = max(self.t_stop, spiketrain.t_stop)
215
216     def format(self, relative=False, quantized=False):
217         """
218         Return an array with a new representation of the spike times
219         
220         Inputs:
221             relative  - if True, spike times are expressed in a relative
222                        time compared to the previsous one
223             quantized - a value to divide spike times with before rounding
224             
225         Examples:
226             >> st.spikes_times=[0, 2.1, 3.1, 4.4]
227             >> st.format(relative=True)
228                 [0, 2.1, 1, 1.3]
229             >> st.format(quantized=2)
230                 [0, 1, 2, 2]
231         """
232         spike_times = self.spike_times.copy()
233
234         if relative and len(spike_times) > 0:
235             spike_times[1:] = spike_times[1:] - spike_times[:-1]
236
237         if quantized:
238             assert quantized > 0, "quantized must either be False or a positive number"
239             #spike_times =  numpy.array([time/self.quantized for time in spike_times],int)
240             spike_times = (spike_times/quantized).round().astype('int')
241
242         return spike_times
243
244     def jitter(self,jitter):
245         """
246         Returns a new SpikeTrain with spiketimes jittered by a normal distribution.
247
248         Inputs:
249               jitter - sigma of the normal distribution
250
251         Examples:
252               >> st_jittered = st.jitter(2.0)
253         """
254        
255         return SpikeTrain(self.spike_times+jitter*(numpy.random.normal(loc=0.0,scale=1.0,size=self.spike_times.shape[0])),t_start=self.t_start,t_stop=self.t_stop)
256        
257
258
259     #######################################################################
260     ## Analysis methods that can be applied to a SpikeTrain object       ##
261     #######################################################################
262     
263     def isi(self):
264         """
265         Return an array with the inter-spike intervals of the SpikeTrain
266         
267         Examples:
268             >> st.spikes_times=[0, 2.1, 3.1, 4.4]
269             >> st.isi()
270                 [2.1, 1., 1.3]
271         
272         See also
273             cv_isi
274         """
275         return numpy.diff(self.spike_times)
276
277     def mean_rate(self, t_start=None, t_stop=None):
278         """
279         Returns the mean firing rate between t_start and t_stop, in Hz
280         
281         Inputs:
282             t_start - in ms. If not defined, the one of the SpikeTrain object is used
283             t_stop  - in ms. If not defined, the one of the SpikeTrain object is used
284         
285         Examples:
286             >> spk.mean_rate()
287                 34.2
288         """
289         if (t_start == None) & (t_stop == None):
290             t_start = self.t_start
291             t_stop  = self.t_stop
292             idx     = self.spike_times
293         else:
294             if t_start == None:
295                 t_start = self.t_start
296             else:
297                 t_start = max(self.t_start, t_start)
298             if t_stop == None:
299                 t_stop=self.t_stop
300             else:
301                 t_stop = min(self.t_stop, t_stop)
302             idx = numpy.where((self.spike_times >= t_start) & (self.spike_times <= t_stop))[0]
303         return 1000.*len(idx)/(t_stop-t_start)
304
305     def cv_isi(self):
306         """
307         Return the coefficient of variation of the isis.
308         
309         cv_isi is the ratio between the standard deviation and the mean of the ISI
310           The irregularity of individual spike trains is measured by the squared
311         coefficient of variation of the corresponding inter-spike interval (ISI)
312         distribution normalized by the square of its mean.
313           In point processes, low values reflect more regular spiking, a
314         clock-like pattern yields CV2= 0. On the other hand, CV2 = 1 indicates
315         Poisson-type behavior. As a measure for irregularity in the network one
316         can use the average irregularity across all neurons.
317         
318         http://en.wikipedia.org/wiki/Coefficient_of_variation
319         
320         See also
321             isi, cv_kl
322             
323         """
324         isi = self.isi()
325         if len(isi) > 0:
326             return numpy.std(isi)/numpy.mean(isi)
327         else:
328             logging.debug("Warning, a CV can't be computed because there are not enough spikes")
329             return numpy.nan
330
331     def cv_kl(self, bins=100):
332         """
333         Provides a measure for the coefficient of variation to describe the
334         regularity in spiking networks. It is based on the Kullback-Leibler
335         divergence and decribes the difference between a given
336         interspike-interval-distribution and an exponential one (representing
337         poissonian spike trains) with equal mean.
338         It yields 1 for poissonian spike trains and 0 for regular ones.
339         
340         Reference:
341             http://incm.cnrs-mrs.fr/LaurentPerrinet/Publications/Voges08fens
342         
343         Inputs:
344             bins - the number of bins used to gather the ISI
345         
346         Examples:
347             >> spklist.cv_kl(100)
348                 0.98
349         
350         See also:
351             cv_isi
352             
353         """
354         isi = self.isi() / 1000.
355         if len(isi) < 2:
356             logging.debug("Warning, a CV can't be computed because there are not enough spikes")
357             return numpy.nan
358         else:
359             if newnum:
360                 proba_isi, xaxis = numpy.histogram(isi, bins=bins, normed=True, new=True)
361                 xaxis = xaxis[:-1]
362             else:
363                 proba_isi, xaxis = numpy.histogram(isi, bins=bins, normed=True)
364             proba_isi /= numpy.sum(proba_isi)
365             bin_size = xaxis[1]-xaxis[0]
366             # differential entropy: http://en.wikipedia.org/wiki/Differential_entropy
367             KL = - numpy.sum(proba_isi * numpy.log(proba_isi+1e-16)) + numpy.log(bin_size)
368             KL -= -numpy.log(self.mean_rate()) + 1.
369             CVkl=numpy.exp(-KL)
370             return CVkl
371    
372
373     def fano_factor_isi(self):
374         """
375         Return the fano factor of this spike trains ISI.
376         
377         The Fano Factor is defined as the variance of the isi divided by the mean of the isi
378         
379         http://en.wikipedia.org/wiki/Fano_factor
380         
381         See also
382             isi, cv_isi
383         """
384         isi = self.isi()
385         if len(isi) > 0:
386             fano = numpy.var(isi)/numpy.mean(isi)
387             return fano
388         else:
389             raise Exception("No spikes in the SpikeTrain !")
390
391     def time_axis(self, time_bin=10):
392         """
393         Return a time axis between t_start and t_stop according to a time_bin
394         
395         Inputs:
396             time_bin - the bin width
397             
398         Examples:
399             >> st = SpikeTrain(range(100),0.1,0,100)
400             >> st.time_axis(10)
401                 [ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90n 100]
402         
403         See also
404             time_histogram
405         """
406         if newnum:
407             axis = numpy.arange(self.t_start, self.t_stop+time_bin, time_bin)
408         else:
409             axis = numpy.arange(self.t_start, self.t_stop, time_bin)
410         return axis
411
412     def raster_plot(self, t_start=None, t_stop=None, display=True, kwargs={}):
413         """
414         Generate a raster plot with the SpikeTrain in a subwindow of interest,
415         defined by t_start and t_stop.
416         
417         Inputs:
418             t_start - in ms. If not defined, the one of the SpikeTrain object is used
419             t_stop  - in ms. If not defined, the one of the SpikeTrain object is used
420             display - if True, a new figure is created. Could also be a subplot
421             kwargs  - dictionary contening extra parameters that will be sent to the plot
422                       function
423         
424         Examples:
425             >> z = subplot(221)
426             >> st.raster_plot(display=z, kwargs={'color':'r'})
427         
428         See also
429             SpikeList.raster_plot
430         """
431         if t_start is None: t_start = self.t_start
432         if t_stop is None:  t_stop = self.t_stop
433         spikes = numpy.extract((self.spike_times >= t_start) & (self.spike_times <= t_stop), self.spike_times)
434         subplot = get_display(display)
435         if not subplot or not HAVE_PYLAB:
436             print PYLAB_ERROR
437             return
438         else:
439             if len(spikes) > 0:
440                 xlabel = "Time (ms)"
441                 ylabel = "Neurons #"
442                 set_labels(subplot, xlabel, ylabel)
443                 subplot.scatter(spikes,numpy.ones(len(spikes)),**kwargs)
444                 pylab.draw()
445
446     def time_offset(self, offset):
447         """
448         Add an offset to the SpikeTrain object. t_start and t_stop are
449         shifted from offset, so does all the spike times.
450         
451         Inputs:
452             offset - the time offset, in ms
453         
454         Examples:
455             >> spktrain = SpikeTrain(arange(0,100,10))
456             >> spktrain.time_offset(50)
457             >> spklist.spike_times
458                 [  50.,   60.,   70.,   80.,   90.,  100.,  110., 
459                 120.,  130.,  140.]
460         """
461         self.t_start += offset
462         self.t_stop  += offset
463         self.spike_times += offset
464
465     def time_slice(self, t_start, t_stop):
466         """
467         Return a new SpikeTrain obtained by slicing between t_start and t_stop. The new
468         t_start and t_stop values of the returned SpikeTrain are the one given as arguments
469         
470         Inputs:
471             t_start - begining of the new SpikeTrain, in ms.
472             t_stop  - end of the new SpikeTrain, in ms.
473
474         Examples:
475             >> spk = spktrain.time_slice(0,100)
476             >> spk.t_start
477                 0
478             >> spk.t_stop
479                 100
480         """
481         spikes = numpy.extract((self.spike_times >= t_start) & (self.spike_times <= t_stop), self.spike_times)
482         return SpikeTrain(spikes, t_start, t_stop)
483
484
485     def time_histogram(self, time_bin=10, normalized=True):
486         """
487         Bin the spikes with the specified bin width. The first and last bins
488         are calculated from `self.t_start` and `self.t_stop`.
489         
490         Inputs:
491             time_bin   - the bin width for gathering spikes_times
492             normalized - if True, the bin values are scaled to represent firing rates
493                          in spikes/second, otherwise otherwise it's the number of spikes
494                          per bin.
495         
496         Examples:
497             >> st=SpikeTrain(range(0,100,5),0.1,0,100)
498             >> st.time_histogram(10)
499                 [200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
500             >> st.time_histogram(10, normalized=False)
501                 [2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
502         
503         See also
504             time_axis
505         """
506         bins = self.time_axis(time_bin)
507         if newnum:
508             hist, edges = numpy.histogram(self.spike_times, bins, new=newnum)
509         else:
510             hist, edges = numpy.histogram(self.spike_times, bins)
511         if normalized and isinstance(time_bin, int): # what about normalization if time_bin is a sequence?
512             hist *= 1000.0/time_bin
513         return hist
514
515     def relative_times(self):
516         """
517         Rescale the spike times to make them relative to t_start.
518         
519         Note that the SpikeTrain object itself is modified, t_start
520         is substracted to spike_times, t_start and t_stop
521         """
522         if self.t_start != 0:
523             self.spike_times -= self.t_start
524             self.t_stop      -= self.t_start
525             self.t_start      = 0.0
526
527     def distance_victorpurpura(self, spktrain, cost=0.5):
528         """
529         Function to calculate the Victor-Purpura distance between two spike trains.
530         See J. D. Victor and K. P. Purpura,
531             Nature and precision of temporal coding in visual cortex: a metric-space
532             analysis.,
533             J Neurophysiol,76(2):1310-1326, 1996
534         
535         Inputs:
536             spktrain - the other SpikeTrain
537             cost     - The cost parameter. See the paper for more information
538         """
539         nspk_1      = len(self)
540         nspk_2      = len(spktrain)
541         if cost == 0:
542             return abs(nspk_1-nspk_2)
543         elif cost > 1e9 :
544             return nspk_1+nspk_2
545         scr = numpy.zeros((nspk_1+1,nspk_2+1))
546         scr[:,0] = numpy.arange(0,nspk_1+1)
547         scr[0,:] = numpy.arange(0,nspk_2+1)
548            
549         if nspk_1 > 0 and nspk_2 > 0:
550             for i in xrange(1,nspk_1+1):
551                 for j in xrange(1,nspk_2+1):
552                     scr[i,j] = min(scr[i-1,j]+1,scr[i,j-1]+1)
553                     scr[i,j] = min(scr[i,j],scr[i-1,j-1]+cost*abs(self.spike_times[i-1]-spktrain.spike_times[j-1]))
554         return scr[nspk_1,nspk_2]
555
556
557     def distance_kreuz(self, spktrain, dt=0.1):
558         """
559         Function to calculate the Kreuz/Politi distance between two spike trains
560         See  Kreuz, T.; Haas, J.S.; Morelli, A.; Abarbanel, H.D.I. & Politi, A.
561             Measuring spike train synchrony.
562             J Neurosci Methods, 165:151-161, 2007
563
564         Inputs:
565             spktrain - the other SpikeTrain
566             dt       - the bin width used to discretize the spike times
567         
568         Examples:
569             >> spktrain.KreuzDistance(spktrain2)
570         
571         See also
572             VictorPurpuraDistance
573         """
574         N              = (self.t_stop-self.t_start)/dt
575         vec_1          = numpy.zeros(N, float)
576         vec_2          = numpy.zeros(N, float)
577         result         = numpy.zeros(N, float)
578         idx_spikes     = numpy.array(self.spike_times/dt,int)
579         previous_spike = 0
580         if len(idx_spikes) > 0:
581             for spike in idx_spikes[1:]:
582                 vec_1[previous_spike:spike] = (spike-previous_spike)*dt
583                 previous_spike = spike
584         idx_spikes     = numpy.array(spktrain.spike_times/dt,int)
585         previous_spike = 0
586         if len(idx_spikes) > 0:
587             for spike in idx_spikes[1:]:
588                 vec_2[previous_spike:spike] = (spike-previous_spike)*dt
589                 previous_spike = spike
590         idx = numpy.where(vec_1 < vec_2)[0]
591         result[idx] = vec_1[idx]/vec_2[idx] - 1
592         idx = numpy.where(vec_1 > vec_2)[0]
593         result[idx] = -vec_2[idx]/vec_1[idx] + 1
594         return numpy.sum(numpy.abs(result))/len(result)
595    
596    
597
598     ####################################################################
599     ### TOO SPECIFIC METHOD ?
600     ### Better documentation
601     ####################################################################
602     def tuning_curve(self, var_array, normalized=False, method='sum'):
603         """
604         Calculate a firing-rate tuning curve with respect to some variable.
605         Assumes that some variable, such as stimulus orientation, varies
606         throughout the recording. The values taken by this variable should be
607         supplied in a numpy array `var_array`. The spike train is binned
608         according to the number of values in `var_array`, e.g., if there are
609         N values, the spikes are binned with a bin width
610             (`self.t_stop`-`self.t_start`)/N
611         so that each bin is associated with a particular value of the variable
612         in `var_array`.
613
614         The return value is a dictionary whose keys are the distinct values in
615         `val_array`. The values in the dict depend on the arguments `method` and
616         `normalized`.
617
618         If `normalized` is False, the responses (bin values) are spike counts,
619         if True, they are firing rates.
620         If `method` == "max", each value is the maximum response for a given
621         value of the variable.
622         If `method` == "sum", each value is the summed response...
623         If `method` == "mean", ... you get the idea.
624
625         (If someone can rewrite this more clearly, please do so!)
626         """
627         binwidth = (self.t_stop - self.t_start)/len(var_array)
628         time_histogram = self.time_histogram(binwidth, normalized=normalized)
629         assert len(time_histogram) == len(var_array)
630         tuning_curve = {}
631         counts = {}
632         for k, x in zip(var_array, time_histogram):
633             if not tuning_curve.has_key(k):
634                 tuning_curve[k] = 0
635                 counts[k] = 0
636             if method in ('sum', 'mean'):
637                 tuning_curve[k] += x
638                 counts[k] += 1
639             elif method == 'max':
640                 tuning_curve[k] = max(x, tuning_curve[k])
641             else:
642                 raise Exception()
643         if method == 'mean':
644             for k in tuning_curve.keys():
645                 tuning_curve[k] = tuning_curve[k]/float(counts[k])
646         return tuning_curve
647
648
649     ####################################################################
650     ### TOO SPECIFIC METHOD ?
651     ### Better documentation
652     ####################################################################
653     def frequency_spectrum(self, time_bin):
654         """
655         Returns the frequency spectrum of the time histogram together with the
656         frequency axis.
657         """
658         hist       = self.time_histogram(time_bin, normalized=False)
659         freq_spect = analysis.simple_frequency_spectrum(hist)
660         freq_bin   = 1000.0/self.duration() # Hz
661         freq_axis  = numpy.arange(len(freq_spect)) * freq_bin   
662         return freq_spect, freq_axis   
663
664
665     ####################################################################
666     ### TOO SPECIFIC METHOD ?
667     ### Better documentation
668     ####################################################################
669     def f1f0_ratio(self, time_bin, f_stim):
670         """
671         Returns the F1/F0 amplitude ratio where the input stimulus frequency is
672         f_stim.
673         """
674         freq_spect = self.frequency_spectrum(time_bin)[0]
675         F0 = freq_spect[0]
676         freq_bin = 1000.0/self.duration()
677         try:
678             F1 = freq_spect[int(round(f_stim/freq_bin))]
679         except IndexError:
680             errmsg = "time_bin (%f) too large to estimate F1/F0 ratio for an input frequency of %f" % (time_bin, f_stim)
681             errmsg += "\nFrequency_spectrum: %s" % freq_spect
682             raise Exception(errmsg)
683         return F1/F0
684        
685
686
687 class SpikeList(object):
688     """
689     SpikeList(spikes, id_list, t_start=None, t_stop=None, dims=None)
690     
691     Return a SpikeList object which will be a list of SpikeTrain objects.
692
693     Inputs:
694         spikes  - a list of (id,time) tuples (id being in id_list)
695         id_list - the list of the ids of all recorded cells (needed for silent cells)
696         t_start - begining of the SpikeList, in ms. If None, will be infered from the data
697         t_stop  - end of the SpikeList, in ms. If None, will be infered from the data
698         dims    - dimensions of the recorded population, if not 1D population
699     
700     t_start and t_stop are shared for all SpikeTrains object within the SpikeList
701     
702     Examples:
703         >> sl = SpikeList([(0, 0.1), (1, 0.1), (0, 0.2)], range(2))
704         >> type( sl[0] )
705             <type SpikeTrain>
706     
707     See also
708         load_spikelist
709     """
710     #######################################################################
711     ## Constructor and key methods to manipulate the SpikeList objects   ##
712     #######################################################################
713     def __init__(self, spikes, id_list, t_start=None, t_stop=None, dims=None):
714         """
715         Constructor of the SpikeList object
716
717         See also
718             SpikeList, load_spikelist
719         """
720         self.t_start     = t_start
721         self.t_stop      = t_stop
722         self.dimensions  = dims
723         self.spiketrains = {}
724        
725         ##### First method :-)
726         ## Lists are still the best. Nevertheless, now that we have very good
727         ## loading times, we may start to think about using a generator instead
728         ## of having to allocate all the memory for the list...
729         
730         import bisect
731         spikes.sort()
732         if len(spikes) > 0:
733             ind, time = zip(*spikes)
734         else:
735             ind, time = [], []
736         for id in id_list:
737             beg = bisect.bisect_left(ind,id)
738             end = bisect.bisect_right(ind,id)
739             self.spiketrains[id] = SpikeTrain(time[beg:end], self.t_start, self.t_stop)
740        
741         if len(self) > 0 and (self.t_start is None or self.t_stop is None):
742             self.__calc_startstop()
743
744     def id_list(self):
745         """
746         Return the list of all the cells ids contained in the
747         SpikeList object
748         
749         Examples
750             >> spklist.id_list()
751                 [0,1,2,3,....,9999]
752         """
753         return numpy.array(self.spiketrains.keys())
754
755     def copy(self):
756         """
757         Return a copy of the SpikeList object
758         """
759         spklist = SpikeList([], [], self.t_start, self.t_stop, self.dimensions)
760         for id in self.id_list():
761             spklist.append(id, self.spiketrains[id])
762         return spklist
763
764     def __calc_startstop(self):
765         """
766         t_start and t_stop are shared for all neurons, so we take min and max values respectively.
767         TO DO : check the t_start and t_stop parameters for a SpikeList. Is it commun to
768         all the spikeTrains within the spikelist or each spikelistes do need its own.
769         """
770         if len(self) > 0:
771             if self.t_start is None:
772                 start_times = numpy.array([self.spiketrains[idx].t_start for idx in self.id_list()])
773                 self.t_start = numpy.min(start_times)
774                 logging.debug("Warning, t_start is infered from the data : %f" %self.t_start)
775                 for id in self.spiketrains.keys():
776                     self.spiketrains[id].t_start = self.t_start
777             if self.t_stop is None:
778                 stop_times = numpy.array([self.spiketrains[idx].t_stop for idx in self.id_list()])
779   &nb