cosmo meeg find layout skl

function layout = cosmo_meeg_find_layout(ds, varargin)
    % finds an MEEG channel layout associated with a dataset
    %
    % layout=cosmo_meeg_find_layout(ds, varargin)
    %
    % Inputs:
    %   'chantype', ct     string indicating the channel type (if the dataset
    %                      has channel labels that allow for different types of
    %                      channels. Depending on the dataset, possible options
    %                      are:
    %                      - 'meg_planar'               pairs of planar MEG
    %                      - 'meg_axial'                axial MEG
    %                      - 'meg_planar_combined'      combined planar MEG
    %                      - 'meg_combined_from_planar' pairs of planar MEG [*]
    %                      - 'eeg'                      eeg channels
    %
    % Output:
    %   layout             MEG channel layout with 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
    %                      [*] when chantype is set to
    %                      'meg_combined_from_planar', layout also contains a
    %                      field .parent which is a layout in itself for the
    %                      'meg_planar_combined channels'. In that case,
    %                      layout.parent has the .pos, .width, .height, .label
    %                      and .name fields (all of size Mx1 or Mx2 for M
    %                      planar-combined channels, and in addition a cell
    %                      .child_label (of size Mx1) which contains the
    %                      channel labels in layout.name.
    %
    % Examples:
    %     % (This example requires FieldTrip)
    %     cosmo_skip_test_if_no_external('fieldtrip');
    %     %
    %     % generate neuromag306 dataset
    %     ds=cosmo_synthetic_dataset('type','meeg','sens','neuromag306_all');
    %     % get layout for the planar channels
    %     pl_layout=cosmo_meeg_find_layout(ds,'chantype','meg_planar');
    %     cosmo_disp(pl_layout.label)
    %     %|| { 'MEG0113'
    %     %||   'MEG0112'
    %     %||   'MEG0122'
    %     %||      :
    %     %||   'MEG2643'
    %     %||   'COMNT'
    %     %||   'SCALE'   }@206x1
    %     cosmo_disp([pl_layout.pos pl_layout.width pl_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
    %     pl_layout.name
    %     %|| 'neuromag306planar.lay'
    %     %
    %     % get layout for axial (magnetometer) channels
    %     mag_layout=cosmo_meeg_find_layout(ds,'chantype','meg_axial');
    %     cosmo_disp(mag_layout.label);
    %     %|| { 'MEG0111'
    %     %||   'MEG0121'
    %     %||   'MEG0131'
    %     %||      :
    %     %||   'MEG2641'
    %     %||   'COMNT'
    %     %||   'SCALE'   }@104x1
    %     %
    %     % get layout for planar channels, but add a 'parent' layout which has the
    %     % combined_planar channels
    %     combined_from_planar_layout=cosmo_meeg_find_layout(ds,'chantype',...
    %                                             'meg_combined_from_planar');
    %     cosmo_disp(combined_from_planar_layout.label);
    %     %|| { 'MEG0113'
    %     %||   'MEG0112'
    %     %||   'MEG0122'
    %     %||      :
    %     %||   'MEG2643'
    %     %||   'COMNT'
    %     %||   'SCALE'   }@206x1
    %     cosmo_disp(combined_from_planar_layout.parent.label);
    %     %|| { 'MEG0112+0113'
    %     %||   'MEG0122+0123'
    %     %||   'MEG0132+0133'
    %     %||         :
    %     %||   'MEG2642+2643'
    %     %||   'COMNT'
    %     %||   'SCALE'        }@104x1
    %     cosmo_disp(combined_from_planar_layout.parent.child_label);
    %     %|| { { 'MEG0112'
    %     %||     'MEG0113' }
    %     %||   { 'MEG0122'
    %     %||     'MEG0123' }
    %     %||   { 'MEG0132'
    %     %||     'MEG0133' }
    %     %||               :
    %     %||   { 'MEG2642'
    %     %||     'MEG2643' }
    %     %||   {  }
    %     %||   {  }          }@104x1
    %
    % See also: ft_prepare_neighbors, cosmo_meeg_chan_neighbors,
    %           cosmo_meeg_chan_neighborhood
    %
    % #   For CoSMoMVPA's copyright information and license terms,   #
    % #   see the COPYING file distributed with CoSMoMVPA.           #

    defaults.chantype = [];
    defaults.min_coverage = .2;
    opt = cosmo_structjoin(defaults, varargin);

    % get the sensor labels and channel types applicable to this dataset
    chantype2senstype = get_dataset_senstypes(ds, opt);

    % get the single channel type based on the input and available channels
    ds_chantype = get_base_chantype(chantype2senstype, opt);

    % get the sensor type corresponding with the channel type
    senstype = chantype2senstype.(ds_chantype);

    % use the mapping from senstype to layout to get the layout
    senstype2layout = cosmo_meeg_senstype2layout_mapping();
    layout = senstype2layout.(senstype);

    % ensure that enough labels are covered in the dataset
    ds_label = get_dataset_channel_label(ds);
    coverage = label_coverage(ds_label(:), layout.label(:));

    % no coverage, throw an error
    if coverage < opt.min_coverage
        error(['channel coverage %.1f%% < 100%% for %s. This means '...
               'that the channel layout was not recognized based '...
               'on the channel labels. Your options are:\n '...
               '1) if this is a custom layout, define a neighbor '...
               'struct with fields .label and neighblabel (similar '....
               'to CoSMoMVPA''s ft_meeg_chan_neighbors and '...
               'FieldTrip''s ft_prepare_neighbors), and provide this '...
               'as the only input to cosmo_meeg_chan_neighborhood\n'...
               '2) if this is a common MEG or EEG layout, please get '...
               'in touch with the CoSMoMVPA developers to see if '...
               'support for this layout can be added'], ...
              100 * coverage, ds_chantype);
    end

    % in case of planar channels with senstype set to
    % meg_combined_from_planar, add a parent layout that maps to the
    % original layout
    is_planar_layout = isempty(cosmo_strsplit(ds_chantype, '_planar', -1));
    if is_planar_layout && strcmp(opt.chantype, 'meg_combined_from_planar')
        parent_senstype = chantype2senstype.meg_combined_from_planar;
        parent_layout = senstype2layout.(parent_senstype);

        sens_label = get_senstype_label(senstype);
        parent_sens_label = get_senstype_label(parent_senstype);

        nlabels = numel(parent_layout.label);
        parent_layout.child_label = cell(nlabels, 1);
        all_child_labels = cell(nlabels, 1);
        for k = 1:nlabels
            i = find(cosmo_match(parent_sens_label, parent_layout.label(k)));
            if numel(i) ~= 1
                parent_layout.child_label{k} = cell(0, 1);
            end

            child_label = sort(sens_label(i, :));
            all_child_labels{k} = child_label(:);
            parent_layout.child_label{k} = child_label(:);
        end

        % sanity check
        assert(cosmo_overlap({sens_label(:)}, ...
                             {cat(1, all_child_labels{:})}) == 1);

        layout.parent = parent_layout;
    end

function label = get_senstype_label(senstype)
    sc = cosmo_meeg_senstype_collection();
    label = sc.(senstype).label;

function senstype_mapping = get_dataset_senstypes(ds, opt)
    % helper function to get supported senstypes
    [unused, senstype_mapping] = cosmo_meeg_chantype(ds, opt);
    keys = fieldnames(senstype_mapping);

    if cosmo_match({'meg_planar'}, keys)
        planar_senstype = senstype_mapping.meg_planar;
        planar_combined_senstype = [planar_senstype '_combined'];

        sc = cosmo_meeg_senstype_collection();
        if isfield(sc, planar_combined_senstype)
            senstype_mapping.meg_combined_from_planar = ...
                                        planar_combined_senstype;
        end
    end

function c = label_coverage(x, y)
    % helper function to compute coverage of labels by a layout
    c = mean(cosmo_match(x(:), y(:)));

function chan_labels = get_dataset_channel_label(ds)
    % helper function to get labels from dataset
    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

function chantype = get_base_chantype(chantype2senstype, opt)
    % helper function to get the base (i.e. the one
    % returned in layout) channel type.
    % (this is not the parent type)
    has_chantype = ischar(opt.chantype) && ~isempty(opt.chantype);
    chantypes = fieldnames(chantype2senstype);
    has_key = @(key)cosmo_match({key}, chantypes);

    error_msg = '';
    if has_chantype
        if strcmp(opt.chantype, 'meg_combined_from_planar')
            if has_key('meg_planar')
                chantype = 'meg_planar';
            else
                error_msg = sprintf(['Cannot use senstype ''%s'' because '...
                                     'meg_planar not available'], ...
                                    opt.chantype);
            end
        elseif ~has_key(opt.chantype)
            error_msg = sprintf('chantype ''%s'' is not supported', ...
                                opt.chantype);
        else
            chantype = opt.chantype;
        end
    else
        if numel(chantypes) > 1
            error_msg = ['''chantype'' argument is not provided, but '...
                         'multiple types are available'];
        else
            chantype = chantypes{1};
        end
    end

    if ~isempty(error_msg)
        error('%s. Set the ''chantype'' argument to one of: ''%s''.', ...
              error_msg, cosmo_strjoin(chantypes, ''', '''));
    end