Chromatic_Integration_Stimulus.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. function varargout = Chromatic_Integration_Stimulus(varargin)
  2. %===================================================================================
  3. % Parameter Default Usage
  4. %===================================================================================
  5. % selecttextfile false Browse prompt to select the parameters text file
  6. % stimduration 30 Stimulus presentation duration screen refresh rate unit (30 = 0.5 sec)
  7. % preframes 120 Duration of background light presented in between the stimulus flashes
  8. % maxcontrast 0.2 Maximum Weber contrast value (ranges from -1 to 1)
  9. % mincontrast -0.2 Minimum Weber contrast value (ranges from -1 to 1)
  10. % contrastdiff 0.02 Contrast steps from mincontrast to maxcontrast e.g. -20 : 2 : 20
  11. % seed -1000 Starting value for random number generator (should be always negative)
  12. % redmeanintensity 0 Mean intensity for red gun of the screen
  13. % greenmeanintensity 0.5 Mean intensity for green gun of the screen
  14. % bluemeanintensity 0.5 Mean intensity for blue gun of the screen
  15. % screensize 864 x 480 Screen resolution, default value is the resolution of the lightcrafter projector
  16. % refreshrate 60 Screen refresh rate in Hz
  17. % fullscreen false Option to display the stimulus in full-screen mode
  18. % help false Option to check the list of available parameters
  19. % lmargin 0 Margins from left side of the screen (not available for this stimulus)
  20. % rmagin 0 Margins from right side of the screen (not available for this stimulus)
  21. % tmargin 0 Margins from top of the screen (not available for this stimulus)
  22. % bmargin 0 Margins from bottom of the screen (not available for this stimulus)
  23. % coneisolating false Option to activate opsin-isolation (excluded for simplicity)
  24. %
  25. %===================================================================================
  26. para = read_stimulus_parameters(varargin{:});
  27. if para.help, return; end
  28. offcontrasts = fliplr(0:-para.contrastdiff:para.mincontrast);
  29. oncontrasts = 0:para.contrastdiff:para.maxcontrast;
  30. greencontrasts = [offcontrasts,oncontrasts];
  31. bluecontrasts = [oncontrasts,offcontrasts];
  32. % convert to weber contrast (mean+(contrast*mean))
  33. greencontrasts = para.greenmeanintensity + (greencontrasts * para.greenmeanintensity);
  34. bluecontrasts = para.bluemeanintensity + (bluecontrasts * para.bluemeanintensity);
  35. numcontrasts = size(greencontrasts,2);
  36. % The darwing of the screen goes here
  37. monitorsize = get(0,'ScreenSize');
  38. scpos = [monitorsize(3)/2-(para.screensize(1)/2), ...
  39. monitorsize(4)/2-(para.screensize(2)/2), para.screensize];
  40. % make an screen like figure
  41. fh = figure('Menu','none','ToolBar','none','Position',scpos,'Color',0.5*[1 1 1]);
  42. fh.Name = 'Example of Chromatic Integration Stimulus, from Khani and Gollisch (2021)';
  43. fh.Colormap = gray;
  44. ah = axes('Units','Normalize','Position',[0 0 1 1]);
  45. axis(ah,[0, para.screensize(1),0, para.screensize(2)]);
  46. axis(ah,'off');
  47. stimsize = [0+para.bmargin, 0+para.lmargin, para.screensize(1)-para.rmargin,...
  48. para.screensize(2)-para.tmargin];
  49. framecounter= 0;
  50. seed = para.seed;
  51. collistorderout = [];
  52. while ishandle(fh)
  53. if framecounter == 0
  54. % start with gray screen
  55. r= rectangle('pos',stimsize,'facecolor',[0.5 0.5 0.5],'edgecolor','none');
  56. end
  57. frameMod = mod(framecounter,para.stimduration + para.preframes);
  58. colorindexMod = floor(mod((framecounter)/(para.stimduration+para.preframes),numcontrasts));
  59. % resest the color order using Fisher-Yates random permutations
  60. if colorindexMod == 0 && frameMod == 0
  61. [col_order, seed] = fisher_Yates_shuffle_order(seed,1:numcontrasts);
  62. collistorderout = [collistorderout; col_order]; %#ok, not the most efficient way!
  63. end
  64. % show gray screen in between stimulus frames
  65. if frameMod < (para.preframes)
  66. r.FaceColor = [0.5 0.5 0.5]; % gray screen during preframes
  67. else
  68. r.FaceColor = [0, greencontrasts(col_order(colorindexMod+1)), bluecontrasts(col_order(colorindexMod+1))];
  69. end
  70. % this is to draw the frames relatively accurately.
  71. drawnow;
  72. java.lang.Thread.sleep(1/para.refreshrate*1e3);
  73. %pause(1/para.refreshrate);
  74. framecounter = framecounter+1;
  75. end
  76. % setting up output to get the stimulus order contrast orders
  77. collistorderout = collistorderout'; % transposing to make vectorization (:) easier
  78. collistorderout = collistorderout(:);
  79. out.numberContrastsShown = floor((framecounter-1)/(para.stimduration+para.preframes));
  80. collistorderout = collistorderout(1:out.numberContrastsShown);
  81. out.stimulusOrder = transpose(collistorderout);
  82. % to get contrasts between input values
  83. greencontrasts = [offcontrasts,oncontrasts];
  84. bluecontrasts = [oncontrasts,offcontrasts];
  85. out.greenContrasts = greencontrasts(collistorderout);
  86. out.blueContrasts = bluecontrasts(collistorderout);
  87. out.frameCounter = framecounter -1 ; % -1 for the last +1
  88. varargout{1} = out;
  89. end
  90. %--------------------------------------------------------------------------------------------------%
  91. %---------- sub-functions ----------%
  92. %--------------------------------------------------------------------------------------------------%
  93. function varargout = fisher_Yates_shuffle_order(seed,inputVec,varargin)
  94. %
  95. %%% fisher_Yates_shuffle_order %%%
  96. %
  97. %
  98. % This function generate psudorandom permution similar to randperm in MATLAB
  99. % but works with psudorandom number generator ran1. It also gives back the
  100. % most recent seed value to continue the permuation in case of repeated trials.
  101. % note that the direction of permutation is along x-axix or for columns of
  102. % MATALB not for the rows.
  103. %
  104. %
  105. % ===============================Inputs====================================
  106. %
  107. % seed : seed value for random number generation.
  108. % inputVec : input vector used for permutation.
  109. %
  110. %================================Output====================================
  111. %
  112. % testOrder : vector of permuted indices for the inputVec.
  113. % newSeed : the recent seed that used in the ran1 function.
  114. % outputVec : the permuted input vector along x-axis
  115. %
  116. % Note that the permution algorithem is based on Fisher-Yates shuffle
  117. % algorithem identical to what is used in the stimulus program.
  118. % for more info check :
  119. % https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
  120. %
  121. % written by Mohammad, 01.02.2016
  122. newSeed = seed;
  123. testOrder = zeros(1,size(inputVec,2));
  124. testOrder(1) = 1;
  125. for i = 2:length(inputVec)-1
  126. [randVal,newSeed] = ran1(newSeed);
  127. j = ceil(i*randVal); % based on Fischer-Yates algorithem
  128. testOrder(i) = testOrder(j);
  129. testOrder(j) = i;
  130. end
  131. testOrder = [testOrder(end),testOrder(1:end-1)]+1; % to match MATLAB indexing
  132. varargout{1} = testOrder;
  133. varargout{2} = newSeed;
  134. for j = 1:size(inputVec,1)
  135. varargout{3}(j,:) = inputVec(j,testOrder);
  136. end
  137. end
  138. %--------------------------------------------------------------------------------------------------%
  139. function paraout = read_stimulus_parameters(varargin)
  140. % first parse the user inputs
  141. p = inputParser(); % check the user options.
  142. p.addParameter('selecttextfile', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
  143. p.addParameter('stimduration', 30, @isnumeric);
  144. p.addParameter('preframes', 120, @isnumeric);
  145. p.addParameter('contrastdiff', 0.02, @isnumeric);
  146. p.addParameter('mincontrast',-0.2, @isnumeric);
  147. p.addParameter('maxcontrast', 0.2, @isnumeric);
  148. p.addParameter('seed', -1000, @isnumeric);
  149. p.addParameter('redmeanintensity', 0, @isnumeric);
  150. p.addParameter('greenmeanintensity', 0.5, @isnumeric);
  151. p.addParameter('bluemeanintensity', 0.5, @isnumeric);
  152. p.addParameter('lmargin',0, @isnumeric);
  153. p.addParameter('rmargin', 0, @isnumeric);
  154. p.addParameter('bmargin', 0, @isnumeric);
  155. p.addParameter('tmargin', 0, @isnumeric);
  156. p.addParameter('coneisolating', true, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
  157. p.addParameter('screensize', [864 480], @isnumeric);
  158. p.addParameter('refreshrate', 60, @isnumeric);
  159. p.addParameter('fullscreen', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
  160. p.addParameter('help', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
  161. p.parse(varargin{:});
  162. % defualt parameters
  163. defpara = p.Results;
  164. if defpara.help
  165. help_info_for_parameters(defpara);
  166. paraout = defpara;
  167. return;
  168. end
  169. if defpara.selecttextfile
  170. % now read the text files
  171. [stimfile, stimpath] = uigetfile('*.txt','Select a chromatic integration stimulus parameter file','chromatic_integration.txt');
  172. fid = fopen([stimpath,filesep,stimfile]);
  173. tline = fgetl(fid);
  174. while ischar(tline)
  175. tline = fgetl(fid);
  176. if tline == -1, break; end
  177. fn = extractBefore(tline,' = ');
  178. if isempty(fn), continue; end
  179. val = extractAfter(tline,' = ');
  180. % to convert to double
  181. if ~isnan(str2double(val))
  182. val = str2double(val);
  183. end
  184. % to convert to logical
  185. if strcmp(val,'true'), val = true; end
  186. if strcmp(val,'false'), val = false; end
  187. % store the values in a structure
  188. stimpara.(fn) = val;
  189. end
  190. fclose(fid);
  191. % compare the text file to the defualt values and fill the missing parameters
  192. fn = fieldnames(defpara);
  193. for ii = 1:numel(fn)
  194. if isfield(stimpara,fn{ii})
  195. paraout.(fn{ii}) = stimpara.(fn{ii});
  196. else
  197. paraout.(fn{ii}) = defpara.(fn{ii});
  198. end
  199. end
  200. else
  201. paraout = defpara;
  202. end
  203. if paraout.fullscreen
  204. monitorsize = get(0,'ScreenSize');
  205. paraout.screensize = monitorsize(3:4) ;
  206. end
  207. end
  208. %--------------------------------------------------------------------------------------------------%
  209. function help_info_for_parameters(defpara)
  210. fn = fieldnames(defpara);
  211. fprintf(['\n\n',repmat('==',1,50),'\r\n']);
  212. fprintf([repmat(' ',1,35),'List of Stimulus Parameters\r\n']);
  213. fprintf([repmat('==',1,50),'\r\n']);
  214. maxtxtlen = max(cellfun(@numel,fn))+10;
  215. for ii = 1:numel(fn)
  216. g = repmat(' ',1,maxtxtlen - numel(fn{ii}));
  217. fprintf(['\t-- \t%s',g,':',repmat(' ',1,10),'%s\n'],fn{ii},num2str(defpara.(fn{ii})));
  218. end
  219. fprintf(['\n',repmat('==',1,50),'\r\n']);
  220. end