PageRenderTime 28ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/toolboxes/fieldtrip/ft_megrealign.m

http://brainstream.googlecode.com/
MATLAB | 450 lines | 241 code | 40 blank | 169 comment | 41 complexity | d4a25a58ae94f46f1a97b14ae4e577a5 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 [data] = ft_megrealign(cfg, data)
  2. % FT_MEGREALIGN interpolates MEG data towards standard gradiometer locations
  3. % by projecting the individual timelocked data towards a coarse source
  4. % reconstructed representation and computing the magnetic field on
  5. % the standard gradiometer locations.
  6. %
  7. % Use as
  8. % [interp] = ft_megrealign(cfg, data)
  9. %
  10. % Required configuration options:
  11. % cfg.template
  12. % cfg.inwardshift
  13. %
  14. % The new gradiometer definition is obtained from a template dataset,
  15. % or can be constructed by averaging the gradiometer positions over
  16. % multiple datasets.
  17. % cfg.template = single dataset that serves as template
  18. % cfg.template(1..N) = datasets that are averaged into the standard
  19. %
  20. % The realignment is done by computing a minumum norm estimate using a
  21. % large number of dipoles that are placed in the upper layer of the brain
  22. % surface, followed by a forward computation towards the template
  23. % gradiometer array. This requires the specification of a volume conduction
  24. % model of the head and of a source model.
  25. %
  26. % A head model must be specified with
  27. % cfg.hdmfile = string, file containing the volume conduction model
  28. % or alternatively manually using
  29. % cfg.vol.r = radius of sphere
  30. % cfg.vol.o = [x, y, z] position of origin
  31. %
  32. % A source model (i.e. a superficial layer with distributed sources) can be
  33. % constructed from a headshape file, or from the volume conduction model
  34. % cfg.spheremesh = number of dipoles in the source layer (default = 642)
  35. % cfg.inwardshift = depth of the source layer relative to the headshape
  36. % surface or volume conduction model (no default
  37. % supplied, see below)
  38. % cfg.headshape = a filename containing headshape, a structure containing a
  39. % single triangulated boundary, or a Nx3 matrix with surface
  40. % points
  41. %
  42. % If you specify a headshape and it describes the skin surface, you should specify an
  43. % inward shift of 2.5 cm.
  44. %
  45. % For a single-sphere or a local-spheres volume conduction model based on the skin
  46. % surface, an inward shift of 2.5 cm is reasonable.
  47. %
  48. % For a single-sphere or a local-spheres volume conduction model based on the brain
  49. % surface, you should probably use an inward shift of about 1 cm.
  50. %
  51. % For a realistic single-shell volume conduction model based on the brain surface, you
  52. % should probably use an inward shift of about 1 cm.
  53. %
  54. % Other options are
  55. % cfg.pruneratio = for singular values, default is 1e-3
  56. % cfg.verify = 'yes' or 'no', show the percentage difference (default = 'yes')
  57. % cfg.feedback = 'yes' or 'no' (default = 'no')
  58. % cfg.channel = Nx1 cell-array with selection of channels (default = 'MEG'),
  59. % see FT_CHANNELSELECTION for details
  60. % cfg.trials = 'all' or a selection given as a 1xN vector (default = 'all')
  61. %
  62. % This implements the method described by T.R. Knosche, Transformation
  63. % of whole-head MEG recordings between different sensor positions.
  64. % Biomed Tech (Berl). 2002 Mar;47(3):59-62.
  65. %
  66. % To facilitate data-handling and distributed computing with the peer-to-peer
  67. % module, this function has the following options:
  68. % cfg.inputfile = ...
  69. % cfg.outputfile = ...
  70. % If you specify one of these (or both) the input data will be read from a *.mat
  71. % file on disk and/or the output data will be written to a *.mat file. These mat
  72. % files should contain only a single variable, corresponding with the
  73. % input/output structure.
  74. %
  75. % See also FT_PREPARE_LOCALSPHERES, FT_PREPARE_SINGLESHELL
  76. % This function depends on FT_PREPARE_DIPOLE_GRID
  77. %
  78. % This function depends on FT_PREPARE_VOL_SENS which has the following options:
  79. % cfg.channel
  80. % cfg.elec
  81. % cfg.elecfile
  82. % cfg.grad
  83. % cfg.gradfile
  84. % cfg.hdmfile, documented
  85. % cfg.order
  86. % cfg.vol, documented
  87. % Copyright (C) 2004-2007, Robert Oostenveld
  88. %
  89. % This file is part of FieldTrip, see http://www.ru.nl/neuroimaging/fieldtrip
  90. % for the documentation and details.
  91. %
  92. % FieldTrip is free software: you can redistribute it and/or modify
  93. % it under the terms of the GNU General Public License as published by
  94. % the Free Software Foundation, either version 3 of the License, or
  95. % (at your option) any later version.
  96. %
  97. % FieldTrip is distributed in the hope that it will be useful,
  98. % but WITHOUT ANY WARRANTY; without even the implied warranty of
  99. % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  100. % GNU General Public License for more details.
  101. %
  102. % You should have received a copy of the GNU General Public License
  103. % along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
  104. %
  105. % $Id: ft_megrealign.m 4955 2011-12-07 21:07:50Z roboos $
  106. revision = '$Id: ft_megrealign.m 4955 2011-12-07 21:07:50Z roboos $';
  107. % do the general setup of the function
  108. ft_defaults
  109. ft_preamble help
  110. ft_preamble callinfo
  111. ft_preamble trackconfig
  112. ft_preamble loadvar data
  113. % check if the input cfg is valid for this function
  114. cfg = ft_checkconfig(cfg, 'renamed', {'plot3d', 'feedback'});
  115. cfg = ft_checkconfig(cfg, 'renamedval', {'headshape', 'headmodel', []});
  116. cfg = ft_checkconfig(cfg, 'required', {'inwardshift', 'template'});
  117. % set the default configuration
  118. if ~isfield(cfg, 'headshape'), cfg.headshape = []; end
  119. if ~isfield(cfg, 'pruneratio'), cfg.pruneratio = 1e-3; end
  120. if ~isfield(cfg, 'spheremesh'), cfg.spheremesh = 642; end
  121. if ~isfield(cfg, 'verify'), cfg.verify = 'yes'; end
  122. if ~isfield(cfg, 'feedback'), cfg.feedback = 'yes'; end
  123. if ~isfield(cfg, 'trials'), cfg.trials = 'all'; end
  124. if ~isfield(cfg, 'channel'), cfg.channel = 'MEG'; end
  125. if ~isfield(cfg, 'topoparam'), cfg.topoparam = 'rms'; end
  126. % store original datatype
  127. dtype = ft_datatype(data);
  128. % check if the input data is valid for this function
  129. data = ft_checkdata(data, 'datatype', 'raw', 'feedback', 'yes', 'hassampleinfo', 'yes', 'ismeg', 'yes');
  130. % do realignment per trial
  131. pertrial = all(ismember({'nasX';'nasY';'nasZ';'lpaX';'lpaY';'lpaZ';'rpaX';'rpaY';'rpaZ'}, data.label));
  132. % put the low-level options pertaining to the dipole grid in their own field
  133. cfg = ft_checkconfig(cfg, 'createsubcfg', {'grid'});
  134. % select trials of interest
  135. if ~strcmp(cfg.trials, 'all')
  136. fprintf('selecting %d trials\n', length(cfg.trials));
  137. data = ft_selectdata(data, 'rpt', cfg.trials);
  138. end
  139. Ntrials = length(data.trial);
  140. % retain only the MEG channels in the data and temporarily store
  141. % the rest, these will be added back to the transformed data later.
  142. cfg.channel = ft_channelselection(cfg.channel, data.label);
  143. dataindx = match_str(data.label, cfg.channel);
  144. restindx = setdiff(1:length(data.label),dataindx);
  145. if ~isempty(restindx)
  146. fprintf('removing %d non-MEG channels from the data\n', length(restindx));
  147. rest.label = data.label(restindx); % first remember the rest
  148. data.label = data.label(dataindx); % then reduce the data
  149. for i=1:Ntrials
  150. rest.trial{i} = data.trial{i}(restindx,:); % first remember the rest
  151. data.trial{i} = data.trial{i}(dataindx,:); % then reduce the data
  152. end
  153. else
  154. rest.label = {};
  155. rest.trial = {};
  156. end
  157. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  158. % construct the average template gradiometer array
  159. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  160. Ntemplate = length(cfg.template);
  161. for i=1:Ntemplate
  162. if ischar(cfg.template{i}),
  163. fprintf('reading template sensor position from %s\n', cfg.template{i});
  164. template(i) = ft_read_sens(cfg.template{i});
  165. elseif isstruct(cfg.template{i}) && isfield(cfg.template{i}, 'coilpos') && isfield(cfg.template{i}, 'coilori') && isfield(cfg.template{i}, 'tra'),
  166. template(i) = cfg.template{i};
  167. end
  168. end
  169. grad = ft_average_sens(template);
  170. % construct the final template gradiometer definition
  171. template = [];
  172. template.grad = grad;
  173. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  174. % FT_PREPARE_VOL_SENS will match the data labels, the gradiometer labels and the
  175. % volume model labels (in case of a multisphere model) and result in a gradiometer
  176. % definition that only contains the gradiometers that are present in the data.
  177. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  178. volcfg = [];
  179. if isfield(cfg, 'hdmfile')
  180. volcfg.hdmfile = cfg.hdmfile;
  181. elseif isfield(cfg, 'vol')
  182. volcfg.vol = cfg.vol;
  183. end
  184. volcfg.grad = data.grad;
  185. volcfg.channel = data.label; % this might be a subset of the MEG channels
  186. gradorig = data.grad; % this is needed later on for plotting. As of
  187. % yet the next step is not entirely correct, because it does not keep track
  188. % of the balancing of the gradiometer array. FIXME this may require some
  189. % thought because the leadfields are computed with low level functions and
  190. % do not easily accommodate for matching the correct channels with each
  191. % other (in order to compute the projection matrix).
  192. [volold, data.grad] = prepare_headmodel(volcfg);
  193. % note that it is neccessary to keep the two volume conduction models
  194. % seperate, since the single-shell Nolte model contains gradiometer specific
  195. % precomputed parameters. Note that this is not guaranteed to result in a
  196. % good projection for local sphere models.
  197. volcfg.grad = template.grad;
  198. volcfg.channel = 'MEG'; % include all MEG channels
  199. [volnew, template.grad] = prepare_headmodel(volcfg);
  200. if strcmp(ft_senstype(data.grad), ft_senstype(template.grad))
  201. [id, it] = match_str(data.grad.label, template.grad.label);
  202. fprintf('mean distance towards template gradiometers is %.2f %s\n', mean(sum((data.grad.chanpos(id,:)-template.grad.chanpos(it,:)).^2, 2).^0.5), template.grad.unit);
  203. else
  204. % the projection is from one MEG system to another MEG system, which makes a comparison of the data difficult
  205. cfg.feedback = 'no';
  206. cfg.verify = 'no';
  207. end
  208. % create the dipole grid on which the data will be projected
  209. tmpcfg = [];
  210. tmpcfg.vol = volold;
  211. tmpcfg.grad = data.grad;
  212. % copy all options that are potentially used in ft_prepare_sourcemodel
  213. try, tmpcfg.grid = cfg.grid; end
  214. try, tmpcfg.mri = cfg.mri; end
  215. try, tmpcfg.headshape = cfg.headshape; end
  216. try, tmpcfg.tightgrid = cfg.tightgrid; end
  217. try, tmpcfg.symmetry = cfg.symmetry; end
  218. try, tmpcfg.smooth = cfg.smooth; end
  219. try, tmpcfg.threshold = cfg.threshold; end
  220. try, tmpcfg.spheremesh = cfg.spheremesh; end
  221. try, tmpcfg.inwardshift = cfg.inwardshift; end
  222. try, tmpcfg.sourceunits = cfg.sourceunits; end
  223. grid = ft_prepare_sourcemodel(tmpcfg);
  224. pos = grid.pos;
  225. % sometimes some of the dipole positions are nan, due to problems with the headsurface triangulation
  226. % remove them to prevent problems with the forward computation
  227. sel = find(any(isnan(pos(:,1)),2));
  228. pos(sel,:) = [];
  229. % compute the forward model for the new gradiometer positions
  230. fprintf('computing forward model for %d dipoles\n', size(pos,1));
  231. lfnew = ft_compute_leadfield(pos, template.grad, volnew);
  232. if ~pertrial,
  233. %this needs to be done only once
  234. lfold = ft_compute_leadfield(pos, data.grad, volold);
  235. [realign, noalign, bkalign] = computeprojection(lfold, lfnew, cfg.pruneratio, cfg.verify);
  236. else
  237. %the forward model and realignment matrices have to be computed for each trial
  238. %this also goes for the singleshell volume conductor model
  239. %x = which('rigidbodyJM'); %this function is needed
  240. %if isempty(x),
  241. % error('you are trying out experimental code for which you need some extra functionality which is currently not in the release version of fieldtrip. if you are interested in trying it out, contact jan-mathijs');
  242. %end
  243. end
  244. % interpolate the data towards the template gradiometers
  245. for i=1:Ntrials
  246. fprintf('realigning trial %d\n', i);
  247. if pertrial,
  248. %warp the gradiometer array according to the motiontracking data
  249. sel = match_str(rest.label, {'nasX';'nasY';'nasZ';'lpaX';'lpaY';'lpaZ';'rpaX';'rpaY';'rpaZ'});
  250. hmdat = rest.trial{i}(sel,:);
  251. if ~all(hmdat==repmat(hmdat(:,1),[1 size(hmdat,2)]))
  252. error('only one position per trial is at present allowed');
  253. else
  254. %M = rigidbodyJM(hmdat(:,1))
  255. M = headcoordinates(hmdat(1:3,1),hmdat(4:6,1),hmdat(7:9,1));
  256. grad = ft_transform_sens(M, data.grad);
  257. end
  258. volcfg.grad = grad;
  259. %compute volume conductor
  260. [volold, grad] = prepare_headmodel(volcfg);
  261. %compute forward model
  262. lfold = ft_compute_leadfield(pos, grad, volold);
  263. %compute projection matrix
  264. [realign, noalign, bkalign] = computeprojection(lfold, lfnew, cfg.pruneratio, cfg.verify);
  265. end
  266. data.realign{i} = realign * data.trial{i};
  267. if strcmp(cfg.verify, 'yes')
  268. % also compute the residual variance when interpolating
  269. [id,it] = match_str(data.grad.label, template.grad.label);
  270. rvrealign = rv(data.trial{i}(id,:), data.realign{i}(it,:));
  271. fprintf('original -> template RV %.2f %%\n', 100 * mean(rvrealign));
  272. datnoalign = noalign * data.trial{i};
  273. datbkalign = bkalign * data.trial{i};
  274. rvnoalign = rv(data.trial{i}, datnoalign);
  275. rvbkalign = rv(data.trial{i}, datbkalign);
  276. fprintf('original -> original RV %.2f %%\n', 100 * mean(rvnoalign));
  277. fprintf('original -> template -> original RV %.2f %%\n', 100 * mean(rvbkalign));
  278. end
  279. end
  280. % plot the topography before and after the realignment
  281. if strcmp(cfg.feedback, 'yes')
  282. warning('showing MEG topography (RMS value over time) in the first trial only');
  283. Nchan = length(data.grad.label);
  284. [id,it] = match_str(data.grad.label, template.grad.label);
  285. pnt1 = data.grad.chanpos(id,:);
  286. pnt2 = template.grad.chanpos(it,:);
  287. prj1 = elproj(pnt1); tri1 = delaunay(prj1(:,1), prj1(:,2));
  288. prj2 = elproj(pnt2); tri2 = delaunay(prj2(:,1), prj2(:,2));
  289. switch cfg.topoparam
  290. case 'rms'
  291. p1 = sqrt(mean(data.trial{1}(id,:).^2, 2));
  292. p2 = sqrt(mean(data.realign{1}(it,:).^2, 2));
  293. case 'svd'
  294. [u, s, v] = svd(data.trial{1}(id,:)); p1 = u(:,1);
  295. [u, s, v] = svd(data.realign{1}(it,:)); p2 = u(:,1);
  296. otherwise
  297. error('unsupported cfg.topoparam');
  298. end
  299. X = [pnt1(:,1) pnt2(:,1)]';
  300. Y = [pnt1(:,2) pnt2(:,2)]';
  301. Z = [pnt1(:,3) pnt2(:,3)]';
  302. % show figure with old an new helmets, volume model and dipole grid
  303. figure
  304. hold on
  305. ft_plot_vol(volold);
  306. plot3(grid.pos(:,1),grid.pos(:,2),grid.pos(:,3),'b.');
  307. plot3(pnt1(:,1), pnt1(:,2), pnt1(:,3), 'r.') % original positions
  308. plot3(pnt2(:,1), pnt2(:,2), pnt2(:,3), 'g.') % template positions
  309. line(X,Y,Z, 'color', 'black');
  310. view(-90, 90);
  311. % show figure with data on old helmet location
  312. figure
  313. hold on
  314. plot3(pnt1(:,1), pnt1(:,2), pnt1(:,3), 'r.') % original positions
  315. plot3(pnt2(:,1), pnt2(:,2), pnt2(:,3), 'g.') % template positions
  316. line(X,Y,Z, 'color', 'black');
  317. axis equal; axis vis3d
  318. triplot(pnt1, tri1, p1);
  319. title('RMS, before realignment')
  320. view(-90, 90)
  321. % show figure with data on new helmet location
  322. figure
  323. hold on
  324. plot3(pnt1(:,1), pnt1(:,2), pnt1(:,3), 'r.') % original positions
  325. plot3(pnt2(:,1), pnt2(:,2), pnt2(:,3), 'g.') % template positions
  326. line(X,Y,Z, 'color', 'black');
  327. axis equal; axis vis3d
  328. triplot(pnt2, tri2, p2);
  329. title('RMS, after realignment')
  330. view(-90, 90)
  331. end
  332. % store the realigned data in a new structure
  333. interp.label = template.grad.label;
  334. interp.grad = template.grad; % replace with the template gradiometer array
  335. interp.trial = data.realign; % remember the processed data
  336. interp.fsample = data.fsample;
  337. interp.time = data.time;
  338. % add the rest channels back to the data, these were not interpolated
  339. if ~isempty(rest.label)
  340. fprintf('adding %d non-MEG channels back to the data (', length(rest.label));
  341. fprintf('%s, ', rest.label{1:end-1});
  342. fprintf('%s)\n', rest.label{end});
  343. for trial=1:length(rest.trial)
  344. interp.trial{trial} = [interp.trial{trial}; rest.trial{trial}];
  345. end
  346. interp.label = [interp.label; rest.label];
  347. end
  348. % copy the trial specific information into the output
  349. if isfield(data, 'trialinfo')
  350. interp.trialinfo = data.trialinfo;
  351. end
  352. % copy the sampleinfo field as well
  353. if isfield(data, 'sampleinfo')
  354. interp.sampleinfo = data.sampleinfo;
  355. end
  356. % convert back to input type if necessary
  357. switch dtype
  358. case 'timelock'
  359. interp = ft_checkdata(interp, 'datatype', 'timelock');
  360. otherwise
  361. % keep the output as it is
  362. end
  363. % do the general cleanup and bookkeeping at the end of the function
  364. ft_postamble trackconfig
  365. ft_postamble callinfo
  366. ft_postamble previous data
  367. % rename the output variable to accomodate the savevar postamble
  368. data = interp;
  369. ft_postamble history data
  370. ft_postamble savevar data
  371. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  372. % subfunction that computes the projection matrix(ces)
  373. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  374. function [realign, noalign, bkalign] = computeprojection(lfold, lfnew, pruneratio, verify)
  375. % compute this inverse only once, although it is used twice
  376. tmp = prunedinv(lfold, pruneratio);
  377. % compute the three interpolation matrices
  378. fprintf('computing interpolation matrix #1\n');
  379. realign = lfnew * tmp;
  380. if strcmp(verify, 'yes')
  381. fprintf('computing interpolation matrix #2\n');
  382. noalign = lfold * tmp;
  383. fprintf('computing interpolation matrix #3\n');
  384. bkalign = lfold * prunedinv(lfnew, pruneratio) * realign;
  385. else
  386. noalign = [];
  387. bkalign = [];
  388. end
  389. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  390. % subfunction that computes the inverse using a pruned SVD
  391. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  392. function [lfi] = prunedinv(lf, r)
  393. [u, s, v] = svd(lf);
  394. if r<1,
  395. % treat r as a ratio
  396. p = find(s<(s(1,1)*r) & s~=0);
  397. else
  398. % treat r as the number of spatial components to keep
  399. diagels = 1:(min(size(s))+1):(min(size(s)).^2);
  400. p = diagels((r+1):end);
  401. end
  402. fprintf('pruning %d from %d, i.e. removing the %d smallest spatial components\n', length(p), min(size(s)), length(p));
  403. s(p) = 0;
  404. s(find(s~=0)) = 1./s(find(s~=0));
  405. lfi = v * s' * u';