Analyze_Spatial_Contrast_Adaptation_Stimulus.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. function Analyze_Spatial_Contrast_Adaptation_Stimulus(varargin)
  2. %
  3. %
  4. %%% Analyze_Spatial_Contrast_Adaptation_Stimulus %%%
  5. %
  6. % This function analyzes the response of the ganglion cells to the spatial
  7. % contrast adaptation stimulus. This is a supplement code for the Khani & Gollisch
  8. % (2017) study about the diversity of spatial contrast adaptation in the mouse retina.
  9. % This function accompanys dataset of Khani and Gollisch (2017). The dataset is available at:
  10. % https://gin.g-node.org/gollischlab/Khani_and_Gollisch_2017_RGC_spiketrains_for_spatial_contrast_adaptation
  11. % This function first loads the data in a format that is provided in by the
  12. % supplementary database of the study. It then generate PSTHs, spike-triggered
  13. % average (STA) and nonlinearity functions for the selected cell.
  14. %
  15. % ===============================Inputs====================================
  16. %
  17. % datapath : path to the folder in the database.
  18. % stimulusnumber : number to select correct stimulus.
  19. % cellnumber : an example cell number from the list of good cells.
  20. % clusternumber : a cluster number for the selected cell.
  21. %
  22. %================================Output====================================
  23. %
  24. % PSTH, STAs and nonlinearities for the selected cell.
  25. %
  26. %--------------------------------------------------------------------------------------------------%
  27. %---------- main-function ----------%
  28. %--------------------------------------------------------------------------------------------------%
  29. % first load the data for an example cell
  30. [dp, stimulusnumber, cellnumber, clusternumber] = select_example_cell(varargin{:});
  31. % analyze the data
  32. ca_data = sca_anylsis(dp, stimulusnumber, cellnumber, clusternumber);
  33. % plotting
  34. plot_sca(ca_data, cellnumber, clusternumber);
  35. end
  36. %--------------------------------------------------------------------------------------------------%
  37. %---------- sub-functions ----------%
  38. %--------------------------------------------------------------------------------------------------%
  39. function expdata = sca_anylsis(dp, stimulusnum, cellnum, clusternum,varargin)
  40. %
  41. expdata = load_data_for_selected_cell(dp, stimulusnum, cellnum, clusternum);
  42. % set all the required parameters
  43. para = expdata.stimpara;
  44. para.refreshrate = 60;
  45. para.upsampleratio = ceil(1e3/para.refreshrate);
  46. para.numNLbins = 40;
  47. para.meanIntensity = 0.5;
  48. para.statime = fliplr(linspace(0,(1e3/60*para.Nblinks)*(para.numNLbins/para.upsampleratio),para.numNLbins));
  49. Nruns = floor((length(expdata.frametimes)-1)/(para.Nblinks*para.switchFrames));
  50. para.Nframes = Nruns*para.Nblinks*para.switchFrames;
  51. para.Nrepeats = floor(Nruns/2); % number of runs per trial;
  52. para.binlength = 250 / 1e3; % 250 ms each bin
  53. % for the PSTH
  54. para.low_contduration = para.switchFrames ./ para.refreshrate;
  55. para.high_contduration = para.switchFrames ./ para.refreshrate;
  56. para.psthtime_low = linspace(0, para.low_contduration ,(para.low_contduration / para.binlength)+1);
  57. para.psthtime_high = linspace(0, para.high_contduration ,(para.high_contduration / para.binlength)+1);
  58. para.trialduration = para.low_contduration + para.high_contduration;
  59. para.psthtimelh = linspace(0, para.trialduration,(para.trialduration / para.binlength));
  60. % loading clusters and frametimings
  61. ft = expdata.frametimes; % work in seconds
  62. ftimes = ft(1:para.Nframes);
  63. % get frame times per trial
  64. ftimes = reshape(ftimes,para.switchFrames,Nruns)';
  65. ftimes_high = ftimes(2:2:end,:); ftimes_high = ftimes_high(1:para.Nrepeats,:);
  66. ftimes_low = ftimes(1:2:end,:); ftimes_low = ftimes_low(1:para.Nrepeats,:);
  67. % getting the stimulus right!
  68. stimulus = Spatial_Contrast_Adaptation_Stimulus(para.seed,para.Nframes,para.switchFrames,Nruns, para.meanIntensity);
  69. % binning spikes
  70. spkbins = (histc(expdata.spiketimes,ft(1:para.Nframes))); %#ok
  71. spkbins = reshape(spkbins,para.switchFrames,Nruns)';
  72. spkbins_high = spkbins(2:2:end,:); spkbins_high = spkbins_high(1:para.Nrepeats,:);
  73. spkbins_low = spkbins(1:2:end,:); spkbins_low = spkbins_low(1:para.Nrepeats,:);
  74. % setting output
  75. expdata.ftimes.high = ftimes_high;
  76. expdata.ftimes.low = ftimes_low;
  77. expdata.stimulus = stimulus;
  78. expdata.stimpara = para;
  79. % calculating rasters and psth
  80. expdata = calc_rasters_psth(expdata);
  81. % calculating STAs and nonlinearities for each contrast and location
  82. [locX_high.sta,locX_high.nlx,locX_high.nly,locX_high.time] = calculate_sta_nonlinearity(spkbins_high, expdata.stimulus.locX_high, para);
  83. [locX_low.sta,locX_low.nlx,locX_low.nly,locX_low.time] = calculate_sta_nonlinearity(spkbins_low, expdata.stimulus.locX_low, para);
  84. [locY_high.sta,locY_high.nlx,locY_high.nly,locY_high.time] = calculate_sta_nonlinearity(spkbins_high, expdata.stimulus.locY_high, para);
  85. [locY_low.sta,locY_low.nlx,locY_low.nly,locY_low.time] = calculate_sta_nonlinearity(spkbins_low, expdata.stimulus.locY_low, para);
  86. % setting output
  87. expdata.locXhigh = locX_high;
  88. expdata.locX_low = locX_low;
  89. expdata.locY_high = locY_high;
  90. expdata.locY_low = locY_low;
  91. end
  92. %--------------------------------------------------------------------------------------------------%
  93. function expdata = calc_rasters_psth(expdata)
  94. fthigh = expdata.ftimes.high;
  95. ftlow = expdata.ftimes.low;
  96. spks = expdata.spiketimes;
  97. para = expdata.stimpara;
  98. [ras_high, ras_low] = deal(cell(para.Nrepeats,1));
  99. delay = 50/1e3; % ignore first spikes after contrast switch
  100. for ii = 1:para.Nrepeats
  101. ras_high{ii} = spks(and(spks >= fthigh(ii,1),spks <= fthigh(ii,end))) - fthigh(ii,1);
  102. ras_low{ii} = spks(and(spks >= (ftlow(ii,1)+delay),spks <= ftlow(ii,end))) - (ftlow(ii,1)+delay);
  103. end
  104. rasters_high = CelltoMatUE(ras_high);
  105. rasters_low = CelltoMatUE(ras_low);
  106. psth_high = mean(histc(rasters_high',para.psthtime_high),2)*(1/para.binlength); %#ok
  107. psth_high = psth_high(1:end-1); psth_high(end) = psth_high(end-1);
  108. para.psthtime_high = para.psthtime_high(1:end-1);
  109. psth_low = mean(histc(rasters_low',para.psthtime_low),2)*(1/para.binlength); %#ok
  110. psth_low = psth_low(1:end-1); psth_low(end) = psth_low(end-1);
  111. para.psthtime_low = para.psthtime_low(1:end-1);
  112. expdata.rasters_high = rasters_high;
  113. expdata.rasters_low = rasters_low;
  114. expdata.psth_high = psth_high';
  115. expdata.psth_low = psth_low';
  116. expdata.psthlh = [psth_low; psth_high]';
  117. end
  118. function [staNorm,nlx,nly,timeVec] = calculate_sta_nonlinearity(runningbin, stimulus, para)
  119. toHzratio = para.Nblinks/para.refreshrate; % normalize the response to Hz
  120. rnstimEn = zeros(para.Nrepeats,para.numNLbins, para.switchFrames-para.numNLbins+1);
  121. for itrial = 1 : para.Nrepeats
  122. stimtrial = stimulus(itrial,:);
  123. hankelMat = hankel(1:para.numNLbins,para.numNLbins:para.switchFrames);
  124. rnstimEn(itrial,:,:) = stimtrial(hankelMat);
  125. end
  126. runningbin = reshape(permute(runningbin(:,para.numNLbins:end,:),[1 3 2]),1,[]);
  127. rnstimEn = reshape(permute(rnstimEn, [2 1 3]), para.numNLbins,[]);
  128. sta = bsxfun(@rdivide,runningbin*rnstimEn',sum(runningbin,2));
  129. staNorm = bsxfun(@times,sta,1./sqrt(sum(sta.^2,2)));
  130. timeVec = (-(para.numNLbins-1):1:0)*toHzratio; %in seconds
  131. runningGenerators = staNorm * rnstimEn;
  132. [nly,nlx] = calculate_nonlinearity(runningGenerators', runningbin', para.numNLbins, toHzratio);
  133. end
  134. %--------------------------------------------------------------------------------------------------%
  135. function [ values, centers,sevalues] = calculate_nonlinearity(linearOutput, spikes, nbins, dt)
  136. qtStep = 1/nbins;
  137. qtEdges = 0:qtStep:1;
  138. genEdges = quantile(linearOutput, qtEdges);
  139. if all(isnan(genEdges)) % this is for cases without spikes where everything is NaNs
  140. values = zeros(size(genEdges));
  141. sevalues = zeros(size(genEdges));
  142. else
  143. [a, spikebins] = histc(linearOutput,genEdges); %#ok
  144. values = accumarray(spikebins, spikes', [nbins+1 1], @sum);
  145. sdvalues = accumarray(spikebins, spikes', [nbins+1 1], @std);
  146. values = values./a/dt;
  147. sevalues = sdvalues./sqrt(a)/dt;
  148. end
  149. values(end) = [];
  150. sevalues(end) = [];
  151. centers = genEdges(1:nbins)+diff(genEdges)/2;
  152. values = transpose(values);
  153. sevalues = transpose(sevalues);
  154. end
  155. %--------------------------------------------------------------------------------------------------%
  156. function [dp, stimulusnumber, cellnumber, clusternumber] = select_example_cell(varargin)
  157. % first parse the user inputs
  158. p = inputParser(); % check the user options.
  159. p.addParameter('datapath', [], @(x) ischar(x) || isstring(x));
  160. p.addParameter('stimulusnumber', [], @isnumeric);
  161. p.addParameter('cellnumber', [], @isnumeric);
  162. p.addParameter('clusternumber', [], @isnumeric);
  163. p.addParameter('help', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
  164. p.parse(varargin{:});
  165. % defualt parameters
  166. defpara = p.Results;
  167. if isempty(defpara.datapath)
  168. dp = uigetdir('Select an experiment folder:');
  169. else
  170. dp = defpara.datapath;
  171. end
  172. if isempty(defpara.cellnumber) || isempty(defpara.clusternumber)
  173. listofgoodcells = load([dp,filesep,'list_of_good_cells.txt'],'-ascii');
  174. cellabels = sprintfc('cell %g, cluster %g',listofgoodcells);
  175. if numel(cellabels) > 1
  176. idx = listdlg('PromptString',{'Select an example ganglion cell:'},...
  177. 'SelectionMode','single','ListString',cellabels,'listsize',[250 250]);
  178. else
  179. idx = 1;
  180. end
  181. cellnumber = listofgoodcells(idx,1);
  182. clusternumber = listofgoodcells(idx,2);
  183. else
  184. cellnumber = defpara.cellnumber;
  185. clusternumber = defpara.clusternumber;
  186. end
  187. if isempty(defpara.stimulusnumber)
  188. stimnames = importdata([dp,filesep,'stimuli_names.txt']);
  189. canames = stimnames(~(cellfun('isempty',(strfind(stimnames,'Contrast_Adaptation')))));
  190. if numel(canames) > 1
  191. idx = listdlg('PromptString',{'Select an example dataset:'},...
  192. 'SelectionMode','single','ListString',canames,'listsize',[450 100]);
  193. canames = canames{idx};
  194. end
  195. stimulusnumber = str2double(extractBefore(canames,'_'));
  196. else
  197. stimulusnumber = defpara.stimulusnumber;
  198. end
  199. end
  200. %--------------------------------------------------------------------------------------------------%
  201. function expdata = load_data_for_selected_cell(dp, stimnum, cellnum, clusternum)
  202. %
  203. ftfolder = [dp,filesep,'frametimes'];
  204. if ~exist(ftfolder,'dir'), ftfolder = dp; end
  205. rasfolder = [dp,filesep,'spiketimes'];
  206. if ~exist(rasfolder,'dir'), rasfolder = dp; end
  207. paramfolder = [dp,filesep,'stimulusparameters'];
  208. if ~exist(paramfolder,'dir'), paramfolder = dp; end
  209. ftnames = dir([ftfolder, filesep, num2str(stimnum,'%02g'),'*_frametimings.txt']);
  210. rastername = [rasfolder, filesep, sprintf('%d_SP_C%d%02d.txt', stimnum, cellnum, clusternum)];
  211. warning('off','all'); % this is supress the warning about long names, hopefully nothing happen in between these lines!
  212. expdata.spiketimes = load(rastername, '-ascii');
  213. expdata.frametimes = load([ftfolder,filesep,ftnames.name], '-ascii');
  214. stimpath = dir([paramfolder,filesep,num2str(stimnum,'%02g'),'*_parameters.txt']);
  215. % now reading the txt file with parameters
  216. fid = fopen([paramfolder,filesep,stimpath.name]);
  217. tline = fgetl(fid);
  218. while ischar(tline)
  219. tline = fgetl(fid);
  220. if tline == -1, break; end
  221. fn = extractBefore(tline,' = ');
  222. if isempty(fn), continue; end
  223. val = extractAfter(tline,' = ');
  224. % to convert to double
  225. if ~isnan(str2double(val))
  226. val = str2double(val);
  227. end
  228. % to convert to logical
  229. if strcmp(val,'true'), val = true; end
  230. if strcmp(val,'false'), val = false; end
  231. % store the values in a structure
  232. stimpara.(fn) = val;
  233. end
  234. fclose(fid);
  235. expdata.stimpara = stimpara;
  236. expdata.name = extractBefore(ftnames.name,'_frametimings.txt');
  237. expdata.paraname = extractBefore(stimpath.name,'_parameters.txt');
  238. if strcmp(expdata.name , expdata.paraname)
  239. expdata = rmfield(expdata,'paraname');
  240. end
  241. warning('on','all');
  242. end
  243. %--------------------------------------------------------------------------------------------------%
  244. function OutputMat = CelltoMatUE (input_cell)
  245. %
  246. inptsize = size(input_cell);
  247. maxLength = max(cellfun('length',input_cell));
  248. if inptsize(2) > inptsize(1)
  249. OutputMat = cell2mat(cellfun(@(x)cat(1,x,nan(maxLength-length(x),1)),input_cell,'un',0));
  250. else
  251. OutputMat = cell2mat(cellfun(@(x)cat(2,x',nan(1,maxLength-length(x))),input_cell,'un',0));
  252. end
  253. end
  254. %--------------------------------------------------------------------------------------------------%
  255. %---------- plotting modules ----------%
  256. %--------------------------------------------------------------------------------------------------%
  257. function plot_sca(ca_data, cellnum, clusternum)
  258. % putting data together to make plotting easier
  259. para = ca_data.stimpara;
  260. sta = [ca_data.locXhigh.sta;ca_data.locX_low.sta;ca_data.locY_high.sta;ca_data.locY_low.sta]';
  261. statime = - [ca_data.locXhigh.time;ca_data.locX_low.time;ca_data.locY_high.time;ca_data.locY_low.time]'*1e3;
  262. nlx = [ca_data.locXhigh.nlx;ca_data.locX_low.nlx;ca_data.locY_high.nlx;ca_data.locY_low.nlx]';
  263. nly = [ca_data.locXhigh.nly;ca_data.locX_low.nly;ca_data.locY_high.nly;ca_data.locY_low.nly]';
  264. figure('pos',[600 50 900 950],'color','w');
  265. % get plots title and legend labels
  266. titr = {'Location X', 'Location Y'};
  267. if para.twofixed
  268. l = {'high-low','low-low'};
  269. s = 'Spatial contrast adaptation';
  270. else
  271. l = {'high-low','low-high'};
  272. s = 'Spatial contrast adaptation (alternating stimulus)';
  273. end
  274. % first plotting psths
  275. subplot(3,2,[1,2])
  276. bar(para.low_contduration+para.psthtime_high(1:end-1),ca_data.psth_high);
  277. hold on;
  278. bar(para.psthtime_low(1:end-1),ca_data.psth_low);
  279. pbaspect([3 1 1]); box off;
  280. xlabel('Time (sec)'); ylabel('Rate (Hz)');
  281. xlim([0 para.low_contduration+para.high_contduration]);
  282. xticks([0 para.low_contduration para.low_contduration+para.high_contduration]);
  283. yAx = get(gca,'YLim'); yAx = ceil(yAx(2)/2)*2; yticks([0 yAx/2 yAx]);
  284. legend(l,'Location','northwest'); legend boxoff;
  285. % plotting STAs and nonlinearities
  286. for ii = 1:2
  287. subplot(3,2,ii+2)
  288. plot(statime(:,2*(ii-1)+1),sta(:,2*(ii-1)+1),statime(:,2*ii),sta(:,2*ii),'LineWidth',2);
  289. pbaspect([1.2 1 1]); box off;
  290. axis([0 600 -0.5 0.5]);
  291. title(['STA ',titr{ii}]);
  292. xlabel('Delay (sec)'); ylabel('Filter (a.u.)');
  293. if ii==2, legend(l,'Location','southeast'); legend boxoff; end
  294. subplot(3,2,ii+4)
  295. plot(nlx(:,2*(ii-1)+1),nly(:,2*(ii-1)+1),nlx(:,2*ii),nly(:,2*ii),'LineWidth',2);
  296. pbaspect([1.2 1 1]); box off; ax = gca;
  297. yAx = ceil(ax.YLim(2)/2)*2;
  298. axis([-3.1 3.1 0 yAx]);
  299. title(['Nonlinearity ',titr{ii}]);
  300. xlabel('Input'); ylabel('\Delta Rate (Hz)');
  301. %ax.XAxisLocation = 'origin'; ax.YAxisLocation = 'origin';
  302. yticks(0:yAx/2:yAx);
  303. xticks(-3:1:3);
  304. end
  305. % mega title on top
  306. annotation('textbox', [0.1, 0.95, 0.8, 0], 'string', [s, ' properties of cell ',...
  307. num2str(cellnum),', cluster ',num2str(clusternum)],'FontSize',16,'EdgeColor','none',...
  308. 'HorizontalAlignment','center','VerticalAlignment','middle');
  309. end
  310. %--------------------------------------------------------------------------------------------------%