PageRenderTime 29ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/examples/toolboxes/fieldtrip/ft_spikedetection.m

http://brainstream.googlecode.com/
MATLAB | 479 lines | 283 code | 55 blank | 141 comment | 67 complexity | 0817d0837eb17717533c3342e2c0fed0 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, LGPL-2.0, GPL-3.0, GPL-2.0, LGPL-2.1, LGPL-3.0, BSD-2-Clause
  1. function [cfg, spike] = ft_spikedetection(cfg)
  2. % FT_SPIKEDETECTION
  3. %
  4. % Use as
  5. % cfg = ft_spikedetection(cfg)
  6. %
  7. % The configuration options can contain
  8. % cfg.dataset = string with the input dataset
  9. % cfg.output = string with the output dataset (default is determined automatic)
  10. % cfg.dataformat = string with the output dataset format, see FT_WRITE_FCDC_SPIKE
  11. % cfg.method = string with the method to use, can be 'all', 'zthresh', 'ztrig', 'flank'
  12. % cfg.interactive = 'yes' or 'no'
  13. % cfg.timestampdefinition = 'orig' or 'sample'
  14. %
  15. % The default is to process the full dataset. You can select a latency range with
  16. % cfg.latency = [begin end], default is [0 inf]
  17. % or you can specify multiple latency segments with
  18. % cfg.latency = [b1 e1; b2 e2; ...]
  19. %
  20. % Specific settings for the zthresh spike detection method are
  21. % cfg.zthresh.neg = negative threshold, e.g. -3
  22. % cfg.zthresh.pos = positive threshold, e.g. 3
  23. % cfg.zthresh.offset = number of samples before peak (default = 16)
  24. % cfg.zthresh.mindist = mininum distance in samples between detected peaks
  25. %
  26. % Specific settings for the flank spike detection method are
  27. % cfg.flank.value = positive or negative threshold
  28. % cfg.flank.offset = number of samples before peak
  29. % cfg.flank.ztransform = 'yes' or 'no'
  30. % cfg.flank.mindist = mininum distance in samples between detected peaks
  31. %
  32. % Furthermore, the configuration can contain options for preprocessing
  33. % cfg.preproc.lpfilter = 'no' or 'yes' lowpass filter
  34. % cfg.preproc.hpfilter = 'no' or 'yes' highpass filter
  35. % cfg.preproc.bpfilter = 'no' or 'yes' bandpass filter
  36. % cfg.preproc.lnfilter = 'no' or 'yes' line noise removal using notch filter
  37. % cfg.preproc.dftfilter = 'no' or 'yes' line noise removal using discrete fourier transform
  38. % cfg.preproc.medianfilter = 'no' or 'yes' jump preserving median filter
  39. % cfg.preproc.lpfreq = lowpass frequency in Hz
  40. % cfg.preproc.hpfreq = highpass frequency in Hz
  41. % cfg.preproc.bpfreq = bandpass frequency range, specified as [low high] in Hz
  42. % cfg.preproc.lnfreq = line noise frequency in Hz, default 50Hz
  43. % cfg.preproc.lpfiltord = lowpass filter order
  44. % cfg.preproc.hpfiltord = highpass filter order
  45. % cfg.preproc.bpfiltord = bandpass filter order
  46. % cfg.preproc.lnfiltord = line noise notch filter order
  47. % cfg.preproc.medianfiltord = length of median filter
  48. % cfg.preproc.lpfilttype = digital filter type, 'but' (default) or 'fir'
  49. % cfg.preproc.hpfilttype = digital filter type, 'but' (default) or 'fir'
  50. % cfg.preproc.bpfilttype = digital filter type, 'but' (default) or 'fir'
  51. % cfg.preproc.lpfiltdir = filter direction, 'twopass' (default) or 'onepass'
  52. % cfg.preproc.hpfiltdir = filter direction, 'twopass' (default) or 'onepass'
  53. % cfg.preproc.bpfiltdir = filter direction, 'twopass' (default) or 'onepass'
  54. % cfg.preproc.detrend = 'no' or 'yes'
  55. % cfg.preproc.blc = 'no' or 'yes'
  56. % cfg.preproc.blcwindow = [begin end] in seconds, the default is the complete trial
  57. % cfg.preproc.hilbert = 'no' or 'yes'
  58. % cfg.preproc.rectify = 'no' or 'yes'
  59. % Copyright (C) 2005-2008, Robert Oostenveld
  60. %
  61. % This file is part of FieldTrip, see http://www.ru.nl/neuroimaging/fieldtrip
  62. % for the documentation and details.
  63. %
  64. % FieldTrip is free software: you can redistribute it and/or modify
  65. % it under the terms of the GNU General Public License as published by
  66. % the Free Software Foundation, either version 3 of the License, or
  67. % (at your option) any later version.
  68. %
  69. % FieldTrip is distributed in the hope that it will be useful,
  70. % but WITHOUT ANY WARRANTY; without even the implied warranty of
  71. % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  72. % GNU General Public License for more details.
  73. %
  74. % You should have received a copy of the GNU General Public License
  75. % along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
  76. %
  77. % $Id: ft_spikedetection.m 1043 2010-05-06 10:27:19Z timeng $
  78. fieldtripdefs
  79. cfg = checkconfig(cfg, 'trackconfig', 'on');
  80. % set the general defaults
  81. if ~isfield(cfg, 'dataset'), cfg.dataset = []; end
  82. if ~isfield(cfg, 'output'), cfg.output = []; end
  83. if ~isfield(cfg, 'channel'), cfg.channel = 'all'; end
  84. if ~isfield(cfg, 'channelprefix'), cfg.channelprefix = []; end
  85. if ~isfield(cfg, 'latency'), cfg.latency = [0 inf]; end
  86. if ~isfield(cfg, 'dataformat'), cfg.dataformat = []; end
  87. if ~isfield(cfg, 'headerformat'), cfg.headerformat = []; end
  88. % set the specific defaults
  89. if ~isfield(cfg, 'method'), cfg.method = 'zthresh'; end
  90. if ~isfield(cfg, 'adjustselection'), cfg.adjustselection = 'yes'; end
  91. if ~isfield(cfg, 'interactive'), cfg.interactive = 'no'; end
  92. if ~isfield(cfg, 'chanvals'), cfg.chanvals = []; end
  93. % set the defaults for the various spike detection methods
  94. switch cfg.method
  95. case 'all'
  96. if ~isfield(cfg, 'all'), cfg.all = []; end
  97. case 'zthresh'
  98. if ~isfield(cfg, 'zthresh'), cfg.zthresh = []; end
  99. if ~isfield(cfg.zthresh, 'neg'), cfg.zthresh.neg = -3; end
  100. if ~isfield(cfg.zthresh, 'pos'), cfg.zthresh.pos = 3; end
  101. if ~isfield(cfg.zthresh, 'offset'), cfg.zthresh.offset = -16; end % in samples
  102. if ~isfield(cfg.zthresh, 'mindist'), cfg.zthresh.mindist = 0; end % in samples
  103. case 'flank'
  104. if ~isfield(cfg, 'flank'), cfg.flank = []; end
  105. if ~isfield(cfg.flank, 'ztransform'), cfg.flank.ztransform = 'yes'; end
  106. if ~isfield(cfg.flank, 'value'), cfg.flank.value = 1.5; end % trigger threshold value
  107. if ~isfield(cfg.flank, 'offset'), cfg.flank.offset = 6; end % in samples
  108. if ~isfield(cfg.flank, 'mindist'), cfg.flank.mindist = 0; end % in samples
  109. otherwise
  110. error('unsupported option for cfg.method');
  111. end
  112. % ensure that the preproc specific options are located in the preproc substructure
  113. cfg = checkconfig(cfg, 'createsubcfg', {'preproc'});
  114. status = mkdir(cfg.output);
  115. if ~status
  116. error(sprintf('error creating spike output dataset %s', cfg.output));
  117. end
  118. % read the header of the completete dataset
  119. hdr = ft_read_header(cfg.dataset, 'headerformat', cfg.headerformat);
  120. cfg.channel = ft_channelselection(cfg.channel, hdr.label);
  121. chansel = match_str(hdr.label, cfg.channel);
  122. if strcmp(cfg.timestampdefinition, 'sample')
  123. % the default would be to keep the original definition of timestamps as determined from looking at the file
  124. % here the definition of timestamps is changed to correspond with samples at the original sampling rate
  125. hdr.TimeStampPerSample = 1;
  126. hdr.FirstTimeStamp = 1;
  127. hdr.LastTimeStamp = hdr.nSamples*hdr.nTrials;
  128. end
  129. if hdr.nSamples<1
  130. error('the input dataset contains no samples');
  131. elseif length(chansel)<1
  132. error('the input selection contains no channels');
  133. end
  134. % give some feedback, based on the complete data
  135. fprintf('data contains %10d channels\n', hdr.nChans);
  136. fprintf('selected %10d channels\n', length(chansel));
  137. numsample = [];
  138. numsegment = size(cfg.latency,1);
  139. for j=1:numsegment
  140. begsample(j) = max(round(cfg.latency(j,1) * hdr.Fs + 1), 1);
  141. endsample(j) = min(round(cfg.latency(j,2) * hdr.Fs ), hdr.nSamples);
  142. numsample(j) = endsample(j) - begsample(j) + 1;
  143. cfg.latency(j,1) = (begsample(j)-1)/hdr.Fs;
  144. cfg.latency(j,2) = (endsample(j) )/hdr.Fs;
  145. end
  146. numsample = sum(numsample);
  147. fprintf('data contains %10d samples\n', hdr.nSamples);
  148. fprintf('selected %10d samples in %d segments\n', numsample, numsegment);
  149. s = floor(hdr.nSamples ./ hdr.Fs);
  150. m = floor(s/60);
  151. h = floor(m/60);
  152. m = m - 60*h;
  153. s = s - 60*m - 60*60*h;
  154. fprintf('duration of data %02dh:%02dm:%02ds\n', h, m, s);
  155. s = floor(numsample ./ hdr.Fs);
  156. m = floor(s/60);
  157. h = floor(m/60);
  158. m = m - 60*h;
  159. s = s - 60*m - 60*60*h;
  160. fprintf('duration of selection %02dh:%02dm:%02ds\n', h, m, s);
  161. fprintf('estimated memory usage %d MB\n', round((numsample*(8+8+2))/(1024^2)));
  162. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  163. % process each channel separetely
  164. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  165. for i=chansel(:)'
  166. fprintf('processing channel %d, ''%s''\n', i, hdr.label{i});
  167. % remain in the interactive phase as long as the user desires
  168. % the interactive loop is also used once when imediately writing to file
  169. runloop = true;
  170. newdata = true;
  171. while runloop
  172. % the loop is used once when writing to file, or multiple times for interactive use
  173. runloop = false;
  174. % reading and filtering may be repeatedly done if the user interactively specified another latency selection
  175. if newdata
  176. fprintf('++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n');
  177. numsegment = size(cfg.latency,1);
  178. clear begsample endsample numsample
  179. for j=1:numsegment
  180. begsample(j) = max(round(cfg.latency(j,1) * hdr.Fs + 1), 1);
  181. endsample(j) = min(round(cfg.latency(j,2) * hdr.Fs ), hdr.nSamples);
  182. numsample(j) = endsample(j) - begsample(j) + 1;
  183. cfg.latency(j,1) = (begsample(j)-1)/hdr.Fs;
  184. cfg.latency(j,2) = (endsample(j) )/hdr.Fs;
  185. end
  186. % read from a single channel and concatenate the segments into one vector
  187. org = zeros(1,sum(numsample));
  188. for j=1:numsegment
  189. fprintf('reading channel %s, latency from %f to %f\n', hdr.label{i}, cfg.latency(j,1), cfg.latency(j,2));
  190. buf = ft_read_data(cfg.dataset, 'header', hdr, 'begsample', begsample(j), 'endsample', endsample(j), 'chanindx', i, 'dataformat', cfg.dataformat);
  191. if j==1
  192. begsegment = 1;
  193. endsegment = numsample(j);
  194. else
  195. begsegment = sum(numsample(1:j-1)) + 1;
  196. endsegment = sum(numsample(1:j )) ;
  197. end
  198. % concatenate the data into one large vector
  199. org(begsegment:endsegment) = buf;
  200. clear buf;
  201. end
  202. % apply preprocessing
  203. fprintf('applying preprocessing options\n');
  204. dat = preproc(org, hdr.label(i), hdr.Fs, cfg.preproc);
  205. end % if newdata
  206. peaks = [];
  207. % this while loop is used to automatically adjust the spike threshold
  208. % if too few/ too many waveforms were selected
  209. % it will break out of the while loop at the end
  210. numadjustment = 1;
  211. while (1)
  212. switch cfg.method
  213. case 'all'
  214. % place a spike marker at each 32 samples in the signal
  215. % this makes a lot of waveforms that together reconstruct the complete continuous data
  216. for j=1:32:(length(dat)-32)
  217. peaks = [peaks j];
  218. end
  219. case 'zthresh'
  220. % do peak detection on z-transformed signal
  221. zdat = (dat-mean(dat))./std(dat);
  222. if ~isinf(cfg.zthresh.neg)
  223. % mindist is expressed in samples rather than sec
  224. dum = peakdetect3(-zdat, -cfg.zthresh.neg, cfg.zthresh.mindist);
  225. peaks = [peaks dum];
  226. end
  227. if ~isinf(cfg.zthresh.pos)
  228. % mindist is expressed in samples rather than sec
  229. dum = peakdetect3( zdat, cfg.zthresh.pos, cfg.zthresh.mindist);
  230. peaks = [peaks dum];
  231. end
  232. % the mindist is not honored for spikes followed immediately by a spike of the other sign
  233. peaks = sort(peaks);
  234. % the begin of each waveform is shifted, to ensure that the spike is in the middle
  235. peaks = peaks - cfg.zthresh.offset;
  236. % ensure that no spikes are found within the first and last segment of the data
  237. % since it is not possible to extract complete waveforms there
  238. peaks(find(peaks<32)) = [];
  239. peaks(find((length(dat)-peaks)<32)) = [];
  240. % --- store the thres val for current channnel
  241. cfg.chanvals(find(chansel==i),1:3) = [cfg.zthresh.neg cfg.zthresh.pos cfg.zthresh.mindist];
  242. case 'flank'
  243. if strcmp(cfg.flank.ztransform, 'yes')
  244. zdat = (dat-mean(dat))./std(dat);
  245. else
  246. zdat = dat;
  247. end
  248. if cfg.flank.value>0
  249. peaks = find(diff(zdat>cfg.flank.value)==1);
  250. elseif cfg.flank.value<0
  251. peaks = find(diff(zdat<cfg.flank.value)==-1);
  252. end
  253. % prevent thres crossing within mindist samples
  254. if ~isempty(peaks) & ~isempty(cfg.flank.mindist)
  255. pd = [inf diff(peaks)];
  256. peaks = peaks(pd>cfg.flank.mindist);
  257. end
  258. % the flanks are shifted by one sample due to the diff
  259. peaks = peaks + 1;
  260. % the begin of each waveform is shifted, to ensure that the spike is in the middle
  261. peaks = peaks - cfg.flank.offset;
  262. % ensure that no spikes are found within the first and last segment of the data
  263. % since it is not possible to extract complete waveforms there
  264. peaks(find(peaks<32)) = [];
  265. peaks(find((length(dat)-peaks)<32)) = [];
  266. % --- store the thres val for current channnel
  267. cfg.chanvals(find(chansel==i),1:3) = [cfg.flank.value NaN cfg.flank.mindist];
  268. end % cfg.method
  269. fprintf('detected %d (avg. rate: %.2f)', length(peaks), (length(peaks) / (length(dat)/hdr.Fs) ));
  270. % check that n detected spikes is "reasonable" (more than 5
  271. % percent, less than 80%), otherwise changes the settings
  272. % note: 32 samples per waveform, length(dat)/32 possible waveforms, hdr.Fs
  273. if ~strcmp(cfg.adjustselection, 'yes')
  274. % no automatic adjustment needs to be done
  275. break;
  276. elseif numadjustment>10
  277. % do not auto-adjust more than 10 times
  278. break;
  279. else
  280. adjustValue = [];
  281. if ( (length(peaks) / (length(dat)/hdr.Fs) ) < 4)
  282. fprintf(', less than avg. rate of 4 spikes per sec. detected.\n');
  283. adjustValue = 1+(numadjustment*0.1);
  284. elseif ~strcmp(cfg.method,'all') & ( (length(peaks) / (length(dat)/hdr.Fs) ) > 600)
  285. fprintf(', more than avg. rate of 600 spikes per sec. detected.\n');
  286. adjustValue = 1-(numadjustment*0.1);
  287. else
  288. % the detected spike rate is "reasonable", no further adjustments neccessary
  289. break;
  290. end
  291. if ~isempty(adjustValue)
  292. maxDat = max(abs(zdat))*0.95; % the minimum threshold value to ensure thres. is in correct range
  293. if strcmp(cfg.method,'zthresh')
  294. cfg.zthresh.neg = max([cfg.zthresh.neg*adjustValue -maxDat]);
  295. cfg.zthresh.pos = min([cfg.zthresh.pos*adjustValue maxDat]);
  296. fprintf('... adjusted thresh. to %.2f / %.2f\n',cfg.zthresh.neg, cfg.zthresh.pos);
  297. elseif strcmp(cfg.method,'flank')
  298. cfg.flank.value = min([cfg.flank.value*adjustValue maxDat]);
  299. fprintf('... adjusted thresh. to %.2f\n',cfg.flank.value);
  300. end
  301. end
  302. numadjustment = numadjustment + 1;
  303. end
  304. end % while automatic threshold adjustment
  305. if strcmp(cfg.interactive, 'no')
  306. % construct a structure like this
  307. % spike.label = 1xNchans cell-array, with channel labels
  308. % spike.waveform = 1xNchans cell-array, each element contains a matrix (Nsamples X Nspikes), can be empty
  309. % spike.timestamp = 1xNchans cell-array, each element contains a vector (1 X Nspikes)
  310. % spike.unit = 1xNchans cell-array, each element contains a vector (1 X Nspikes)
  311. % note that it only contains a single channel
  312. spike = [];
  313. if isempty(cfg.channelprefix)
  314. % the label should be a cell-array of length one
  315. spike.label = hdr.label(i);
  316. else
  317. % add a prefix to the channel name
  318. spike.label = {[cfg.channelprefix '_' hdr.label{i}]};
  319. end
  320. spike.waveform = {zeros(32,length(peaks))}; % FIXME implement variable length waveforms
  321. spike.timestamp = {zeros(1,length(peaks), class(hdr.FirstTimeStamp))};
  322. spike.unit = {zeros(1,length(peaks))};
  323. for j=1:length(peaks)
  324. begsmp = peaks(j);
  325. endsmp = peaks(j) + 32 - 1; % FIXME implement a peak shift
  326. spike.waveform{1}(:,j) = dat(begsmp:endsmp);
  327. spike.timestamp{1}(j) = hdr.FirstTimeStamp + typecast((peaks(j)-1)*hdr.TimeStampPerSample, class(hdr.FirstTimeStamp));
  328. end
  329. % write the spike data to a new file
  330. datafile = fullfile(cfg.output, spike.label{1}); % this is without filename extension
  331. fprintf(', writing to %s\n', datafile);
  332. ft_write_fcdc_spike(datafile, spike, 'dataformat', cfg.dataformat, 'fsample', hdr.Fs, 'TimeStampPerSample', hdr.TimeStampPerSample*hdr.Fs);
  333. % jump out of the interactive loop
  334. runloop = false;
  335. newdata = false;
  336. elseif strcmp(cfg.interactive, 'yes')
  337. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  338. % show the spike data on screen
  339. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  340. clf
  341. fprintf('\n\n');
  342. fprintf('------------------------------------------------------------\n');
  343. fprintf('Channel %s, detected spike rate is %d per second, ', hdr.label{i}, round(length(peaks)./(cfg.latency(2)-cfg.latency(1))));
  344. fprintf('change settings or press return to accept\n');
  345. fprintf('------------------------------------------------------------\n');
  346. % use the z-transformed data
  347. dat = zdat;
  348. subplot('position', [0.1 0.3 0.1 0.6]);
  349. [hdat, hval] = hist(double(dat), 256);
  350. h = plot(hdat, hval);
  351. x = [0 max(hdat)*2];
  352. if strcmp(cfg.method, 'zthresh')
  353. y = [cfg.zthresh.neg cfg.zthresh.neg];
  354. line(x, y, 'color', 'g');
  355. y = [cfg.zthresh.pos cfg.zthresh.pos];
  356. line(x, y, 'color', 'g');
  357. elseif strcmp(cfg.method, 'flank')
  358. y = [cfg.flank.value cfg.flank.value];
  359. line(x, y, 'color', 'g');
  360. end
  361. abc = axis; abc(1) = 0; abc(2) = 1.2*max(hdat); axis(abc);
  362. subplot('position', [0.3 0.3 0.6 0.6]);
  363. if size(cfg.latency,1)==1
  364. % create a time axis that matches the data
  365. time = linspace(cfg.latency(1,1), cfg.latency(1,2), length(dat));
  366. else
  367. % the data consiss of multiple concatenated segments, create a dummy time axis
  368. warning('the displayed time axis does not represent real time in the recording');
  369. time = (0:(length(dat)-1))/hdr.Fs;
  370. end
  371. h = plot(time, dat);
  372. if strcmp(cfg.method, 'zthresh')
  373. x = [time(1) time(end)];
  374. y = [cfg.zthresh.neg cfg.zthresh.neg];
  375. line(x, y, 'color', 'g');
  376. x = [time(1) time(end)];
  377. y = [cfg.zthresh.pos cfg.zthresh.pos];
  378. line(x, y, 'color', 'g');
  379. elseif strcmp(cfg.method, 'flank')
  380. x = [time(1) time(end)];
  381. y = [cfg.flank.value cfg.flank.value];
  382. line(x, y, 'color', 'g');
  383. end
  384. spiketime = (peaks-1)/hdr.Fs;
  385. spiketime = spiketime + time(1);
  386. hold on
  387. plot(spiketime, zeros(size(peaks)), 'r.');
  388. hold off
  389. abc = axis; abc(1) = time(1); abc(2) = time(end); axis(abc);
  390. subplot('position', [0.3 0.1 0.6 0.1]);
  391. plot(spiketime, zeros(size(peaks)), '.');
  392. abc = axis; abc(1) = time(1); abc(2) = time(end); axis(abc);
  393. % start with a wider figure already, this prevents manual resizing
  394. set(gcf,'Position',[42 302 879 372],'Color','w')
  395. % ask for a new latency selection
  396. tmp=(cfg.latency'); oldval = sprintf('%.1f %.1f, ',tmp(:));
  397. [cfg.latency, newdata] = smartinput(['cfg.latency [' oldval '] = '], cfg.latency);
  398. runloop = newdata;
  399. fn = fieldnames(getfield(cfg, cfg.method));
  400. for k=1:length(fn)
  401. eval(['oldval = cfg.' cfg.method '.' fn{k} ';']);
  402. if isnumeric(oldval), oldvalinf = sprintf('%.1f',oldval); else, oldvalinf = oldval; end
  403. [newval, changed] = smartinput(sprintf('cfg.%s.%s [%s]= ', cfg.method, fn{k},oldvalinf), oldval);
  404. eval(['cfg.' cfg.method '.' fn{k} ' = newval;']);
  405. runloop = (runloop | changed);
  406. end
  407. if ~runloop
  408. warning('detected spikes are not written to disk in interactive mode');
  409. end
  410. end % elseif interactive
  411. end % while runloop
  412. end % for each file
  413. % get the output cfg
  414. cfg = checkconfig(cfg, 'trackconfig', 'off', 'checksize', 'yes');
  415. % add the version details of this function call to the configuration
  416. try
  417. % get the full name of the function
  418. cfg.version.name = mfilename('fullpath');
  419. catch
  420. % required for compatibility with Matlab versions prior to release 13 (6.5)
  421. [st, i] = dbstack;
  422. cfg.version.name = st(i);
  423. end
  424. cfg.version.id = '$Id: ft_spikedetection.m 1043 2010-05-06 10:27:19Z timeng $';