PageRenderTime 27ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/examples/toolboxes/fieldtrip/inverse/beamformer_dics.m

http://brainstream.googlecode.com/
MATLAB | 485 lines | 350 code | 29 blank | 106 comment | 69 complexity | f8459e06fca10d5bcf3e88af1d7746ef MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, LGPL-2.0, GPL-3.0, GPL-2.0, LGPL-2.1, LGPL-3.0, BSD-2-Clause
  1. function [dipout] = beamformer_dics(dip, grad, vol, dat, Cf, varargin)
  2. % BEAMFORMER_DICS scans on pre-defined dipole locations with a single dipole
  3. % and returns the beamformer spatial filter output for a dipole on every
  4. % location. Dipole locations that are outside the head will return a
  5. % NaN value.
  6. %
  7. % Use as
  8. % [dipout] = beamformer_dics(dipin, grad, vol, dat, cov, varargin)
  9. % where
  10. % dipin is the input dipole model
  11. % grad is the gradiometer definition
  12. % vol is the volume conductor definition
  13. % dat is the data matrix with the ERP or ERF
  14. % cov is the data covariance or cross-spectral density matrix
  15. % and
  16. % dipout is the resulting dipole model with all details
  17. %
  18. % The input dipole model consists of
  19. % dipin.pos positions for dipole, e.g. regular grid
  20. % dipin.mom dipole orientation (optional)
  21. %
  22. % Additional options should be specified in key-value pairs and can be
  23. % 'Pr' = power of the external reference channel
  24. % 'Cr' = cross spectral density between all data channels and the external reference channel
  25. % 'refdip' = location of dipole with which coherence is computed
  26. % 'lambda' = regularisation parameter
  27. % 'powmethod' = can be 'trace' or 'lambda1'
  28. % 'feedback' = give progress indication, can be 'text', 'gui' or 'none'
  29. % 'fixedori' = use fixed or free orientation, can be 'yes' or 'no'
  30. % 'projectnoise' = project noise estimate through filter, can be 'yes' or 'no'
  31. % 'realfilter' = construct a real-valued filter, can be 'yes' or 'no'
  32. % 'keepfilter' = remember the beamformer filter, can be 'yes' or 'no'
  33. % 'keepleadfield' = remember the forward computation, can be 'yes' or 'no'
  34. % 'keepcsd' = remember the estimated cross-spectral density, can be 'yes' or 'no'
  35. %
  36. % These options influence the forward computation of the leadfield
  37. % 'reducerank' = reduce the leadfield rank, can be 'no' or a number (e.g. 2)
  38. % 'normalize' = normalize the leadfield
  39. % 'normalizeparam' = parameter for depth normalization (default = 0.5)
  40. %
  41. % If the dipole definition only specifies the dipole location, a rotating
  42. % dipole (regional source) is assumed on each location. If a dipole moment
  43. % is specified, its orientation will be used and only the strength will
  44. % be fitted to the data.
  45. % Copyright (C) 2003-2008, Robert Oostenveld
  46. %
  47. % Subversion does not use the Log keyword, use 'svn log <filename>' or 'svn -v log | less' to get detailled information
  48. if mod(nargin-5,2)
  49. % the first 5 arguments are fixed, the other arguments should come in pairs
  50. error('invalid number of optional arguments');
  51. end
  52. % these optional settings do not have defaults
  53. Pr = keyval('Pr', varargin);
  54. Cr = keyval('Cr', varargin);
  55. refdip = keyval('refdip', varargin);
  56. powmethod = keyval('powmethod', varargin); % the default for this is set below
  57. realfilter = keyval('realfilter', varargin); % the default for this is set below
  58. % these settings pertain to the forward model, the defaults are set in compute_leadfield
  59. reducerank = keyval('reducerank', varargin);
  60. normalize = keyval('normalize', varargin);
  61. normalizeparam = keyval('normalizeparam', varargin);
  62. % these optional settings have defaults
  63. feedback = keyval('feedback', varargin); if isempty(feedback), feedback = 'text'; end
  64. keepcsd = keyval('keepcsd', varargin); if isempty(keepcsd), keepcsd = 'no'; end
  65. keepfilter = keyval('keepfilter', varargin); if isempty(keepfilter), keepfilter = 'no'; end
  66. keepleadfield = keyval('keepleadfield', varargin); if isempty(keepleadfield), keepleadfield = 'no'; end
  67. lambda = keyval('lambda', varargin); if isempty(lambda ), lambda = 0; end
  68. projectnoise = keyval('projectnoise', varargin); if isempty(projectnoise), projectnoise = 'yes'; end
  69. fixedori = keyval('fixedori', varargin); if isempty(fixedori), fixedori = 'no'; end
  70. % convert the yes/no arguments to the corresponding logical values
  71. keepcsd = strcmp(keepcsd, 'yes');
  72. keepfilter = strcmp(keepfilter, 'yes');
  73. keepleadfield = strcmp(keepleadfield, 'yes');
  74. projectnoise = strcmp(projectnoise, 'yes');
  75. fixedori = strcmp(fixedori, 'yes');
  76. % FIXME besides regular/complex lambda1, also implement a real version
  77. % default is to use the largest singular value of the csd matrix, see Gross 2001
  78. if isempty(powmethod)
  79. powmethod = 'lambda1';
  80. end
  81. % default is to be consistent with the original description of DICS in Gross 2001
  82. if isempty(realfilter)
  83. realfilter = 'no';
  84. end
  85. % use these two logical flags instead of doing the string comparisons each time again
  86. powtrace = strcmp(powmethod, 'trace');
  87. powlambda1 = strcmp(powmethod, 'lambda1');
  88. if ~isempty(Cr)
  89. % ensure that the cross-spectral density with the reference signal is a column matrix
  90. Cr = Cr(:);
  91. end
  92. if isfield(dip, 'mom') && fixedori
  93. error('you cannot specify a dipole orientation and fixedmom simultaneously');
  94. end
  95. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  96. % find the dipole positions that are inside/outside the brain
  97. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  98. if ~isfield(dip, 'inside') && ~isfield(dip, 'outside');
  99. [dip.inside, dip.outside] = find_inside_vol(dip.pos, vol);
  100. elseif isfield(dip, 'inside') && ~isfield(dip, 'outside');
  101. dip.outside = setdiff(1:size(dip.pos,1), dip.inside);
  102. elseif ~isfield(dip, 'inside') && isfield(dip, 'outside');
  103. dip.inside = setdiff(1:size(dip.pos,1), dip.outside);
  104. end
  105. % select only the dipole positions inside the brain for scanning
  106. dip.origpos = dip.pos;
  107. dip.originside = dip.inside;
  108. dip.origoutside = dip.outside;
  109. if isfield(dip, 'mom')
  110. dip.mom = dip.mom(:,dip.inside);
  111. end
  112. if isfield(dip, 'leadfield')
  113. fprintf('using precomputed leadfields\n');
  114. dip.leadfield = dip.leadfield(dip.inside);
  115. end
  116. if isfield(dip, 'filter')
  117. fprintf('using precomputed filters\n');
  118. dip.filter = dip.filter(dip.inside);
  119. end
  120. if isfield(dip, 'subspace')
  121. fprintf('using subspace projection\n');
  122. dip.subspace = dip.subspace(dip.inside);
  123. end
  124. dip.pos = dip.pos(dip.inside, :);
  125. dip.inside = 1:size(dip.pos,1);
  126. dip.outside = [];
  127. % dics has the following sub-methods, which depend on the function input arguments
  128. % power only, cortico-muscular coherence and cortico-cortical coherence
  129. if ~isempty(Cr) && ~isempty(Pr) && isempty(refdip)
  130. % compute cortico-muscular coherence, using reference cross spectral density
  131. submethod = 'dics_refchan';
  132. elseif isempty(Cr) && isempty(Pr) && ~isempty(refdip)
  133. % compute cortico-cortical coherence with a dipole at the reference position
  134. submethod = 'dics_refdip';
  135. elseif isempty(Cr) && isempty(Pr) && isempty(refdip)
  136. % only compute power of a dipole at the grid positions
  137. submethod = 'dics_power';
  138. else
  139. error('invalid combination of input arguments for dics');
  140. end
  141. isrankdeficient = (rank(Cf)<size(Cf,1));
  142. if isrankdeficient && ~isfield(dip, 'filter')
  143. warning('cross-spectral density matrix is rank deficient')
  144. end
  145. % it is difficult to give a quantitative estimate of lambda, therefore also
  146. % support relative (percentage) measure that can be specified as string (e.g. '10%')
  147. if ~isempty(lambda) && ischar(lambda) && lambda(end)=='%'
  148. ratio = sscanf(lambda, '%f%%');
  149. ratio = ratio/100;
  150. lambda = ratio * trace(Cf)/size(Cf,1);
  151. end
  152. if projectnoise
  153. % estimate the noise power, which is further assumed to be equal and uncorrelated over channels
  154. if isrankdeficient
  155. % estimated noise floor is equal to or higher than lambda
  156. noise = lambda;
  157. else
  158. % estimate the noise level in the covariance matrix by the smallest singular value
  159. noise = svd(Cf);
  160. noise = noise(end);
  161. % estimated noise floor is equal to or higher than lambda
  162. noise = max(noise, lambda);
  163. end
  164. end
  165. % the inverse only has to be computed once for all dipoles
  166. if strcmp(realfilter, 'yes')
  167. % the filter is computed using only the leadfield and the inverse covariance or CSD matrix
  168. % therefore using the real-valued part of the CSD matrix here ensures a real-valued filter
  169. invCf = pinv(real(Cf) + lambda * eye(size(Cf)));
  170. else
  171. invCf = pinv(Cf + lambda * eye(size(Cf)));
  172. end
  173. if isfield(dip, 'subspace')
  174. fprintf('using subspace projection\n');
  175. % remember the original data prior to the voxel-dependant subspace projection
  176. Cf_pre_subspace = Cf;
  177. if strcmp(submethod, 'dics_refchan')
  178. Cr_pre_subspace = Cr;
  179. Pr_pre_subspace = Pr;
  180. end
  181. end
  182. % start the scanning with the proper metric
  183. progress('init', feedback, 'scanning grid');
  184. switch submethod
  185. case 'dics_power'
  186. % only compute power of a dipole at the grid positions
  187. for i=1:size(dip.pos,1)
  188. if isfield(dip, 'leadfield')
  189. % reuse the leadfield that was previously computed
  190. lf = dip.leadfield{i};
  191. elseif isfield(dip, 'mom')
  192. % compute the leadfield for a fixed dipole orientation
  193. lf = ft_compute_leadfield(dip.pos(i,:), grad, vol, 'reducerank', reducerank, 'normalize', normalize, 'normalizeparam', normalizeparam) * dip.mom(:,i);
  194. else
  195. % compute the leadfield
  196. lf = ft_compute_leadfield(dip.pos(i,:), grad, vol, 'reducerank', reducerank, 'normalize', normalize, 'normalizeparam', normalizeparam);
  197. end
  198. if isfield(dip, 'subspace')
  199. % do subspace projection of the forward model
  200. lf = dip.subspace{i} * lf;
  201. % the cross-spectral density becomes voxel dependent due to the projection
  202. Cf = dip.subspace{i} * Cf_pre_subspace * dip.subspace{i}';
  203. invCf = pinv(dip.subspace{i} * (Cf_pre_subspace + lambda * eye(size(Cf))) * dip.subspace{i}');
  204. end
  205. if fixedori
  206. % compute the leadfield for the optimal dipole orientation
  207. % subsequently the leadfield for only that dipole orientation will be used for the final filter computation
  208. filt = pinv(lf' * invCf * lf) * lf' * invCf;
  209. [u, s, v] = svd(real(filt * Cf * ctranspose(filt)));
  210. eta = u(:,1);
  211. lf = lf * eta;
  212. dipout.ori{i} = eta;
  213. end
  214. if isfield(dip, 'filter')
  215. % use the provided filter
  216. filt = dip.filter{i};
  217. else
  218. % construct the spatial filter
  219. filt = pinv(lf' * invCf * lf) * lf' * invCf; % Gross eqn. 3, use PINV/SVD to cover rank deficient leadfield
  220. end
  221. csd = filt * Cf * ctranspose(filt); % Gross eqn. 4 and 5
  222. if powlambda1
  223. dipout.pow(i) = lambda1(csd); % compute the power at the dipole location, Gross eqn. 8
  224. elseif powtrace
  225. dipout.pow(i) = real(trace(csd)); % compute the power at the dipole location
  226. end
  227. if keepcsd
  228. dipout.csd{i} = csd;
  229. end
  230. if projectnoise
  231. if powlambda1
  232. dipout.noise(i) = noise * lambda1(filt * ctranspose(filt));
  233. elseif powtrace
  234. dipout.noise(i) = noise * real(trace(filt * ctranspose(filt)));
  235. end
  236. if keepcsd
  237. dipout.noisecsd{i} = noise * filt * ctranspose(filt);
  238. end
  239. end
  240. if keepfilter
  241. dipout.filter{i} = filt;
  242. end
  243. if keepleadfield
  244. dipout.leadfield{i} = lf;
  245. end
  246. progress(i/size(dip.pos,1), 'scanning grid %d/%d\n', i, size(dip.pos,1));
  247. end
  248. case 'dics_refchan'
  249. % compute cortico-muscular coherence, using reference cross spectral density
  250. for i=1:size(dip.pos,1)
  251. if isfield(dip, 'leadfield')
  252. % reuse the leadfield that was previously computed
  253. lf = dip.leadfield{i};
  254. elseif isfield(dip, 'mom')
  255. % compute the leadfield for a fixed dipole orientation
  256. lf = ft_compute_leadfield(dip.pos(i,:), grad, vol, 'reducerank', reducerank, 'normalize', normalize) .* dip.mom(i,:)';
  257. else
  258. % compute the leadfield
  259. lf = ft_compute_leadfield(dip.pos(i,:), grad, vol, 'reducerank', reducerank, 'normalize', normalize);
  260. end
  261. if isfield(dip, 'subspace')
  262. % do subspace projection of the forward model
  263. lf = dip.subspace{i} * lf;
  264. % the cross-spectral density becomes voxel dependent due to the projection
  265. Cf = dip.subspace{i} * Cf_pre_subspace * dip.subspace{i}';
  266. invCf = pinv(dip.subspace{i} * (Cf_pre_subspace + lambda * eye(size(Cf))) * dip.subspace{i}');
  267. end
  268. if fixedori
  269. % compute the leadfield for the optimal dipole orientation
  270. % subsequently the leadfield for only that dipole orientation will be used for the final filter computation
  271. filt = pinv(lf' * invCf * lf) * lf' * invCf;
  272. [u, s, v] = svd(real(filt * Cf * ctranspose(filt)));
  273. eta = u(:,1);
  274. lf = lf * eta;
  275. dipout.ori{i} = eta;
  276. end
  277. if isfield(dip, 'filter')
  278. % use the provided filter
  279. filt = dip.filter{i};
  280. else
  281. % construct the spatial filter
  282. filt = pinv(lf' * invCf * lf) * lf' * invCf; % use PINV/SVD to cover rank deficient leadfield
  283. end
  284. if powlambda1
  285. [pow, ori] = lambda1(filt * Cf * ctranspose(filt)); % compute the power and orientation at the dipole location, Gross eqn. 4, 5 and 8
  286. elseif powtrace
  287. pow = real(trace(filt * Cf * ctranspose(filt))); % compute the power at the dipole location
  288. end
  289. csd = filt*Cr; % Gross eqn. 6
  290. if powlambda1
  291. % FIXME this should use the dipole orientation with maximum power
  292. coh = lambda1(csd)^2 / (pow * Pr); % Gross eqn. 9
  293. elseif powtrace
  294. coh = norm(csd)^2 / (pow * Pr);
  295. end
  296. dipout.pow(i) = pow;
  297. dipout.coh(i) = coh;
  298. if keepcsd
  299. dipout.csd{i} = csd;
  300. end
  301. if projectnoise
  302. if powlambda1
  303. dipout.noise(i) = noise * lambda1(filt * ctranspose(filt));
  304. elseif powtrace
  305. dipout.noise(i) = noise * real(trace(filt * ctranspose(filt)));
  306. end
  307. if keepcsd
  308. dipout.noisecsd{i} = noise * filt * ctranspose(filt);
  309. end
  310. end
  311. if keepfilter
  312. dipout.filter{i} = filt;
  313. end
  314. if keepleadfield
  315. dipout.leadfield{i} = lf;
  316. end
  317. progress(i/size(dip.pos,1), 'scanning grid %d/%d\n', i, size(dip.pos,1));
  318. end
  319. case 'dics_refdip'
  320. if isfield(dip, 'subspace')
  321. error('subspace projections are not supported for beaming cortico-cortical coherence');
  322. end
  323. if fixedori
  324. error('fixed orientations are not supported for beaming cortico-cortical coherence');
  325. end
  326. % compute cortio-cortical coherence with a dipole at the reference position
  327. lf1 = ft_compute_leadfield(refdip, grad, vol, 'reducerank', reducerank, 'normalize', normalize);
  328. % construct the spatial filter for the first (reference) dipole location
  329. filt1 = pinv(lf1' * invCf * lf1) * lf1' * invCf; % use PINV/SVD to cover rank deficient leadfield
  330. if powlambda1
  331. Pref = lambda1(filt1 * Cf * ctranspose(filt1)); % compute the power at the first dipole location, Gross eqn. 8
  332. elseif powtrace
  333. Pref = real(trace(filt1 * Cf * ctranspose(filt1))); % compute the power at the first dipole location
  334. end
  335. for i=1:size(dip.pos,1)
  336. if isfield(dip, 'leadfield')
  337. % reuse the leadfield that was previously computed
  338. lf2 = dip.leadfield{i};
  339. elseif isfield(dip, 'mom')
  340. % compute the leadfield for a fixed dipole orientation
  341. lf2 = ft_compute_leadfield(dip.pos(i,:), grad, vol, 'reducerank', reducerank, 'normalize', normalize) .* dip.mom(i,:)';
  342. else
  343. % compute the leadfield
  344. lf2 = ft_compute_leadfield(dip.pos(i,:), grad, vol, 'reducerank', reducerank, 'normalize', normalize);
  345. end
  346. if isfield(dip, 'filter')
  347. % use the provided filter
  348. filt2 = dip.filter{i};
  349. else
  350. % construct the spatial filter for the second dipole location
  351. filt2 = pinv(lf2' * invCf * lf2) * lf2' * invCf; % use PINV/SVD to cover rank deficient leadfield
  352. end
  353. csd = filt1 * Cf * ctranspose(filt2); % compute the cross spectral density between the two dipoles, Gross eqn. 4
  354. if powlambda1
  355. pow = lambda1(filt2 * Cf * ctranspose(filt2)); % compute the power at the second dipole location, Gross eqn. 8
  356. elseif powtrace
  357. pow = real(trace(filt2 * Cf * ctranspose(filt2))); % compute the power at the second dipole location
  358. end
  359. if powlambda1
  360. coh = lambda1(csd)^2 / (pow * Pref); % compute the coherence between the first and second dipole
  361. elseif powtrace
  362. coh = real(trace((csd)))^2 / (pow * Pref); % compute the coherence between the first and second dipole
  363. end
  364. dipout.pow(i) = pow;
  365. dipout.coh(i) = coh;
  366. if keepcsd
  367. dipout.csd{i} = csd;
  368. end
  369. if projectnoise
  370. if powlambda1
  371. dipout.noise(i) = noise * lambda1(filt2 * ctranspose(filt2));
  372. elseif powtrace
  373. dipout.noise(i) = noise * real(trace(filt2 * ctranspose(filt2)));
  374. end
  375. if keepcsd
  376. dipout.noisecsd{i} = noise * filt2 * ctranspose(filt2);
  377. end
  378. end
  379. if keepleadfield
  380. dipout.leadfield{i} = lf2;
  381. end
  382. progress(i/size(dip.pos,1), 'scanning grid %d/%d\n', i, size(dip.pos,1));
  383. end
  384. end % switch submethod
  385. progress('close');
  386. dipout.inside = dip.originside;
  387. dipout.outside = dip.origoutside;
  388. dipout.pos = dip.origpos;
  389. % reassign the scan values over the inside and outside grid positions
  390. if isfield(dipout, 'leadfield')
  391. dipout.leadfield(dipout.inside) = dipout.leadfield;
  392. dipout.leadfield(dipout.outside) = {[]};
  393. end
  394. if isfield(dipout, 'filter')
  395. dipout.filter(dipout.inside) = dipout.filter;
  396. dipout.filter(dipout.outside) = {[]};
  397. end
  398. if isfield(dipout, 'ori')
  399. dipout.ori(dipout.inside) = dipout.ori;
  400. dipout.ori(dipout.outside) = {[]};
  401. end
  402. if isfield(dipout, 'pow')
  403. dipout.pow(dipout.inside) = dipout.pow;
  404. dipout.pow(dipout.outside) = nan;
  405. end
  406. if isfield(dipout, 'noise')
  407. dipout.noise(dipout.inside) = dipout.noise;
  408. dipout.noise(dipout.outside) = nan;
  409. end
  410. if isfield(dipout, 'coh')
  411. dipout.coh(dipout.inside) = dipout.coh;
  412. dipout.coh(dipout.outside) = nan;
  413. end
  414. if isfield(dipout, 'csd')
  415. dipout.csd(dipout.inside) = dipout.csd;
  416. dipout.csd(dipout.outside) = {[]};
  417. end
  418. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  419. % helper function to obtain the largest singular value
  420. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  421. function [s, ori] = lambda1(x)
  422. % determine the largest singular value, which corresponds to the power along the dominant direction
  423. [u, s, v] = svd(x);
  424. s = s(1);
  425. ori = u(:,1);
  426. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  427. % helper function to compute the pseudo inverse. This is the same as the
  428. % standard Matlab function, except that the default tolerance is twice as
  429. % high.
  430. % Copyright 1984-2004 The MathWorks, Inc.
  431. % $Revision: 919 $ $Date: 2009/06/17 13:40:37 $
  432. % default tolerance increased by factor 2 (Robert Oostenveld, 7 Feb 2004)
  433. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  434. function X = pinv(A,varargin)
  435. [m,n] = size(A);
  436. if n > m
  437. X = pinv(A',varargin{:})';
  438. else
  439. [U,S,V] = svd(A,0);
  440. if m > 1, s = diag(S);
  441. elseif m == 1, s = S(1);
  442. else s = 0;
  443. end
  444. if nargin == 2
  445. tol = varargin{1};
  446. else
  447. tol = 10 * max(m,n) * max(s) * eps;
  448. end
  449. r = sum(s > tol);
  450. if (r == 0)
  451. X = zeros(size(A'),class(A));
  452. else
  453. s = diag(ones(r,1)./s(1:r));
  454. X = V(:,1:r)*s*U(:,1:r)';
  455. end
  456. end