CExp.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. classdef CExp < handle
  2. % class of experiment
  3. % store experiment data, structure, procedure etc
  4. % 10 July, 2011
  5. % Created by: Z. Shi, shi@lmu.de
  6. % ver. 0.2
  7. % 21 June, 2012
  8. % add option: Continue. If will continue run experiment with given subject name
  9. % add additional Data variable
  10. %
  11. %constructor: two ways to initialize:
  12. % constant method:
  13. % p1, repetition,
  14. % p2, factors
  15. % adapative method:
  16. % p1, alpha
  17. % p2, beta
  18. %optional parameters: blockReptition, blockFactors;
  19. % adaptive method:maxTrials,maxRespRev, gamma, lambda
  20. properties
  21. sName = 'test'; %subject information
  22. sAge = 20;
  23. sGender = 'f';
  24. sPara; %additional parameter
  25. eType = 1; % 1 - constant stimuli; 2- bayesian adaptive method
  26. % type 1
  27. seq; % sequences of the trials
  28. resp; % corresponding responses
  29. aData; % store additional data, can be anything...
  30. % only for type 2
  31. beta;
  32. alpha;
  33. gamma;
  34. lambda;
  35. maxTrls; %maximum trial number
  36. maxRev; % maximum reversal number
  37. % index
  38. curTrl; %current trial index
  39. curIntensity;
  40. practice;
  41. % other information
  42. startTime;
  43. endTime;
  44. end
  45. methods
  46. function obj = CExp(p1,p2,varargin)
  47. % constant method:
  48. % p1, repetition,
  49. % p2, factors
  50. % adapative method:
  51. % p1, alpha
  52. % p2, beta
  53. %optional parameters: blockRepetition, blockFactors;
  54. % adaptive method:maxTrials,maxRespRev, gamma, lambda
  55. p = inputParser;
  56. p.addOptional('eType','c', @(x) any(strcmpi(x,{'c','a'})));
  57. p.addParamValue('blockRepetition',1,@(x) x>0);
  58. p.addParamValue('blockFactors', [], @isnumeric);
  59. p.addParamValue('maxTrials', 40, @isnumeric);
  60. p.addParamValue('maxRespRev',8, @isnumeric);
  61. p.addParamValue('gamma',0.025,@(x) min(x)>=0);
  62. p.addParamValue('lambda',0.025, @(x) x>=0);
  63. p.addParamValue('practice',5, @(x) x>=0);
  64. p.parse(varargin{:});
  65. if p.Results.eType == 'c' %constant method
  66. obj.eType = 1;
  67. obj.curTrl = 1;
  68. obj.seq = obj.genTrials(p1,p2,p.Results.blockRepetition,p.Results.blockFactors);
  69. obj.resp = [];
  70. obj.maxTrls = length(obj.seq);
  71. else % adaptive method
  72. obj.alpha = p1;
  73. obj.beta = p2;
  74. obj.gamma = p.Results.gamma;
  75. obj.lambda = p.Results.lambda;
  76. obj.maxTrls = p.Results.maxTrials;
  77. obj.maxRev = p.Results.maxRespRev;
  78. obj.practice = p.Results.practice;
  79. obj.eType = 2;
  80. obj.curTrl = 1;
  81. obj.seq = zeros(obj.maxTrls,2);
  82. obj.resp = [];
  83. end
  84. end
  85. function obj = guessThreshold(obj,x)
  86. obj.seq(1,1:2) = [x 0];
  87. obj.curIntensity = x;
  88. end
  89. function obj = setResp(obj,resp)
  90. obj.resp(obj.curTrl,1:size(resp,2)) = resp;
  91. obj.curTrl = obj.curTrl+1;
  92. end
  93. function curSeq = getCondition(obj)
  94. curSeq = obj.seq(obj.curTrl,:);
  95. end
  96. function obj = updateThreshold(obj, p_target)
  97. % logistic function
  98. % p = Logistic(x, alpha, beta, gamma, lambda)
  99. % alpha - threshold
  100. % beta - slope
  101. % gamma - chance performance / fa
  102. % lambda - lapsing rate
  103. % Based on MLP toolbox
  104. % updated intensity and fa are stored in obj.seq
  105. if obj.curTrl < obj.practice
  106. % practice
  107. ra = randperm(length(obj.alpha));
  108. obj.seq(obj.curTrl,1)= obj.alpha(ra(1));
  109. obj.seq(obj.curTrl,2) = obj.gamma(1);
  110. else
  111. ll=zeros(length(obj.alpha), 1);
  112. x = obj.seq(obj.practice:max(obj.curTrl-1,1),1);
  113. responses = obj.resp(obj.practice:max(obj.curTrl-1,1));
  114. % calculate the likelihood of each psychometric function
  115. for i=1:length(obj.alpha)
  116. for j=1:length(obj.gamma)
  117. ll(i, j)=CalculateLikelihood(obj,x, responses, obj.alpha(i), obj.gamma(j));
  118. end
  119. end
  120. % find the most likely psychometric function
  121. [i, j]=find(ll==max(max(ll)));
  122. if length(i)+length(j) > 2
  123. i = i(1);
  124. j = j(1);
  125. end;
  126. % calculate the level of the stimulus at p_target performance
  127. % using inverse logistic function
  128. obj.curIntensity = obj.alpha(i)-(1/obj.beta)*log(((1-obj.lambda-obj.gamma(j))./(p_target-obj.gamma(j)))-1);
  129. obj.seq(obj.curTrl,1)= obj.curIntensity;
  130. obj.seq(obj.curTrl,2) = obj.gamma(j);
  131. end
  132. end
  133. function bFinish = canStop(obj)
  134. if obj.eType == 2
  135. bFinish = 0;
  136. revnum = sum(abs(diff(obj.resp(obj.practice:obj.curTrl-1,1))));
  137. if revnum > obj.maxRev
  138. bFinish = 1;
  139. end
  140. if obj.curTrl > obj.maxTrls
  141. bFinish = 1;
  142. end
  143. else
  144. disp('This is only available for adaptive method');
  145. end
  146. end
  147. function ll=CalculateLikelihood(obj,x, responses, alpha, gamma)
  148. if obj.eType == 2
  149. warning off
  150. ll = 0;
  151. % calculate logistic probablity
  152. p=gamma+((1-obj.lambda-gamma).*(1./(1+exp(obj.beta.*(alpha-x)))));
  153. ll = sum(log(p(responses ==1)))+ sum(log(1-p(responses == 0)));
  154. else
  155. disp('This is only available for adaptive method');
  156. end
  157. end
  158. function h = plotLogistic(obj, a, g)
  159. %plot a logistic function for specify parameter, only available
  160. %for adaptive method
  161. if obj.eType == 2
  162. x = obj.alpha;
  163. if nargin < 2
  164. a = median(obj.alpha);
  165. g = median(obj.gamma);
  166. end
  167. y = g+((1-obj.lambda-g).*(1./(1+exp(obj.beta.*(a-x)))));
  168. h = figure;
  169. plot(x,y);
  170. else
  171. disp('This is only available for adaptive method');
  172. h = -1;
  173. end
  174. end
  175. function trials=genTrials(obj,withinblock_repetitions, withinblock_factors, betweenblock_repetitions, betweenblock_factors)
  176. % syntax: genTrials(withinblock_repetition, betweenblock_repetition, withinblock_factors, [ betweenblock_factors])
  177. % eg: genTrials(2, [2 3],10); generate two factors (2 levels and 3 levels resp. ) with within block repetition 2 and between block repetition 10
  178. % coded by: strongway
  179. rand('seed',sum(clock*100));
  180. if nargin < 3
  181. error('Incorrect number of arguments for genTrials function');
  182. elseif nargin == 3
  183. betweenblock_repetitions = 1;
  184. betweenblock_factors = [];
  185. elseif nargin == 4
  186. % when there's only betweenblock_repetitions, which is equal to swap
  187. % between block repetitions and factors -strongway 14. Dec. 2006
  188. betweenblock_factors = betweenblock_repetitions;
  189. betweenblock_repetitions = 1;
  190. end
  191. trials = [];
  192. block_design = [];
  193. numblock = betweenblock_repetitions;
  194. if ~isempty(betweenblock_factors)
  195. block_design = fullfact(betweenblock_factors);
  196. block_design = repmat(block_design, betweenblock_repetitions,1);
  197. idxb = randperm(length(block_design));
  198. block_design = block_design(idxb,:);
  199. numblock = length(block_design);
  200. end
  201. for iblock = 1:numblock
  202. %generate within block trials
  203. inblock_trials = fullfact(withinblock_factors);
  204. inblock_trials = repmat(inblock_trials,withinblock_repetitions,1);
  205. idx=randperm(size(inblock_trials,1));
  206. inblock_trials = inblock_trials(idx,:);
  207. if ~isempty(block_design)
  208. %add between factors
  209. blockwise_factors = repmat(block_design(iblock,:),length(inblock_trials),1);
  210. inblock_trials = [inblock_trials blockwise_factors];
  211. end
  212. trials = [trials; inblock_trials];
  213. end
  214. end
  215. function obj = subInfo(obj,sp1,sp2)
  216. % Get Subject information, sp1 and sp2 for additional parameters caption and parameters.
  217. promptParameters = {'Subject Name', 'Age', 'Gender (F or M?)'};
  218. defaultParameters = {'test', '20','F'};
  219. if nargin == 2
  220. promptParameters = [promptParameters, sp1];
  221. defaultParameters = {'test', '20','F','[]'};
  222. end
  223. if nargin == 3
  224. promptParameters = [promptParameters, sp1];
  225. defaultParameters = {'test', '20','F',sp2};
  226. end
  227. sub = inputdlg(promptParameters, 'Subject Info ', 1, defaultParameters);
  228. obj.sName = sub{1};
  229. obj.sAge = eval(sub{2});
  230. obj.sGender = sub{3};
  231. if nargin >= 2
  232. obj.sPara = eval(sub{4});
  233. end
  234. obj.startTime = now;
  235. % if it is continue, read the data
  236. if nargin == 3 && strcmp(sp1,'continue')
  237. if obj.sPara == 1
  238. subfile = ['data' filesep obj.sName '.mat'];
  239. if ~exist(subfile)
  240. error('No such data file');
  241. else
  242. load(subfile); % trials, expInfo, seq, resp
  243. %restore the data
  244. obj.seq = seq;
  245. obj.resp = resp;
  246. obj.aData = aData;
  247. obj.curTrl = length(obj.resp)+1;
  248. clear trials expInfo aData seq resp;
  249. end
  250. else % new subject, check if there is already a file
  251. subfile = ['data' filesep obj.sName '.mat'];
  252. if exist(subfile)
  253. error('Data file already exist');
  254. end
  255. end
  256. end
  257. end
  258. function saveData(obj,filename)
  259. if nargin<2
  260. filename = obj.sName;
  261. end
  262. obj.endTime = now;
  263. if ~isdir('data')
  264. mkdir('data'); %create a directory for storing files
  265. end
  266. seq = obj.seq;
  267. resp = obj.resp;
  268. trials = [seq(1:length(obj.resp),:), resp];
  269. expInfo.name = obj.sName;
  270. expInfo.age = obj.sAge;
  271. expInfo.sex = obj.sGender;
  272. expInfo.para = obj.sPara;
  273. expInfo.time = [obj.startTime, obj.endTime];
  274. aData = obj.aData;
  275. save(['data' filesep filename],'trials','expInfo','aData','seq','resp');
  276. end
  277. end
  278. end