function [chantypes, senstype_mapping] = cosmo_meeg_chantype(ds, varargin)
% return channel types and optionally a feature mask matching a type
%
% [chantypes,senstype_mapping]=cosmo_meeg_chantype(ds)
%
% Inputs:
% ds dataset struct for MEEG dataset
%
% Output:
% chantypes 1xN cell with type of each channel in ds, where
% N is the number of channels.
% senstype_mapping struct with keys the unique chantypes, and values
% the sensor (acquisition) type
%
% Example:
% % This example requires FieldTrip
% cosmo_skip_test_if_no_external('fieldtrip');
% %
% % generate synthetic dataset with meg_planar and meg_axial channels
% % as found in the neuromag306 system
% ds=cosmo_synthetic_dataset('type','meeg','sens','neuromag306_all',...
% 'size','big','nchunks',1,'ntargets',1);
% [chantypes,senstypes]=cosmo_meeg_chantype(ds);
% cosmo_disp(chantypes,'edgeitems',2);
% %|| { 'meg_axial' 'meg_planar' ... 'meg_planar' 'meg_planar' }@1x306
% cosmo_disp(senstypes,'strlen',inf);
% %|| .meg_axial
% %|| 'neuromag306alt_mag'
% %|| .meg_planar
% %|| 'neuromag306alt_planar'
% %
% % filter the dataset to only contain the planar channels:
% %
% % see which features have a matching channel
% chan_indices=find(cosmo_match(chantypes,'meg_planar'));
% planar_msk=cosmo_match(ds.fa.chan,chan_indices);
% % slice and prune dataset along feature dimension
% ds_planar=cosmo_slice(ds,planar_msk,2);
% ds_planar=cosmo_dim_prune(ds_planar);
% % the output dataset has only the 204 planar channels left
% cosmo_disp(ds_planar.a.fdim,'edgeitems',2);
% %|| .labels
% %|| { 'chan'
% %|| 'time' }
% %|| .values
% %|| { { 'MEG0112' 'MEG0113' ... 'MEG2642' 'MEG2643' }@1x204
% %|| [ -0.2 -0.15 ... 0.05 0.1 ]@1x7 }
% %
% cosmo_disp(ds_planar.fa.chan);
% %|| [ 1 2 3 ... 202 203 204 ]@1x1428
%
% # For CoSMoMVPA's copyright information and license terms, #
% # see the COPYING file distributed with CoSMoMVPA. #
persistent cached_opt
persistent cached_ds_labels
persistent cached_chantypes
persistent cached_senstype_mapping
ds_labels = get_channel_labels(ds);
defaults = struct();
% quality scores of match between labels and senstype
% ('secret' feature)
defaults.label_threshold = .25;
defaults.layout_threshold = .3;
defaults.both_threshold = .1;
opt = cosmo_structjoin(defaults, varargin);
if ~(isequal(opt, cached_opt) && isequal(cached_ds_labels, ds_labels))
[cached_chantypes, cached_senstype_mapping] = get_meeg_chantype( ...
ds_labels, opt);
cached_opt = opt;
cached_ds_labels = ds_labels;
end
chantypes = cached_chantypes;
senstype_mapping = cached_senstype_mapping;
function [chantypes, senstype_mapping] = get_meeg_chantype(labels, opt)
nlabels = numel(labels);
senstype_collection = cosmo_meeg_senstype_collection();
keys = fieldnames(senstype_collection);
nkeys = numel(keys);
% get channel types and labels for each sensor type
all_chantypes = cell(nkeys, 1);
all_senstypes = cell(nkeys, 1);
all_sens_labels = cell(nkeys, 1);
for k = 1:nkeys
key = keys{k};
senstype = senstype_collection.(key);
all_senstypes{k} = senstype.sens;
all_sens_labels{k} = senstype.label(:);
all_chantypes{k} = senstype.type;
end
% compute quality for each sensor type
all_quality = compute_overlap(labels, all_sens_labels);
% restrict to the best one in each modality
[keep_idxs, quality] = get_best_idxs(all_quality, opt);
keep_types = all_chantypes(keep_idxs);
keep_quality = quality(keep_idxs);
if isempty(keep_quality)
error('Could not identify channel type');
end
% set the channel types
[idxs, unq_types_cell] = cosmo_index_unique({keep_types});
sens_chantypes = unq_types_cell{1};
nsenstypes = numel(idxs);
visited_msk = false(size(labels));
chantypes = cell(size(labels));
senstype_mapping = struct();
for k = 1:nsenstypes
idx = idxs{k};
[unused, i] = max(keep_quality(idx));
all_idx = keep_idxs(idx(i));
hit_msk = cosmo_match(labels, all_sens_labels{all_idx});
chantypes(~visited_msk & hit_msk) = sens_chantypes(k);
visited_msk = visited_msk | hit_msk;
senstype_mapping.(sens_chantypes{k}) = keys{all_idx};
end
chantypes(~visited_msk) = {'unknown'};
function [keep_idxs, quality] = get_best_idxs(all_quality, opt)
qs = [all_quality, mean(all_quality, 2)];
thrs = [opt.label_threshold, opt.layout_threshold opt.both_threshold];
nsens = size(qs, 1);
keep_msk = true(nsens, 1);
for j = 1:3
quality = qs(:, j);
exceed_thr = quality > thrs(j);
if any(exceed_thr)
keep_msk = keep_msk & exceed_thr;
end
end
if any(keep_msk)
keep_idxs = find(keep_msk);
return
end
error('Could not identify channel type');
function quality = compute_overlap(x, ys)
[q1, q2] = cosmo_overlap(ys, {x});
quality = [q1 q2];
function chan_labels = get_channel_labels(ds)
if iscellstr(ds)
chan_labels = ds;
else
[dim, index, attr_name, dim_name] = cosmo_dim_find(ds, 'chan', true);
chan_labels = ds.a.(dim_name).values{index};
end