plotSpikeRaster.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. function [xPoints, yPoints,h] = plotSpikeRaster(spikes,varargin)
  2. % PLOTSPIKERASTER Create raster plot from binary spike data or spike times
  3. % Efficiently creates raster plots with formatting support. Faster than
  4. % common implementations. Multiple plot types and parameters available!
  5. % Look at Parameters section below.
  6. %
  7. % Inputs:
  8. % M x N logical array (binary spike data):
  9. % where M is the number of trials and N is the number of time
  10. % bins with maximum of 1 spike per bin. Assumes time starts at 0.
  11. % M x 1 cell of spike times:
  12. % M is the number of trials and each cell contains a 1 x N vector
  13. % of spike times. Units should be in seconds.
  14. %
  15. % Output:
  16. % xPoints - vector of x points used for the plot.
  17. % yPoints - vector of y points used for the plot.
  18. %
  19. % Parameters:
  20. % PlotType - default 'horzline'. Several types of plots available:
  21. % 1. 'horzline' - plots spikes as gray horizontal lines.
  22. % 2. 'vertline' - plots spikes as vertical lines, centered
  23. % vertically on the trial number.
  24. % 3. 'scatter' - plots spikes as gray dots.
  25. %
  26. % ONLY FOR BINARY SPIKE DATA:
  27. % 4. 'imagesc' - plots using imagesc. Flips colormap so
  28. % black indicates a spike. Not affected by SpikeDuration,
  29. % RelSpikeStartTime, and similar timing parameters.
  30. % 5. 'horzline2' - more efficient plotting than horzline if
  31. % you have many timebins, few trials, and high spike density.
  32. % Note: SpikeDuration parameter DOES NOT WORK IF LESS THAN
  33. % TIME PER BIN.
  34. % 6. 'vertline2' - more efficient plotting than vertline if
  35. % you have few timebins, many trials, and high spike density.
  36. % Note: Horzline and vertline should be fine for most tasks.
  37. %
  38. % FigHandle - default gcf (get current figure).
  39. % Specify a specific figure or subplot to plot in. If no figure
  40. % is specified, plotting will occur on the current figure. If no
  41. % figure is available, a new figure will be created.
  42. %
  43. % LineFormat - default line is gray. Used for 'horzline' and
  44. % 'vertline' plots only. Usage example:
  45. % LineFormat = struct()
  46. % LineFormat.Color = [0.3 0.3 0.3];
  47. % LineFormat.LineWidth = 0.35;
  48. % LineFormat.LineStyle = ':';
  49. % plotSpikeRaster(spikes,'LineFormat',LineFormat)
  50. %
  51. % MarkerFormat - default marker is a gray dot with size 1. Used for
  52. % scatter type plots only. Usage is the same as LineFormat.
  53. %
  54. % AutoLabel - default 0.
  55. % Automatically labels x-axis as 'Time (ms)' or 'Time (s)' and
  56. % y-axis as 'Trial'.
  57. %
  58. % XLimForCell - default [NaN NaN].
  59. % Sets x-axis window limits if using cell spike time data. If
  60. % unchanged, the default limits will be 0.05% of the range. For
  61. % better performance, this parameter should be set.
  62. %
  63. % TimePerBin - default 0.001 (1 millisecond).
  64. % Sets the duration of each timebin for binary spike train data.
  65. %
  66. % SpikeDuration - default 0.001 (1 millisecond).
  67. % Sets the horizontal spike length for cell spike time data.
  68. %
  69. % RelSpikeStartTime - default 0 seconds.
  70. % Determines the starting point of the spike relative to the time
  71. % indicated by spike times or time bins. For example, a relative
  72. % spike start time of -0.0005 would center 1ms spikes for a
  73. % horzline plot of binary spike data.
  74. %
  75. % rasterWindowOffset - default NaN
  76. % Exactly the same as relSpikeStartTime, but unlike
  77. % relSpikeStartTime, the name implies that it can be used to make
  78. % x-axis start at a certain time. If set, takes precedence over
  79. % relSpikeStartTime.
  80. %
  81. % VertSpikePosition - default 0 (centered on trial).
  82. % Determines where the spike position is relative to the trial. A
  83. % value of 0 is centered on the trial number - so a spike on
  84. % trial 3 would have its y-center on 3. Example: A common type of
  85. % spike raster plots vertical spikes from previous trial to
  86. % current trial. Set VertSpikePosition to -0.5 to center the
  87. % spike between trials.
  88. %
  89. % VertSpikeHeight - default 1 (spans 1 trial).
  90. % Determines height of spike for 'vertline' plots. Decrease to
  91. % separate trials with a gap.
  92. %
  93. % Examples:
  94. % plotSpikeRaster(spikeTimes);
  95. % Plots raster plot with horizontal lines.
  96. %
  97. % plotSpikeRaster(spikeTimes,'PlotType','vertline');
  98. % Plots raster plot with vertical lines.
  99. %
  100. % plotSpikeRaster(spikeTimes,'FigHandle',h,'AutoLabel',1,...
  101. % 'XLimForCell',[0 10],'HorzSpikeLength',0.002,);
  102. % Plots raster plot on figure with handle h using horizontal
  103. % lines of length 0.002, with a window from 0 to 10 seconds,
  104. % and automatic labeling.
  105. %
  106. % plotSpikeRaster(spikeTimes,'PlotType','scatter',...
  107. % 'MarkerFormat',MarkerFormat);
  108. % Plots raster plot using dots with a format specified by
  109. % MarkerFormat.
  110. %% AUTHOR : Jeffrey Chiou
  111. %% $DATE : 07-Feb-2014 12:15:47 $
  112. %% $Revision : 1.2 $
  113. %% DEVELOPED : 8.1.0.604 (R2013a)
  114. %% FILENAME : plotSpikeRaster.m
  115. %% Set Defaults and Load optional arguments
  116. LineFormat.Color = [0.2 0.2 0.2];
  117. MarkerFormat.MarkerSize = 1;
  118. MarkerFormat.Color = [0.2 0.2 0.2];
  119. MarkerFormat.LineStyle = 'none';
  120. p = inputParser;
  121. p.addRequired('spikes',@(x) islogical(x) || iscell(x));
  122. p.addParamValue('FigHandle',gcf,@isinteger);
  123. p.addParamValue('PlotType','horzLine',@ischar);
  124. p.addParamValue('LineFormat',LineFormat,@isstruct)
  125. p.addParamValue('MarkerFormat',MarkerFormat,@isstruct);
  126. p.addParamValue('AutoLabel',0, @islogical);
  127. p.addParamValue('XLimForCell',[NaN NaN],@(x) isnumeric(x) && isvector(x));
  128. p.addParamValue('TimePerBin',0.001,@(x) isnumeric(x) && isscalar(x));
  129. p.addParamValue('SpikeDuration',0.001,@(x) isnumeric(x) && isscalar(x));
  130. p.addParamValue('RelSpikeStartTime',0,@(x) isnumeric(x) && isscalar(x));
  131. p.addParamValue('RasterWindowOffset',NaN,@(x) isnumeric(x) && isscalar(x));
  132. p.addParamValue('VertSpikePosition',0,@(x) isnumeric(x) && isscalar(x));
  133. p.addParamValue('VertSpikeHeight',1,@(x) isnumeric(x) && isscalar(x));
  134. p.parse(spikes,varargin{:});
  135. spikes = p.Results.spikes;
  136. figH = p.Results.FigHandle;
  137. plotType = lower(p.Results.PlotType);
  138. lineFormat = struct2opt(p.Results.LineFormat);
  139. markerFormat = struct2opt(p.Results.MarkerFormat);
  140. autoLabel = p.Results.AutoLabel;
  141. xLimForCell = p.Results.XLimForCell;
  142. timePerBin = p.Results.TimePerBin;
  143. spikeDuration = p.Results.SpikeDuration;
  144. relSpikeStartTime = p.Results.RelSpikeStartTime;
  145. rasterWindowOffset = p.Results.RasterWindowOffset;
  146. vertSpikePosition = p.Results.VertSpikePosition;
  147. vertSpikeHeight = p.Results.VertSpikeHeight;
  148. if ~isnan(rasterWindowOffset) && relSpikeStartTime==0
  149. relSpikeStartTime = rasterWindowOffset;
  150. elseif ~isnan(rasterWindowOffset) && relSpikeStartTime~=0
  151. disp(['Warning: RasterWindoWOffset and RelSpikeStartTime perform the same function. '...
  152. 'The value set in RasterWindowOffset will be used over RelSpikesStartTime']);
  153. relSpikeStartTime = rasterWindowOffset;
  154. end
  155. %% Initialize figure and begin plotting logic
  156. figure(figH);
  157. hold on;
  158. if islogical(spikes)
  159. %% Binary spike train matrix case. Initialize variables and set axes.
  160. nTrials = size(spikes,1);
  161. nTimes = size(spikes,2);
  162. % Convert Parameters to correct units using TimePerBin. Default is 1 ms
  163. % per bin (0.001s)
  164. spikeDuration = spikeDuration/timePerBin;
  165. relSpikeStartTime = relSpikeStartTime/timePerBin;
  166. % Note: xlim and ylim are much, much faster than axis or set(gca,...).
  167. xlim([0+relSpikeStartTime nTimes+1+relSpikeStartTime]);
  168. ylim([0 nTrials+1]);
  169. switch plotType
  170. case 'horzline'
  171. %% Horizontal Lines
  172. % Find the trial (yPoints) and timebin (xPoints) of each spike
  173. [trials,timebins] = find(spikes);
  174. trials = trials';
  175. timebins = timebins';
  176. xPoints = [ timebins + relSpikeStartTime;
  177. timebins + relSpikeStartTime + spikeDuration;
  178. NaN(size(timebins)) ];
  179. yPoints = [ trials + vertSpikePosition;
  180. trials + vertSpikePosition;
  181. NaN(size(trials)) ];
  182. xPoints = xPoints(:);
  183. yPoints = yPoints(:);
  184. h = plot(xPoints,yPoints,'k',lineFormat{:});
  185. case 'vertline'
  186. %% Vertical Lines
  187. % Find the trial (yPoints) and timebin (xPoints) of each spike
  188. [trials,timebins] = find(spikes);
  189. trials = trials';
  190. timebins = timebins';
  191. halfSpikeHeight = vertSpikeHeight/2;
  192. xPoints = [ timebins + relSpikeStartTime;
  193. timebins + relSpikeStartTime;
  194. NaN(size(timebins)) ];
  195. yPoints = [ trials - halfSpikeHeight + vertSpikePosition;
  196. trials + halfSpikeHeight + vertSpikePosition;
  197. NaN(size(trials)) ];
  198. xPoints = xPoints(:);
  199. yPoints = yPoints(:);
  200. h = plot(xPoints,yPoints,'k',lineFormat{:});
  201. case 'horzline2'
  202. %% Horizontal lines, for many timebins
  203. % Plots a horizontal line the width of a time bin for each
  204. % spike. Efficient for fewer trials and many timebins.
  205. xPoints = [];
  206. yPoints = [];
  207. for trials = 1:nTrials
  208. % If there are spikes, plot a line. Otherwise, do nothing.
  209. if sum(spikes(trials,:)) > 0
  210. % Find the difference in spike times. Padding at the
  211. % front and back with a zero accounts for spikes in
  212. % the first and last indices, and keeps startY and endY
  213. % the same size
  214. spikeDiff = diff([0 spikes(trials,:) 0]);
  215. % Ex. [0 1 1] -> [0 0 1 1 0]. diff(...) -> [0 1 0 -1]
  216. % Find line segments to plot (line segments longer than
  217. % one trial are for spikes on consecutive trials)
  218. startX = find(spikeDiff > 0);
  219. % Ex. cont. from above: find(...) -> 2
  220. endX = find(spikeDiff < 0);
  221. % Ex. cont. from above: find(...) -> 4. Thus a line
  222. % segment will be defined from 2 to 4 (x-axis)
  223. % Combine x points and adjust x points according to
  224. % parameters. Add Add NaNs to break up line segments.
  225. trialXPoints = [startX + relSpikeStartTime;...
  226. endX + relSpikeStartTime + (spikeDuration - 1);...
  227. NaN(size(startX)) ];
  228. % Explanation for 'spikeDuration - 1': spikeDuration
  229. % has been converted already using timePerBin, so the
  230. % offset at the end is simply duration - one timeBin,
  231. % since 1 timebin's worth of spikes has passed. Only
  232. % affects last spike if less than time per bin.
  233. % Convert x points array to vector
  234. trialXPoints = trialXPoints(:)';
  235. % Add y points centered on the trial by default
  236. % (adjustable with vertSpikePosition parameter)
  237. trialYPoints = trials*ones(size(trialXPoints)) + vertSpikePosition;
  238. % Store this trial's x and y points. Unfortunately,
  239. % preallocating and trimming may actually be slower,
  240. % depending on data.
  241. xPoints = [xPoints trialXPoints];
  242. yPoints = [yPoints trialYPoints];
  243. end
  244. end
  245. h = plot(xPoints, yPoints,'k', lineFormat{:});
  246. case 'vertline2'
  247. %% Vertical lines, for many trials
  248. % Plot one long line for each timebin. Reduces the number of
  249. % objects to plot. Efficient for many trials and fewer timebins
  250. xPoints = [];
  251. yPoints = [];
  252. for time = 1:nTimes
  253. if sum(spikes(:,time)) > 0
  254. % Find the difference in spike times. Same principle as
  255. % horzline2. See comments above for explanation.
  256. spikeDiff = diff([0 spikes(:,time)' 0]);
  257. % Find line segments to plot (line segments longer than
  258. % one trial are for spikes on consecutive trials)
  259. startY = find(spikeDiff > 0);
  260. endY = find(spikeDiff < 0);
  261. % Add NaNs to break up line segments
  262. timeBinYPoints = [startY + vertSpikePosition;...
  263. endY + vertSpikePosition; NaN(size(startY))];
  264. % Convert to vector
  265. timeBinYPoints = timeBinYPoints(:)';
  266. timeBinXPoints = time*ones(size(timeBinYPoints));
  267. % Store this timebin's x and y points.
  268. xPoints = [xPoints timeBinXPoints];
  269. % Subtract 0.5 from each y point so spikes are centered
  270. % by default (adjustable with vertSpikePosition)
  271. yPoints = [yPoints timeBinYPoints-0.5];
  272. end
  273. end
  274. h = plot(xPoints, yPoints, 'k', lineFormat{:});
  275. case 'scatter'
  276. %% Dots or other markers (scatterplot style)
  277. % Find the trial (yPoints) and timebin (xPoints) of each
  278. % spike
  279. [yPoints,xPoints] = find(spikes==1);
  280. xPoints = xPoints + relSpikeStartTime;
  281. h = plot(xPoints,yPoints,'.k',markerFormat{:});
  282. case 'imagesc'
  283. %% Imagesc
  284. imagesc(spikes);
  285. % Flip the colormap since the default is white for 1, black for
  286. % 0.
  287. colormap(flipud(colormap('gray')));
  288. otherwise
  289. error('Invalid plot type. Must be horzline, vertline, horzline2, vertline2, scatter, or imagesc');
  290. end % switch
  291. set(gca,'YDir','reverse');
  292. %% Label
  293. if autoLabel
  294. xlabel('Time (ms)');
  295. ylabel('Trial');
  296. end
  297. else % Equivalent to elseif iscell(spikes).
  298. %% Cell case
  299. % Validation: First check to see if cell array is a vector, and each
  300. % trial within is a vector.
  301. if ~isvector(spikes)
  302. error('Spike cell array must be an M x 1 vector.')
  303. end
  304. trialIsVector = cellfun(@isvector,spikes);
  305. if sum(trialIsVector) < length(spikes)
  306. error('Cells must contain 1 x N vectors of spike times.');
  307. end
  308. % Now make sure cell array is M x 1 and not 1 x M.
  309. if size(spikes,2) > 1 && size(spikes,1) == 1
  310. spikes = spikes';
  311. end
  312. % Make sure each trial is 1 x N and not N x 1
  313. nRowsInTrial = cellfun(@(x) size(x,1),spikes);
  314. % If there is more than 1 row in any trial, add a warning, and
  315. % transpose those trials. Allows for empty trials/cells (nRows > 1
  316. % instead of > 0).
  317. if sum(nRowsInTrial > 1) > 0
  318. trialsToReformat = find(nRowsInTrial > 1);
  319. disp('Warning - some cells (trials) have more than 1 row. Those trials will be transposed.');
  320. for t = trialsToReformat
  321. spikes{trialsToReformat} = spikes{trialsToReformat}';
  322. end
  323. end
  324. nTrials = length(spikes);
  325. % Find x-axis limits that aren't manually set (default [NaN NaN]), and
  326. % automatically set them. This is because we don't assume spikes start
  327. % at 0 - we can have negative spike times.
  328. limitsToSet = isnan(xLimForCell);
  329. if sum(limitsToSet) > 0
  330. % First find range of spike times
  331. minTimes = cellfun(@min,spikes,'UniformOutput',false);
  332. minTime = min( [ minTimes{:} ] );
  333. maxTimes = cellfun(@max,spikes,'UniformOutput',false);
  334. maxTime = max( [ maxTimes{:} ] );
  335. timeRange = maxTime - minTime;
  336. % Find 0.05% of the range.
  337. xStartOffset = relSpikeStartTime - 0.0005*timeRange;
  338. xEndOffset = relSpikeStartTime + 0.0005*timeRange + spikeDuration;
  339. newLim = [ minTime+xStartOffset, maxTime+xEndOffset ];
  340. xLimForCell(limitsToSet) = newLim(limitsToSet);
  341. % End result, if both limits are automatically set, is that the x
  342. % axis is expanded 0.1%, so you can see initial and final spikes.
  343. end
  344. xlim(xLimForCell);
  345. ylim([0 nTrials+1]);
  346. if strcmpi(plotType,'vertline') || strcmpi(plotType,'horzline')
  347. %% Vertical or horizontal line logic
  348. nTotalSpikes = sum(cellfun(@length,spikes));
  349. % Preallocation is possible since we know how many points to
  350. % plot, unlike discrete case. 3 points per spike - the top pt,
  351. % bottom pt, and NaN.
  352. xPoints = NaN(nTotalSpikes*3,1);
  353. yPoints = xPoints;
  354. currentInd = 1;
  355. if strcmpi(plotType,'vertline')
  356. %% Vertical Lines
  357. halfSpikeHeight = vertSpikeHeight/2;
  358. for trials = 1:nTrials
  359. nSpikes = length(spikes{trials});
  360. nanSeparator = NaN(1,nSpikes);
  361. trialXPoints = [ spikes{trials} + relSpikeStartTime;...
  362. spikes{trials} + relSpikeStartTime; nanSeparator ];
  363. trialXPoints = trialXPoints(:);
  364. trialYPoints = [ (trials-halfSpikeHeight)*ones(1,nSpikes);...
  365. (trials+halfSpikeHeight)*ones(1,nSpikes); nanSeparator ];
  366. trialYPoints = trialYPoints(:);
  367. % Save points and update current index
  368. xPoints(currentInd:currentInd+nSpikes*3-1) = trialXPoints;
  369. yPoints(currentInd:currentInd+nSpikes*3-1) = trialYPoints;
  370. currentInd = currentInd + nSpikes*3;
  371. end
  372. else % (if plotType is 'horzline')
  373. %% Horizontal Lines
  374. for trials = 1:nTrials
  375. nSpikes = length(spikes{trials});
  376. nanSeparator = NaN(1,nSpikes);
  377. trialXPoints = [ spikes{trials} + relSpikeStartTime;...
  378. spikes{trials} + relSpikeStartTime + spikeDuration;...
  379. nanSeparator ];
  380. trialXPoints = trialXPoints(:);
  381. trialYPoints = [ trials*ones(1,nSpikes);...
  382. trials*ones(1,nSpikes); nanSeparator ];
  383. trialYPoints = trialYPoints(:);
  384. % Save points and update current index
  385. xPoints(currentInd:currentInd+nSpikes*3-1) = trialXPoints;
  386. yPoints(currentInd:currentInd+nSpikes*3-1) = trialYPoints;
  387. currentInd = currentInd + nSpikes*3;
  388. end
  389. end
  390. % Plot everything at once! We will reverse y-axis direction later.
  391. h = plot(xPoints, yPoints, 'k', lineFormat{:});
  392. elseif strcmpi(plotType,'scatter')
  393. %% Dots or other markers (scatterplot style)
  394. % Combine all spike times into one vector
  395. xPoints = [ spikes{:} ];
  396. % Getting the trials is trickier. 3 steps:
  397. % 1. First convert all the spike times into ones.
  398. trials = cellfun( @(x) {ones(size(x))}, spikes );
  399. % 2. Then multiply by trial number.
  400. for trialNum = 1:length(spikes)
  401. trials{trialNum} = trialNum*trials{trialNum};
  402. end
  403. % 3. Finally convert into a vector
  404. yPoints = [ trials{:} ];
  405. % Now we can plot! We will reverse y-axis direction later.
  406. h = plot(xPoints,yPoints,'.k',markerFormat{:});
  407. elseif strcmpi(plotType,'imagesc') || strcmpi(plotType,'vertline2') || strcmpi(plotType,'horzline2')
  408. error('Can''t use imagesc/horzline2/vertline2 with cell array. Use with logical array of binary spike train data.');
  409. else
  410. error('Invalid plot type. With cell array of spike times, plot type must be horzline, vertline, or scatter.');
  411. end % plot type switching
  412. %% Reverse y-axis direction and label
  413. set(gca,'YDir','reverse');
  414. if autoLabel
  415. xlabel('Time (s)');
  416. ylabel('Trial');
  417. end
  418. end % logical vs cell switching
  419. %% Figure formatting
  420. % Draw the tick marks on the outside
  421. set(gca,'TickDir','out')
  422. % Use special formatting if there is only a single trial.
  423. % Source - http://labrigger.com/blog/2011/12/05/raster-plots-and-matlab/
  424. if size(spikes,1) == 1
  425. set(gca,'YTick', []) % don't draw y-axis ticks
  426. set(gca,'PlotBoxAspectRatio',[1 0.05 1]) % short and wide
  427. set(gca,'YColor',get(gcf,'Color')) % hide the y axis
  428. ylim([0.5 1.5])
  429. end
  430. hold off;
  431. end % main function
  432. function paramCell = struct2opt(paramStruct)
  433. % Converts structure to parameter-value pairs
  434. % Example usage:
  435. % formatting = struct()
  436. % formatting.color = 'black';
  437. % formatting.fontweight = 'bold';
  438. % formatting.fontsize = 24;
  439. % formatting = struct2opt(formatting);
  440. % xlabel('Distance', formatting{:});
  441. % Adapted from:
  442. % http://stackoverflow.com/questions/15013026/how-can-i-unpack-a-matlab-structure-into-function-arguments
  443. % by user 'yuk'
  444. fname = fieldnames(paramStruct);
  445. fval = struct2cell(paramStruct);
  446. paramCell = [fname, fval]';
  447. paramCell = paramCell(:);
  448. end % struct2opt