|
@@ -0,0 +1,371 @@
|
|
|
+
|
|
|
+
|
|
|
+function Local_Chromatic_Integration_Stimulus(varargin)
|
|
|
+%
|
|
|
+%===================================================================================
|
|
|
+% Parameter Default Usage
|
|
|
+%===================================================================================
|
|
|
+%
|
|
|
+% selecttextfile false Browse prompt to select the parameters text file
|
|
|
+% stimduration 600 Stimulus presentation duration in refresh rate unit (600 = 10 sec).
|
|
|
+% maxcontrast 0.2 Maximum Weber contrast value (ranges from -1 to 1)
|
|
|
+% mincontrast -0.2 Minimum Weber contrast value (ranges from -1 to 1)
|
|
|
+% contrastdiff 0.02 Contrast steps from mincontrast to maxcontrast e.g. -20 : 2 : 20
|
|
|
+% stixelwidth 20 Width of each local stimulus in pixels
|
|
|
+% stixelheight 20 Height of each local stimulus in pixels
|
|
|
+% gapwidth 2 Number of locations to be avoided around each locally selected stimulus area
|
|
|
+% gapheight 2 Same as gapwidth but for the y-axis of each selected location
|
|
|
+% seedlocation -1000 Seed for random number generator for locations’ list (should be negative)
|
|
|
+% seedonoroffpixels -10000 Seed for random number generator of binary contrasts (not used here)
|
|
|
+% seedcolorcontrast -2000 Seed for random number generator for color contrast list (should be negative)
|
|
|
+% redmeanintensity 0 Mean intensity for red gun of the screen
|
|
|
+% greenmeanintensity 0.5 Mean intensity for green gun of the screen
|
|
|
+% bluemeanintensity 0.5 Mean intensity for blue gun of the screen
|
|
|
+% screensize 864 x 480 Screen resolution, default value is the resolution of the lightcrafter projector
|
|
|
+% refreshrate 60 Screen refresh rate in Hz
|
|
|
+% drawcircle true Option to display stimulus as circle or squares
|
|
|
+% drawannulus false Option to display the stimulus as annulus (not available for this stimulus)
|
|
|
+% fullscreen false Option to display the stimulus in full-screen mode
|
|
|
+% help false Option to check the list of available parameters
|
|
|
+% lmargin 0 Margins from left side of the screen (not available for this stimulus)
|
|
|
+% rmagin 0 Margins from right side of the screen (not available for this stimulus)
|
|
|
+% tmargin 0 Margins from top of the screen (not available for this stimulus)
|
|
|
+% bmargin 0 Margins from bottom of the screen (not available for this stimulus)
|
|
|
+% coneisolating false Option to activate opsin-isolation (excluded for simplicity)
|
|
|
+%
|
|
|
+%===================================================================================
|
|
|
+
|
|
|
+
|
|
|
+para = read_stimulus_parameters(varargin{:});
|
|
|
+if para.help, return; end
|
|
|
+
|
|
|
+
|
|
|
+offcontrasts = fliplr(0:-para.contrastdiff:para.mincontrast);
|
|
|
+oncontrasts = 0:para.contrastdiff:para.maxcontrast;
|
|
|
+
|
|
|
+greencontrasts = [offcontrasts,oncontrasts];
|
|
|
+bluecontrasts = [oncontrasts,offcontrasts];
|
|
|
+
|
|
|
+% convert to weber contrast (mean+(contrast*mean))
|
|
|
+greencontrasts = para.greenmeanintensity + (greencontrasts * para.greenmeanintensity);
|
|
|
+bluecontrasts = para.bluemeanintensity + (bluecontrasts * para.bluemeanintensity);
|
|
|
+
|
|
|
+% first get the corrdinates of all the locations
|
|
|
+[f, v] = draw_locations(para);
|
|
|
+
|
|
|
+% The darwing of the screen goes here
|
|
|
+monitorsize = get(0,'ScreenSize');
|
|
|
+scpos = [monitorsize(3)/2-(para.screensize(1)/2), ...
|
|
|
+ monitorsize(4)/2-(para.screensize(2)/2), para.screensize];
|
|
|
+% make an screen like figure
|
|
|
+fh = figure('Menu','none','ToolBar','none','Position',scpos,'Color',0.5*[1 1 1]);
|
|
|
+fh.Name = 'Example of Local Chromatic Integration Stimulus, from Khani and Gollisch (2021)';
|
|
|
+fh.Colormap = gray;
|
|
|
+ah = axes('Units','Normalize','Position',[0 0 1 1]);
|
|
|
+ axis(ah,[0, para.screensize(1),0, para.screensize(2)]);
|
|
|
+ axis(ah,'off');
|
|
|
+
|
|
|
+% inital variables
|
|
|
+framecounter= 0;
|
|
|
+seedlocation = para.seedlocation; % to randomize locations
|
|
|
+seedcolor = para.seedcolorcontrast; % to randomize contrast order
|
|
|
+stimdur = ceil((para.stimduration ./ para.refreshrate)/ 0.5); % start by an estimate of one cycle
|
|
|
+
|
|
|
+while ishandle(fh)
|
|
|
+
|
|
|
+ if framecounter == 2, tic; end % start the timer on the second frame
|
|
|
+ frameMod = mod(framecounter,stimdur);
|
|
|
+
|
|
|
+ if frameMod == 0
|
|
|
+ [contrastlist, seedlocation, seedcolor] = select_sparse_locations(seedlocation, seedcolor, greencontrasts, bluecontrasts, para);
|
|
|
+ end
|
|
|
+ if framecounter == 0
|
|
|
+ p = patch('Faces',f,'Vertices',v,'FaceVertexcData',contrastlist,'Facecolor','Flat','edgecolor','none');
|
|
|
+ else
|
|
|
+ p.FaceVertexCData = contrastlist;
|
|
|
+ end
|
|
|
+
|
|
|
+ % this is to draw the frames relatively accurately.
|
|
|
+ drawnow;
|
|
|
+ java.lang.Thread.sleep(1/para.refreshrate*1e3);
|
|
|
+ %pause(1/para.refreshrate);
|
|
|
+ if framecounter == 2
|
|
|
+ t = toc; % take the time of one trial and adjust the time based on that.
|
|
|
+ stimdur = ceil((para.stimduration ./ para.refreshrate)/ t);
|
|
|
+ end
|
|
|
+
|
|
|
+ framecounter = framecounter+1;
|
|
|
+end
|
|
|
+
|
|
|
+end
|
|
|
+
|
|
|
+%--------------------------------------------------------------------------------------------------%
|
|
|
+%---------- sub-functions ----------%
|
|
|
+%--------------------------------------------------------------------------------------------------%
|
|
|
+
|
|
|
+function varargout = fisher_Yates_shuffle_order(seed,inputVec,varargin)
|
|
|
+%
|
|
|
+%%% fisher_Yates_shuffle_order %%%
|
|
|
+%
|
|
|
+%
|
|
|
+% This function generate psudorandom permution similar to randperm in MATLAB
|
|
|
+% but works with psudorandom number generator ran1. It also gives back the
|
|
|
+% most recent seed value to continue the permuation in case of repeated trials.
|
|
|
+% note that the direction of permutation is along x-axix or for columns of
|
|
|
+% MATALB not for the rows.
|
|
|
+%
|
|
|
+%
|
|
|
+% ===============================Inputs====================================
|
|
|
+%
|
|
|
+% seed : seed value for random number generation.
|
|
|
+% inputVec : input vector used for permutation.
|
|
|
+%
|
|
|
+%================================Output====================================
|
|
|
+%
|
|
|
+% testOrder : vector of permuted indices for the inputVec.
|
|
|
+% newSeed : the recent seed that used in the ran1 function.
|
|
|
+% outputVec : the permuted input vector along x-axis
|
|
|
+%
|
|
|
+% Note that the permution algorithem is based on Fisher-Yates shuffle
|
|
|
+% algorithem identical to what is used in the stimulus program.
|
|
|
+% for more info check :
|
|
|
+% https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
|
|
|
+%
|
|
|
+% written by Mohammad, 01.02.2016
|
|
|
+
|
|
|
+newSeed = seed;
|
|
|
+testOrder = zeros(1,size(inputVec,2));
|
|
|
+testOrder(1) = 1;
|
|
|
+
|
|
|
+for i = 2:length(inputVec)-1
|
|
|
+
|
|
|
+ [randVal,newSeed] = ran1(newSeed);
|
|
|
+
|
|
|
+ j = ceil(i*randVal); % based on Fischer-Yates algorithem
|
|
|
+ testOrder(i) = testOrder(j);
|
|
|
+ testOrder(j) = i;
|
|
|
+
|
|
|
+end
|
|
|
+
|
|
|
+testOrder = [testOrder(end),testOrder(1:end-1)]+1; % to match MATLAB indexing
|
|
|
+varargout{1} = testOrder;
|
|
|
+varargout{2} = newSeed;
|
|
|
+for j = 1:size(inputVec,1)
|
|
|
+ varargout{3}(j,:) = inputVec(j,testOrder);
|
|
|
+end
|
|
|
+
|
|
|
+end
|
|
|
+
|
|
|
+%--------------------------------------------------------------------------------------------------%
|
|
|
+
|
|
|
+
|
|
|
+function [f, v] = draw_locations(para)
|
|
|
+
|
|
|
+Nx = ceil(para.screensize(1)/para.stixelwidth);
|
|
|
+Ny = ceil(para.screensize(2)/para.stixelheight);
|
|
|
+
|
|
|
+if para.drawcircle, numpoints = 100; else, numpoints = 5; end
|
|
|
+
|
|
|
+locs = nan(Nx*Ny,numpoints,2);
|
|
|
+iter = 1;
|
|
|
+for ii = 1 : para.stixelheight : para.screensize(2)
|
|
|
+ for jj = 1 : para.stixelwidth : para.screensize(1)
|
|
|
+ if para.drawcircle
|
|
|
+ locs(iter,1:numpoints,1) = jj + ((para.stixelwidth/2).*cos(linspace(0, 2*pi,numpoints))) - para.stixelwidth/2;
|
|
|
+ locs(iter,1:numpoints,2) = ii + ((para.stixelheight/2).*sin(linspace(0, 2*pi,numpoints))) - para.stixelheight/2;
|
|
|
+ else % little hack to draw squares
|
|
|
+ locs(iter,1:numpoints,1) = [jj, jj+para.stixelwidth, jj+para.stixelwidth, jj, jj]-para.stixelwidth;
|
|
|
+ locs(iter,1:numpoints,2) = [ii, ii, ii+para.stixelheight, ii+para.stixelheight,ii]-para.stixelheight;
|
|
|
+ end
|
|
|
+ iter = iter+1;
|
|
|
+ end
|
|
|
+end
|
|
|
+x = squeeze(locs(:,:,1))';
|
|
|
+y = squeeze(locs(:,:,2))';
|
|
|
+
|
|
|
+f = reshape(1:Nx*Ny*numpoints,numpoints,Nx*Ny)'; % faces for patch fuction
|
|
|
+v = [x(:),y(:)]; % vertices for patch function
|
|
|
+%c = repmat(0.5*[1 1 1],Nx*Ny,1); % colors for patch function
|
|
|
+end
|
|
|
+
|
|
|
+%--------------------------------------------------------------------------------------------------%
|
|
|
+
|
|
|
+function [stimMat, seedlocationout, seedcolorout] = select_sparse_locations(seedlocation, seedcolor, greencontrasts, bluecontrasts, para)
|
|
|
+
|
|
|
+numcontrasts = size(greencontrasts,2);
|
|
|
+Nx = ceil(para.screensize(1)/para.stixelwidth);
|
|
|
+Ny = ceil(para.screensize(2)/para.stixelheight);
|
|
|
+
|
|
|
+stimMat = zeros(Ny*Nx,3)+0.5; % set all locations to gray
|
|
|
+locationlist = (0:Nx*Ny-1)';
|
|
|
+neighbourlist = get_neighbourlist(Nx, Ny, para.gapwidth, para.gapheight);
|
|
|
+
|
|
|
+% make an estimate of all possible locations
|
|
|
+possibleXYlocs = zeros(floor(numel(locationlist)/min(cellfun(@numel,neighbourlist))),1);
|
|
|
+
|
|
|
+%outside loop is faster
|
|
|
+[randvals, seedlocationout] = ran1(seedlocation, numel(possibleXYlocs));
|
|
|
+randIdx = 1;
|
|
|
+
|
|
|
+availablelist = locationlist;
|
|
|
+currXYlocs = possibleXYlocs;
|
|
|
+idx = 1;
|
|
|
+
|
|
|
+while numel(availablelist)>0
|
|
|
+
|
|
|
+ randval = randvals(randIdx);
|
|
|
+ randIdx = randIdx + 1;
|
|
|
+ stixelInd = floor(randval * size(availablelist,1)) + 1;
|
|
|
+ toadd = availablelist(stixelInd);
|
|
|
+ toremove = neighbourlist{Ny * mod(toadd,Nx) + floor(toadd/Nx)+1};
|
|
|
+ %using the mex function for speed
|
|
|
+ % availablelist(builtin('_ismemberoneoutput',availablelist,sort(toremove)))=[];
|
|
|
+ % this is for older matlab, for new matlab use the line below
|
|
|
+ availablelist(ismembc(availablelist,sort(toremove))) = [];
|
|
|
+
|
|
|
+ currXYlocs(idx) = toadd+1;
|
|
|
+ idx = idx + 1;
|
|
|
+end
|
|
|
+
|
|
|
+contlist = cell(1,ceil((idx-1)/length(1:numcontrasts)));
|
|
|
+for jj = 1:ceil((idx-1) / length(1 : numcontrasts))
|
|
|
+ [contlist{jj}, seedcolor] = fisher_Yates_shuffle_order(seedcolor, 1:numcontrasts);
|
|
|
+end
|
|
|
+seedcolorout = seedcolor;
|
|
|
+contindices = cell2mat(contlist)';
|
|
|
+%contperframe = contindices(1:idx-1);
|
|
|
+stimMat(currXYlocs(1:idx-1),1) = 0;
|
|
|
+stimMat(currXYlocs(1:idx-1),2) = greencontrasts (contindices(1:idx-1));
|
|
|
+stimMat(currXYlocs(1:idx-1),3) = bluecontrasts (contindices(1:idx-1));
|
|
|
+
|
|
|
+end
|
|
|
+
|
|
|
+%--------------------------------------------------------------------------------------------------%
|
|
|
+
|
|
|
+function neighbourlist = get_neighbourlist(nx, ny, gapx, gapy)
|
|
|
+
|
|
|
+neighbourlist = cell(nx*ny,1);
|
|
|
+idx = 1;
|
|
|
+for x = 0: nx-1
|
|
|
+ for y = 0: ny-1
|
|
|
+ localneigbour = [];
|
|
|
+ for currX = x-gapx : x+gapx
|
|
|
+ for currY = y-gapy : y+gapy
|
|
|
+ if (currX >= 0 && currY >= 0 )
|
|
|
+ localneigbour = [localneigbour, nx * currY + currX]; %#ok
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+ neighbourlist{idx} = localneigbour;
|
|
|
+ idx = idx+1;
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+end
|
|
|
+
|
|
|
+%--------------------------------------------------------------------------------------------------%
|
|
|
+
|
|
|
+function paraout = read_stimulus_parameters(varargin)
|
|
|
+
|
|
|
+% first parse the user inputs
|
|
|
+p = inputParser(); % check the user options.
|
|
|
+p.addParameter('selecttextfile', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
|
|
|
+p.addParameter('stimduration', 30, @isnumeric);
|
|
|
+p.addParameter('contrastdiff', 0.02, @isnumeric);
|
|
|
+p.addParameter('mincontrast',-0.2, @isnumeric);
|
|
|
+p.addParameter('maxcontrast', 0.2, @isnumeric);
|
|
|
+
|
|
|
+p.addParameter('stixelwidth', 20, @isnumeric);
|
|
|
+p.addParameter('stixelheight', 20, @isnumeric);
|
|
|
+p.addParameter('gapwidth', 2, @isnumeric);
|
|
|
+p.addParameter('gapheight', 2, @isnumeric);
|
|
|
+
|
|
|
+p.addParameter('seedlocation', -1000, @isnumeric);
|
|
|
+p.addParameter('seedonoroffpixels', -10000, @isnumeric);
|
|
|
+p.addParameter('seedcolorcontrast', -2000, @isnumeric);
|
|
|
+
|
|
|
+p.addParameter('redmeanintensity', 0, @isnumeric);
|
|
|
+p.addParameter('greenmeanintensity', 0.5, @isnumeric);
|
|
|
+p.addParameter('bluemeanintensity', 0.5, @isnumeric);
|
|
|
+p.addParameter('lmargin',0, @isnumeric);
|
|
|
+p.addParameter('rmargin', 0, @isnumeric);
|
|
|
+p.addParameter('bmargin', 0, @isnumeric);
|
|
|
+p.addParameter('tmargin', 0, @isnumeric);
|
|
|
+p.addParameter('coneisolating', true, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
|
|
|
+p.addParameter('drawcircle', true, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
|
|
|
+p.addParameter('drawannulus', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
|
|
|
+p.addParameter('screensize', [864 480], @isnumeric);
|
|
|
+p.addParameter('refreshrate', 60, @isnumeric);
|
|
|
+p.addParameter('fullscreen', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
|
|
|
+p.addParameter('help', false, @(x) islogical(x) || (isnumeric(x) && ismember(x,[0,1])));
|
|
|
+
|
|
|
+p.parse(varargin{:});
|
|
|
+% defualt parameters
|
|
|
+defpara = p.Results;
|
|
|
+
|
|
|
+if defpara.help
|
|
|
+ help_info_for_parameters(defpara);
|
|
|
+ paraout = defpara;
|
|
|
+ return;
|
|
|
+end
|
|
|
+
|
|
|
+if defpara.selecttextfile
|
|
|
+ % now read the text files
|
|
|
+ [stimfile, stimpath] = uigetfile('*.txt','Select a chromatic integration stimulus parameter file','chromatic_integration.txt');
|
|
|
+
|
|
|
+ fid = fopen([stimpath,filesep,stimfile]);
|
|
|
+ tline = fgetl(fid);
|
|
|
+ while ischar(tline)
|
|
|
+ tline = fgetl(fid);
|
|
|
+ if tline == -1, break; end
|
|
|
+ fn = extractBefore(tline,' = ');
|
|
|
+ if isempty(fn), continue; end
|
|
|
+ val = extractAfter(tline,' = ');
|
|
|
+ % to convert to double
|
|
|
+ if ~isnan(str2double(val))
|
|
|
+ val = str2double(val);
|
|
|
+ end
|
|
|
+ % to convert to logical
|
|
|
+ if strcmp(val,'true'), val = true; end
|
|
|
+ if strcmp(val,'false'), val = false; end
|
|
|
+ % store the values in a structure
|
|
|
+ stimpara.(fn) = val;
|
|
|
+ end
|
|
|
+ fclose(fid);
|
|
|
+
|
|
|
+ % compare the text file to the defualt values and fill the missing parameters
|
|
|
+ fn = fieldnames(defpara);
|
|
|
+ for ii = 1:numel(fn)
|
|
|
+ if isfield(stimpara,fn{ii})
|
|
|
+ paraout.(fn{ii}) = stimpara.(fn{ii});
|
|
|
+ else
|
|
|
+ paraout.(fn{ii}) = defpara.(fn{ii});
|
|
|
+ end
|
|
|
+ end
|
|
|
+else
|
|
|
+ paraout = defpara;
|
|
|
+end
|
|
|
+
|
|
|
+if paraout.fullscreen
|
|
|
+ monitorsize = get(0,'ScreenSize');
|
|
|
+ paraout.screensize = monitorsize(3:4) ;
|
|
|
+end
|
|
|
+
|
|
|
+end
|
|
|
+
|
|
|
+%--------------------------------------------------------------------------------------------------%
|
|
|
+
|
|
|
+function help_info_for_parameters(defpara)
|
|
|
+
|
|
|
+fn = fieldnames(defpara);
|
|
|
+fprintf(['\n\n',repmat('==',1,50),'\r\n']);
|
|
|
+fprintf([repmat(' ',1,35),'List of Stimulus Parameters\r\n']);
|
|
|
+fprintf([repmat('==',1,50),'\r\n']);
|
|
|
+maxtxtlen = max(cellfun(@numel,fn))+10;
|
|
|
+for ii = 1:numel(fn)
|
|
|
+ g = repmat(' ',1,maxtxtlen - numel(fn{ii}));
|
|
|
+ fprintf(['\t-- \t%s',g,':',repmat(' ',1,10),'%s\n'],fn{ii},num2str(defpara.(fn{ii})));
|
|
|
+end
|
|
|
+fprintf(['\n',repmat('==',1,50),'\r\n']);
|
|
|
+
|
|
|
+end
|