function senstype2layout=cosmo_meeg_senstype2layout_mapping(varargin)
% return mapping from MEEG sensor types to sensor layouts
%
% senstype2layout=cosmo_meeg_senstype2layout_mapping()
%
% Output:
% senstype2layout struct where the fieldnames (keys) are senstypes
% (acquisition systems with a possible suffix
% indicating a type of channel)
% If a senstype does not have an associated layout
% then the corresponding value is set to [].
% Otherwise each value has fields:
% .pos Nx2 x and y coordinates (for N channels)
% .width Nx1 channel widths
% .height Nx1 channel heights
% .label Nx1 cell with channel labels
% .name string with name of layout
%
% Examples:
% % (This example requires FieldTrip)
% cosmo_skip_test_if_no_external('fieldtrip');
% %
% senstype2layout=cosmo_meeg_senstype2layout_mapping();
% % get layout for neuromag306 MEG planar (gradiometers)
% layout=senstype2layout.neuromag306alt_planar;
% cosmo_disp(layout.label)
% %|| { 'MEG0113'
% %|| 'MEG0112'
% %|| 'MEG0122'
% %|| :
% %|| 'MEG2643'
% %|| 'COMNT'
% %|| 'SCALE' }@206x1
% cosmo_disp([layout.pos layout.width layout.height])
% %|| [ -0.408 0.253 0.0358 0.0369
% %|| -0.408 0.284 0.0358 0.0369
% %|| -0.328 0.285 0.0358 0.0369
% %|| : : : :
% %|| 0.373 -0.0821 0.0358 0.0369
% %|| -0.547 -0.5 0.0358 0.0369
% %|| 0.547 -0.5 0.0358 0.0369 ]@206x4
% layout.name
% %|| 'neuromag306planar.lay'
%
% % (This example requires FieldTrip)
% cosmo_skip_test_if_no_external('fieldtrip');
% %
% senstype2layout=cosmo_meeg_senstype2layout_mapping();
% % get layout for neuromag306 MEG combined planar
% % (combined gradiometers)
% layout=senstype2layout.neuromag306alt_planar_combined;
% cosmo_disp(layout.label)
% %|| { 'MEG0112+0113'
% %|| 'MEG0122+0123'
% %|| 'MEG0132+0133'
% %|| :
% %|| 'MEG2642+2643'
% %|| 'COMNT'
% %|| 'SCALE' }@104x1
% cosmo_disp([layout.pos layout.width 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
% layout.name
% %|| 'neuromag306cmb.lay'
%
% % (This example requires FieldTrip)
% cosmo_skip_test_if_no_external('fieldtrip');
% %
% senstype2layout=cosmo_meeg_senstype2layout_mapping();
% % get layout for EEG elec1020
% layout=senstype2layout.eeg1020;
% cosmo_disp(layout.label)
% %|| { 'Fp1'
% %|| 'Fpz'
% %|| 'Fp2'
% %|| :
% %|| 'O2'
% %|| 'COMNT'
% %|| 'SCALE' }@23x1
% cosmo_disp([layout.pos layout.width layout.height])
% %|| [ -0.139 0.428 0.139 0.104
% %|| 0 0.45 0.139 0.104
% %|| 0.139 0.428 0.139 0.104
% %|| : : : :
% %|| 0.139 -0.428 0.139 0.104
% %|| -0.547 -0.5 0.139 0.104
% %|| 0.547 -0.5 0.139 0.104 ]@23x4
% layout.name
% %|| 'EEG1020.lay'
%
% Notes:
% - this function requires FieldTrip, as it uses its collection of
% layouts
% - the output from this function is similar to FieldTrip's
% ft_prepare_layout, but positions are not scaled as in FieldTrip
% - this function caches previously read layouts, for optimization
% reasons. run "clear functions" to reset the cache.
% - this function uses cosmo_meeg_layout_collection and
% cosmo_meeg_senstype_collection to match sensor types with layouts
% - this function does not provide layouts for all sensor types; if you
% find a layout of interest is missing, please get in touch with the
% developers
%
% See also: cosmo_meeg_layout_collection, cosmo_meeg_senstype_collection
%
% # For CoSMoMVPA's copyright information and license terms, #
% # see the COPYING file distributed with CoSMoMVPA. #
% 'secret' options with thresholds to find best matching layout
defaults=struct();
defaults.thr_layout=.7;
defaults.thr_senstype=.8;
opt=cosmo_structjoin(defaults,varargin);
senstype2layout=get_mapping(opt);
function senstype2layout=get_mapping(opt)
% helper function to actually compute the mapping
persistent cached_opt;
persistent cached_senstype2layout;
if ~isequal(opt,cached_opt) || isempty(cached_senstype2layout)
sc=cosmo_meeg_senstype_collection();
names=fieldnames(sc);
n=numel(names);
senstype2layout=struct();
for k=1:n
name=names{k};
lay=find_layout(name,opt);
senstype2layout.(name)=lay;
end
cached_opt=opt;
cached_senstype2layout=senstype2layout;
end
senstype2layout=cached_senstype2layout;
function pairs=senstypes_alt_names()
% return pairs of sensor types with and without the 'alt' name, e.g.
% neuromag306alt_planar and neuromag306_planar
sc=cosmo_meeg_senstype_collection();
names=fieldnames(sc);
names_without_alt=strrep(names,'alt','');
msk_with_alt=~cosmo_match(names, names_without_alt);
idxs_with_alt=find(msk_with_alt);
ncandidates=numel(idxs_with_alt);
pairs=cell(ncandidates,2);
pos=0;
for k=1:ncandidates
idx_with_alt=idxs_with_alt(k);
name_without_alt=names_without_alt{idx_with_alt};
idx_all=find(cosmo_match(names_without_alt,name_without_alt));
idx_without_alt=setdiff(idx_all,idx_with_alt);
switch numel(idx_without_alt)
case 0
continue
case 1
pos=pos+1;
name_with_alt=names{idx_with_alt};
pairs(pos,:)={name_without_alt,name_with_alt};
otherwise
assert(false);
end
end
pairs=pairs(1:pos,:);
function alt_name=find_alt_name(name)
alt_name=[];
pairs=senstypes_alt_names();
for col=1:2
msk=cosmo_match(pairs(:,col),name);
if any(msk)
assert(sum(msk)==1)
alt_name=pairs{msk,3-col};
return
end
end
function lay=find_layout(name,opt)
% find layout based on sensname
% if no layout is found, lay is returned as []
lay=find_layout_helper(name,opt);
if ~isempty(lay)
return
end
% not found, try to use the alternative name
alt_name=find_alt_name(name);
if ~isempty(alt_name)
lay=find_layout_helper(alt_name,opt);
end
function lay=find_layout_helper(name,opt)
% helper function: first tries to get the layout directly, if that does
% not work, try to infer planar channels from the combined channels
sc=cosmo_meeg_senstype_collection();
label=sc.(name).label;
lay=find_layout_from_label(label,opt);
if ~isempty(lay)
return
end
lay=infer_planar_layout_from_combined(name,opt);
function planar_lay=infer_planar_layout_from_combined(planar_name,opt)
% helper function in case no planar layout is found (e.g. ctf151)
planar_lay=[];
combined_name=[planar_name '_combined'];
sc=cosmo_meeg_senstype_collection();
if all(cosmo_isfield(sc,{planar_name,combined_name}))
planar_label=sc.(planar_name).label;
combined_label=sc.(combined_name).label;
combined_lay=find_layout_from_label(combined_label,opt);
if isempty(combined_lay)
return;
end
% get rid of COMNT and SCALE labels
keep=cosmo_match(combined_lay.label,combined_label);
combined_lay=slice_layout(combined_lay,keep);
if ~isequal(combined_lay.label,combined_label)
return
end
% ensure mapping between number of labels
nlabels=size(combined_lay.pos,1);
combined2planar_idxs=reshape(repmat(1:nlabels,2,1),[],1);
planar_lay=slice_layout(combined_lay,combined2planar_idxs);
planar_lay.label=reshape(planar_label',[],1);
planar_lay.name=[];
end
function lay=slice_layout(lay, to_select)
% helper function to select a subset of channels
nrows=size(lay.pos,1);
keys=fieldnames(lay);
for k=1:numel(keys)
key=keys{k};
value=lay.(key);
if size(value,1)==nrows
lay.(key)=value(to_select,:);
end
end
function lay=find_layout_from_label(label,opt)
% use labels from the layout to identify it
lay_coll=cosmo_meeg_layout_collection();
lay_label=cellfun(@(x)x.label,lay_coll,'UniformOutput',false);
[in_lay, in_label]=cosmo_overlap(lay_label,{label});
[coverage,i]=max(mean([in_lay, in_label],2));
% try to be smart and use decent thresholds
if in_lay(i)<opt.thr_layout || coverage<opt.thr_senstype
lay=[];
else
lay=lay_coll{i};
end