test meeg layout collection

function test_suite = test_meeg_layout_collection
    % tests for cosmo_meeg_layout_collection
    %
    % #   For CoSMoMVPA's copyright information and license terms,   #
    % #   see the COPYING file distributed with CoSMoMVPA.           #
    try % assignment of 'localfunctions' is necessary in Matlab >= 2016
        test_functions = localfunctions();
    catch % no problem; early Matlab versions can use initTestSuite fine
    end
    initTestSuite;

function test_fieldtrip_correspondence
    if cosmo_skip_test_if_no_external('fieldtrip')
        return
    end

    lc = cosmo_meeg_layout_collection();
    rp_lay = randperm(numel(lc));

    ntest_layout = 2;
    ntest_chan_max = 10;

    % may or may not be present
    ignore_labels = {'COMNT', 'SCALE'};

    % temporarily switch off warnings (emitted by fieldtrip)
    warning_state = warning('query', 'all');
    cleaner = onCleanup(@()warning(warning_state));
    warning('off', 'all');

    % shorter form
    tolerance = 1e-4;
    aeae = @(x, y)max(abs(x(:) - y(:))) < tolerance && isequal(size(x), size(y));

    for ii = 1:ntest_layout
        layout = lc{rp_lay(ii)};

        cfg = struct();
        cfg.layout = layout.name;
        layout_ft = ft_prepare_layout(cfg);

        % test outlnie
        for j = 1:max(numel(layout_ft.outline), numel(layout.outline))
            aeae(layout_ft.outline{j}, layout.outline{j});
        end

        % test mask
        for j = 1:max(numel(layout_ft.mask), numel(layout.mask))
            aeae(layout_ft.outline{j}, layout.outline{j});
        end

        % test labels
        keep_idxs = find(~cosmo_match(layout.label, ignore_labels));
        keep_ft_idxs = find(~cosmo_match(layout_ft.label, ignore_labels));
        assertEqual(sort(layout.label(keep_idxs)), ...
                    sort(layout_ft.label(keep_ft_idxs)));

        % test positions
        ntest_chan = min(numel(keep_idxs), ntest_chan_max);
        rp_chan = randperm(numel(keep_idxs));
        for jj = 1:ntest_chan
            test_label_idx = keep_idxs(rp_chan(jj));
            label = layout.label{test_label_idx};
            if strcmp(layout_ft.label{test_label_idx}, label)
                % little optimization
                pos = test_label_idx;
            else
                pos = find(cosmo_match(layout_ft.label, {label}));
                assert(numel(pos) == 1);
            end
            aeae(layout_ft.width(pos, :), layout.width(test_label_idx, :));
            aeae(layout_ft.height(pos, :), layout.height(test_label_idx, :));
            aeae(layout_ft.pos(pos, :), layout.pos(test_label_idx, :));
        end
    end

function test_meeg_layout_collection_
    if cosmo_skip_test_if_no_external('fieldtrip')
        return
    end

    % get layout properties
    % order is:
    % - layout name
    % - number of channels
    % - some channel positions
    % - labels of channel positions
    lay_props = get_layout_properties();

    % test each one
    n = numel(lay_props);

    clear('cosmo_meeg_layout_collection');
    lc = cosmo_meeg_layout_collection();
    lc_cached = cosmo_meeg_layout_collection();
    assertEqual(lc, lc_cached);
    lc_names = cellfun(@(x) x.name, lc, 'UniformOutput', false);
    for k = 1:n
        lay_prop = lay_props{k};
        name = lay_prop{1};
        nchan = lay_prop{2};
        pos = lay_prop{3};
        label = lay_prop{4};

        i = find(cosmo_match(lc_names, name));
        assertEqual(numel(i), 1);

        lay = lc{i};
        keep = find(~cosmo_match(lay.label, {'COMNT', 'SCALE'}));
        assertEqual(numel(keep), nchan);

        for j = 1:numel(label)
            lab_i = find(cosmo_match(lay.label, label{j}));
            assert(numel(lab_i) == 1);
            assertElementsAlmostEqual(lay.pos(lab_i, :), pos(j, :), ...
                                      'absolute', 1e-4);
        end
    end

function lay_props = get_layout_properties()
    % the layout properties below were generated with the following code:
    % names={'yokogawa440.lay', 'neuromag306planar.lay',...
    %      'neuromag306mag.lay', 'neuromag306cmb.lay',...
    %      'neuromag306all.lay', 'elec1020.lay',...
    %      'elec1010.lay', 'elec1005.lay', 'biosemi32.lay',...
    %      'biosemi128.lay', 'biosemi256.lay', 'EEG1020.lay',...
    %      'EEG1010.lay', 'EEG1005.lay', 'CTF151.lay',...
    %      'CTF275.lay', '4D148.lay', '4D248.lay'};
    % lc=cosmo_meeg_layout_collection();
    % lcn=cellfun(@(x) x.name,lc,'UniformOutput',false);
    % for k=1:numel(names)
    %     fn=[names{k}];
    %     m=find(cosmo_match(lcn,fn));
    %     lay=lc{m};
    %     keep=find(~cosmo_match(lay.label,{'COMNT','SCALE'}));
    %     nch=numel(keep);
    %     desc=sprintf(['{''%s'',%d,...\n\t\t\t[%.4f %.4f;%.4f %.4f],'...
    %                     '...\n\t\t\t{''%s'',''%s''}},...'],...
    %                     lay.name,nch,lay.pos(keep([1 end]),:)',...
    %                     lay.label{keep(1)},lay.label{keep(end)});
    %     fprintf('%s\n',desc);
    % end

    lay_props = { {'4D248.lay', 248, ...
                   [0.0038 0.0232; 0.3897 0.3453], ...
                   {'A1', 'A248'}}, ...
                 {'yokogawa440.lay', 440, ...
                  [-0.3337 0.3729; -0.0108 0.0726], ...
                  {'AG001', 'AG440'}}, ...
                 {'neuromag306planar.lay', 204, ...
                  [-0.4084 0.2532; 0.3733 -0.0820], ...
                  {'MEG0113', 'MEG2643'}}, ...
                 {'neuromag306mag.lay', 102, ...
                  [-0.4084 0.2732; 0.3733 -0.1036], ...
                  {'MEG0111', 'MEG2641'}}, ...
                 {'neuromag306cmb.lay', 102, ...
                  [-0.4084 0.2732; 0.3733 -0.1036], ...
                  {'MEG0112+0113', 'MEG2642+2643'}}, ...
                 {'neuromag306all.lay', 306, ...
                  [-0.4099 0.2532; 0.3761 -0.0976], ...
                  {'MEG0113', 'MEG2641'}}, ...
                 {'elec1020.lay', 21, ...
                  [-0.1710 0.4010; 0.1710 -0.4010], ...
                  {'Fp1', 'O2'}}, ...
                 {'elec1010.lay', 86, ...
                  [-0.1112 0.4261; 0.1391 -0.4257], ...
                  {'Fp1', 'I2'}}, ...
                 {'elec1005.lay', 335, ...
                  [-0.1112 0.3881; 0.1252 -0.3814], ...
                  {'Fp1', 'OI2'}}, ...
                 {'biosemi32.lay', 32, ...
                  [-0.1391 0.4500; 0.0000 0.0113], ...
                  {'Fp1', 'Cz'}}, ...
                 {'biosemi128.lay', 128, ...
                  [0.0000 0.0500; -0.4096 -0.2145], ...
                  {'A1', 'D32'}}, ...
                 {'biosemi256.lay', 256, ...
                  [0.0000 0.0587; -0.2290 -0.4060], ...
                  {'A1', 'H32'}}, ...
                 {'EEG1020.lay', 21, ...
                  [-0.1390 0.4280; 0.1390 -0.4280], ...
                  {'Fp1', 'O2'}}, ...
                 {'EEG1010.lay', 86, ...
                  [-0.1112 0.4260; 0.1390 -0.4256], ...
                  {'Fp1', 'I2'}}, ...
                 {'EEG1005.lay', 335, ...
                  [-0.1112 0.3880; 0.1252 -0.3814], ...
                  {'Fp1', 'OI2'}}, ...
                 {'CTF151.lay', 151, ...
                  [-0.0344 0.1732; 0.0008 -0.2668], ...
                  {'MLC11', 'MZP02'}}, ...
                 {'CTF275.lay', 275, ...
                  [-0.0171 0.1815; 0.0002 -0.2056], ...
                  {'MLC11', 'MZP01'}}, ...
                 {'4D148.lay', 148, ...
                  [-0.0109 0.0939; 0.3709 0.3364], ...
                  {'A1', 'A148'}}, ...
                 {'4D248.lay', 248, ...
                  [0.0038 0.0232; 0.3897 0.3453], ...
                  {'A1', 'A248'}}};