Local_Chromatic_Integration_Stimulus.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. function Local_Chromatic_Integration_Stimulus(varargin)
  2. %
  3. %===================================================================================
  4. % Parameter Default Usage
  5. %===================================================================================
  6. %
  7. % selecttextfile false Browse prompt to select the parameters text file
  8. % stimduration 600 Stimulus presentation duration in refresh rate unit (600 = 10 sec).
  9. % maxcontrast 0.2 Maximum Weber contrast value (ranges from -1 to 1)
  10. % mincontrast -0.2 Minimum Weber contrast value (ranges from -1 to 1)
  11. % contrastdiff 0.02 Contrast steps from mincontrast to maxcontrast e.g. -20 : 2 : 20
  12. % stixelwidth 20 Width of each local stimulus in pixels
  13. % stixelheight 20 Height of each local stimulus in pixels
  14. % gapwidth 2 Number of locations to be avoided around each locally selected stimulus area
  15. % gapheight 2 Same as gapwidth but for the y-axis of each selected location
  16. % seedlocation -1000 Seed for random number generator for locations’ list (should be negative)
  17. % seedonoroffpixels -10000 Seed for random number generator of binary contrasts (not used here)
  18. % seedcolorcontrast -2000 Seed for random number generator for color contrast list (should be negative)
  19. % redmeanintensity 0 Mean intensity for red gun of the screen
  20. % greenmeanintensity 0.5 Mean intensity for green gun of the screen
  21. % bluemeanintensity 0.5 Mean intensity for blue gun of the screen
  22. % screensize 864 x 480 Screen resolution, default value is the resolution of the lightcrafter projector
  23. % refreshrate 60 Screen refresh rate in Hz
  24. % drawcircle true Option to display stimulus as circle or squares
  25. % drawannulus false Option to display the stimulus as annulus (not available for this stimulus)
  26. % fullscreen false Option to display the stimulus in full-screen mode
  27. % help false Option to check the list of available parameters
  28. % lmargin 0 Margins from left side of the screen (not available for this stimulus)
  29. % rmagin 0 Margins from right side of the screen (not available for this stimulus)
  30. % tmargin 0 Margins from top of the screen (not available for this stimulus)
  31. % bmargin 0 Margins from bottom of the screen (not available for this stimulus)
  32. % coneisolating false Option to activate opsin-isolation (excluded for simplicity)
  33. %
  34. %===================================================================================
  35. para = read_stimulus_parameters(varargin{:});
  36. if para.help, return; end
  37. offcontrasts = fliplr(0:-para.contrastdiff:para.mincontrast);
  38. oncontrasts = 0:para.contrastdiff:para.maxcontrast;
  39. greencontrasts = [offcontrasts,oncontrasts];
  40. bluecontrasts = [oncontrasts,offcontrasts];
  41. % convert to weber contrast (mean+(contrast*mean))
  42. greencontrasts = para.greenmeanintensity + (greencontrasts * para.greenmeanintensity);
  43. bluecontrasts = para.bluemeanintensity + (bluecontrasts * para.bluemeanintensity);
  44. % first get the corrdinates of all the locations
  45. [f, v] = draw_locations(para);
  46. % The darwing of the screen goes here
  47. monitorsize = get(0,'ScreenSize');
  48. scpos = [monitorsize(3)/2-(para.screensize(1)/2), ...
  49. monitorsize(4)/2-(para.screensize(2)/2), para.screensize];
  50. % make an screen like figure
  51. fh = figure('Menu','none','ToolBar','none','Position',scpos,'Color',0.5*[1 1 1]);
  52. fh.Name = 'Example of Local Chromatic Integration Stimulus, from Khani and Gollisch (2021)';
  53. fh.Colormap = gray;
  54. ah = axes('Units','Normalize','Position',[0 0 1 1]);
  55. axis(ah,[0, para.screensize(1),0, para.screensize(2)]);
  56. axis(ah,'off');
  57. % inital variables
  58. framecounter= 0;
  59. seedlocation = para.seedlocation; % to randomize locations
  60. seedcolor = para.seedcolorcontrast; % to randomize contrast order
  61. stimdur = ceil((para.stimduration ./ para.refreshrate)/ 0.5); % start by an estimate of one cycle
  62. while ishandle(fh)
  63. if framecounter == 2, tic; end % start the timer on the second frame
  64. frameMod = mod(framecounter,stimdur);
  65. if frameMod == 0
  66. [contrastlist, seedlocation, seedcolor] = select_sparse_locations(seedlocation, seedcolor, greencontrasts, bluecontrasts, para);
  67. end
  68. if framecounter == 0
  69. p = patch('Faces',f,'Vertices',v,'FaceVertexcData',contrastlist,'Facecolor','Flat','edgecolor','none');
  70. else
  71. p.FaceVertexCData = contrastlist;
  72. end
  73. % this is to draw the frames relatively accurately.
  74. drawnow;
  75. java.lang.Thread.sleep(1/para.refreshrate*1e3);
  76. %pause(1/para.refreshrate);
  77. if framecounter == 2
  78. t = toc; % take the time of one trial and adjust the time based on that.
  79. stimdur = ceil((para.stimduration ./ para.refreshrate)/ t);
  80. end
  81. framecounter = framecounter+1;
  82. end
  83. end
  84. %--------------------------------------------------------------------------------------------------%
  85. %---------- sub-functions ----------%
  86. %--------------------------------------------------------------------------------------------------%
  87. function varargout = fisher_Yates_shuffle_order(seed,inputVec,varargin)
  88. %
  89. %%% fisher_Yates_shuffle_order %%%
  90. %
  91. %
  92. % This function generate psudorandom permution similar to randperm in MATLAB
  93. % but works with psudorandom number generator ran1. It also gives back the
  94. % most recent seed value to continue the permuation in case of repeated trials.
  95. % note that the direction of permutation is along x-axix or for columns of
  96. % MATALB not for the rows.
  97. %
  98. %
  99. % ===============================Inputs====================================
  100. %
  101. % seed : seed value for random number generation.
  102. % inputVec : input vector used for permutation.
  103. %
  104. %================================Output====================================
  105. %
  106. % testOrder : vector of permuted indices for the inputVec.
  107. % newSeed : the recent seed that used in the ran1 function.
  108. % outputVec : the permuted input vector along x-axis
  109. %
  110. % Note that the permution algorithem is based on Fisher-Yates shuffle
  111. % algorithem identical to what is used in the stimulus program.
  112. % for more info check :
  113. % https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
  114. %
  115. % written by Mohammad, 01.02.2016
  116. newSeed = seed;
  117. testOrder = zeros(1,size(inputVec,2));
  118. testOrder(1) = 1;
  119. for i = 2:length(inputVec)-1
  120. [randVal,newSeed] = ran1(newSeed);
  121. j = ceil(i*randVal); % based on Fischer-Yates algorithem
  122. testOrder(i) = testOrder(j);
  123. testOrder(j) = i;
  124. end
  125. testOrder = [testOrder(end),testOrder(1:end-1)]+1; % to match MATLAB indexing
  126. varargout{1} = testOrder;
  127. varargout{2} = newSeed;
  128. for j = 1:size(inputVec,1)
  129. varargout{3}(j,:) = inputVec(j,testOrder);
  130. end
  131. end
  132. %--------------------------------------------------------------------------------------------------%
  133. function [f, v] = draw_locations(para)
  134. Nx = ceil(para.screensize(1)/para.stixelwidth);
  135. Ny = ceil(para.screensize(2)/para.stixelheight);
  136. if para.drawcircle, numpoints = 100; else, numpoints = 5; end
  137. locs = nan(Nx*Ny,numpoints,2);
  138. iter = 1;
  139. for ii = 1 : para.stixelheight : para.screensize(2)
  140. for jj = 1 : para.stixelwidth : para.screensize(1)
  141. if para.drawcircle
  142. locs(iter,1:numpoints,1) = jj + ((para.stixelwidth/2).*cos(linspace(0, 2*pi,numpoints))) - para.stixelwidth/2;
  143. locs(iter,1:numpoints,2) = ii + ((para.stixelheight/2).*sin(linspace(0, 2*pi,numpoints))) - para.stixelheight/2;
  144. else % little hack to draw squares
  145. locs(iter,1:numpoints,1) = [jj, jj+para.stixelwidth, jj+para.stixelwidth, jj, jj]-para.stixelwidth;
  146. locs(iter,1:numpoints,2) = [ii, ii, ii+para.stixelheight, ii+para.stixelheight,ii]-para.stixelheight;
  147. end
  148. iter = iter+1;
  149. end
  150. end
  151. x = squeeze(locs(:,:,1))';
  152. y = squeeze(locs(:,:,2))';
  153. f = reshape(1:Nx*Ny*numpoints,numpoints,Nx*Ny)'; % faces for patch fuction
  154. v = [x(:),y(:)]; % vertices for patch function
  155. %c = repmat(0.5*[1 1 1],Nx*Ny,1); % colors for patch function
  156. end
  157. %--------------------------------------------------------------------------------------------------%
  158. function [stimMat, seedlocationout, seedcolorout] = select_sparse_locations(seedlocation, seedcolor, greencontrasts, bluecontrasts, para)
  159. numcontrasts = size(greencontrasts,2);
  160. Nx = ceil(para.screensize(1)/para.stixelwidth);
  161. Ny = ceil(para.screensize(2)/para.stixelheight);
  162. stimMat = zeros(Ny*Nx,3)+0.5; % set all locations to gray
  163. locationlist = (0:Nx*Ny-1)';
  164. neighbourlist = get_neighbourlist(Nx, Ny, para.gapwidth, para.gapheight);
  165. % make an estimate of all possible locations
  166. possibleXYlocs = zeros(floor(numel(locationlist)/min(cellfun(@numel,neighbourlist))),1);
  167. %outside loop is faster
  168. [randvals, seedlocationout] = ran1(seedlocation, numel(possibleXYlocs));
  169. randIdx = 1;
  170. availablelist = locationlist;
  171. currXYlocs = possibleXYlocs;
  172. idx = 1;
  173. while numel(availablelist)>0
  174. randval = randvals(randIdx);
  175. randIdx = randIdx + 1;
  176. stixelInd = floor(randval * size(availablelist,1)) + 1;
  177. toadd = availablelist(stixelInd);
  178. toremove = neighbourlist{Ny * mod(toadd,Nx) + floor(toadd/Nx)+1};
  179. %using the mex function for speed
  180. % availablelist(builtin('_ismemberoneoutput',availablelist,sort(toremove)))=[];
  181. % this is for older matlab, for new matlab use the line below
  182. availablelist(ismembc(availablelist,sort(toremove))) = [];
  183. currXYlocs(idx) = toadd+1;
  184. idx = idx + 1;
  185. end
  186. contlist = cell(1,ceil((idx-1)/length(1:numcontrasts)));
  187. for jj = 1:ceil((idx-1) / length(1 : numcontrasts))
  188. [contlist{jj}, seedcolor] = fisher_Yates_shuffle_order(seedcolor, 1:numcontrasts);
  189. end
  190. seedcolorout = seedcolor;
  191. contindices = cell2mat(contlist)';
  192. %contperframe = contindices(1:idx-1);
  193. stimMat(currXYlocs(1:idx-1),1) = 0;
  194. stimMat(currXYlocs(1:idx-1),2) = greencontrasts (contindices(1:idx-1));
  195. stimMat(currXYlocs(1:idx-1),3) = bluecontrasts (contindices(1:idx-1));
  196. end
  197. %--------------------------------------------------------------------------------------------------%
  198. function neighbourlist = get_neighbourlist(nx, ny, gapx, gapy)
  199. neighbourlist = cell(nx*ny,1);
  200. idx = 1;
  201. for x = 0: nx-1
  202. for y = 0: ny-1
  203. localneigbour = [];
  204. for currX = x-gapx : x+gapx
  205. for currY = y-gapy : y+gapy
  206. if (currX >= 0 && currY >= 0 )
  207. localneigbour = [localneigbour, nx * currY + currX]; %#ok
  208. end
  209. end
  210. end
  211. neighbourlist{idx} = localneigbour;
  212. idx = idx+1;
  213. end
  214. end
  215. end
  216. %--------------------------------------------------------------------------------------------------%
  217. function paraout = read_stimulus_parameters(varargin)
  218. % first parse the user inputs
  219. p = inputParser(); % check the user options.
  220. p.addParameter('selecttextfile', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
  221. p.addParameter('stimduration', 30, @isnumeric);
  222. p.addParameter('contrastdiff', 0.02, @isnumeric);
  223. p.addParameter('mincontrast',-0.2, @isnumeric);
  224. p.addParameter('maxcontrast', 0.2, @isnumeric);
  225. p.addParameter('stixelwidth', 20, @isnumeric);
  226. p.addParameter('stixelheight', 20, @isnumeric);
  227. p.addParameter('gapwidth', 2, @isnumeric);
  228. p.addParameter('gapheight', 2, @isnumeric);
  229. p.addParameter('seedlocation', -1000, @isnumeric);
  230. p.addParameter('seedonoroffpixels', -10000, @isnumeric);
  231. p.addParameter('seedcolorcontrast', -2000, @isnumeric);
  232. p.addParameter('redmeanintensity', 0, @isnumeric);
  233. p.addParameter('greenmeanintensity', 0.5, @isnumeric);
  234. p.addParameter('bluemeanintensity', 0.5, @isnumeric);
  235. p.addParameter('lmargin',0, @isnumeric);
  236. p.addParameter('rmargin', 0, @isnumeric);
  237. p.addParameter('bmargin', 0, @isnumeric);
  238. p.addParameter('tmargin', 0, @isnumeric);
  239. p.addParameter('coneisolating', true, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
  240. p.addParameter('drawcircle', true, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
  241. p.addParameter('drawannulus', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
  242. p.addParameter('screensize', [864 480], @isnumeric);
  243. p.addParameter('refreshrate', 60, @isnumeric);
  244. p.addParameter('fullscreen', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
  245. p.addParameter('help', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
  246. p.parse(varargin{:});
  247. % defualt parameters
  248. defpara = p.Results;
  249. if defpara.help
  250. help_info_for_parameters(defpara);
  251. paraout = defpara;
  252. return;
  253. end
  254. if defpara.selecttextfile
  255. % now read the text files
  256. [stimfile, stimpath] = uigetfile('*.txt','Select a chromatic integration stimulus parameter file','chromatic_integration.txt');
  257. fid = fopen([stimpath,filesep,stimfile]);
  258. tline = fgetl(fid);
  259. while ischar(tline)
  260. tline = fgetl(fid);
  261. if tline == -1, break; end
  262. fn = extractBefore(tline,' = ');
  263. if isempty(fn), continue; end
  264. val = extractAfter(tline,' = ');
  265. % to convert to double
  266. if ~isnan(str2double(val))
  267. val = str2double(val);
  268. end
  269. % to convert to logical
  270. if strcmp(val,'true'), val = true; end
  271. if strcmp(val,'false'), val = false; end
  272. % store the values in a structure
  273. stimpara.(fn) = val;
  274. end
  275. fclose(fid);
  276. % compare the text file to the defualt values and fill the missing parameters
  277. fn = fieldnames(defpara);
  278. for ii = 1:numel(fn)
  279. if isfield(stimpara,fn{ii})
  280. paraout.(fn{ii}) = stimpara.(fn{ii});
  281. else
  282. paraout.(fn{ii}) = defpara.(fn{ii});
  283. end
  284. end
  285. else
  286. paraout = defpara;
  287. end
  288. if paraout.fullscreen
  289. monitorsize = get(0,'ScreenSize');
  290. paraout.screensize = monitorsize(3:4) ;
  291. end
  292. end
  293. %--------------------------------------------------------------------------------------------------%
  294. function help_info_for_parameters(defpara)
  295. fn = fieldnames(defpara);
  296. fprintf(['\n\n',repmat('==',1,50),'\r\n']);
  297. fprintf([repmat(' ',1,35),'List of Stimulus Parameters\r\n']);
  298. fprintf([repmat('==',1,50),'\r\n']);
  299. maxtxtlen = max(cellfun(@numel,fn))+10;
  300. for ii = 1:numel(fn)
  301. g = repmat(' ',1,maxtxtlen - numel(fn{ii}));
  302. fprintf(['\t-- \t%s',g,':',repmat(' ',1,10),'%s\n'],fn{ii},num2str(defpara.(fn{ii})));
  303. end
  304. fprintf(['\n',repmat('==',1,50),'\r\n']);
  305. end