| 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 |
|
|---|
| 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 |
|
|---|
| 100 |
|
|---|
| 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 |
|
|---|
| 105 |
|
|---|
| 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 |
|
|---|
| 110 |
|
|---|
| 111 |
self.spike_times = numpy.sort(self.spike_times, kind="quicksort") |
|---|
| 112 |
|
|---|
| 113 |
|
|---|
| 114 |
|
|---|
| 115 |
|
|---|
| 116 |
|
|---|
| 117 |
|
|---|
| 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: |
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 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): |
|---|
| 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 |
|
|---|
| 600 |
|
|---|
| 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 |
|
|---|
| 651 |
|
|---|
| 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() |
|---|
| 661 |
freq_axis = numpy.arange(len(freq_spect)) * freq_bin |
|---|
| 662 |
return freq_spect, freq_axis |
|---|
| 663 |
|
|---|
| 664 |
|
|---|
| 665 |
|
|---|
| 666 |
|
|---|
| 667 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 726 |
|
|---|
| 727 |
|
|---|
| 728 |
|
|---|
| 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 |
|---|