|
53 | 53 | % 'nonlin3' apply a 3rd order non-linear warp
|
54 | 54 | % 'nonlin4' apply a 4th order non-linear warp
|
55 | 55 | % 'nonlin5' apply a 5th order non-linear warp
|
| 56 | +% 'dykstra2012' non-linear wrap only for headshape |
| 57 | +% method useful for projecting ECoG onto |
| 58 | +% cortex hull. |
56 | 59 | % cfg.channel = Nx1 cell-array with selection of channels (default = 'all'),
|
57 | 60 | % see FT_CHANNELSELECTION for details
|
58 | 61 | % cfg.fiducial = cell-array with the name of three fiducials used for
|
|
85 | 88 | % single triangulated boundary, or a Nx3 matrix with surface
|
86 | 89 | % points
|
87 | 90 | %
|
88 |
| -% See also FT_READ_SENS, FT_VOLUMEREALIGN, FT_INTERACTIVEREALIGN |
| 91 | +% If you want to align ECoG electrodes to the pial surface, you first need to |
| 92 | +% compute the cortex hull with FT_PREPARE_MESH. dykstra2012 uses algorithm |
| 93 | +% described in Dykstra et al. (2012, Neuroimage) in which electrodes are |
| 94 | +% projected onto pial surface while minimizing the displacement of the |
| 95 | +% electrodes from original location and maintaining the grid shape. It relies |
| 96 | +% on the optimization toolbox. |
| 97 | +% cfg.method = 'headshape' |
| 98 | +% cfg.warp = 'dykstra2012' |
| 99 | +% cfg.headshape = a filename containing headshape, a structure containing a |
| 100 | +% single triangulated boundary, or a Nx3 matrix with surface |
| 101 | +% points |
| 102 | +% cfg.feedback = 'yes' or 'no' (feedback includes the output of the iteration |
| 103 | +% procedure. |
| 104 | +% |
| 105 | +% See also FT_READ_SENS, FT_VOLUMEREALIGN, FT_INTERACTIVEREALIGN, FT_PREPARE_MESH |
89 | 106 |
|
90 | 107 | % Copyright (C) 2005-2015, Robert Oostenveld
|
91 | 108 | %
|
|
297 | 314 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
298 | 315 | if strcmp(cfg.method, 'template')
|
299 | 316 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
300 |
| - |
| 317 | + |
301 | 318 | % determine electrode selection and overlapping subset for warping
|
302 | 319 | cfg.channel = ft_channelselection(cfg.channel, elec.label);
|
303 | 320 | for i=1:Ntemplate
|
304 | 321 | cfg.channel = ft_channelselection(cfg.channel, target(i).label);
|
305 | 322 | end
|
306 |
| - |
| 323 | + |
307 | 324 | % make consistent subselection of electrodes
|
308 | 325 | [cfgsel, datsel] = match_str(cfg.channel, elec.label);
|
309 | 326 | elec.label = elec.label(datsel);
|
|
313 | 330 | target(i).label = target(i).label(datsel);
|
314 | 331 | target(i).elecpos = target(i).elecpos(datsel,:);
|
315 | 332 | end
|
316 |
| - |
| 333 | + |
317 | 334 | % compute the average of the target electrode positions
|
318 | 335 | average = ft_average_sens(target);
|
319 |
| - |
| 336 | + |
320 | 337 | fprintf('warping electrodes to average template... '); % the newline comes later
|
321 | 338 | [norm.elecpos, norm.m] = ft_warp_optim(elec.elecpos, average.elecpos, cfg.warp);
|
322 | 339 | norm.label = elec.label;
|
323 |
| - |
| 340 | + |
324 | 341 | dpre = mean(sqrt(sum((average.elecpos - elec.elecpos).^2, 2)));
|
325 | 342 | dpost = mean(sqrt(sum((average.elecpos - norm.elecpos).^2, 2)));
|
326 | 343 | fprintf('mean distance prior to warping %f, after warping %f\n', dpre, dpost);
|
327 |
| - |
| 344 | + |
328 | 345 | if strcmp(cfg.feedback, 'yes')
|
329 | 346 | % create an empty figure, continued below...
|
330 | 347 | figure
|
|
334 | 351 | xlabel('x')
|
335 | 352 | ylabel('y')
|
336 | 353 | zlabel('z')
|
337 |
| - |
| 354 | + |
338 | 355 | % plot all electrodes before warping
|
339 | 356 | ft_plot_sens(elec, 'r*');
|
340 |
| - |
| 357 | + |
341 | 358 | % plot all electrodes after warping
|
342 | 359 | ft_plot_sens(norm, 'm.', 'label', 'label');
|
343 |
| - |
| 360 | + |
344 | 361 | % plot the template electrode locations
|
345 | 362 | ft_plot_sens(average, 'b.');
|
346 |
| - |
| 363 | + |
347 | 364 | % plot lines connecting the input and the realigned electrode locations with the template locations
|
348 | 365 | my_line3(elec.elecpos, average.elecpos, 'color', 'r');
|
349 | 366 | my_line3(norm.elecpos, average.elecpos, 'color', 'm');
|
350 | 367 | end
|
351 |
| - |
| 368 | + |
352 | 369 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
353 | 370 | elseif strcmp(cfg.method, 'headshape')
|
354 | 371 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
355 |
| - |
| 372 | + |
356 | 373 | % determine electrode selection and overlapping subset for warping
|
357 | 374 | cfg.channel = ft_channelselection(cfg.channel, elec.label);
|
358 |
| - |
| 375 | + |
359 | 376 | % make subselection of electrodes
|
360 | 377 | [cfgsel, datsel] = match_str(cfg.channel, elec.label);
|
361 | 378 | elec.label = elec.label(datsel);
|
|
372 | 389 | dpost = ft_warp_error(norm.m, elec.elecpos, headshape, cfg.warp);
|
373 | 390 | fprintf('mean distance prior to warping %f, after warping %f\n', dpre, dpost);
|
374 | 391 | end
|
375 |
| - |
| 392 | + |
376 | 393 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
377 | 394 | elseif strcmp(cfg.method, 'fiducial')
|
378 | 395 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
379 |
| - |
| 396 | + |
380 | 397 | % the fiducials have to be present in the electrodes and in the template set
|
381 | 398 | label = intersect(lower(elec.label), lower(target.label));
|
382 |
| - |
| 399 | + |
383 | 400 | if ~isfield(cfg, 'fiducial') || isempty(cfg.fiducial)
|
384 | 401 | % try to determine the names of the fiducials automatically
|
385 | 402 | option1 = {'nasion' 'left' 'right'};
|
|
405 | 422 | end
|
406 | 423 | end
|
407 | 424 | fprintf('matching fiducials {''%s'', ''%s'', ''%s''}\n', cfg.fiducial{1}, cfg.fiducial{2}, cfg.fiducial{3});
|
408 |
| - |
| 425 | + |
409 | 426 | % determine electrode selection
|
410 | 427 | cfg.channel = ft_channelselection(cfg.channel, elec.label);
|
411 | 428 | [cfgsel, datsel] = match_str(cfg.channel, elec.label);
|
412 | 429 | elec.label = elec.label(datsel);
|
413 | 430 | elec.elecpos = elec.elecpos(datsel,:);
|
414 |
| - |
| 431 | + |
415 | 432 | if length(cfg.fiducial)~=3
|
416 | 433 | error('you must specify exaclty three fiducials');
|
417 | 434 | end
|
418 |
| - |
| 435 | + |
419 | 436 | % do case-insensitive search for fiducial locations
|
420 | 437 | nas_indx = match_str(lower(elec.label), lower(cfg.fiducial{1}));
|
421 | 438 | lpa_indx = match_str(lower(elec.label), lower(cfg.fiducial{2}));
|
|
426 | 443 | elec_nas = elec.elecpos(nas_indx,:);
|
427 | 444 | elec_lpa = elec.elecpos(lpa_indx,:);
|
428 | 445 | elec_rpa = elec.elecpos(rpa_indx,:);
|
429 |
| - |
| 446 | + |
430 | 447 | % FIXME change the flow in the remainder
|
431 | 448 | % if one or more template electrode sets are specified, then align to the average of those
|
432 | 449 | % if no template is specified, then align so that the fiducials are along the axis
|
433 |
| - |
| 450 | + |
434 | 451 | % find the matching fiducials in the template and average them
|
435 | 452 | tmpl_nas = nan(Ntemplate,3);
|
436 | 453 | tmpl_lpa = nan(Ntemplate,3);
|
|
449 | 466 | tmpl_nas = mean(tmpl_nas,1);
|
450 | 467 | tmpl_lpa = mean(tmpl_lpa,1);
|
451 | 468 | tmpl_rpa = mean(tmpl_rpa,1);
|
452 |
| - |
| 469 | + |
453 | 470 | % realign both to a common coordinate system
|
454 | 471 | elec2common = ft_headcoordinates(elec_nas, elec_lpa, elec_rpa);
|
455 | 472 | templ2common = ft_headcoordinates(tmpl_nas, tmpl_lpa, tmpl_rpa);
|
456 |
| - |
| 473 | + |
457 | 474 | % compute the combined transform
|
458 | 475 | norm = [];
|
459 | 476 | norm.m = templ2common \ elec2common;
|
460 |
| - |
| 477 | + |
461 | 478 | % apply the transformation to the fiducials as sanity check
|
462 | 479 | norm.elecpos(1,:) = ft_warp_apply(norm.m, elec_nas, 'homogeneous');
|
463 | 480 | norm.elecpos(2,:) = ft_warp_apply(norm.m, elec_lpa, 'homogeneous');
|
464 | 481 | norm.elecpos(3,:) = ft_warp_apply(norm.m, elec_rpa, 'homogeneous');
|
465 | 482 | norm.label = cfg.fiducial;
|
466 |
| - |
| 483 | + |
467 | 484 | nas_indx = match_str(lower(elec.label), lower(cfg.fiducial{1}));
|
468 | 485 | lpa_indx = match_str(lower(elec.label), lower(cfg.fiducial{2}));
|
469 | 486 | rpa_indx = match_str(lower(elec.label), lower(cfg.fiducial{3}));
|
|
473 | 490 | rpa_indx = match_str(lower(norm.label), lower(cfg.fiducial{3}));
|
474 | 491 | dpost = mean(sqrt(sum((norm.elecpos([nas_indx lpa_indx rpa_indx],:) - [tmpl_nas; tmpl_lpa; tmpl_rpa]).^2, 2)));
|
475 | 492 | fprintf('mean distance between fiducials prior to realignment %f, after realignment %f\n', dpre, dpost);
|
476 |
| - |
| 493 | + |
477 | 494 | if strcmp(cfg.feedback, 'yes')
|
478 | 495 | % create an empty figure, continued below...
|
479 | 496 | figure
|
|
483 | 500 | xlabel('x')
|
484 | 501 | ylabel('y')
|
485 | 502 | zlabel('z')
|
486 |
| - |
| 503 | + |
487 | 504 | % plot the first three electrodes before transformation
|
488 | 505 | my_plot3(elec.elecpos(1,:), 'r*');
|
489 | 506 | my_plot3(elec.elecpos(2,:), 'r*');
|
490 | 507 | my_plot3(elec.elecpos(3,:), 'r*');
|
491 | 508 | my_text3(elec.elecpos(1,:), elec.label{1}, 'color', 'r');
|
492 | 509 | my_text3(elec.elecpos(2,:), elec.label{2}, 'color', 'r');
|
493 | 510 | my_text3(elec.elecpos(3,:), elec.label{3}, 'color', 'r');
|
494 |
| - |
| 511 | + |
495 | 512 | % plot the template fiducials
|
496 | 513 | my_plot3(tmpl_nas, 'b*');
|
497 | 514 | my_plot3(tmpl_lpa, 'b*');
|
498 | 515 | my_plot3(tmpl_rpa, 'b*');
|
499 | 516 | my_text3(tmpl_nas, ' nas', 'color', 'b');
|
500 | 517 | my_text3(tmpl_lpa, ' lpa', 'color', 'b');
|
501 | 518 | my_text3(tmpl_rpa, ' rpa', 'color', 'b');
|
502 |
| - |
| 519 | + |
503 | 520 | % plot all electrodes after transformation
|
504 | 521 | my_plot3(norm.elecpos, 'm.');
|
505 | 522 | my_plot3(norm.elecpos(1,:), 'm*');
|
|
509 | 526 | my_text3(norm.elecpos(2,:), norm.label{2}, 'color', 'm');
|
510 | 527 | my_text3(norm.elecpos(3,:), norm.label{3}, 'color', 'm');
|
511 | 528 | end
|
512 |
| - |
| 529 | + |
513 | 530 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
514 | 531 | elseif strcmp(cfg.method, 'interactive')
|
515 | 532 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
540 | 557 | clear global norm
|
541 | 558 | norm = tmp;
|
542 | 559 | clear tmp
|
543 |
| - |
| 560 | + |
544 | 561 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
545 | 562 | elseif strcmp(cfg.method, 'project')
|
546 | 563 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
547 | 564 | [dum, prj] = project_elec(elec.elecpos, headshape.pos, headshape.tri);
|
548 | 565 | % replace the electrodes with the projected version
|
549 | 566 | elec.elecpos = prj;
|
550 |
| - |
| 567 | + |
551 | 568 | else
|
552 | 569 | error('unknown method');
|
553 | 570 | end % if method
|
|
560 | 577 | if strcmp(lower(cfg.warp), 'dykstra2012')
|
561 | 578 | elec_realigned = norm;
|
562 | 579 | elec_realigned.unit = elec_original.unit;
|
563 |
| - |
| 580 | + |
564 | 581 | else
|
565 | 582 | % the transformation is a linear or non-linear warp, i.e. a vector
|
566 | 583 | try
|
|
574 | 591 | end
|
575 | 592 | % remember the transformation
|
576 | 593 | elec_realigned.(cfg.warp) = norm.m;
|
577 |
| - |
| 594 | + |
578 | 595 | end
|
579 |
| - |
| 596 | + |
580 | 597 | case {'fiducial' 'interactive'}
|
581 | 598 | % the transformation is a 4x4 homogenous matrix
|
582 | 599 | % apply the transformation to the original complete set of sensors
|
583 | 600 | elec_realigned = ft_transform_sens(norm.m, elec_original);
|
584 | 601 | % remember the transformation
|
585 | 602 | elec_realigned.homogeneous = norm.m;
|
586 |
| - |
| 603 | + |
587 | 604 | case 'project'
|
588 | 605 | % nothing to be done
|
589 | 606 | elec_realigned = elec;
|
590 |
| - |
| 607 | + |
591 | 608 | otherwise
|
592 | 609 | error('unknown method');
|
593 | 610 | end
|
|
0 commit comments