PageRenderTime 38ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/examples/toolboxes/fieldtrip/ft_electroderealign.m

http://brainstream.googlecode.com/
MATLAB | 717 lines | 476 code | 61 blank | 180 comment | 60 complexity | c9cde055ee76e9d5143140b69015d8fe 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 [norm] = ft_electroderealign(cfg)
  2. % FT_ELECTRODEREALIGN rotates and translates electrode positions to
  3. % template electrode positions or towards the head surface. It can
  4. % either perform a rigid body transformation, in which only the
  5. % coordinate system is changed, or it can apply additional deformations
  6. % to the input electrodes.
  7. %
  8. % Use as
  9. % [elec] = ft_electroderealign(cfg)
  10. %
  11. % Three different methods for aligning the input electrodes are implemented:
  12. % based on a warping method, based on the fiducials or interactive with a
  13. % graphical user interface. Each of these approaches is described below.
  14. %
  15. % 1) You can apply a spatial transformation/deformation (i.e. 'warp')
  16. % that automatically minimizes the distance between the electrodes
  17. % and the template or standard electrode set. The warping methods use
  18. % a non-linear search to optimize the error between input and template
  19. % electrodes or the head surface.
  20. %
  21. % 2) You can apply a rigid body realignment based on three fiducial locations.
  22. % Realigning using the fiducials only ensures that the fiducials (typically
  23. % nose, left and right ear) are along the same axes in the input eectrode
  24. % set as in the template electrode set.
  25. %
  26. % 3) You can display the electrode positions together with the skin surface,
  27. % and manually (using the graphical user interface) adjust the rotation,
  28. % translation and scaling parameters, so that the two match.
  29. %
  30. % 4) You can display the skin surface and manually position the electrodes by
  31. % clicking.
  32. %
  33. % The configuration can contain the following options
  34. % cfg.method = string representing the method for aligning or placing the electrodes
  35. % 'template' realign the electrodes to a template electrode set
  36. % 'fiducial' realign using the NAS, LPA and RPA fiducials
  37. % 'interactive' realign manually using a graphical user interface
  38. % 'manual' manual positioning of the electrodes by clicking in a graphical user interface
  39. % cfg.warp = string describing the spatial transformation for the template method
  40. % 'rigidbody' apply a rigid-body warp (default)
  41. % 'globalrescale' apply a rigid-body warp with global rescaling
  42. % 'traditional' apply a rigid-body warp with individual axes rescaling
  43. % 'nonlin1' apply a 1st order non-linear warp
  44. % 'nonlin2' apply a 2nd order non-linear warp
  45. % 'nonlin3' apply a 3rd order non-linear warp
  46. % 'nonlin4' apply a 4th order non-linear warp
  47. % 'nonlin5' apply a 5th order non-linear warp
  48. % cfg.channel = Nx1 cell-array with selection of channels (default = 'all'),
  49. % see FT_CHANNELSELECTION for details
  50. % cfg.fiducial = cell-array with the name of three fiducials used for
  51. % realigning (default = {'nasion', 'lpa', 'rpa'})
  52. % cfg.casesensitive = 'yes' or 'no', determines whether string comparisons
  53. % between electrode labels are case sensitive (default = 'yes')
  54. % cfg.feedback = 'yes' or 'no' (default = 'no')
  55. %
  56. % The electrode set that will be realigned is specified as
  57. % cfg.elecfile = string with filename, or alternatively
  58. % cfg.elec = structure with electrode definition
  59. %
  60. % If you want to align the electrodes to a single template electrode set
  61. % or to multiple electrode sets (which will be averaged), you should
  62. % specify the template electrode sets as
  63. % cfg.template = single electrode set that serves as standard
  64. % or
  65. % cfg.template{1..N} = list of electrode sets that are averaged into the standard
  66. % The template electrode sets can be specified either as electrode
  67. % structures (i.e. when they are already read in memory) or as electrode
  68. % files.
  69. %
  70. % If you only want to realign using the fiducials, the template has to contain
  71. % the three fiducials, e.g.
  72. % cfg.template.pnt(1,:) = [110 0 0] % location of the nose
  73. % cfg.template.pnt(2,:) = [0 90 0] % left ear
  74. % cfg.template.pnt(3,:) = [0 -90 0] % right ear
  75. % cfg.template.label = {''nasion', 'lpa', 'rpa'}
  76. %
  77. % If you want to align existing electrodes to the head surface or position
  78. % new electrodes on the head surface, you should specify the head surface as
  79. % cfg.headshape = a filename containing headshape, a structure containing a
  80. % single triangulated boundary, or a Nx3 matrix with surface
  81. % points
  82. %
  83. % See also FT_READ_SENS, FT_VOLUMEREALIGN
  84. % Copyright (C) 2005-2010, Robert Oostenveld
  85. %
  86. % This file is part of FieldTrip, see http://www.ru.nl/neuroimaging/fieldtrip
  87. % for the documentation and details.
  88. %
  89. % FieldTrip is free software: you can redistribute it and/or modify
  90. % it under the terms of the GNU General Public License as published by
  91. % the Free Software Foundation, either version 3 of the License, or
  92. % (at your option) any later version.
  93. %
  94. % FieldTrip is distributed in the hope that it will be useful,
  95. % but WITHOUT ANY WARRANTY; without even the implied warranty of
  96. % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  97. % GNU General Public License for more details.
  98. %
  99. % You should have received a copy of the GNU General Public License
  100. % along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
  101. %
  102. % $Id: ft_electroderealign.m 1093 2010-05-19 07:59:12Z roboos $
  103. fieldtripdefs
  104. % this is used for feedback of the lower-level functions
  105. global fb
  106. % set the defaults
  107. if ~isfield(cfg, 'channel'), cfg.channel = 'all'; end
  108. if ~isfield(cfg, 'feedback'), cfg.feedback = 'no'; end
  109. if ~isfield(cfg, 'casesensitive'), cfg.casesensitive = 'yes'; end
  110. if ~isfield(cfg, 'headshape'), cfg.headshape = []; end % for triangulated head surface, without labels
  111. if ~isfield(cfg, 'template'), cfg.template = []; end % for electrodes or fiducials, always with labels
  112. if ~isfield(cfg, 'warp'), cfg.warp = 'rigidbody'; end
  113. cfg = checkconfig(cfg, 'renamed', {'realignfiducials', 'fiducial'});
  114. cfg = checkconfig(cfg, 'renamed', {'realignfiducial', 'fiducial'});
  115. cfg = checkconfig(cfg, 'forbidden', 'outline');
  116. if isfield(cfg, 'headshape') && isa(cfg.headshape, 'config')
  117. % convert the nested config-object back into a normal structure
  118. cfg.headshape = struct(cfg.headshape);
  119. end
  120. if strcmp(cfg.feedback, 'yes')
  121. % use the global fb field to tell the warping toolbox to print feedback
  122. fb = 1;
  123. else
  124. fb = 0;
  125. end
  126. % get the electrode definition that should be warped
  127. if isfield(cfg, 'elec')
  128. elec = cfg.elec;
  129. elseif isfield(cfg, 'elecfile')
  130. elec = ft_read_sens(cfg.elecfile);
  131. else
  132. % start with an empty set of electrodes (usefull for manual positioning)
  133. elec = [];
  134. elec.pnt = zeros(0,3);
  135. elec.label = cell(0,1);
  136. elec.unit = 'mm';
  137. end
  138. elec = ft_convert_units(elec); % ensure that the units are specified
  139. usetemplate = isfield(cfg, 'template') && ~isempty(cfg.template);
  140. useheadshape = isfield(cfg, 'headshape') && ~isempty(cfg.headshape);
  141. if usetemplate
  142. % get the template electrode definitions
  143. if ~iscell(cfg.template)
  144. cfg.template = {cfg.template};
  145. end
  146. Ntemplate = length(cfg.template);
  147. for i=1:Ntemplate
  148. if isstruct(cfg.template{i})
  149. template(i) = cfg.template{i};
  150. else
  151. template(i) = ft_read_sens(cfg.template{i});
  152. end
  153. end
  154. for i=1:Ntemplate
  155. template(i) = ft_convert_units(template(i), elec.unit); % ensure that the units are consistent with the electrodes
  156. end
  157. elseif useheadshape
  158. % get the surface describing the head shape
  159. if isstruct(cfg.headshape) && isfield(cfg.headshape, 'pnt')
  160. % use the headshape surface specified in the configuration
  161. headshape = cfg.headshape;
  162. elseif isnumeric(cfg.headshape) && size(cfg.headshape,2)==3
  163. % use the headshape points specified in the configuration
  164. headshape.pnt = cfg.headshape;
  165. elseif ischar(cfg.headshape)
  166. % read the headshape from file
  167. headshape = ft_read_headshape(cfg.headshape);
  168. else
  169. error('cfg.headshape is not specified correctly')
  170. end
  171. if ~isfield(headshape, 'tri')
  172. % generate a closed triangulation from the surface points
  173. headshape.pnt = unique(headshape.pnt, 'rows');
  174. headshape.tri = projecttri(headshape.pnt);
  175. end
  176. headshape = ft_convert_units(headshape, elec.unit); % ensure that the units are consistent with the electrodes
  177. else
  178. error('you should either specify template electrode positions, template fiducials or a head shape');
  179. end
  180. % remember the original electrode locations and labels
  181. orig = elec;
  182. % convert all labels to lower case for string comparisons
  183. % this has to be done AFTER keeping the original labels and positions
  184. if strcmp(cfg.casesensitive, 'no')
  185. for i=1:length(elec.label)
  186. elec.label{i} = lower(elec.label{i});
  187. end
  188. for j=1:length(template)
  189. for i=1:length(template(j).label)
  190. template(j).label{i} = lower(template(j).label{i});
  191. end
  192. end
  193. end
  194. if strcmp(cfg.feedback, 'yes')
  195. % create an empty figure, continued below...
  196. figure
  197. axis equal
  198. axis vis3d
  199. hold on
  200. xlabel('x')
  201. ylabel('y')
  202. zlabel('z')
  203. end
  204. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  205. if usetemplate && strcmp(cfg.method, 'template')
  206. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  207. % determine electrode selection and overlapping subset for warping
  208. cfg.channel = ft_channelselection(cfg.channel, elec.label);
  209. for i=1:Ntemplate
  210. cfg.channel = ft_channelselection(cfg.channel, template(i).label);
  211. end
  212. % make subselection of electrodes
  213. [cfgsel, datsel] = match_str(cfg.channel, elec.label);
  214. elec.label = elec.label(datsel);
  215. elec.pnt = elec.pnt(datsel,:);
  216. for i=1:Ntemplate
  217. [cfgsel, datsel] = match_str(cfg.channel, template(i).label);
  218. template(i).label = template(i).label(datsel);
  219. template(i).pnt = template(i).pnt(datsel,:);
  220. end
  221. % compute the average of the template electrode positions
  222. all = [];
  223. for i=1:Ntemplate
  224. all = cat(3, all, template(i).pnt);
  225. end
  226. avg = mean(all,3);
  227. stderr = std(all, [], 3);
  228. fprintf('warping electrodes to template... '); % the newline comes later
  229. [norm.pnt, norm.m] = warp_optim(elec.pnt, avg, cfg.warp);
  230. norm.label = elec.label;
  231. dpre = mean(sqrt(sum((avg - elec.pnt).^2, 2)));
  232. dpost = mean(sqrt(sum((avg - norm.pnt).^2, 2)));
  233. fprintf('mean distance prior to warping %f, after warping %f\n', dpre, dpost);
  234. if strcmp(cfg.feedback, 'yes')
  235. % plot all electrodes before warping
  236. my_plot3(elec.pnt, 'r.');
  237. my_plot3(elec.pnt(1,:), 'r*');
  238. my_plot3(elec.pnt(2,:), 'r*');
  239. my_plot3(elec.pnt(3,:), 'r*');
  240. my_text3(elec.pnt(1,:), elec.label{1}, 'color', 'r');
  241. my_text3(elec.pnt(2,:), elec.label{2}, 'color', 'r');
  242. my_text3(elec.pnt(3,:), elec.label{3}, 'color', 'r');
  243. % plot all electrodes after warping
  244. my_plot3(norm.pnt, 'm.');
  245. my_plot3(norm.pnt(1,:), 'm*');
  246. my_plot3(norm.pnt(2,:), 'm*');
  247. my_plot3(norm.pnt(3,:), 'm*');
  248. my_text3(norm.pnt(1,:), norm.label{1}, 'color', 'm');
  249. my_text3(norm.pnt(2,:), norm.label{2}, 'color', 'm');
  250. my_text3(norm.pnt(3,:), norm.label{3}, 'color', 'm');
  251. % plot the template electrode locations
  252. my_plot3(avg, 'b.');
  253. my_plot3(avg(1,:), 'b*');
  254. my_plot3(avg(2,:), 'b*');
  255. my_plot3(avg(3,:), 'b*');
  256. my_text3(avg(1,:), norm.label{1}, 'color', 'b');
  257. my_text3(avg(2,:), norm.label{2}, 'color', 'b');
  258. my_text3(avg(3,:), norm.label{3}, 'color', 'b');
  259. % plot lines connecting the input/warped electrode locations with the template locations
  260. my_line3(elec.pnt, avg, 'color', 'r');
  261. my_line3(norm.pnt, avg, 'color', 'm');
  262. end
  263. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  264. elseif useheadshape && strcmp(cfg.method, 'template')
  265. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  266. % determine electrode selection and overlapping subset for warping
  267. cfg.channel = ft_channelselection(cfg.channel, elec.label);
  268. % make subselection of electrodes
  269. [cfgsel, datsel] = match_str(cfg.channel, elec.label);
  270. elec.label = elec.label(datsel);
  271. elec.pnt = elec.pnt(datsel,:);
  272. fprintf('warping electrodes to head shape... '); % the newline comes later
  273. [norm.pnt, norm.m] = warp_optim(elec.pnt, headshape, cfg.warp);
  274. norm.label = elec.label;
  275. dpre = warp_error([], elec.pnt, headshape, cfg.warp);
  276. dpost = warp_error(norm.m, elec.pnt, headshape, cfg.warp);
  277. fprintf('mean distance prior to warping %f, after warping %f\n', dpre, dpost);
  278. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  279. elseif strcmp(cfg.method, 'fiducial')
  280. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  281. % try to determine the fiducials automatically if not specified
  282. option1 = {'nasion' 'left' 'right'};
  283. option2 = {'nasion' 'lpa' 'rpa'};
  284. option3 = {'nz' 'lpa' 'rpa'};
  285. if ~isfield(cfg, 'fiducial')
  286. if length(match_str(elec.label, option1))==3
  287. cfg.fiducial = option1;
  288. elseif length(match_str(elec.label, option2))==3
  289. cfg.fiducial = option2;
  290. elseif length(match_str(elec.label, option3))==3
  291. cfg.fiducial = option3;
  292. else
  293. error('could not determine three fiducials, please specify cfg.fiducial')
  294. end
  295. end
  296. fprintf('using fiducials {''%s'', ''%s'', ''%s''}\n', cfg.fiducial{1}, cfg.fiducial{2}, cfg.fiducial{3});
  297. % determine electrode selection
  298. cfg.channel = ft_channelselection(cfg.channel, elec.label);
  299. [cfgsel, datsel] = match_str(cfg.channel, elec.label);
  300. elec.label = elec.label(datsel);
  301. elec.pnt = elec.pnt(datsel,:);
  302. if length(cfg.fiducial)~=3
  303. error('you must specify three fiducials');
  304. end
  305. % do case-insensitive search for fiducial locations
  306. nas_indx = match_str(lower(elec.label), lower(cfg.fiducial{1}));
  307. lpa_indx = match_str(lower(elec.label), lower(cfg.fiducial{2}));
  308. rpa_indx = match_str(lower(elec.label), lower(cfg.fiducial{3}));
  309. if length(nas_indx)~=1 || length(lpa_indx)~=1 || length(rpa_indx)~=1
  310. error('not all fiducials were found in the electrode set');
  311. end
  312. elec_nas = elec.pnt(nas_indx,:);
  313. elec_lpa = elec.pnt(lpa_indx,:);
  314. elec_rpa = elec.pnt(rpa_indx,:);
  315. % find the matching fiducials in the template and average them
  316. templ_nas = [];
  317. templ_lpa = [];
  318. templ_rpa = [];
  319. for i=1:Ntemplate
  320. nas_indx = match_str(lower(template(i).label), lower(cfg.fiducial{1}));
  321. lpa_indx = match_str(lower(template(i).label), lower(cfg.fiducial{2}));
  322. rpa_indx = match_str(lower(template(i).label), lower(cfg.fiducial{3}));
  323. if length(nas_indx)~=1 || length(lpa_indx)~=1 || length(rpa_indx)~=1
  324. error(sprintf('not all fiducials were found in template %d', i));
  325. end
  326. templ_nas(end+1,:) = template(i).pnt(nas_indx,:);
  327. templ_lpa(end+1,:) = template(i).pnt(lpa_indx,:);
  328. templ_rpa(end+1,:) = template(i).pnt(rpa_indx,:);
  329. end
  330. templ_nas = mean(templ_nas,1);
  331. templ_lpa = mean(templ_lpa,1);
  332. templ_rpa = mean(templ_rpa,1);
  333. % realign both to a common coordinate system
  334. elec2common = headcoordinates(elec_nas, elec_lpa, elec_rpa);
  335. templ2common = headcoordinates(templ_nas, templ_lpa, templ_rpa);
  336. % compute the combined transform and realign the electrodes to the template
  337. norm = [];
  338. norm.m = elec2common * inv(templ2common);
  339. norm.pnt = warp_apply(norm.m, elec.pnt, 'homogeneous');
  340. norm.label = elec.label;
  341. nas_indx = match_str(lower(elec.label), lower(cfg.fiducial{1}));
  342. lpa_indx = match_str(lower(elec.label), lower(cfg.fiducial{2}));
  343. rpa_indx = match_str(lower(elec.label), lower(cfg.fiducial{3}));
  344. dpre = mean(sqrt(sum((elec.pnt([nas_indx lpa_indx rpa_indx],:) - [templ_nas; templ_lpa; templ_rpa]).^2, 2)));
  345. nas_indx = match_str(lower(norm.label), lower(cfg.fiducial{1}));
  346. lpa_indx = match_str(lower(norm.label), lower(cfg.fiducial{2}));
  347. rpa_indx = match_str(lower(norm.label), lower(cfg.fiducial{3}));
  348. dpost = mean(sqrt(sum((norm.pnt([nas_indx lpa_indx rpa_indx],:) - [templ_nas; templ_lpa; templ_rpa]).^2, 2)));
  349. fprintf('mean distance between fiducials prior to realignment %f, after realignment %f\n', dpre, dpost);
  350. if strcmp(cfg.feedback, 'yes')
  351. % plot the first three electrodes before transformation
  352. my_plot3(elec.pnt(1,:), 'r*');
  353. my_plot3(elec.pnt(2,:), 'r*');
  354. my_plot3(elec.pnt(3,:), 'r*');
  355. my_text3(elec.pnt(1,:), elec.label{1}, 'color', 'r');
  356. my_text3(elec.pnt(2,:), elec.label{2}, 'color', 'r');
  357. my_text3(elec.pnt(3,:), elec.label{3}, 'color', 'r');
  358. % plot the template fiducials
  359. my_plot3(templ_nas, 'b*');
  360. my_plot3(templ_lpa, 'b*');
  361. my_plot3(templ_rpa, 'b*');
  362. my_text3(templ_nas, ' nas', 'color', 'b');
  363. my_text3(templ_lpa, ' lpa', 'color', 'b');
  364. my_text3(templ_rpa, ' rpa', 'color', 'b');
  365. % plot all electrodes after transformation
  366. my_plot3(norm.pnt, 'm.');
  367. my_plot3(norm.pnt(1,:), 'm*');
  368. my_plot3(norm.pnt(2,:), 'm*');
  369. my_plot3(norm.pnt(3,:), 'm*');
  370. my_text3(norm.pnt(1,:), norm.label{1}, 'color', 'm');
  371. my_text3(norm.pnt(2,:), norm.label{2}, 'color', 'm');
  372. my_text3(norm.pnt(3,:), norm.label{3}, 'color', 'm');
  373. end
  374. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  375. elseif strcmp(cfg.method, 'interactive')
  376. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  377. % open a figure
  378. fig = figure;
  379. % add the data to the figure
  380. set(fig, 'CloseRequestFcn', @cb_close);
  381. setappdata(fig, 'elec', elec);
  382. setappdata(fig, 'transform', eye(4));
  383. if useheadshape
  384. setappdata(fig, 'headshape', headshape);
  385. end
  386. if usetemplate
  387. % FIXME interactive realigning to template electrodes is not yet supported
  388. % this requires a consistent handling of channel selection etc.
  389. setappdata(fig, 'template', template);
  390. end
  391. % add the GUI elements
  392. cb_creategui(gca);
  393. cb_redraw(gca);
  394. rotate3d on
  395. waitfor(fig);
  396. % get the data from the figure that was left behind as global variable
  397. global norm
  398. tmp = norm;
  399. clear global norm
  400. norm = tmp;
  401. clear tmp
  402. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  403. elseif strcmp(cfg.method, 'manual')
  404. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  405. % open a figure
  406. fig = figure;
  407. rotate3d on
  408. plot_mesh(headshape, 'edgecolor', 'k')
  409. xyz = select_point3d(headshape, 'multiple', true);
  410. orig.pnt = xyz;
  411. for i=1:size(orig.pnt,1)
  412. orig.label{i,1} = 'unknown';
  413. end
  414. else
  415. error('unknown method');
  416. end
  417. % apply the spatial transformation to all electrodes, and replace the
  418. % electrode labels by their case-sensitive original values
  419. switch cfg.method
  420. case {'template' 'fiducial', 'interactive'}
  421. norm.pnt = warp_apply(norm.m, orig.pnt, cfg.warp);
  422. case 'manual'
  423. % the positions are already assigned in correspondence with the mesh
  424. norm = orig;
  425. otherwise
  426. error('unknown method');
  427. end
  428. if isfield(orig, 'label')
  429. norm.label = orig.label;
  430. end
  431. % add version information to the configuration
  432. try
  433. % get the full name of the function
  434. cfg.version.name = mfilename('fullpath');
  435. catch
  436. % required for compatibility with Matlab versions prior to release 13 (6.5)
  437. [st, i] = dbstack;
  438. cfg.version.name = st(i);
  439. end
  440. cfg.version.id = '$Id: ft_electroderealign.m 1093 2010-05-19 07:59:12Z roboos $';
  441. % remember the configuration
  442. norm.cfg = cfg;
  443. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  444. % some simple SUBFUNCTIONs that facilitate 3D plotting
  445. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  446. function h = my_plot3(xyz, varargin)
  447. h = plot3(xyz(:,1), xyz(:,2), xyz(:,3), varargin{:});
  448. function h = my_text3(xyz, varargin)
  449. h = text(xyz(:,1), xyz(:,2), xyz(:,3), varargin{:});
  450. function my_line3(xyzB, xyzE, varargin)
  451. for i=1:size(xyzB,1)
  452. line([xyzB(i,1) xyzE(i,1)], [xyzB(i,2) xyzE(i,2)], [xyzB(i,3) xyzE(i,3)], varargin{:})
  453. end
  454. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  455. % SUBFUNCTION to layout a moderately complex graphical user interface
  456. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  457. function h = layoutgui(fig, geometry, position, style, string, value, tag, callback);
  458. horipos = geometry(1); % lower left corner of the GUI part in the figure
  459. vertpos = geometry(2); % lower left corner of the GUI part in the figure
  460. width = geometry(3); % width of the GUI part in the figure
  461. height = geometry(4); % height of the GUI part in the figure
  462. horidist = 0.05;
  463. vertdist = 0.05;
  464. options = {'units', 'normalized', 'HorizontalAlignment', 'center'}; % 'VerticalAlignment', 'middle'
  465. Nrow = size(position,1);
  466. h = cell(Nrow,1);
  467. for i=1:Nrow
  468. if isempty(position{i})
  469. continue;
  470. end
  471. position{i} = position{i} ./ sum(position{i});
  472. Ncol = size(position{i},2);
  473. ybeg = (Nrow-i )/Nrow + vertdist/2;
  474. yend = (Nrow-i+1)/Nrow - vertdist/2;
  475. for j=1:Ncol
  476. xbeg = sum(position{i}(1:(j-1))) + horidist/2;
  477. xend = sum(position{i}(1:(j ))) - horidist/2;
  478. pos(1) = xbeg*width + horipos;
  479. pos(2) = ybeg*height + vertpos;
  480. pos(3) = (xend-xbeg)*width;
  481. pos(4) = (yend-ybeg)*height;
  482. h{i}{j} = uicontrol(fig, ...
  483. options{:}, ...
  484. 'position', pos, ...
  485. 'style', style{i}{j}, ...
  486. 'string', string{i}{j}, ...
  487. 'tag', tag{i}{j}, ...
  488. 'value', value{i}{j}, ...
  489. 'callback', callback{i}{j} ...
  490. );
  491. end
  492. end
  493. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  494. function cb_creategui(hObject, eventdata, handles);
  495. % define the position of each GUI element
  496. position = {
  497. [2 1 1 1]
  498. [2 1 1 1]
  499. [2 1 1 1]
  500. [1]
  501. [1]
  502. [1]
  503. [1]
  504. [1 1]
  505. };
  506. % define the style of each GUI element
  507. style = {
  508. {'text' 'edit' 'edit' 'edit'}
  509. {'text' 'edit' 'edit' 'edit'}
  510. {'text' 'edit' 'edit' 'edit'}
  511. {'pushbutton'}
  512. {'pushbutton'}
  513. {'toggle'}
  514. {'toggle'}
  515. {'text' 'edit'}
  516. };
  517. % define the descriptive string of each GUI element
  518. string = {
  519. {'rotate' 0 0 0}
  520. {'translate' 0 0 0}
  521. {'scale' 1 1 1}
  522. {'redisplay'}
  523. {'apply'}
  524. {'toggle grid'}
  525. {'toggle axes'}
  526. {'alpha' 0.7}
  527. };
  528. % define the value of each GUI element
  529. value = {
  530. {[] [] [] []}
  531. {[] [] [] []}
  532. {[] [] [] []}
  533. {[]}
  534. {[]}
  535. {0}
  536. {0}
  537. {[] []}
  538. };
  539. % define a tag for each GUI element
  540. tag = {
  541. {'' 'rx' 'ry' 'rz'}
  542. {'' 'tx' 'ty' 'tz'}
  543. {'' 'sx' 'sy' 'sz'}
  544. {''}
  545. {''}
  546. {'toggle grid'}
  547. {'toggle axes'}
  548. {'' 'alpha'}
  549. };
  550. % define the callback function of each GUI element
  551. callback = {
  552. {[] @cb_redraw @cb_redraw @cb_redraw}
  553. {[] @cb_redraw @cb_redraw @cb_redraw}
  554. {[] @cb_redraw @cb_redraw @cb_redraw}
  555. {@cb_redraw}
  556. {@cb_apply}
  557. {@cb_redraw}
  558. {@cb_redraw}
  559. {[] @cb_redraw}
  560. };
  561. fig = get(hObject, 'parent');
  562. layoutgui(fig, [0.7 0.05 0.25 0.50], position, style, string, value, tag, callback);
  563. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  564. function cb_redraw(hObject, eventdata, handles);
  565. fig = get(hObject, 'parent');
  566. headshape = getappdata(fig, 'headshape');
  567. elec = getappdata(fig, 'elec');
  568. template = getappdata(fig, 'template');
  569. % get the transformation details
  570. rx = str2num(get(findobj(fig, 'tag', 'rx'), 'string'));
  571. ry = str2num(get(findobj(fig, 'tag', 'ry'), 'string'));
  572. rz = str2num(get(findobj(fig, 'tag', 'rz'), 'string'));
  573. tx = str2num(get(findobj(fig, 'tag', 'tx'), 'string'));
  574. ty = str2num(get(findobj(fig, 'tag', 'ty'), 'string'));
  575. tz = str2num(get(findobj(fig, 'tag', 'tz'), 'string'));
  576. sx = str2num(get(findobj(fig, 'tag', 'sx'), 'string'));
  577. sy = str2num(get(findobj(fig, 'tag', 'sy'), 'string'));
  578. sz = str2num(get(findobj(fig, 'tag', 'sz'), 'string'));
  579. R = rotate ([rx ry rz]);
  580. T = translate([tx ty tz]);
  581. S = scale ([sx sy sz]);
  582. H = S * T * R;
  583. elec = ft_transform_sens(H, elec);
  584. axis vis3d; cla
  585. xlabel('x')
  586. ylabel('y')
  587. zlabel('z')
  588. if ~isempty(headshape)
  589. triplot(headshape.pnt, headshape.tri, [], 'faces_skin');
  590. alpha(str2num(get(findobj(fig, 'tag', 'alpha'), 'string')));
  591. end
  592. if ~isempty(template)
  593. triplot(template.pnt, [], [], 'nodes_blue')
  594. end
  595. triplot(elec.pnt, [], [], 'nodes');
  596. if isfield(elec, 'line')
  597. triplot(elec.pnt, elec.line, [], 'edges');
  598. end
  599. if isfield(elec, 'fid') && ~isempty(elec.fid.pnt)
  600. triplot(elec.fid.pnt, [], [], 'nodes_red');
  601. end
  602. if get(findobj(fig, 'tag', 'toggle axes'), 'value')
  603. axis on
  604. else
  605. axis off
  606. end
  607. if get(findobj(fig, 'tag', 'toggle grid'), 'value')
  608. grid on
  609. else
  610. grid off
  611. end
  612. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  613. function cb_apply(hObject, eventdata, handles);
  614. fig = get(hObject, 'parent');
  615. elec = getappdata(fig, 'elec');
  616. transform = getappdata(fig, 'transform');
  617. % get the transformation details
  618. rx = str2num(get(findobj(fig, 'tag', 'rx'), 'string'));
  619. ry = str2num(get(findobj(fig, 'tag', 'ry'), 'string'));
  620. rz = str2num(get(findobj(fig, 'tag', 'rz'), 'string'));
  621. tx = str2num(get(findobj(fig, 'tag', 'tx'), 'string'));
  622. ty = str2num(get(findobj(fig, 'tag', 'ty'), 'string'));
  623. tz = str2num(get(findobj(fig, 'tag', 'tz'), 'string'));
  624. sx = str2num(get(findobj(fig, 'tag', 'sx'), 'string'));
  625. sy = str2num(get(findobj(fig, 'tag', 'sy'), 'string'));
  626. sz = str2num(get(findobj(fig, 'tag', 'sz'), 'string'));
  627. R = rotate ([rx ry rz]);
  628. T = translate([tx ty tz]);
  629. S = scale ([sx sy sz]);
  630. H = S * T * R;
  631. elec = ft_transform_headshape(H, elec);
  632. transform = H * transform;
  633. set(findobj(fig, 'tag', 'rx'), 'string', 0);
  634. set(findobj(fig, 'tag', 'ry'), 'string', 0);
  635. set(findobj(fig, 'tag', 'rz'), 'string', 0);
  636. set(findobj(fig, 'tag', 'tx'), 'string', 0);
  637. set(findobj(fig, 'tag', 'ty'), 'string', 0);
  638. set(findobj(fig, 'tag', 'tz'), 'string', 0);
  639. set(findobj(fig, 'tag', 'sx'), 'string', 1);
  640. set(findobj(fig, 'tag', 'sy'), 'string', 1);
  641. set(findobj(fig, 'tag', 'sz'), 'string', 1);
  642. setappdata(fig, 'elec', elec);
  643. setappdata(fig, 'transform', transform);
  644. cb_redraw(hObject);
  645. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  646. function cb_close(hObject, eventdata, handles);
  647. % make the current transformation permanent and subsequently allow deleting the figure
  648. cb_apply(gca);
  649. % get the updated electrode from the figure
  650. fig = hObject;
  651. % hmmm, this is ugly
  652. global norm
  653. norm = getappdata(fig, 'elec');
  654. norm.m = getappdata(fig, 'transform');
  655. set(fig, 'CloseRequestFcn', @delete);
  656. delete(fig);