logisi_algorithm_2018_LAa.R 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. # Laura Aarnos 030818
  2. # Burst analysis - logISI
  3. # Original creator: ellesec
  4. # from: https://github.com/ellesec/burstanalysis/blob/master/Burst_detection_methods/logisi_pasq_method.R
  5. #library(pracma)
  6. #Function to run logISI method
  7. logisi.pasq.method_mod<-function(spike.train, cutoff=0.1){
  8. no_bursts_found <- matrix(nrow = 0, ncol = 1)
  9. cutoff<-ifelse(is.null(cutoff), 0.1, cutoff)
  10. if (length(spike.train)>5) {
  11. isi.low <- logisi.break.calc(spike.train, cutoff) #Calculates threshold as isi.low
  12. if (is.null(isi.low) || isi.low>=1 ){
  13. logisi.par <- list(min.ibi=0, min.durn=0, min.spikes=5,
  14. isi.low=cutoff) #If no value for isi.low found, or isi.low above 1 second, find bursts using threshold equal to cutoff (default 100ms)
  15. result<-logisi.find.burst(spike.train, logisi.par)
  16. } else if (isi.low<0) {
  17. result<-NA
  18. } else if (isi.low>cutoff & isi.low <1) {
  19. logisi.par <- list(min.ibi=isi.low, min.durn=0, min.spikes=5,
  20. isi.low=cutoff) #If isi.low >cutoff, find bursts using threshold equal to cutoff (default 100ms)
  21. bursts<-logisi.find.burst(spike.train, logisi.par)
  22. if (!is.na(bursts)[1]){
  23. logisi.par2 <- list(min.ibi=0, min.durn=0, min.spikes=5,
  24. isi.low=isi.low) #If bursts have been found, add burst related spikes using threshold of isi.low
  25. brs<-logisi.find.burst(spike.train, logisi.par2)
  26. result<-add.brs(bursts, brs, spike.train)
  27. } else {
  28. result<-bursts
  29. }
  30. } else {
  31. logisi.par <- list(min.ibi=0, min.durn=0, min.spikes=5,
  32. isi.low=isi.low) #If isi.low<cutoff, find bursts using a threshold equal to isi.low
  33. bursts<-logisi.find.burst(spike.train, logisi.par)
  34. if (!is.na(bursts)[1]){
  35. logisi.par2 <- list(min.ibi=cutoff, min.durn=0, min.spikes=5,
  36. isi.low=isi.low) #If bursts have been found, merge bursts using IBI threshold of cutoff
  37. brs<-logisi.find.burst(spike.train, logisi.par2)
  38. result<-add.brs(bursts, brs, spike.train)
  39. } else {
  40. result<-bursts
  41. }
  42. }
  43. } else {
  44. result<-NA
  45. }
  46. if (is.na(result)[1]){
  47. result <- no_bursts_found
  48. }
  49. result
  50. }
  51. #Finds peaks in logISI histogram
  52. get.peaks<-function(h, Pd=2, Th=0, Np=NULL){
  53. m<-0
  54. L<-length(h$density)
  55. j<-0
  56. Np<-ifelse(is.null(Np), L, Np)
  57. pks<-NULL
  58. locs<-NULL
  59. void.th<-0.7
  60. while((j<L)&&(m<Np)){
  61. j<-j+1
  62. endL<-max(1,j-Pd)
  63. if (m>0 && j<min(c(locs[m]+Pd, L-1))){
  64. j<-min(c(locs[m]+Pd, L-1))
  65. endL<-j-Pd
  66. }
  67. endR<-min(L, j+Pd)
  68. temp<-h$density[endL:endR]
  69. aa<-which(j==endL:endR)
  70. temp[aa]<--Inf
  71. if (Pd>1){
  72. idx1<-max(1, aa-2)
  73. idx2<-min(aa+2, length(temp))
  74. idx3<-max(1, aa-1)
  75. idx4<-min(aa+1, length(temp))
  76. if (sum((h$density[j]>(temp[c(1:idx1, idx2:length(temp))]+Th))==FALSE)==0 && sum((h$density[j]>(temp[idx3:idx4]))==FALSE)==0 && j!=1 && j!=L){
  77. m<-m+1
  78. pks[m]<-h$density[j]
  79. locs[m]<-j
  80. } } else if (sum((h$density[j]>(temp+Th))==FALSE)==0 ) {
  81. m<-m+1
  82. pks[m]<-h$density[j]
  83. locs[m]<-j
  84. }
  85. }
  86. ret<-data.frame(pks=pks, locs=locs)
  87. }
  88. #Function to find cutoff threshold.
  89. find.thresh<-function(h, ISITh=100){
  90. void.th<-0.7
  91. gp<-get.peaks(h)
  92. num.peaks<-length(gp$pks)
  93. pkx<-h$breaks[gp$locs]
  94. intra.indx<-which(pkx<ISITh)
  95. if(length(intra.indx)>=1){
  96. max.intra<-max(gp$pks[intra.indx])
  97. max.idx<-which.max(gp$pks[intra.indx])
  98. } else {
  99. return(-1000)
  100. }
  101. x1<-pkx[max.idx]
  102. y1<-max.intra
  103. locs1<-gp$locs[max.idx]
  104. num.peaks.after.burst<-num.peaks-max.idx
  105. if (num.peaks.after.burst==0){
  106. return(NULL)
  107. } else {
  108. gp2<-gp[(max.idx+1):num.peaks,]
  109. ymin<-sapply(gp2$locs, function(x) min(h$density[locs1:x]))
  110. xmin<-sapply(gp2$locs, function(x) which.min(h$density[locs1:x]))+locs1-1
  111. voidParameter<-1-(ymin/sqrt(y1*gp2$pks))
  112. }
  113. indxvoid<-suppressWarnings(min(which(voidParameter>=void.th)))
  114. if(is.infinite(indxvoid)) {
  115. flags<-c(1,0)
  116. return(NULL)
  117. } else {
  118. ISImax<-h$breaks[xmin[indxvoid]]
  119. return(ISImax)
  120. }
  121. }
  122. #Calculates cutoff for burst detection
  123. logisi.break.calc<-function(st, cutoff){
  124. isi<-diff(st)*1000
  125. max.isi<-ceiling(log10(max(isi)))
  126. isi<-isi[isi>=1]
  127. br<-logspace(0, max.isi, 10*max.isi)
  128. h<-hist(isi, breaks=br, plot=FALSE)
  129. h$density<-h$counts/sum(h$counts)
  130. h$density<-lowess(h$density, f=0.05)$y
  131. thr<-find.thresh(h, cutoff*1000)
  132. if(!is.null(thr)){
  133. thr<-thr/1000
  134. }
  135. thr
  136. }
  137. ###Function to add burst related spikes to edges of bursts
  138. add.brs<-function(bursts, brs, spike.train){
  139. is.between<-function(x,a,b){ betw<-0
  140. if(x>=a &x<=b)
  141. {
  142. betw<-1
  143. }
  144. betw
  145. }
  146. burst.adj<-data.frame(beg=rep(0, dim(bursts)[1]), end=rep(0, dim(bursts)[1]) )
  147. for (i in 1:dim(bursts)[1]) {
  148. for (j in 1:dim(brs)[1]) {
  149. if(is.between(bursts[i,1], brs[j,1], brs[j,2]) | is.between(bursts[i,2], brs[j,1], brs[j,2]))
  150. {
  151. burst.adj$beg[i]<-min(bursts[i,1], brs[j,1])
  152. burst.adj$end[i]<-max(bursts[i,2], brs[j,2])
  153. break
  154. } else {
  155. burst.adj$beg[i]<-bursts[i,1]
  156. burst.adj$end[i]<-bursts[i,2]
  157. }
  158. if(brs[j,2]>bursts[i,2]) {
  159. break
  160. }
  161. }
  162. }
  163. diff.begs<-diff(burst.adj[,"beg"])
  164. rep.bursts.begs<-which(diff.begs==0)
  165. if (any(rep.bursts.begs)) {
  166. burst.adj<-burst.adj[-rep.bursts.begs,]
  167. }
  168. diff.ends<-diff(burst.adj[,"end"])
  169. rep.bursts.end<-which(diff.ends==0)+1
  170. if (any(rep.bursts.end)) {
  171. burst.adj<-burst.adj[-rep.bursts.end,]
  172. }
  173. start.times<-spike.train[burst.adj$beg]
  174. end.times<- spike.train[burst.adj$end]
  175. durn<-end.times-start.times
  176. len<-burst.adj$end-burst.adj$beg+1
  177. mean_isis<-durn/(len-1)
  178. N.burst<-dim(burst.adj)[1]
  179. ibi<-c(NA, start.times[-1]-end.times[-N.burst])
  180. result<-cbind(beg=burst.adj$beg, end=burst.adj$end, ibi=ibi, len=len, durn=durn, mean_isis=mean_isis, si=rep(1, N.burst))
  181. result
  182. }
  183. ##Function for finding bursts, taken from sjemea
  184. logisi.find.burst<- function(spikes, par, debug=FALSE) {
  185. ## For one spike train, find the burst using log isi method.
  186. ## e.g.
  187. ## find.bursts(s$spikes[[5]])
  188. ## init.
  189. ## params currently in LOGISI.PAR
  190. ##
  191. no.bursts = NA; #value to return if no bursts found.
  192. ##beg.isi = par$beg.isi
  193. ##end.isi = par$end.isi
  194. min.ibi = par$min.ibi
  195. min.durn = par$min.durn
  196. min.spikes = par$min.spikes
  197. isi.low = par$isi.low
  198. nspikes = length(spikes)
  199. ## Create a temp array for the storage of the bursts. Assume that
  200. ## it will not be longer than Nspikes/2 since we need at least two
  201. ## spikes to be in a burst.
  202. max.bursts <- floor(nspikes/2)
  203. bursts <- matrix(NA, nrow=max.bursts, ncol=3)
  204. colnames(bursts) = c("beg", "end", "ibi")
  205. burst <- 0 #current burst number
  206. ## Phase 1 -- burst detection. Each interspike interval of the data
  207. ## is compared with the threshold THRE. If the interval is greater
  208. ## than the threshold value, it can not be part of a burst; if the
  209. ## interval is smaller or equal to the threhold, the interval may be
  210. ## part of a burst.
  211. ## LAST.END is the time of the last spike in the previous burst.
  212. ## This is used to calculate the IBI.
  213. ## For the first burst, this is no previous IBI
  214. last.end = NA; #for first burst, there is no IBI.
  215. eps<-10^(-10)
  216. n = 2
  217. in.burst = FALSE
  218. while ( n <= nspikes) {
  219. next.isi = spikes[n] - spikes[n-1]
  220. if (in.burst) {
  221. if (next.isi - isi.low>eps) {
  222. ## end of burst
  223. end = n-1; in.burst = FALSE
  224. ibi = spikes[beg] - last.end; last.end = spikes[end]
  225. res = c(beg, end, ibi)
  226. burst = burst + 1
  227. if (burst > max.bursts) {
  228. print("too many bursts!!!")
  229. browser()
  230. }
  231. bursts[burst,] <- res
  232. }
  233. } else {
  234. ## not yet in burst.
  235. if (next.isi - isi.low <=eps) {
  236. ## Found the start of a new burst.
  237. beg = n-1; in.burst = TRUE
  238. }
  239. }
  240. n = n+1
  241. }
  242. ## At the end of the burst, check if we were in a burst when the
  243. ## train finished.
  244. if (in.burst) {
  245. end = nspikes
  246. ibi = spikes[beg] - last.end
  247. res = c(beg, end, ibi)
  248. burst = burst + 1
  249. if (burst > max.bursts) {
  250. print("too many bursts!!!")
  251. browser()
  252. }
  253. bursts[burst,] <- res
  254. }
  255. ## Check if any bursts were found.
  256. if (burst > 0 ) {
  257. ## truncate to right length, as bursts will typically be very long.
  258. bursts = bursts[1:burst,,drop=FALSE]
  259. } else {
  260. ## no bursts were found, so return an empty structure.
  261. return(no.bursts)
  262. }
  263. if (debug) {
  264. print("End of phase1\n")
  265. print(bursts)
  266. }
  267. ## Phase 2 -- merging of bursts. Here we see if any pair of bursts
  268. ## have an IBI less than MIN.IBI; if so, we then merge the bursts.
  269. ## We specifically need to check when say three bursts are merged
  270. ## into one.
  271. ibis = bursts[,"ibi"]
  272. merge.bursts = which(ibis < min.ibi)
  273. if (any(merge.bursts)) {
  274. ## Merge bursts efficiently. Work backwards through the list, and
  275. ## then delete the merged lines afterwards. This works when we
  276. ## have say 3+ consecutive bursts that merge into one.
  277. for (burst in rev(merge.bursts)) {
  278. bursts[burst-1, "end"] = bursts[burst, "end"]
  279. bursts[burst, "end"] = NA #not needed, but helpful.
  280. }
  281. bursts = bursts[-merge.bursts,,drop=FALSE] #delete the unwanted info.
  282. }
  283. if (debug) {
  284. print("End of phase 2\n")
  285. print(bursts)
  286. }
  287. ## Phase 3 -- remove small bursts: less than min duration (MIN.DURN), or
  288. ## having too few spikes (less than MIN.SPIKES).
  289. ## In this phase we have the possibility of deleting all spikes.
  290. ## LEN = number of spikes in a burst.
  291. ## DURN = duration of burst.
  292. len = bursts[,"end"] - bursts[,"beg"] + 1
  293. durn = spikes[bursts[,"end"]] - spikes[bursts[,"beg"]]
  294. bursts = cbind(bursts, len, durn)
  295. rejects = which ( (durn < min.durn) | ( len < min.spikes) )
  296. if (any(rejects)) {
  297. bursts = bursts[-rejects,,drop=FALSE]
  298. }
  299. if (nrow(bursts) == 0) {
  300. ## All the bursts were removed during phase 3.
  301. bursts = no.bursts
  302. } else {
  303. ## Compute mean ISIS
  304. len = bursts[,"end"] - bursts[,"beg"] + 1
  305. durn = spikes[bursts[,"end"]] - spikes[bursts[,"beg"]]
  306. mean_isis = durn/(len-1)
  307. ## Recompute IBI (only needed if phase 3 deleted some cells).
  308. if (nrow(bursts)>1) {
  309. ibi2 = c(NA, calc.ibi(spikes, bursts))
  310. } else {
  311. ibi2 = NA
  312. }
  313. bursts[,"ibi"] = ibi2
  314. si = rep(1, length(mean_isis ))
  315. bursts = cbind(bursts, mean_isis, si)
  316. }
  317. ## End -- return burst structure.
  318. bursts
  319. }
  320. calc.ibi <- function(spikes, b) {
  321. ## Compute the interburst intervals (IBI) for one spike train.
  322. ## Only valid if more than one burst.
  323. nburst = num.bursts(b)
  324. if ( nburst == 0) {
  325. res = NA #no bursts
  326. } else {
  327. if (nburst == 1) {
  328. res = NA #cannot compute IBI w/only 1 burst.
  329. } else {
  330. ## find end spike in each burst.
  331. end = b[,"beg"] + b[,"len"] - 1
  332. ## for NBURST bursts, there will be NBURST-1 IBIs.
  333. start.spikes = b[2:nburst,"beg"]
  334. end.spikes = end[1:(nburst-1)]
  335. ## NEX uses a strange definition of IBI -- it counts the time between
  336. ## the first spike of burst N and the first spike of burst N+1 as the
  337. ## IBI. If we want to use that definition, use the following line:
  338. ##end.spikes = b[1:(nburst-1),"beg"]
  339. res = spikes[start.spikes] - spikes[end.spikes]
  340. }
  341. }
  342. res
  343. }
  344. num.bursts <- function(b) {
  345. ## Return the number of bursts found for a spike train.
  346. if(is.na(b[1]))
  347. 0
  348. else
  349. nrow(b)
  350. }