spm_eeg_simulate.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. function [Dnew,meshsourceind]=spm_eeg_simulate(D,prefix,patchmni,simsignal,ormni,woi,whitenoise,SNRdB,trialind,mnimesh,dipfwhm,nAmdipmom);
  2. %function [Dnew,meshsourceind]=spm_eeg_simulate(D,prefix,patchmni,simsignal,woi,whitenoise,SNRdB,trialind,mnimesh,dipfwhm);
  3. %% Simulate a number of MSP patches at specified locations on existing mesh
  4. %
  5. % Created by: Jose David Lopez - ralph82co@gmail.com
  6. % Gareth Barnes - g.barnes@ucl.ac.uk
  7. % Vladimir Litvak - litvak.vladimir@gmail.com
  8. %
  9. %% D dataset
  10. %% prefix : prefix of new simulated dataset
  11. %% patchmni : patch centres in mni space or patch indices
  12. %% simsignal : Nsources x time series in nAm withinn woi
  13. %% woi: window of interest in seconds
  14. %% whitenoise level in rms femto Tesla or micro volts
  15. %% SNRdB power signal to noise ratio in dBs
  16. %% trialind: trials on which the simulated data will be added to the noise
  17. %% mnimesh : a new mesh with vertices in mni space
  18. %% dipfwhm - patch smoothness in mm
  19. %% Outputs
  20. %% Dnew- new dataset
  21. %% meshsourceind- vertex indices of sources on the mesh
  22. % $Id: spm_eeg_simulate.m 7118 2017-06-20 10:33:27Z guillaume $
  23. %% LOAD IN ORGINAL DATA
  24. useind=1; % D to use
  25. if nargin<2,
  26. prefix='';
  27. end;
  28. if nargin<3,
  29. patchmni=[];
  30. end;
  31. if nargin<4,
  32. simsignal=[];
  33. end;
  34. if nargin<5,
  35. ormni=[];
  36. end;
  37. if nargin<6,
  38. woi=[];
  39. end;
  40. if nargin<7,
  41. whitenoise=[];
  42. end;
  43. if nargin<8,
  44. SNRdB=[];
  45. end;
  46. if nargin<9,
  47. trialind=[];
  48. end;
  49. if nargin<10,
  50. mnimesh=[];
  51. end;
  52. if nargin<11
  53. dipfwhm=[]; %% number of iterations used to smooth patch out (more iterations, larger patch)
  54. end;
  55. if isempty(prefix),
  56. prefix='sim';
  57. end;
  58. if isempty(dipfwhm),
  59. dipfwhm=6; %% FWHM in mm
  60. end;
  61. if isempty(woi),
  62. woi=[D{useind}.time(1) D{useind}.time(end)];
  63. end;
  64. val=D{useind}.val;
  65. if isempty(patchmni),
  66. patchmni=[-45.4989 -30.6967 4.9213;...
  67. 46.7322 -31.2311 4.0085];
  68. end;
  69. if ~xor(isempty(whitenoise),isempty(SNRdB))
  70. error('Must specify either white noise level or sensor level SNR');
  71. end;
  72. [a1 b1 c1]=fileparts(D{useind}.fname);
  73. newfilename=[prefix b1];
  74. %% forcing overwrite of an existing file
  75. Dnew=D{useind}.clone([prefix b1]);
  76. if isempty(trialind)
  77. trialind=1:Dnew.ntrials;
  78. end;
  79. modstr=deblank(modality(D{1}));
  80. disp(sprintf('Simulating data on %s channels only',modstr));
  81. if ~isempty(mnimesh),
  82. Dnew.inv{val}.mesh.tess_mni.vert=mnimesh.vert;
  83. Dnew.inv{val}.mesh.tess_mni.face=mnimesh.face;
  84. Dnew.inv{val}.forward.mesh.vert=spm_eeg_inv_transform_points(Dnew.inv{val}.datareg.fromMNI,mnimesh.vert);
  85. Dnew.inv{val}.forward.mesh.face=mnimesh.face;
  86. end; % if
  87. % Two synchronous sources
  88. if ~isempty(patchmni),
  89. Ndips=size(patchmni,1);
  90. else
  91. Ndips=0;
  92. end;
  93. if size(simsignal,1)~=Ndips,
  94. error('number of signals given does not match number of sources');
  95. end;
  96. meshsourceind=[];
  97. disp('Using closest mesh vertices to the specified coordinates')
  98. for d=1:Ndips,
  99. vdist= Dnew.inv{val}.mesh.tess_mni.vert-repmat(patchmni(d,:),size(Dnew.inv{val}.mesh.tess_mni.vert,1),1);
  100. dist=sqrt(dot(vdist',vdist'));
  101. [mnidist(d),meshsourceind(d)] =min(dist);
  102. end;
  103. disp(sprintf('Furthest distance from dipole location to mesh %3.2f mm',max(mnidist)));
  104. if max(mnidist)>0.1
  105. warning('Supplied vertices do not sit on the mesh!');
  106. end;
  107. Ndip = size(simsignal,1); % Number of dipoles
  108. sensorunits = Dnew.units; %% of sensors (T or fT)
  109. try Dnew.inv{val}.forward.vol.unit, %% units of forward model for distance (m or mm)
  110. switch(Dnew.inv{val}.forward.vol.unit), %% correct for non-SI lead field scaling
  111. case 'mm'
  112. Lscale=1000*1000;
  113. case 'cm'
  114. Lscale=100*100;
  115. case 'm'
  116. Lscale=1.0;
  117. otherwise
  118. error('unknown volume unit');
  119. end;
  120. catch
  121. disp('No distance units found');
  122. Lscale=1.0;
  123. end;
  124. Ntrials = Dnew.ntrials; % Number of trials
  125. % define period over which dipoles are active
  126. startf1 = woi(1); % (sec) start time
  127. endf1 = woi(2); %% end time
  128. f1ind = intersect(find(Dnew.time>startf1),find(Dnew.time<=endf1));
  129. if length(f1ind)~=size(simsignal,2),
  130. error('Signal does not fit in time window');
  131. end;
  132. %if isequal(modstr, 'MEG')
  133. try
  134. chanind = Dnew.indchantype({'MEG', 'MEGPLANAR'}, 'GOOD');
  135. catch
  136. chanind = Dnew.indchantype(modality(D{1}), 'GOOD');
  137. end
  138. labels=Dnew.chanlabels(chanind);
  139. %chans = Dnew.indchantype(modstr, 'GOOD');
  140. simscale=1.0;
  141. try
  142. %% white noise is input in fT or uV so convert it to data sensorunits
  143. switch sensorunits{chanind(1)}
  144. case 'T'
  145. simscale=1e-15; %% convert from fT to T
  146. %whitenoise=whitenoise./1e15; %% rms femto Tesla
  147. %tmp=tmp./1e15; %% also computed in fT originally
  148. case 'fT'
  149. simscale=1.0; %% sensors already in fT
  150. %whitenoise=whitenoise; %% rms Tesla
  151. %tmp=tmp;
  152. case 'uV'
  153. whitenoise=whitenoise; %% micro volts
  154. tmp=tmp;
  155. case 'V'
  156. whitenoise=whitenoise./1e6; %% volts
  157. tmp=tmp./1e6;
  158. error('not supported for EEG at the moment');
  159. otherwise
  160. error('unknown sensor unit')
  161. end;
  162. catch
  163. disp('No sensor sensorunits found');
  164. end;
  165. whitenoise=whitenoise.*simscale;
  166. if ~isempty(ormni), %%%% DIPOLE SIMULATION
  167. disp('SIMULATING DIPOLE SOURCES');
  168. if size(ormni)~=size(patchmni),
  169. error('A 3D orientation must be specified for each source location');
  170. end;
  171. posdipmm=Dnew.inv{val}.datareg.fromMNI*[patchmni ones(size(ormni,1),1)]'; %% put into MEG space
  172. posdipmm=posdipmm(1:3)';
  173. %% need to make a pure rotation for orientation transform to native space
  174. M1 = Dnew.inv{val}.datareg.fromMNI;
  175. [U, L, V] = svd(M1(1:3, 1:3));
  176. ordip=ormni*(U*V');
  177. ordip=ordip./sqrt(dot(ordip,ordip)); %% make sure it is unit vector
  178. %% NB COULD ADD A PURE DIPOLE SIMULATION IN FUTURE
  179. sens=Dnew.inv{val}.forward.sensors;
  180. vol=Dnew.inv{val}.forward.vol;
  181. %% Get good channels
  182. useind=Dnew.indchantype(Dnew.modality);
  183. useind=setxor(Dnew.badchannels,goodchans);
  184. tmp=zeros(length(chanind),Dnew.nsamples);
  185. for i=1:Ndip,
  186. gmn = ft_compute_leadfield(posdipmm(i,:)*1e-3, sens, vol, 'dipoleunit', 'nA*m','chanunit',sensorunits);
  187. gain=gmn*ordip';
  188. tmp(:,f1ind)=tmp(:,f1ind)+gain(usedind,:)*simsignal(i,:);
  189. end; % for i
  190. else %%% CURRENT DENSITY ON SURFACE SIMULATION
  191. disp('SIMULATING CURRENT DISTRIBUTIONS ON MESH');
  192. %% CREATE A NEW FORWARD model for e mesh
  193. fprintf('Computing Gain Matrix: ')
  194. spm_input('Creating gain matrix',1,'d'); % Shows gain matrix computation
  195. [L Dnew] = spm_eeg_lgainmat(Dnew); % Gain matrix
  196. if isfield(Dnew.inv{val}.forward,'scale'),
  197. L=L./Dnew.inv{val}.forward.scale; %% account for rescaling of lead fields
  198. end;
  199. Nd = size(L,2); % number of dipoles
  200. Nchans=size(L,1);
  201. fprintf(' - done\n')
  202. nativemesh=Dnew.inv{val}.forward.mesh;
  203. Qe=[];, %% SNR may be defined by sensor level data in which case we have to get data first then go back
  204. %[Qp,Qe,priors,priorfname] = spm_eeg_invert_EBconstruct_priors(Dnew,val,nativemesh,priors,Qe,L,'sim');
  205. base.FWHMmm=dipfwhm;
  206. base.nAm=nAmdipmom;
  207. [a1,b1,c1]=fileparts(Dnew.fname);
  208. priordir=[Dnew.path filesep 'simprior_' b1 ];
  209. mkdir(priordir);
  210. fprintf('Saving prior in directory %s\n',priordir);
  211. [Qp,Qe,priorfname]=spm_eeg_invert_setuppatches(meshsourceind,nativemesh,base,priordir,Qe,L);
  212. % Add waveform of all smoothed sources to their equivalent dipoles
  213. % QGs add up to 0.9854
  214. fullsignal=zeros(Ndip,Dnew.nsamples); %% simulation padded with zeros
  215. fullsignal(1:Ndip,f1ind)=simsignal;
  216. tmp = sparse(zeros(Nchans,Dnew.nsamples)); % simulated data
  217. X=zeros(size(full(Qp{1}.q)));
  218. for j=1:Ndip
  219. Lq=L*Qp{j}.q; %% lead field * prior source distribution
  220. X=X+full(Qp{j}.q);
  221. for i=1:Dnew.nsamples,
  222. tmp(:,i) = tmp(:,i) + Lq*fullsignal(j,i); %% +sqrt(Qe)*randn(size(Qe,1),1);
  223. end;
  224. end;
  225. tmp=tmp.*simscale; %% scale to sensor units
  226. end; % if ori
  227. allchanstd=std(tmp');
  228. meanrmssignal=mean(allchanstd);
  229. if ~isempty(SNRdB),
  230. whitenoise = meanrmssignal.*(10^(-SNRdB/20));
  231. disp(sprintf('Setting white noise to give sensor level SNR of %dB',SNRdB));
  232. end;
  233. Qe=eye(Nchans).*(whitenoise^2); %% sensor level noise
  234. YY=zeros(length(chanind),length(chanind));
  235. n=0;
  236. for i=1:Ntrials
  237. if any(i == trialind), %% only add signal to specific trials
  238. Dnew(chanind,:,i) = full(tmp);
  239. else
  240. Dnew(chanind,:,i)=zeros(size(tmp));
  241. end;
  242. Dnew(:,:,i)=Dnew(:,:,i)+randn(size(Dnew(:,:,i))).*whitenoise; %% add white noise in fT
  243. y=squeeze(Dnew(chanind,:,i));
  244. YY=YY+y*y';
  245. n=n+size(y,2); %% number of samples
  246. end
  247. YY=YY./n; %% NORMALIZE HERE
  248. F=[];
  249. UL=L;
  250. save(priorfname,'Qp','Qe','UL','F', spm_get_defaults('mat.format'));
  251. figure;
  252. ploton=1;
  253. [LQpL,Q,sumLQpL,QE,Csensor]=spm_eeg_assemble_priors(L,Qp,{Qe},ploton);
  254. figure;
  255. subplot(3,1,1);
  256. imagesc(YY);colorbar;
  257. title('Empirical data covariance per sample: YY');
  258. subplot(3,1,2);
  259. imagesc(Csensor);colorbar;
  260. title('Prior total sensor covariance');
  261. subplot(3,1,3);
  262. imagesc(YY-Csensor);colorbar;
  263. title('Difference');
  264. %% Plot and save
  265. [dum,tmpind]=sort(allchanstd);
  266. dnewind=chanind(tmpind);
  267. if isempty(ormni)
  268. hold on
  269. mnivert=Dnew.inv{val}.mesh.tess_mni.vert;
  270. Nj = size(mnivert,1);
  271. M = X;
  272. G = sqrt(sparse(1:Nj,1,M,Nj,1));
  273. Fgraph = spm_figure('GetWin','Graphics');
  274. j = find(M);
  275. clf(Fgraph)
  276. figure(Fgraph)
  277. spm_mip(G(j),mnivert(j,:)',6);
  278. axis image
  279. title({sprintf('Generated source activity')});
  280. drawnow
  281. end;
  282. figure
  283. aux = tmp(tmpind(end),:);
  284. subplot(2,1,1);
  285. plot(Dnew.time,Dnew(dnewind(end),:,1),Dnew.time,aux,'r');
  286. title('Measured activity over max sensor');
  287. legend('Noisy','Noiseless');
  288. ylabel(sensorunits{chanind(1)});
  289. subplot(2,1,2);
  290. aux = tmp(tmpind(floor(length(tmpind)/2)),:);
  291. plot(Dnew.time,Dnew(dnewind(floor(length(tmpind)/2)),:,1),Dnew.time,aux,'r');
  292. title('Measured activity over median sensor');
  293. legend('Noisy','Noiseless');
  294. ylabel(sensorunits{chanind(1)});
  295. xlabel('Time in sec');
  296. Dnew.save;
  297. fprintf('\n Finish\n')