function db=cosmo_meeg_layout_collection()
% return supported MEEG channel layouts
%
% db=cosmo_meeg_layout_collection()
%
% Output:
% db Nx1 cell for N layouts. Each elements has the following
% fields:
% .pos Mx2 x and y coordinates for M channels
% .width Mx1 width of channel
% .height Mx1 height of channel
% .label Mx1 cell string with channel labels
% .name string with filename of layout
%
% Example:
% % (This example requires FieldTrip)
% cosmo_skip_test_if_no_external('fieldtrip');
% %
% % get all layouts
% layouts=cosmo_meeg_layout_collection();
% %
% % find index of neuromag306cmb layout
% names=cellfun(@(x)x.name,layouts,'UniformOutput',false);
% i=find(cosmo_match(names,'neuromag306cmb.lay'));
% %
% % show neuromag306 combined planar layout (with 102 channels)
% pl_layout=layouts{i};
% cosmo_disp(pl_layout.label)
% %|| { 'MEG0112+0113'
% %|| 'MEG0122+0123'
% %|| 'MEG0132+0133'
% %|| :
% %|| 'MEG2642+2643'
% %|| 'COMNT'
% %|| 'SCALE' }@104x1
% cosmo_disp([pl_layout.pos pl_layout.width pl_layout.height])
% %|| [ -0.408 0.273 0.0717 0.0791
% %|| -0.328 0.306 0.0717 0.0791
% %|| -0.377 0.179 0.0717 0.0791
% %|| : : : :
% %|| 0.373 -0.104 0.0717 0.0791
% %|| -0.547 -0.5 0.0717 0.0791
% %|| 0.547 -0.5 0.0717 0.0791 ]@104x4
% pl_layout.name
% %|| 'neuromag306cmb.lay'
%
% Note:
% - this function requires FieldTrip, as it uses its collection of
% layouts
% - the output from this function is similar to FieldTrip's
% ft_prepare_layout.
% - this function caches previously read layouts, for optimization
% reasons. run "clear functions" to reset the cache.
%
% # For CoSMoMVPA's copyright information and license terms, #
% # see the COPYING file distributed with CoSMoMVPA. #
% cache the layouts, so that after the first call layouts don't have to
% be read from disc
persistent cached_layout_db;
if isempty(cached_layout_db)
cached_layout_db=read_all_layouts();
end
db=cached_layout_db;
function layouts=read_all_layouts()
cosmo_check_external('fieldtrip');
% see where fieldtrip is
ft_dir=fileparts(which('ft_defaults'));
lay_dir=fullfile(ft_dir,'template','layout');
% get layout filenames
lay_fns=dir(fullfile(lay_dir,'*.lay'));
nlay=numel(lay_fns);
if nlay==0, error('No layout founds in %s', lay_dir); end
layout_names={lay_fns.name}';
% allocate space for output
layouts=cell(nlay,1);
% read each layout
for k=1:nlay
lay_name=layout_names{k};
lay_fn=fullfile(lay_dir,lay_name);
lay=cosmo_meeg_read_layout(lay_fn);
lay.name=lay_name;
layouts{k}=lay;
end
layouts=apply_fixers(layouts);
function layouts=apply_fixers(layouts)
fixers={@fix_elec1020_old_fieldtrip,...
@fix_make_ft_compatible};
for j=1:numel(fixers)
fixer=fixers{j};
layouts=fixer(layouts);
end
function db=fix_elec1020_old_fieldtrip(db)
% old fieldtrip had channel locations differently;
% fix that there so that the unit tests pass
i=find_layout(db,'elec1020.lay');
lay=db{i};
if isequal(lay.pos(1,:),[-0.308949 0.951110]) && ...
isequal(unique(lay.width),.75)
suffix=cellfun(@(x)x(end),lay.label)';
suffix1=suffix=='1';
suffix2=suffix=='2';
assert(isequal(lay.label(suffix1),{'Fp1','O1'}'));
assert(isequal(lay.label(suffix2),{'Fp2','O2'}'));
lay.pos(suffix1,:)=[-.38 .89111; -.38 -.89111];
lay.pos(suffix2,:)=[.38 .891004; .38 -.891004];
lay.width(:)=.35;
lay.height(:)=.25;
db{i}=lay;
end
function i=find_layout(db,name)
names=cellfun(@(x)x.name,db,'UniformOutput',false);
i=find(cosmo_match(names,name));
assert(numel(i)==1);
function layouts=fix_make_ft_compatible(layouts)
for j=1:numel(layouts)
layouts{j}=make_single_ft_compatible(layouts{j});
end
function layout=make_single_ft_compatible(layout)
processors={@layout_ft_add_outline,...
@layout_ft_scale,...
@layout_ft_add_misc_labels};
for k=1:numel(processors)
processor=processors{k};
layout=processor(layout);
end
function layout=layout_ft_add_outline(layout)
% add outline and mask to the layout
alpha=(0:.01:1)'*(2*pi);
head=[cos(alpha) sin(alpha)]*.5;
nose=[0.09 0.496;
0 0.575;
-0.09 0.496];
right_ear=[ 0.4970 0.0555
0.5100 0.0775
0.5180 0.0783
0.5299 0.0746
0.5419 0.0555
0.5400 -0.0055
0.5470 -0.0932
0.5320 -0.1313
0.5100 -0.1384
0.4890 -0.1199 ];
left_ear=bsxfun(@times,[-1 1],right_ear);
layout.outline={head, nose, right_ear, left_ear};
layout.mask={head};
function layout=layout_ft_scale(layout)
% do not consider the COMNT and SCALE channels
chan_msk=~cosmo_match(layout.label,{'COMNT','SCALE'});
chan_pos=layout.pos(chan_msk,:);
extent=(max(chan_pos)-min(chan_pos));
% put everything in circle around origin with radius .45
layout.pos=(bsxfun(@rdivide,bsxfun(@minus,...
layout.pos,min(chan_pos)),extent))*.9-.45;
layout.width=layout.width/extent(1);
layout.height=layout.height/extent(2);
function stacked_layout=stack_channels(layouts)
% helper function to stack the channels
% if any element is empty it is just ignored
keys={'pos','width','height','label'};
nlayout=numel(layouts);
nkeys=numel(keys);
stacked_layout=layouts{1};
for k=1:nkeys
key=keys{k};
values_cell=cell(nlayout,1);
keep=false(nlayout,1);
for j=1:nlayout
layout=layouts{j};
if ~isempty(layout)
values_cell{j}=layout.(key);
keep(j)=true;
end
end
stacked_layout.(key)=cat(1,values_cell{keep});
end
function layout=layout_ft_add_misc_labels(layout)
comnt_layout=get_single_label(layout,'COMNT',[-.45 -.45]);
scale_layout=get_single_label(layout,'SCALE',[ .45 -.45]);
layout=stack_channels({layout,comnt_layout,scale_layout});
function y=get_single_label(x,label,pos)
% return a layout
if cosmo_match({label},x.label);
y=[];
else
y.width=mean(x.width,1);
y.height=mean(x.height,1);
y.label={label};
y.pos=pos;
end