classdef featuresObject
    % Version 2: Individual object for train and test
    % Working from M50 - removing unneeded code
    % - Anything unique to first test/train sets
    % - Grads
    % Moving in external functions - compile features and get filelists

    properties
        tt = 'Test' % Train or Test
        type = string('Uncompiled');
        params
        gradsParams = []
        use
        useGrads % Now vector, see set.useGrads
        fileLists
        feaNames
        labels % Training labels
        keepIdx % New keepIdx (no NaN rows)
        applyNewSafeIdx = 1;
        dataSet
        subSegLists % Not used
        divS
        files % Cell: train, test x divS
        SSL % Cell: train, test
        hybrid = 0 % Add subject numbers to general model?
        path % Path to main data directory
    end

    methods
        function obj = featuresObject(params, use)

            if isfield(params, 'tt')
                obj.tt = params.tt;
            end

            if isfield(params, 'divS')
                obj.params = params;
                % Get list of divS to use
                % set method sorts
                obj.divS = params.divS;
            else
                obj.params = 'Auto';
                obj = obj.findDivS;
            end

            if isfield(params, 'applyNewSafeIdx')
                obj.applyNewSafeIdx = params.applyNewSafeIdx;
            end

            % Check hybrid mode
            if isfield(params, 'hybrid')
                obj.hybrid = params.hybrid;
            end

            % Set paths
            obj.path = params.paths;

            % Save file lists and use
            obj = setFileLists(obj, {});
            obj.use = use;

            % Run compile now?
            % obj = compileFeatures(obj);
        end

        function obj = set.divS(obj, divS)
            obj.divS = sort(divS, 'descend');
        end

        function obj = compileFeatures(obj)

            % Check the requested files are available
            obj.files = ...
                obj.checkFiles();

            % Load files
            nD = numel(obj.divS);
            for d = 1:nD
                fn = ['divS', num2str(obj.divS(d))];

                disp(['Loading ', fn])

                data = load(obj.files{1,d});

                allData.(fn).SSL = data.subSegList;

                % Compile this data
                % Moved external function to static
                [allData.(fn).dataSet, obj.feaNames] = ...
                    obj.comboFeatures(data.feas, ...
                    data.subSegList, obj.use);
            end
            clear data

            % Join available data
            if nD>1
                % Create new table
                nt = [];
                for d = 1:nD-1

                    % Join next (d+1) to this (d)
                    d1 = ['divS', num2str(obj.divS(d))];
                    d2 = ['divS', num2str(obj.divS(d+1))];
                    disp(['Joining ', d2 ' to ', d1])

                    % Create join keys
                    % Use pedantic key generation (from M41)
                    % First get list of unique files without using unique
                    % (it sorts)
                    disp('Keygen:')
                    fns = string(allData.(d1).SSL.Files);
                    toDel = zeros(numel(fns),1);
                    for f = 2:numel(fns)
                        % If this row is same as above row, mark to delete
                        toDel(f) = fns(f).contains(fns(f-1));
                    end
                    unqFiles = fns(~toDel);
                    % Now, for this list of files, generate keys
                    % This is slow
                    k1 = [];
                    k2 = [];
                    nFiles = numel(unqFiles);
                    for f = 1:nFiles

                        k1Idx = ...
                            string(allData.(d1).SSL.Files)...
                            .contains(unqFiles(f));
                        nK1 = sum(k1Idx);

                        k2Idx = ...
                            string(allData.(d2).SSL.Files)...
                            .contains(unqFiles(f));
                        nK2 = sum(k2Idx);

                        nextIdx = length(k1)+1;

                        k1Add = nextIdx : (nextIdx-1)+nK1;
                        k2Add = round(linspace(k1Add(1),k1Add(end), nK2));

                        k1 = [k1; k1Add']; %#ok<AGROW> % Can't preallocate
                        k2 = [k2; k2Add']; %#ok<AGROW>
                        if ~mod(f,1000)
                            disp([num2str(f), '/' num2str(nFiles)])
                        end
                    end

                    % Join
                    % Simplify syntax for now (add test/train loop?)
                    % Need to drop subSegID col, and append divs to col names
                    if d==1
                        jd1 = allData.(d1).dataSet(:,2:end);
                    else
                        jd1 = nt;
                    end
                    jd1.Properties.VariableNames = ...
                        cellstr(string(jd1.Properties.VariableNames) + d1);
                    jd1.jk = k1;
                    jd2 = allData.(d2).dataSet(:,2:end);
                    jd2.Properties.VariableNames = ...
                        cellstr(string(jd2.Properties.VariableNames) + d2);
                    jd2.jk = k2;

                    nt = join(jd2, jd1);
                    % Remove key
                    nt.jk = [];

                    clear jd1 jd2

                end
            else % Just 1 divS, no joining
                % But remember to drop subSegID
                nt = ...
                    allData.(['divS', num2str(obj.divS)]).dataSet(:,2:end);
            end

            % Save to object
            obj.dataSet = nt;
            obj.feaNames = nt.Properties.VariableNames;

            % Labels should be last divS
            labs = allData.(['divS', num2str(obj.divS(end))]).SSL.Class;
            obj.labels = labs;

            % Also save final subSegList
            obj.SSL = allData.(['divS', num2str(obj.divS(end))]).SSL;

            % Set feature type
            obj = setType(obj);

            % Set safeIdx (if training)
            switch obj.tt
                case {'train', 'Train'}
                    obj = setSafeIdx(obj);
                case {'test', 'Test'}
                    % Set keepIdx
                    obj = setkeepIdx(obj);
            end

            % Add subject numbers to sets if hybrid
            if obj.hybrid
                obj = addSubs(obj);
            end

        end

        function obj = addSubs(obj)
            switch obj.dataSet.Properties.VariableNames{1}
                case 'Subject'
                    % Subject already added, do nothing
                otherwise
                    obj.dataSet = ...
                        [obj.SSL{1}(:, 'Subject'), obj.dataSet];
            end
        end

        function obj = setType(obj)
            % General/Single/Hybrid | NoGrads/Grads/PartialGrads
            % eg. Hybrid.PartialGrads
            % Model will inherit type

            if numel(unique(obj.SSL.Subject))>1 && obj.hybrid == 1
                s1 = 'Hybrid';
            else
                % Features will contain all 3 subs
                % Set model object to single later if needed
                obj.hybrid = 0;
                s1 = 'General';
            end

            if all(obj.useGrads)
                s2 = 'Grads';
            elseif any(obj.useGrads) && numel(obj.useGrads)>1
                s2 = 'PartialGrads';
            elseif ~all(obj.useGrads)
                s2 = 'NoGrads';
            end

            obj.type = string({s1, s2});

        end

        % Set file lists
        function obj = setFileLists(obj, fileLists)
            if isempty(fileLists)
                obj.fileLists = ...
                    getFileLists(obj);
            else
                obj.fileLists = fileLists;
            end
        end

        % Get file lists - static functions (were external)
        function [fileList, labels] = ...
                getFileLists(obj)
            % NB: Labels not being returned - readd for training

            switch obj.tt
                case {'train', 'Train'}
                    [fileList] = ...
                        obj.genFileListSomeSingles({'1','2','3'}, ...
                        obj.tt, obj.path);
                case {'test', 'Test'}
                    [fileList] = ...
                        obj.genFileListNoConcat({'1','2','3'}, ...
                        obj.tt, obj.path);
            end
        end

        % Not a set method
        function obj = setkeepIdx(obj)
            % Sets keepIdx

            % Set newKeepIdx
            keepIdx1 = obj.newKeepIdx(obj.dataSet);

            obj.keepIdx = keepIdx1;
        end

        % Load and apply safe idx from second release of data
        function obj = setSafeIdx(obj)
            if obj.applyNewSafeIdx

                % Load csv
                % Contains all training data and affected test data
                safe = readtable('train_and_test_data_labels_safe.csv');

                % Drop refernces to test data
                flStr = string(obj.fileLists.File);
                safeStr = string(safe.image);
                trIdx = safeStr.contains(flStr);
                safe = safe(trIdx, :);
                % Remove class column for safety
                safe.class = [];
                % Rename key variable
                safe.Properties.VariableNames{1} = 'File';

                % Readd the new files in the training set (with modified
                % names)
                % Files in fileList
                str = string(obj.fileLists.File);
                % But not in safeStr
                missingIdx = ~str.contains(safeStr);
                % Add thses to safe, along with safe tag
                nAdd = sum(missingIdx);
                add = table(cellstr(str(missingIdx)), ones(nAdd,1));
                add.Properties.VariableNames{1} = 'File';
                add.Properties.VariableNames{2} = 'safe';

                % NB: Union drops dupes, but there shouldn't be any
                safe = union(safe, add);

                % Join
                % To fileList
                nt = join(obj.fileLists, safe);

                % Order as fileList?
                % plot(nt.SubSegID); hold on; plot(obj.fileLists.SubSegID)
                if ~all(nt.SubSegID == obj.fileLists.SubSegID)
                    keyboard
                else
                    obj.fileLists = nt;
                end

                % Join
                % To biggest subSegList
                % (.File is called .Files here)
                safe.Properties.VariableNames{1} = 'Files';
                nSSL = join(obj.SSL, safe);

                % Order as fileList?
                % plot(nSSL.SubSegID); hold on; plot(obj.SSL.SubSegID)
                if ~all(nSSL.SubSegID == obj.SSL.SubSegID)
                    keyboard
                else
                    obj.SSL = nSSL;
                end

                % Set newKeepIdx
                keepIdx2 = nSSL.safe;

            else
                % Don't apply new safeIdx
                keepIdx2 = true(height(obj.SSL,1));
            end

            % Get oklist from training data and combine
            keepIdx1 = obj.newKeepIdx(obj.dataSet);

            obj.keepIdx = keepIdx1 & keepIdx2;
        end

        function divS = findDivS(obj)
            % Find available features files
            files = dir(['*', obj.tt, 'divS*_*.mat']); %#ok<*PROP>
            nFiles = numel(files);

            divS = NaN(1, nFiles);
            for f = 1:nFiles
                s = string(files(f).name);
                divS(f) = s.extractAfter('divS').extractBefore('_').double;
            end
        end

        function files = checkFiles(obj)
            nD = numel(obj.divS);
            files = cell(1, nD);

            for d = 1:nD
                % Check
                fn = [obj.tt, '_divS', num2str(obj.divS(d)), ...
                    '.mat'];
                fn = dir(fn);

                if exist(fn.name, 'file')
                    % Exists
                    files{1,d} = fn.name;
                else
                    % Run generation
                    files{1,d} = [obj.tt, '_divS', num2str(obj.divS(d)), ...
                        '.mat'];

                    obj.params.divS = obj.divS(d);
                    obj.params.fn = files{1,d};

                    [~, ~, ~] = ... Outputs unused here
                        obj.redivideSegmentsConcatSome(obj.fileLists, ...
                        obj.params, obj.tt);

                end
            end
        end
    end

    methods (Static)
        function [allData, feaNames] = ...
                comboFeatures(feas, ...
                subSegList, use)
            % Compile data tables from feature structures

            fld = fieldnames(feas.data);
            allData = NaN(size(feas.data.(fld{1}),1), 0, 'single');
            feaNames = cell(1,0);

            fns = fieldnames(use);
            nF = numel(fns);
            for f = 1:nF
                if use.(fns{f})

                    % Collect selected data
                    allData = [allData, feas.data.(fns{f})]; %#ok<AGROW>

                    % Collect names
                    feaNames = [feaNames, feas.names.(fns{f})]; %#ok<AGROW>
                end
            end

            % Make sure feaNames doesn't contain spaces
            feaNames = strrep(feaNames, ' ', '_');
            % Add SubSegID to use as key later
            feaNames = ['SubSegID', feaNames];

            % Temp bodge to allow for non-multiples (M40)
            % There may be additional row of allTrain (rounding error?)
            aUse = 1:height(subSegList);

            allData = ...
                array2table(...
                [subSegList.SubSegID, double(allData(aUse,:))]);
            allData.Properties.VariableNames = cellstr(feaNames);

        end

        function keepIdx = newKeepIdx(data)
            % All data is prcessed
            % All zero windows will return NaNs for some features
            keepIdx = all(~isnan(data{:,:}),2);
        end

        function [fullFileList] = genFileListSomeSingles(subs, str, paths)
            warning('off', 'MATLAB:table:RowsAddedExistingVars')

            vars = {...
                NaN, 'Subject', 'Subject'; ...
                NaN, 'ID', ''; ...
                NaN, 'Class', ''; ...
                NaN, 'Segment', 'The 10 min segment this is from'; ...
                NaN, 'nInSegment', 'n within segment'; ...
                NaN, 'SegID', 'n within segment'; ...
                NaN, 'SubSegID', 'n within segment'; ...
                cell(1), 'Filename', ''; ...
                cell(1), 'File', ''; ...
                NaN, 'n', 'total n'; ...
                };

            fullFileList = table(vars{:,1});
            fullFileList.Properties.VariableNames = vars(:,2);
            fullFileList.Properties.VariableDescriptions = vars(:,3);

            nSubs = numel(subs);
            % Generate file list
            tn = 0;
            for s = 1:nSubs
                % Create sub table for this subject
                fileList = table(vars{:,1});
                fileList.Properties.VariableNames = vars(:,2);
                fileList.Properties.VariableDescriptions = vars(:,3);

                sDir = [paths.dataDir, str, '_', subs{s}, '\'];

                % files = dir([sDir, subs{s}, '*']);
                files = dir([sDir, '*.mat']);
                nFilesSub = length(files);
                r = 0;
                for n = 1:nFilesSub
                    tn=tn+1;
                    fn = [sDir, files(n).name];
                    disp(['Importing file: ', fn, '(', num2str(n), ...
                        '/', num2str(nFilesSub), ')']);

                    switch str
                        case {'train', 'Train'}
                            Y = str2double(fn(end-4));
                            IDIdx = strfind(files(n).name, '_');
                            ID = files(n).name(IDIdx(1)+1:IDIdx(2)-1);
                            ID = str2double(ID);
                            % ID duplicated between 0 and 1s
                            % Add 6000 to 1 segement IDs
                            % (More than max of each subject, may still be duplicate IDs
                            % between subjects)
                            if Y==1
                                ID = ID+6000;
                            end

                        otherwise % Test
                            Y = NaN;
                            IDIdx1 = strfind(files(n).name, '_');
                            IDIdx2 = strfind(files(n).name, '.');
                            ID = files(n).name(IDIdx1+1:IDIdx2-1);
                            ID = str2double(ID);
                    end

                    r = r+1;
                    fileList.Subject(r,1) = s;
                    fileList.Filename{r,1} = fn;
                    fileList.File{r,1} = files(n).name;
                    fileList.Class(r,1) = Y;
                    fileList.ID(r,1) = ID;
                    % fileList.n(r,1) = tn;
                end

                % Subject has been added, sort by ID and label segements
                fileList = sortrows(fileList, 'ID', 'ascend');

                % In some files,
                % The are 6 files for each segment
                % In others, they are singles
                % Load list of singles for this subject
                singles = load(['singles', num2str(s), '.mat']);

                exSinglesIdx = ~string(fileList.File).contains(singles.safe4);
                nFilesSubEx = sum(exSinglesIdx);
                % Label the grouped data
                fileList.Segment(find(exSinglesIdx)) = ...
                    reshape(repmat((1:nFilesSubEx/6)', 1, 6)',nFilesSubEx,1); %#ok<FNDSB>
                fileList.nInSegment(find(exSinglesIdx)) = repmat((1:6)', ...
                    nFilesSubEx/6, 1); %#ok<FNDSB>

                % Now label the remaing as single segments
                fileList.Segment(find(~exSinglesIdx)) = ...
                    max(fileList.Segment)+1:...
                    (numel(singles.safe4)+ max(fileList.Segment)); %#ok<FNDSB>
                fileList.nInSegment(find(~exSinglesIdx)) = 1; %#ok<FNDSB>

                % Append to full table
                fullFileList = [fullFileList; fileList]; %#ok<AGROW>
                clear fileList
            end

            % Remove empty first row
            fullFileList(1,:) = [];
            fullFileList.n(:,1) = 1:tn;
            segID = cellstr([num2str(fullFileList.Subject), ...
                repmat('000', height(fullFileList),1), ...
                num2str(fullFileList.Segment)]);
            subSegID = cellstr([num2str(fullFileList.Subject), ...
                repmat('000', height(fullFileList),1), ...
                num2str(fullFileList.Segment), ...
                num2str(fullFileList.nInSegment)]);

            cellstr([segID{:}, ...
                ]);

            % FFS
            for s = 1:height(fullFileList)
                segID{s} = str2double(strrep(segID{s}, ' ', '0'));
                subSegID{s} = str2double(strrep(subSegID{s}, ' ', '0'));
            end
            fullFileList.SegID = cell2mat(segID);
            fullFileList.SubSegID = cell2mat(subSegID);
            % plot(fullFileListTrain.ID); hold on; plot(fullFileListTrain.Segment)
        end

        function [fullFileList] = genFileListNoConcat(subs, str, paths)
            warning('off', 'MATLAB:table:RowsAddedExistingVars')

            vars = {...
                NaN, 'Subject', 'Subject'; ...
                NaN, 'ID', ''; ...
                NaN, 'Class', ''; ...
                NaN, 'Segment', 'The 10 min segment this is from'; ...
                NaN, 'nInSegment', 'n within segment'; ...
                NaN, 'SegID', 'n within segment'; ...
                NaN, 'SubSegID', 'n within segment'; ...
                cell(1), 'Filename', ''; ...
                cell(1), 'File', ''; ...
                NaN, 'n', 'total n'; ...
                };

            fullFileList = table(vars{:,1});
            fullFileList.Properties.VariableNames = vars(:,2);
            fullFileList.Properties.VariableDescriptions = vars(:,3);

            nSubs = numel(subs);
            % Generate file list
            tn = 0;
            for s = 1:nSubs
                % Create sub table for this subject
                fileList = table(vars{:,1});
                fileList.Properties.VariableNames = vars(:,2);
                fileList.Properties.VariableDescriptions = vars(:,3);

                sDir = [paths.dataDir, str, '_', subs{s}, '_New\'];

                % files = dir([sDir, subs{s}, '*']);
                files = dir([sDir, '*.mat']);
                nFilesSub = length(files);
                r = 0;
                for n = 1:nFilesSub
                    tn=tn+1;
                    fn = [sDir, files(n).name];
                    disp(['Importing file: ', fn, ...
                        '(', num2str(n), '/', num2str(nFilesSub), ')']);

                    switch str
                        case {'train', 'Train'}
                            Y = str2double(fn(end-4));
                            IDIdx = strfind(files(n).name, '_');
                            ID = files(n).name(IDIdx(1)+1:IDIdx(2)-1);
                            ID = str2double(ID);
                            % ID duplicated between 0 and 1s
                            % Add 6000 to 1
                            % segement IDs (More than max of each subject,
                            % may still be duplicate IDs between subjects)
                            if Y==1
                                ID = ID+6000;
                            end

                        otherwise % Test
                            Y = NaN;
                            IDIdx1 = strfind(files(n).name, '_');
                            IDIdx2 = strfind(files(n).name, '.');
                            ID = files(n).name(IDIdx1+1:IDIdx2-1);
                            ID = str2double(ID);
                    end

                    r = r+1;
                    fileList.Subject(r,1) = s;
                    fileList.Filename{r,1} = fn;
                    fileList.File{r,1} = files(n).name;
                    fileList.Class(r,1) = Y;
                    fileList.ID(r,1) = ID;
                    % fileList.n(r,1) = tn;
                end

                % Subject has been added, sort by ID and label segements
                fileList = sortrows(fileList, 'ID', 'ascend');
                % The are 6 files for each segment
                % fileList.Segment = reshape(repmat((1:nFilesSub/6)', 1, 6)',nFilesSub,1);
                % New for master 33: Each file is individual segment
                fileList.Segment = (1:height(fileList))';
                fileList.nInSegment = repmat(1, nFilesSub, 1);

                % Append to full table
                fullFileList = [fullFileList; fileList]; %#ok<AGROW>
                clear fileList
            end

            % Remove empty first row
            fullFileList(1,:) = [];
            fullFileList.n(:,1) = 1:tn;
            segID = cellstr([num2str(fullFileList.Subject), ...
                repmat('000', height(fullFileList),1), ...
                num2str(fullFileList.Segment)]);
            subSegID = cellstr([num2str(fullFileList.Subject), ...
                repmat('000', height(fullFileList),1), ...
                num2str(fullFileList.Segment), ...
                num2str(fullFileList.nInSegment)]);

            cellstr([segID{:}, ...
                ]);

            % FFS
            for s = 1:height(fullFileList)
                segID{s} = str2double(strrep(segID{s}, ' ', '0'));
                subSegID{s} = str2double(strrep(subSegID{s}, ' ', '0'));
            end
            fullFileList.SegID = cell2mat(segID);
            fullFileList.SubSegID = cell2mat(subSegID);
            % plot(fullFileListTrain.ID); hold on; plot(fullFileListTrain.Segment)
        end

        function [subSegList, fileList, feas] = ...
                redivideSegmentsConcatSome(fileList, params, str)
            % Divide and OK check
            warning('off', 'MATLAB:table:RowsAddedExistingVars')

            % Unpack params
            divS = params.divS;
            OKThresh = 0.5;
            params.plotOn = 0;

            % First check it's necessary to run
            fn = params.fn;
            vars = {'feas', 'subSegList'};
            if exist(fn, 'file') && ~params.force
                disp('Reloading')
                load(fn, vars{:});
            else

                % Prepare table
                vars2 = {...
                    NaN, 'Subject', 'Subject'; ...
                    NaN, 'ID', ''; ...
                    NaN, 'Class', ''; ...
                    NaN, 'Segment', 'The 10 min segment this is from'; ...
                    NaN, 'nInSegment', 'n within segment'; ...
                    NaN, 'SegID', 'n within segment'; ...
                    NaN, 'SubSegID', 'n within segment'; ...
                    cell(1), 'Filenames', ''; ...
                    cell(1), 'Files', ''; ...
                    NaN, 'n', 'total n'; ...
                    NaN, 'SubN', 'n in whole segment'; ...
                    NaN, 'Of', 'total epochs in 6-file concatonated segment. Should always be the same!'; ...
                    NaN, 'Lengh', 'Length of subSeg in S'; ...
                    NaN, 'Prop0', 'Proportion of zeros'; ...
                    true, 'OK', 'Passed OK Check?'; ...
                    };

                subSegList = table(vars2{:,1});
                subSegList.Properties.VariableNames = vars2(:,2);
                subSegList.Properties.VariableDescriptions = vars2(:,3);

                % Set lengths
                % unqSub = unique(fileList.Subject);
                % nSub = numel(unqSub);
                unqSeg = unique(fileList.SegID);
                nSeg = numel(unqSeg);
                % unqSubSeg = unique(fileList.SubSegID);
                % nSubSeg = numel(unqSubSeg);

                % Known parameters - fixed, not discovered or varied.
                fs = 400;
                dl = 240000;
                nChans = 16;
                % nInSeg = 1; % Master33: Now dynamic

                % Consistent paraemters
                divPts = divS*fs;
                % nDivs = floor((dl*nInSeg)/divPts);
                % cut = dl*nInSeg - nDivs*divPts;
                % Changed master33
                %     if nDivs>6
                %         from = reshape(repmat(1:6, nDivs/6, 1), nDivs, 1);
                %     else
                %         from = (1:6)';
                %     end
                % from = ones(nDivs,1)

                % totalToDo = height(fileList);

                % Features processed here
                % tRows = height(fileList)/nInSeg*nDivs;
                % tRows = height(fileList)*ceil((10*60)/divS);

                % Count singles and blocks of 6 here
                % Concatonate .Subject and .ID as chars
                ac = string([num2str(fileList.Subject), num2str(fileList.Segment)]);
                % Convert to string and .replace any spaces, return as doubles
                ac = ac.replace(' ' ,'').double();
                % Use this as accarray index on ones
                counts = accumarray(ac, ones(height(fileList),1));

                n1s = sum(counts == 1);
                n6s = sum(counts == 6);

                row1s = n1s*ceil((10*60*1)/divS);
                row6s = n6s*ceil((10*60*6)/divS);

                tRows = row1s + row6s;

                feas.data.hillsBandsLogAv = NaN(tRows, ...
                    numel(params.HillsBands.Range), 'single');
                feas.data.hillsBandsLog2D = NaN(tRows, ...
                    numel(params.HillsBands.Range)*16, 'single');
                feas.data.maxHills2D = NaN(tRows, ...
                    16, 'single');
                feas.data.maxHillsAv = NaN(tRows, ...
                    1, 'single');
                feas.data.summ32D = NaN(tRows, ...
                    8*nChans, 'single');
                feas.data.summ3Av = NaN(tRows, ...
                    8, 'single');
                feas.data.bandsLin2D = NaN(tRows, ...
                    11*16, 'single');
                feas.data.bandsLinAv = NaN(tRows, ...
                    11, 'single');
                feas.data.maxBands2D = NaN(tRows, ...
                    16, 'single');
                feas.data.maxBandsAv = NaN(tRows, ...
                    1, 'single');
                feas.data.mCorrsT = NaN(tRows, ...
                    4*16, 'single');
                feas.data.mCorrsF = NaN(tRows, ...
                    3*16, 'single');

                sslRow = 0;
                finalFeaRow = 0;
                % for s = 1:nSeg % For each unique segment
                for s = 1:nSeg % For each file individually
                    % Get ID
                    sn = unqSeg(s);

                    % Get index of files
                    segIdx = fileList.SegID == sn;

                    % Get subFileList structure just for this segment
                    % Might be one file, might be 6
                    subFileList = fileList(segIdx,:);

                    % Moved here to make dynamic
                    nInSeg = height(subFileList);
                    nDivs = floor((dl*nInSeg)/divPts);
                    cut = dl*nInSeg - nDivs*divPts;

                    if cut>0
                        % If cut is >0 there are unused values at end
                        % Add one more window achnored to end to fill these values
                        fillWindow = true;

                        % Also add 1 to nDivs, but not yet
                    else
                        fillWindow = false;
                    end

                    from = round(linspace(1,height(subFileList),nDivs))'; % Estimated for non-multiples of length
                    % Previous - didn't work witn non-multiples eg divS = 400
                    % from = reshape(repmat(subFileList.nInSegment', ...
                    %    nDivs/nInSeg, 1), nDivs, 1);

                    % Load and concatonate the data in the segement
                    % If there's more than one file
                    data1 = NaN(dl*nInSeg, nChans, 'single');
                    files = subFileList.File';
                    filenames = subFileList.Filename';
                    nFiles = numel(files);
                    sIdx = 1;
                    eIdx = 240000;
                    for ss = 1:nFiles
                        a = load(filenames{ss});
                        if nInSeg==1
                            disp(['Not-Concatonating ', filenames{ss}])
                        else
                            disp(['Concatonating ', filenames{ss}])
                        end
                        data1(sIdx:eIdx,:) = a.dataStruct.data;

                        sIdx = sIdx + 240000;
                        eIdx = eIdx + 240000;
                    end
                    clear a

                    % Do preprocessing here

                    % Epoch
                    % Cut off any less than full bins, from start
                    % data = data(1+cut:end,:);
                    % Can increase data here by using sliding window instead

                    % Epoch - M40
                    % Now cutting end off, and if any is cut, adding this to process as
                    % start cut off (below)
                    data = data1(1:end-cut,:);

                    % Idiot check
                    % dataEp = reshape(data', 16, divPts, nDivs);
                    % dataEp = permute(dataEp, [2, 1, 3]);
                    % figure
                    % subplot(2,1,1), plot(data(1:4000, 1:6))
                    % subplot(2,1,2), plot(dataEp(:, 1:6, 1))
                    % figure
                    % subplot(2,1,1), plot(data(4001:8000, 1:6))
                    % subplot(2,1,2), plot(dataEp(:, 1:6, 2))
                    data = reshape(data', 16, divPts, nDivs);
                    data = permute(data, [2, 1, 3]);

                    if fillWindow
                        % Add the end cut off window to data
                        % fw = data1(1+cut:end,:);
                        fw = data1(end-(divPts-1):end,:);
                        fw = reshape(fw', 16, divPts, 1);
                        fw = permute(fw, [2, 1, 3]);

                        % Set new nDivs here and add window to process to 3rd dim of
                        % data
                        nDivs = nDivs+1;
                        data(:,:,nDivs) = fw;
                        % Also remake from
                        from = round(linspace(1,height(subFileList),nDivs))';
                    end

                    % For each of the new epochs, do OK check and add to table
                    nInSegment = 0;
                    for n = 1:nDivs
                        sslRow = sslRow + 1;
                        nInSegment = nInSegment+1;
                        % Get the index (of the 6 rows from fileList) that this data came
                        % from
                        frIdx = from(n);

                        % Copy in columns from subFilefileList
                        nc = width(subFileList);
                        for c = 1:nc
                            subSegList(sslRow, c) = subFileList(frIdx, c);
                        end

                        % OK Check
                        prop0 = sum(sum(data(:,:,n)==0)) / (divPts*nChans);
                        chk = prop0 < OKThresh;

                        % Add additional information
                        subSegList.SubN(sslRow,1) = n;
                        subSegList.Of(sslRow,1) = nDivs;
                        subSegList.Length(sslRow,1) = divS;
                        subSegList.Prop0(sslRow,1) = prop0;
                        subSegList.OK(sslRow,1) = chk;
                        subSegList.nInSegment(sslRow,1) = nInSegment;

                    end

                    disp('Computing Hills bands feature')
                    feaRow = finalFeaRow;
                    for n = 1:nDivs
                        feaRow = feaRow+1;
                        [feas.data.hillsBandsLog2D(feaRow, :), ...
                            feas.data.hillsBandsLogAv(feaRow, :), ...
                            feas.data.maxHills2D(feaRow, :), ...
                            feas.data.maxHillsAv(feaRow, :), ...
                            feas.names.hillsBandsLog2D, ...
                            feas.names.hillsBandsLogAv, ...
                            feas.names.maxHills2D, ...
                            feas.names.maxHillsAv] = ...
                            featuresObject.extractHillBands(data(:,:,n), ...
                            params);
                    end

                    disp('Computing summary stats features')
                    feaRow = finalFeaRow;
                    for n = 1:nDivs
                        feaRow = feaRow+1;
                        [feas.data.summ32D(feaRow, :), ...
                            feas.data.summ3Av(feaRow, :), ...
                            feas.names.summ32D, ...
                            feas.names.summ3Av] = ...
                            featuresObject.extractSumm3(data(:,:,n), ...
                            params);
                    end

                    disp('Computing bandsLin feature')
                    feaRow = finalFeaRow;
                    for n = 1:nDivs
                        feaRow = feaRow+1;
                        [feas.data.bandsLin2D(feaRow, :), ...
                            feas.data.bandsLinAv(feaRow, :), ...
                            feas.data.maxBands2D(feaRow, :), ...
                            feas.data.maxBandsAv(feaRow, :), ...
                            feas.names.bandsLin2D, ...
                            feas.names.bandsLinAv, ...
                            feas.names.maxBands2D, ...
                            feas.names.maxBandsAv] = ...
                            featuresObject.extractBandsLin(data(:,:,n), ...
                            params);
                    end

                    disp('Computing channel correlations T feature')
                    feaRow = finalFeaRow;
                    for n = 1:nDivs
                        feaRow = feaRow+1;
                        [feas.data.mCorrsT(feaRow, :), ...
                            feas.names.mCorrsT] = ...
                            featuresObject.extractChannelCorrelationT(data(:,:,n), ...
                            params);
                    end

                    disp('Computing channel correlations F feature')
                    feaRow = finalFeaRow;
                    for n = 1:nDivs
                        feaRow = feaRow+1;
                        [feas.data.mCorrsF(feaRow, :), ...
                            feas.names.mCorrsF] = ...
                            featuresObject.extractChannelCorrelationF(data(:,:,n), ...
                            params);
                    end

                    finalFeaRow = feaRow;

                    disp([str, ' Done: ', num2str(s), '/', num2str(nSeg), ...
                        '(', num2str(s/nSeg*100), '%)'])

                end

                % Reapply subSegID
                subSegList.SubSegID = ...
                    str2double(strrep(cellstr([num2str(subSegList.SegID), ...
                    num2str(subSegList.nInSegment)]), ' ', '0'));

                save(fn, vars{:}, '-v7.3')
            end
        end

        [bandsLin2D, bandsLinAv, ...
            mB2D, mBAv, ...
            names2D, namesAv, ...
            namesmB2D, namesmBAv] = extractBandsLin(data, params)

        [mCorrsF2D, names2D] = ...
            extractChannelCorrelationF(data, params)

        [mCorrsT2D, names2D] = ...
            extractChannelCorrelationT(data, params)

        [HBLog2D, HBLogAv, ...
            mB2D, mBAv, ...
            names2D, namesAv, ...
            namesmB2D, namesmBAv] = ...
            extractHillBands(data, params)

        [summ32D, summ3Av, names32D, names3Av] = extractSumm3(data, params)

    end
end


clear

if(isunix)
    features_exe = '"../../../build/bin/FeatureExtraction"';
else
    features_exe = '"../../../x64/Release/FeatureExtraction.exe"';
end

find_FERA2011;

output_dir = 'E:\datasets\face_datasets_processed\fera2011';
FERA2011_dir = [FERA2011_dir, '/../'];

% Go two levels deep
fera_dirs = dir([FERA2011_dir, '*']);
fera_dirs = fera_dirs(3:end);
parfor f1=1:numel(fera_dirs)

    fera_dirs_level_2 = dir([FERA2011_dir, fera_dirs(f1).name]);
    fera_dirs_level_2 = fera_dirs_level_2(3:end);

    for f2=1:numel(fera_dirs_level_2)

        vid_files = dir([FERA2011_dir, fera_dirs(f1).name, '/', fera_dirs_level_2(f2).name, '/*.avi']);

        for v=1:numel(vid_files)

            input_file = [FERA2011_dir, fera_dirs(f1).name, '/', fera_dirs_level_2(f2).name, '/', vid_files(v).name];
            [~, name, ~] = fileparts(vid_files(v).name);
            out_name = [fera_dirs(f1).name '_' name];
            command = sprintf('%s -f "%s" -out_dir "%s" -hogalign -pdmparams -of %s', features_exe, input_file, output_dir, out_name );

            dos(command);

        end

    end

end


function fnames = nestedfieldnames(field,pre)
%Return a cell array of strings containing nested structure args(recursive)
fnames = {};
if(~isstruct(field))
    error('This function only works on structures');
end
fn = fieldnames(field);
no = length(fn);
if(no < 1)
    error('The supplied structure must have at least one field');
end
if(nargin < 2)
    pre = '';
else
    pre = [pre '.'];
end

for i = 1:no
    if(isstruct(field.(fn{i})))
        fnames{i} = nestedfieldnames(field.(fn{i}),[pre fn{i}]); %#ok<AGROW>
    else
        fnames{i} = [pre fn{i}]; %#ok<AGROW>
    end
end
%Flatten cell array
fnames = flatten(fnames);

function C = flatten(A)
% Ref: http://www.mathworks.com/matlabcentral/fileexchange/27009-flatten-nested-cell-arrays/content/flatten.m
% (recursive)
C = {};
for i=1:numel(A)
    if(~iscell(A{i}))
        C = [C,A{i}]; %#ok<AGROW>
    else
        Ctemp = flatten(A{i});
        C = [C,Ctemp{:}]; %#ok<AGROW>
    end
end


function [testFileList]=getTestFiles(testSet)

% function [testFileList]=getTestFiles(testSet)
% ------------------------------------------------------------------------
% This function obtains the list of test files from the documentation
% folder. The test files are either the HELP_* files (testSet='help'), the
% DEMO_* files (testSet='demo') or all (testSet='all').
%
% Change log:
% 2021/08/05 Created
% ------------------------------------------------------------------------

%%
testFileList=fullfile(fileparts(fileparts(mfilename('fullpath'))),'docs');

%Get list of M-files in documentation folder
allFiles_publish = dir(fullfile(testFileList,'*.m'));
allFiles_publish={allFiles_publish(1:end).name};
allFiles_publish=sort(allFiles_publish(:));

logicExclude=~gcontains(allFiles_publish,'obj_DEMO_'); %Skip objective functions
logicDemo=gcontains(allFiles_publish,'DEMO_'); %Logic for demo files
logicHelp=gcontains(allFiles_publish,'HELP_'); %Logic for help files

%Get list of files, either all or just help or demo files
switch lower(testSet) %Lower so both HELP and help etc work
    case 'all'
        testFileList=allFiles_publish((logicHelp | logicDemo) & logicExclude);
    case 'demo'
        testFileList=allFiles_publish(logicDemo & logicExclude);
    case 'help'
        testFileList=allFiles_publish(logicHelp & logicExclude);
    otherwise
        error([testMode,' is not a valid option. Use: all, demo, or help']);
end

end
%%
% _*GIBBON footer text*_
%
% License: <https://github.com/gibbonCode/GIBBON/blob/master/LICENSE>
%
% GIBBON: The Geometry and Image-based Bioengineering add-On. A toolbox for
% image segmentation, image-based modeling, meshing, and finite element
% analysis.
%
% Copyright (C) 2006-2022 Kevin Mattheus Moerman and the GIBBON contributors
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.


function getPart()
partNum=5;
partList={'bird','cat','cow','dog','horse','sheep'};
theConf=configurations;

for i=1:numel(partList)
    Name_batch=partList{i};
    objset=readAnnotation(Name_batch,theConf);

    tarSize=[224,224];
    getIMDBMask(Name_batch,objset,partNum,tarSize,theConf);
    tarSize=[227,227];
    getIMDBMask(Name_batch,objset,partNum,tarSize,theConf);
end
end

function getIMDBMask(Name_batch,objset,partNum,tarSize,theConf)
objNum=numel(objset);
mask=zeros(tarSize(1),tarSize(2),objNum,'uint8');
partPos=cell(objNum,1);
for i=1:objNum
    tmp=getPart_cate(objset(i),theConf,tarSize);
    mask(:,:,i)=uint8(tmp);
    pos=ones(2,partNum).*(-1);
    for part=1:partNum
        [hL,wL]=find(tmp==part);
        if(isempty(hL))
            continue;
        end
        pos(1,part)=mean(hL);
        pos(2,part)=mean(wL);
    end
    partPos{i}=pos;
    fprintf('%s   scale %d   %d\n',Name_batch,tarSize(1),i);
end
filename=sprintf('../results/imdb/%d/%s/partMask.mat',tarSize(1),Name_batch);
save(filename,'mask','partPos');
end

function mask=getPart_cate(obj,theConf,tarSize)
Batch_Name = obj.name;
switch(Batch_Name)
    case 'bird'
        partMerge={[1,2,3,4],[5,6,7,8],[9,10,11,12],13};
    case 'cat'
        partMerge={[1,2,3,4,5,6],[7,8],[9,10,11,12],[13,14,15,16],17};
    case 'cow'
        partMerge={[1,2,3,4,5,6,7,8],[9,10],[11,12,13,14],[15,16,17,18]};
    case 'dog'
        partMerge={[1,2,3,4,5,6,20],[7,8],[9,10,11,12],[13,14,15,16],17};
    case 'horse'
        partMerge={[1,2,3,4,5,6],[9,10],[11,12,13,14,30,31],[15,16,17,18,32,33]};
    case 'sheep'
        partMerge={[1,2,3,4,5,6,7,8],[9,10],[11,12,13,14],[15,16,17,18]};
    otherwise
        error('Errors here.');
end
[mask,bndbox]=getMask(obj,theConf,partMerge);
mask=mask(bndbox.ymin:bndbox.ymax,bndbox.xmin:bndbox.xmax);
yL=round(linspace(1,size(mask,1),tarSize(1)));
xL=round(linspace(1,size(mask,2),tarSize(2)));
mask=mask(yL,xL);
end

function [mask,bndbox]=getMask(obj,theConf,partMerge)
for i=1:numel(partMerge)
    pL=partMerge{i};
    for j=1:numel(pL)
        [tmp,bndbox]=getPart_one(obj,pL(j),theConf);
        if((i==1)&&(j==1))
            mask=zeros(size(tmp));
        end
        mask(tmp>0)=i;
    end
end
end

function [part_mask,bndbox]=getPart_one(obj, partID, theConf)

images_path = [theConf.data.imgdir, obj.folder];
anno_file = [theConf.data.annodir,'/%s.mat'];
imname = obj.filename;
batch_name = obj.name;
bndbox = obj.bndbox;
bndbox.ymax=str2double(bndbox.ymax);
bndbox.ymin=str2double(bndbox.ymin);
bndbox.xmax=str2double(bndbox.xmax);
bndbox.xmin=str2double(bndbox.xmin);
batch2classID=containers.Map('KeyType','char','ValueType','int32');
batch2classID('bird')=3;
batch2classID('cat')=8;
batch2classID('cow')=10;
batch2classID('dog')=12;
batch2classID('horse')=13;
batch2classID('sheep')=17;
classID=batch2classID(batch_name);

pimap = part2ind();
img = imread([images_path, '/', imname]);
% load annotation -- anno
load(sprintf(anno_file, imname(1:end-4)));

[cls_mask, inst_mask, part_mask] = mat2map(anno, img, pimap);

tmp=(cls_mask==classID).*(part_mask==partID);
tmp([1:bndbox.ymin,bndbox.ymax:size(tmp,1)],:)=0;
tmp(:,[1:bndbox.xmin,bndbox.xmax:size(tmp,2)])=0;
inst_mask=inst_mask.*uint8(tmp);
part_mask=part_mask.*uint8(tmp);

for h=1:size(part_mask,1)
    for w=1:size(part_mask,2)
        if cls_mask(h,w)~=classID
            part_mask(h,w)=0;
            inst_mask(h,w)=0;
        elseif ((part_mask(h,w)~=partID) || (h>bndbox.ymax) || (h<bndbox.ymin) || (w<bndbox.xmin) || (w>bndbox.xmax))
            % skip if no part annotation or pixel outside of bndbox
            part_mask(h,w)=0;
            inst_mask(h,w)=0;
        end
    end
end
end

function [cls_mask, inst_mask, part_mask] = mat2map(anno, img, pimap)
% Read the annotation and present it in terms of 3 segmentation mask maps (
% i.e., the class maks, instance maks and part mask). pimap defines a
% mapping between part name and index (See part2ind.m).

cls_mask = zeros(size(img,1), size(img,2), 'uint8');
inst_mask = zeros(size(img,1), size(img,2), 'uint8');
part_mask = zeros(size(img,1), size(img,2), 'uint8');

for oo = 1:numel(anno.objects)
    % The objects are ordered such that later objects can occlude previous
    % objects
    obj = anno.objects(oo);
    class_ind = obj.class_ind;
    silh = obj.mask;            % the silhouette mask of the object
    assert(size(silh,1) == size(img,1) && size(silh,2) == size(img,2));
    inst_mask(silh) = oo;
    cls_mask(silh) = class_ind;

    for pp = 1:numel(obj.parts)
        part_name = obj.parts(pp).part_name;
        assert(isKey(pimap{class_ind}, part_name));     % must define part index for every part
        assert(all(silh(obj.parts(pp).mask)));          % all part region is a subregion of the object
        pid = pimap{class_ind}(part_name);
        part_mask(obj.parts(pp).mask) = pid;
    end
end
end

function pimap = part2ind()
% Define the part index of each objects.
% One can merge different parts by using the same index for the
% parts that are desired to be merged.
% For example, one can merge
% the left lower leg (llleg) and the left upper leg (luleg) of person by setting:
% pimap{15}('llleg')      = 19;               % left lower leg
% pimap{15}('luleg')      = 19;               % left upper leg

pimap = cell(20, 1);
% Will define part index map for the 20 PASCAL VOC object classes in ascending
% alphabetical order (the standard PASCAL VOC order).
for ii = 1:20
    pimap{ii} = containers.Map('KeyType','char','ValueType','int32');
end

% [aeroplane]
pimap{1}('body')        = 1;
pimap{1}('stern')       = 2;
pimap{1}('lwing')       = 3;                % left wing
pimap{1}('rwing')       = 4;                % right wing
pimap{1}('tail')        = 5;
for ii = 1:10
    pimap{1}(sprintf('engine_%d', ii)) = 10+ii; % multiple engines
end
for ii = 1:10
    pimap{1}(sprintf('wheel_%d', ii)) = 20+ii;  % multiple wheels
end

% [bicycle]
pimap{2}('fwheel')      = 1;                % front wheel
pimap{2}('bwheel')      = 2;                % back wheel
pimap{2}('saddle')      = 3;
pimap{2}('handlebar')   = 4;                % handle bar
pimap{2}('chainwheel')  = 5;                % chain wheel
for ii = 1:10
    pimap{2}(sprintf('headlight_%d', ii)) = 10 + ii;
end

% [bird]
pimap{3}('head')        = 1;
pimap{3}('leye')        = 2;                % left eye
pimap{3}('reye')        = 3;                % right eye
pimap{3}('beak')        = 4;
pimap{3}('torso')       = 5;
pimap{3}('neck')        = 6;
pimap{3}('lwing')       = 7;                % left wing
pimap{3}('rwing')       = 8;                % right wing
pimap{3}('lleg')        = 9;                % left leg
pimap{3}('lfoot')       = 10;               % left foot
pimap{3}('rleg')        = 11;               % right leg
pimap{3}('rfoot')       = 12;               % right foot
pimap{3}('tail')        = 13;

% [boat]
% only has silhouette mask

% [bottle]
pimap{5}('cap')         = 1;
pimap{5}('body')        = 2;

% [bus]
pimap{6}('frontside')   = 1;
pimap{6}('leftside')    = 2;
pimap{6}('rightside')   = 3;
pimap{6}('backside')    = 4;
pimap{6}('roofside')    = 5;
pimap{6}('leftmirror')  = 6;
pimap{6}('rightmirror') = 7;
pimap{6}('fliplate')    = 8;                % front license plate
pimap{6}('bliplate')    = 9;                % back license plate
for ii = 1:10
    pimap{6}(sprintf('door_%d',ii)) = 10 + ii;
end
for ii = 1:10
    pimap{6}(sprintf('wheel_%d',ii)) = 20 + ii;
end
for ii = 1:10
    pimap{6}(sprintf('headlight_%d',ii)) = 30 + ii;
end
for ii = 1:20
    pimap{6}(sprintf('window_%d',ii)) = 40 + ii;
end

% [car]
keySet = keys(pimap{6});
valueSet = values(pimap{6});
pimap{7} = containers.Map(keySet, valueSet);         % car has the same set of parts with bus

% [cat]
pimap{8}('head')        = 1;
pimap{8}('leye')        = 2;                % left eye
pimap{8}('reye')        = 3;                % right eye
pimap{8}('lear')        = 4;                % left ear
pimap{8}('rear')        = 5;                % right ear
pimap{8}('nose')        = 6;
pimap{8}('torso')       = 7;
pimap{8}('neck')        = 8;
pimap{8}('lfleg')       = 9;                % left front leg
pimap{8}('lfpa')        = 10;               % left front paw
pimap{8}('rfleg')       = 11;               % right front leg
pimap{8}('rfpa')        = 12;               % right front paw
pimap{8}('lbleg')       = 13;               % left back leg
pimap{8}('lbpa')        = 14;               % left back paw
pimap{8}('rbleg')       = 15;               % right back leg
pimap{8}('rbpa')        = 16;               % right back paw
pimap{8}('tail')        = 17;

% [chair]
% only has sihouette mask

% [cow]
pimap{10}('head')       = 1;
pimap{10}('leye')       = 2;                % left eye
pimap{10}('reye')       = 3;                % right eye
pimap{10}('lear')       = 4;                % left ear
pimap{10}('rear')       = 5;                % right ear
pimap{10}('muzzle')     = 6;
pimap{10}('lhorn')      = 7;                % left horn
pimap{10}('rhorn')      = 8;                % right horn
pimap{10}('torso')      = 9;
pimap{10}('neck')       = 10;
pimap{10}('lfuleg')     = 11;               % left front upper leg
pimap{10}('lflleg')     = 12;               % left front lower leg
pimap{10}('rfuleg')     = 13;               % right front upper leg
pimap{10}('rflleg')     = 14;               % right front lower leg
pimap{10}('lbuleg')     = 15;               % left back upper leg
pimap{10}('lblleg')     = 16;               % left back lower leg
pimap{10}('rbuleg')     = 17;               % right back upper leg
pimap{10}('rblleg')     = 18;               % right back lower leg
pimap{10}('tail')       = 19;

% [diningtable]
% only has silhouette mask

% [dog]
keySet = keys(pimap{8});
valueSet = values(pimap{8});
pimap{12} = containers.Map(keySet, valueSet);         	% dog has the same set of parts with cat,
                                            		% except for the additional
                                            		% muzzle
pimap{12}('muzzle')     = 20;

% [horse]
keySet = keys(pimap{10});
valueSet = values(pimap{10});
pimap{13} = containers.Map(keySet, valueSet);        	% horse has the same set of parts with cow,
                                                        % except it has hoof instead of horn
remove(pimap{13}, {'lhorn', 'rhorn'});
pimap{13}('lfho') = 30;
pimap{13}('rfho') = 31;
pimap{13}('lbho') = 32;
pimap{13}('rbho') = 33;

% [motorbike]
pimap{14}('fwheel')     = 1;
pimap{14}('bwheel')     = 2;
pimap{14}('handlebar')  = 3;
pimap{14}('saddle')     = 4;
for ii = 1:10
    pimap{14}(sprintf('headlight_%d', ii)) = 10 + ii;
end

% [person]
pimap{15}('head')       = 1;
pimap{15}('leye')       = 2;                    % left eye
pimap{15}('reye')       = 3;                    % right eye
pimap{15}('lear')       = 4;                    % left ear
pimap{15}('rear')       = 5;                    % right ear
pimap{15}('lebrow')     = 6;                    % left eyebrow
pimap{15}('rebrow')     = 7;                    % right eyebrow
pimap{15}('nose')       = 8;
pimap{15}('mouth')      = 9;
pimap{15}('hair')       = 10;

pimap{15}('torso')      = 11;
pimap{15}('neck')       = 12;
pimap{15}('llarm')      = 13;                   % left lower arm
pimap{15}('luarm')      = 14;                   % left upper arm
pimap{15}('lhand')      = 15;                   % left hand
pimap{15}('rlarm')      = 16;                   % right lower arm
pimap{15}('ruarm')      = 17;                   % right upper arm
pimap{15}('rhand')      = 18;                   % right hand

pimap{15}('llleg')      = 19;               	% left lower leg
pimap{15}('luleg')      = 20;               	% left upper leg
pimap{15}('lfoot')      = 21;               	% left foot
pimap{15}('rlleg')      = 22;               	% right lower leg
pimap{15}('ruleg')      = 23;               	% right upper leg
pimap{15}('rfoot')      = 24;               	% right foot

% [pottedplant]
pimap{16}('pot')        = 1;
pimap{16}('plant')      = 2;

% [sheep]
keySet = keys(pimap{10});
valueSet = values(pimap{10});
pimap{17} = containers.Map(keySet, valueSet);        % sheep has the same set of parts with cow

% [sofa]
% only has sihouette mask

% [train]
pimap{19}('head')       = 1;
pimap{19}('hfrontside') = 2;                	% head front side
pimap{19}('hleftside')  = 3;                	% head left side
pimap{19}('hrightside') = 4;                	% head right side
pimap{19}('hbackside')  = 5;                 	% head back side
pimap{19}('hroofside')  = 6;                	% head roof side

for ii = 1:10
    pimap{19}(sprintf('headlight_%d',ii)) = 10 + ii;
end

for ii = 1:10
    pimap{19}(sprintf('coach_%d',ii)) = 20 + ii;
end

for ii = 1:10
    pimap{19}(sprintf('cfrontside_%d', ii)) = 30 + ii;   % coach front side
end

for ii = 1:10
    pimap{19}(sprintf('cleftside_%d', ii)) = 40 + ii;   % coach left side
end

for ii = 1:10
    pimap{19}(sprintf('crightside_%d', ii)) = 50 + ii;  % coach right side
end

for ii = 1:10
    pimap{19}(sprintf('cbackside_%d', ii)) = 60 + ii;   % coach back side
end

for ii = 1:10
    pimap{19}(sprintf('croofside_%d', ii)) = 70 + ii;   % coach roof side
end

% [tvmonitor]
pimap{20}('screen')     = 1;
end


function obj = addContact(obj, contact, fric_coef, geometry, load_path)
    % Adds contact constraints for the robot manipulator
    %
    % Parameters:
    %  contact: the contact frame object @type ContactFrame
    %  fric_coef: the friction coefficient @type struct
    %  geometry: the geometry constant of the contact @type struct
    %
    % Optional fields of geometry:
    %  la: the distance from the origin to the rolling edge along
    %  the positive x-axis  @type double
    %  lb: the distance from the origin to the rolling edge along
    %  the negative x-axis  @type double
    %  La: the distance from the origin to the rolling edge along
    %  the negative y-axis  @type double
    %  Lb: the distance from the origin to the rolling edge along
    %  the positive y-axis  @type double
    %  RefFrame: The frame describing the ground contact zero configuration.
    %
    % Optional fields of fric_coef:
    %  mu: the (static) coefficient of friction. @type double
    %  gamma: the coefficient of torsional friction @type double

    assert(isa(contact, 'ContactFrame'),...
        'The contact must be given as an object of ContactFrame class.');

    if nargin < 3
        fric_coef = [];
    end
    % Equivalent to grasp map in Murray Ch 5. Maps the wrench base into the
    % nominal contact reference frame.
    if nargin < 4
        geometry = [];
        if ((~isfield(geometry, 'RefFrame')) && ~isempty(geometry))
            geometry.RefFrame = eye(3);
        end
    end

    if nargin < 5
       load_path = [];
    end

    if isempty(load_path)
        if isempty(geometry) || ~isfield(geometry, 'RefFrame')
            ref = eye(3);
        else
            ref = geometry.RefFrame;
        end
        G = [eye(3), zeros(3,3); zeros(3,3), ref] * contact.WrenchBase;

        % compute the spatial position (cartesian position + Euler angles)
        pos = getCartesianPosition(obj, contact);
        rpy = getRelativeEulerAngles(obj, contact, ref);
        %         rpy = getEulerAngles(obj, contact);

        h = transpose([pos, rpy]); %effectively as transpose
        % extract the contrained elements
        constr =  G' * h;
        % compute the body jacobian
        jac = getBodyJacobian(obj, contact);
        %         if strcmp(contact.Type,'PointContactWithFriction') || strcmp(contact.Type,'PointContactWithoutFriction')
        %             jac_pos = jacobian(pos,obj.States.x); % directly use partial derivatives for position
        %             jac = [jac_pos;jac_rot(4:6,:)];
        %         else
        %             jac = jac_rot;
        %         end
        % extract the contrained elements
        idx = sum(contact.WrenchBase,2);
        constr_jac = jac(find(idx),:); %#ok<FNDSB>

        % label for the holonomic constraint
        label_full = cellfun(@(x)[contact.Name,x],...
            {'PosX','PosY','PosZ','Roll','Pitch','Yaw'},'UniformOutput',false);
        for i=size(contact.WrenchBase,2):-1:1
            label{i} = label_full{find(contact.WrenchBase(:,i))};         %#ok<FNDSB>
        end

        % create a holonomic constraint object
        contact_constr = HolonomicConstraint(obj,...
            constr, contact.Name,...
            'Jacobian',constr_jac,...
            'ConstrLabel',{label},...
            'DerivativeOrder',2);
        % add as a set of holonomic constraints
        obj = addHolonomicConstraint(obj, contact_constr);

    else
        % label for the holonomic constraint
        label_full = cellfun(@(x)[contact.Name,x],...
            {'PosX','PosY','PosZ','Roll','Pitch','Yaw'},'UniformOutput',false);
        for i=size(contact.WrenchBase,2):-1:1
            label{i} = label_full{find(contact.WrenchBase(:,i))};         %#ok<FNDSB>
        end
        % create an empty holonomic constraint object first with correct
        % name
        contact_constr = HolonomicConstraint(obj, ...
            [], contact.Name,...
            'LoadPath',load_path,...
            'ConstrLabel',{label},...
            'DerivativeOrder',2);
        % add as a set of holonomic constraints
        obj = addHolonomicConstraint(obj, contact_constr, load_path);
    end

    % the contact wrench input vector
    f_name = contact_constr.InputName;
    f = obj.Inputs.ConstraintWrench.(f_name);

    % if the friction coefficients are given, enforce friction cone
    % constraints
    if ~isempty(fric_coef)
        % get the friction cone constraint
        [friction_cone, fc_label, auxdata] = getFrictionCone(contact, f, fric_coef);

        % create an unilateral constraint object
        fc_cstr = UnilateralConstraint(obj, friction_cone,...
            ['fc' contact.Name], f_name, ...
            'ConstrLabel',{fc_label(:)'},...
            'AuxData',auxdata);
        % add as a set of unilateral constraitns
        obj = addUnilateralConstraint(obj, fc_cstr);
    end

    % if the contact geometry is given, enforce zero moment point
    % constraints
    if ~isempty(geometry)
        % get the friction cone constraint
        [zmp, zmp_label, auxdata] = getZMPConstraint(contact, f, geometry);

        % create an unilateral constraint object
        if ~isempty(zmp)
            zmp_cstr = UnilateralConstraint(obj, zmp,...
                ['zmp' contact.Name], f_name, ...
                'ConstrLabel',{zmp_label(:)'},...
                'AuxData',auxdata);
            % add as a set of unilateral constraitns
            obj = addUnilateralConstraint(obj, zmp_cstr);
        end
    end
end


function imageWrapper = DMLoadImagesFromMetadataGrouping(metadataGrouping, dicomLibrary, reporting)
    % DMLoadImagesFromMetadataGrouping. Loads metadata from a series of DICOM files
    %
    %     Syntax
    %     ------
    %
    %         image_volume = DMLoadImagesFromMetadataGrouping(metadata_list, reporting)
    %
    %             imageWrapper     a CoreWrapper object containing the image volume
    %
    %             metadataGrouping a DMFileGrouping containing metadata
    %
    %             dicomLibrary     (Optional) An object implementing DMDicomLibraryInterface
    %
    %             reporting       (Optional) A CoreReporting or other implementor of CoreReportingInterface,
    %                             for error and progress reporting. Create a CoreReporting
    %                             with no arguments to hide all reporting. If no
    %                             reporting object is specified then a default
    %                             reporting object with progress dialog is
    %                             created
    %
    %
    %     Licence
    %     -------
    %     Part of DicoMat. https://github.com/tomdoel/dicomat
    %     Author: Tom Doel, 2013.  www.tomdoel.com
    %     Distributed under the BSD 3-Clause license. Please see the file LICENSE for details.
    %

    if nargin < 3
        reporting = CoreReportingDefault;
    end

    if nargin < 2
        dicomLibrary = DMDicomLibrary.getLibrary;
    end

    reporting.ShowProgress('Reading pixel data');
    reporting.UpdateProgressValue(0);

    imageWrapper = CoreWrapper;

    num_slices = length(metadataGrouping.Metadata);

    % Load image slice
    first_image_slice = dicomLibrary.dicomread(metadataGrouping.Metadata{1});
    if isempty(first_image_slice)
        return;
    end

    % Pre-allocate image matrix
    size_i = metadataGrouping.Metadata{1}.Rows;
    size_j = metadataGrouping.Metadata{1}.Columns;
    size_k = num_slices;
    samples_per_pixel = metadataGrouping.Metadata{1}.SamplesPerPixel;

    % Pre-allocate image matrix
    data_type = whos('first_image_slice');
    data_type_class = data_type.class;
    if (strcmp(data_type_class, 'char'))
        reporting.ShowMessage('DMLoadImagesFromMetadataGrouping:SettingDatatypeToInt8', 'Char datatype detected. Setting to int8');
        data_type_class = 'int8';
    end
    imageWrapper.RawImage = zeros([size_i, size_j, size_k, samples_per_pixel], data_type_class);
    imageWrapper.RawImage(:, :, 1, :) = first_image_slice;

    for file_index = 2 : num_slices
        imageWrapper.RawImage(:, :, file_index, :) = dicomLibrary.dicomread(metadataGrouping.Metadata{file_index});
        reporting.UpdateProgressValue(round(100*(file_index)/num_slices));
    end

    reporting.CompleteProgress;
end


% PURPOSE: sets ERPLAB Foreground Color
%
% FORMAT
%
% Fcolorerplab(c)
%
% INPUT
%
% c      - color. e.g. [0 1 0] -> green
%
% *** This function is part of ERPLAB Toolbox ***
% Author: Javier Lopez-Calderon
% Center for Mind and Brain
% University of California, Davis,
% Davis, CA
% 2011

function Fcolorerplab(c)
if nargin<1
        c = uisetcolor([0.83 0.82 0.79],'ERPLAB Foreground Color') ;
end
erpworkingmemory('ColorF', c);


classdef GeometricRefFrameSet < matlab.mixin.SetGet
    %GeometricRefFrameSet Summary of this class goes here
    %   Detailed explanation goes here

    properties
        refFrames(1,:) AbstractGeometricRefFrame = AbstractGeometricRefFrame.empty(1,0);

        lvdData LvdData
    end

    methods
        function obj = GeometricRefFrameSet(lvdData)
            obj.lvdData = lvdData;
        end

        function addRefFrame(obj, refFrame)
            obj.refFrames(end+1) = refFrame;
        end

        function removeRefFrame(obj, refFrame)
            obj.refFrames([obj.refFrames] == refFrame) = [];
        end

        function [listBoxStr, refFrames] = getListboxStr(obj)
            listBoxStr = {};

            for(i=1:length(obj.refFrames))
                listBoxStr{end+1} = obj.refFrames(i).getListboxStr(); %#ok<AGROW>
            end

            refFrames = obj.refFrames;
        end

        function indCoordSyses = getRefFramesForInds(obj, inds)
            indCoordSyses = obj.refFrames(inds);
        end

        function inds = getIndsForRefFrames(obj, indRefFrames)
            inds = find(ismember(obj.refFrames, indRefFrames));
        end

        function indRefFrame = getRefFrameAtInd(obj, ind)
            if(ind > 0 && ind <= length(obj.refFrames))
                indRefFrame = obj.refFrames(ind);
            else
                indRefFrame = AbstractGeometricRefFrame.empty(1,0);
            end
        end

        function numRefFrames = getNumRefFrames(obj)
            numRefFrames = length(obj.refFrames);
        end

        function tf = usesGroundObj(obj, groundObj)
            tf = false;
            for(i=1:length(obj.refFrames))
                tf = tf || obj.refFrames(i).usesGroundObj(groundObj);
            end
        end

        function tf = usesGeometricPoint(obj, point)
            tf = false;
            for(i=1:length(obj.refFrames))
                tf = tf || obj.refFrames(i).usesGeometricPoint(point);
            end
        end

        function tf = usesGeometricVector(obj, vector)
            tf = false;
            for(i=1:length(obj.refFrames))
                tf = tf || obj.refFrames(i).usesGeometricVector(vector);
            end
        end

        function tf = usesGeometricCoordSys(obj, coordSys)
            tf = false;
            for(i=1:length(obj.refFrames))
                tf = tf || obj.refFrames(i).usesGeometricCoordSys(coordSys);
            end
        end

        function tf = usesGeometricRefFrame(obj, refFrame)
            tf = false;
            for(i=1:length(obj.refFrames))
                tf = tf || obj.refFrames(i).usesGeometricRefFrame(refFrame);
            end
        end

        function tf = usesGeometricAngle(obj, angle)
            tf = false;
            for(i=1:length(obj.refFrames))
                tf = tf || obj.refFrames(i).usesGeometricAngle(angle);
            end
        end

        function tf = usesGeometricPlane(obj, plane)
            tf = false;
            for(i=1:length(obj.refFrames))
                tf = tf || obj.refFrames(i).usesGeometricPlane(plane);
            end
        end
    end
end


%% Run a PREP report on an individual dataset -- good for producing figures.

%% Specific examples
% dataFile = 'N:\ARLAnalysis\VEP\VEPRobust_1Hz_Post_Median_Unfiltered\vep_03.set';
% summaryFolder = 'D:\\Temp';
% basename = 'vep';
% publishOn = false;

% dataFile = 'N:\ARLAnalysis\VEP\VEPRobust_1Hz_Post_Median_Unfiltered\vep_15.set';
% summaryFolder = 'D:\\Temp';
% basename = 'vep';
% publishOn = false;

% dataFile = 'N:\ARLAnalysis\VEP\VEPRobust_1Hz_Post_Median_Unfiltered\vep_02.set';
% summaryFolder = 'D:\\Temp';
% basename = 'vep';
% publishOn = false;

% dataFile = 'N:\\ARLAnalysis\\NCTU\NCTURobust_1Hz_New\\session\\67\\eeg_studyLevel2_NCTU_Lane-Keeping_Task_session_67_subject_1_task_motionless_s54_081209n_recording_1.set';
% summaryFolder = 'D:\\Temp';
% basename = 'nctu-lk';
% publishOn = false;

% dataFile = 'N:\ARLAnalysis\NCTU\NCTURobust_1Hz_New\session\79\eeg_studyLevel2_NCTU_Lane-Keeping_Task_session_79_subject_1_task_motion_s77_120504m_recording_1.set';
% summaryFolder = 'D:\\Temp';
% basename = 'nctu-lk';
% publishOn = false;

% dataFile = 'N:\\ARLAnalysis\\NCTU\\NCTURobust_1Hz_New\\session\\80\\eeg_studyLevel2_NCTU_Lane-Keeping_Task_session_80_subject_1_task_motion_s80_120522m_recording_1.set';
% summaryFolder = 'D:\\Temp';
% basename = 'nctu-lk';
% publishOn = false;

% dataFile = 'N:\ARLAnalysis\Shooter\Shooter_Robust_1Hz_Unfiltered\\ARIT_0131_CNT_events.set';
% summaryFolder = 'D:\\Temp';
% basename = 'shooter-1';
% publishOn = false;

dataFile = 'N:\\ARLAnalysis\\RSVP_HeadIT\\RSVP_Robust_1Hz\\rsvp_01.set';
summaryFolder = 'D:\\Temp';
basename = 'rsvp-1';
publishOn = true;

%% Setup up the summary folder
summaryReportName = [basename '_summary.html'];
sessionFolder = '.';
reportSummary = [summaryFolder filesep summaryReportName];
if exist(reportSummary, 'file')
   delete(reportSummary);
end

%% Run the pipeline
[pathstr, fname, ext] = fileparts(dataFile);
sessionReportName = [fname '.pdf'];
load(dataFile, '-mat');
tempReportLocation = [summaryFolder filesep sessionFolder ...
    filesep 'prepPipelineReport.pdf'];
actualReportLocation = [summaryFolder filesep sessionFolder ...
    filesep sessionReportName];
summaryReportLocation = [summaryFolder filesep summaryReportName];
summaryFile = fopen(summaryReportLocation, 'a+', 'n', 'UTF-8');
relativeReportLocation = [sessionFolder filesep sessionReportName];
consoleFID = 1;
publishPrepReport(EEG, reportSummary, actualReportLocation, 1, publishOn);

%% Calculate the mean correlations
meanBefore = EEG.etc.noiseDetection.reference.noisyStatisticsOriginal.maximumCorrelations;
meanBeforeInterp = EEG.etc.noiseDetection.reference.noisyStatisticsBeforeInterpolation.maximumCorrelations;
meanAfter = EEG.etc.noiseDetection.reference.noisyStatistics.maximumCorrelations;
fprintf('Mean before: %g\n', mean(meanBefore(:)));
fprintf('mean before Interpolation: %g\n', mean(meanBeforeInterp(:)));
fprintf('Mean after: %g\n', mean(meanAfter(:)));


%   CLASS Go_Settings
% =========================================================================
%
% DESCRIPTION
%   Class to store all the Processing parameters
%
% EXAMPLE
%   state = Go_Settings();
%
% FOR A LIST OF CONSTANTs and METHODS use doc Go_Settings

%--------------------------------------------------------------------------
%               ___ ___ ___
%     __ _ ___ / __| _ | __|
%    / _` / _ \ (_ |  _|__ \
%    \__, \___/\___|_| |___/
%    |___/                    v 1.0RC1
%
%--------------------------------------------------------------------------
%  Copyright (C) 2021 Geomatics Research & Development srl (GReD)
%  Written by:        Andrea Gatti
%  Contributors:      Andrea Gatti, Giulio Taliaferro, ...
%  A list of all the historical goGPS contributors is in CREDITS.nfo
%--------------------------------------------------------------------------
%
%    This program is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    This program is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with this program.  If not, see <http://www.gnu.org/licenses/>.
%
%--------------------------------------------------------------------------
% 01100111 01101111 01000111 01010000 01010011
%--------------------------------------------------------------------------

classdef Go_Settings < Settings_Interface

    properties (Constant, Access = 'protected')
    end

    % Real constant
    properties(Constant, Access = 'private')
        CUR_INI = 'goGPS_settings.ini';

        LOG_DEFAULT_MODE = 1; % 0 text mode, 1 graphic mode
        LOG_COLOR_MODE = 1;

        GUI_DEFAULT_MODE = 'dark';
        GUI_DEFAULT_EXPORT_MODE = 'light';

        FLAG_EXPORT_TRANSPARENT = true;
    end

    properties (Constant, Access = 'public')
    end

    properties (SetAccess = protected, GetAccess = protected)
        % Location of the current ini file
        log_default_mode = Go_Settings.LOG_DEFAULT_MODE; % 0 text mode, 1 graphic mode
        log_color_mode = Go_Settings.LOG_COLOR_MODE;

        gui_default_mode = Go_Settings.GUI_DEFAULT_MODE;
        gui_default_export_mode = Go_Settings.GUI_DEFAULT_EXPORT_MODE;

        flag_export_transparent = Go_Settings.FLAG_EXPORT_TRANSPARENT;
    end

    properties (SetAccess = public, GetAccess = public)
        cur_ini = Go_Settings.CUR_INI;
        creation_time = GPS_Time(now);
    end

    % =========================================================================
    %%  INIT
    % =========================================================================
    methods(Static,Access = private)
        % Guard the constructor against external invocation.  We only want
        % to allow a single instance of this class.  See description in
        % Singleton superclass.

        function this = Go_Settings(ini_settings_file)
            % Creator
            %
            % SYNTAX
            %   s_obj = Go_Settings(<ini_settings_file>, <prj_home>);

            log = Core.getLogger();
            log.addMarkedMessage('Preparing goGPS settings...');
            log.newLine();

            if nargin == 1 && not(isempty(ini_settings_file))
                this.cur_ini = ini_settings_file;
            end

            if (exist(this.cur_ini, 'file') == 2)
                this.import(this.cur_ini);
            else log.addMarkedMessage('Using default settings');
                log.newLine();
            end
        end
    end

    methods (Static)
        function this = getInstance(ini_settings_file)
            % Concrete implementation.  See Singleton superclass.
            persistent unique_instance_gocfg__
            if isempty(unique_instance_gocfg__)
                if nargin == 1
                    this = Go_Settings(ini_settings_file);
                else
                    this = Go_Settings();
                end
                unique_instance_gocfg__ = this;
            else
                this = unique_instance_gocfg__;
                if nargin == 1
                    this.import(ini_settings_file)
                end
            end
        end
    end

    % =========================================================================
    %%  INTERFACE REQUIREMENTS
    % =========================================================================
    methods (Access = 'public')

        function releoad(this)
            this.import();
        end

        function importIniFile(this, file_name)
            this.import(file_name);
        end

        function import(this, file_name)
            % This function import processing settings from another setting object or ini file
            %
            % SYNTAX
            %   s_obj.import(state)

            if nargin < 2 || isempty(file_name)
                file_name = this.cur_ini;
            end

            ini = Ini_Manager(file_name);

            try this.log_default_mode = ini.getData('LOG', 'default_mode'); catch, keyboard, end
            try this.log_color_mode = ini.getData('LOG', 'color_mode'); catch, keyboard, end

            try this.gui_default_mode = ini.getData('GUI', 'default_mode'); catch, keyboard, end
            try this.gui_default_export_mode = ini.getData('GUI', 'default_export_mode'); catch, keyboard, end

            try this.flag_export_transparent = ini.getData('EXPORT', 'flag_export_transparent'); catch, keyboard, end

            this.check(); % check after import
        end

        function str_cell = export(this, str_cell)
            % Conversion to string ini format of the minimal information needed to reconstruct the this
            %
            % SYNTAX
            %   s_obj.export(str_cell)

            log.addMarkedMessage('Export of goGPS settings - to do...');
        end

        function toString(this)
            ini = Ini_Manager(this.cur_ini);
            ini.showData;
        end
    end

    %%  CHECKING FUNCTIONS - to be kept here to access private parameters
    % =========================================================================
    methods (Access = 'protected')
        function checkLogicalField(this, field_name)
            % Check if a logical field of the object is a valid logical number
            % To make the function works it is needed to have defined the default
            % value of the field as a constant with same name but upper case
            %
            % SYNTAX
            %   this.checkLogicalField(string_field_name);
            this.(field_name) = this.checkLogical(field_name, this.(field_name), this.(upper(field_name)));
        end

        function checkCellStringField(this, field_name, empty_is_valid, check_existence)
            % Check if a string field of the object is a valid string
            % To make the function works it is needed to have defined the default
            % value of the field as a constant with same name but upper case
            %
            % SYNTAX
            %   this.Field(string_field_name, <empty_is_valid == false>, <check_existence == false>);
            switch nargin
                case 2, this.(field_name) = this.checkCellString(field_name, this.(field_name), this.(upper(field_name)));
                case 3, this.(field_name) = this.checkCellString(field_name, this.(field_name), this.(upper(field_name)), empty_is_valid);
                case 4, this.(field_name) = this.checkCellString(field_name, this.(field_name), this.(upper(field_name)), empty_is_valid, check_existence);
                otherwise, error('Settings checkCellStringField called with the wrong number of parameters');
            end
        end

        function is_existing = checkStringField(this, field_name, empty_is_valid, check_existence)
            % Check if a string field of the object is a valid string
            % To make the function works it is needed to have defined the default
            % value of the field as a constant with same name but upper case
            %
            % check_existence 0: do not check
            %                 1: check and do nothing
            %                 2: check and                                try                                                                to correct
            %
            % SYNTAX
            %
            %   this.checkStringField(string_field_name, <empty_is_valid == false>, <check_existence == false>);
            switch nargin
                case 2, [this.(field_name), is_existing] = this.checkString(field_name, this.(field_name), this.(upper(field_name)));
                case 3, [this.(field_name), is_existing] = this.checkString(field_name, this.(field_name), this.(upper(field_name)), empty_is_valid);
                case 4, [this.(field_name), is_existing] = this.checkString(field_name, this.(field_name), this.(upper(field_name)), empty_is_valid, check_existence);
                otherwise, error('Settings checkStringField called with the wrong number of parameters');
            end
        end

        function is_existing = checkPathField(this, field_name, empty_is_valid, check_existence)
            % Check if a string path field of the object is a valid path
            % To make the function works it is needed to have defined the default
            % value of the field as a constant with same name but upper case
            %
            % SYNTAX
            %   this.checkPathField(string_field_name, <empty_is_valid == false>, <check_existence == false>);
            fnp = File_Name_Processor();
            tmp_path = fnp.checkPath(this.(field_name), this.getHomeDir);
            tmp_default_path = fnp.getFullDirPath(fnp.checkPath(this.(upper(field_name)), this.getHomeDir), this.getHomeDir);
            if ~isempty(tmp_path) || empty_is_valid
                this.(field_name) = fnp.getFullDirPath(tmp_path, this.prj_home, [], fnp.getFullDirPath(tmp_default_path));
                switch nargin
                    case 2, [this.(field_name), is_existing] = this.checkString(field_name, tmp_path, tmp_default_path);
                    case 3, [this.(field_name), is_existing] = this.checkString(field_name, tmp_path, tmp_default_path, empty_is_valid);
                    case 4, [this.(field_name), is_existing] = this.checkString(field_name, tmp_path, tmp_default_path, empty_is_valid, check_existence);
                    otherwise, error('Settings checkStringField called with the wrong number of parameters');
                end
                this.(field_name) = fnp.getFullDirPath(this.(field_name), this.getHomeDir);
            else
                this.(field_name) = tmp_default_path;
            end
        end

        function checkNumericField(this, field_name, limits, valid_val)
            % Check if a numeric field of the object is valid
            % To make the function works it is needed to have defined the default
            % value of the field as a constant with same name but upper case
            %
            % SYNTAX
            %   this.checkNumericField(string_field_name, <limits>, <valid_values>);
            switch nargin
                case 2, this.(field_name) = this.checkNumber(field_name, this.(field_name), this.(upper(field_name)));
                case 3, this.(field_name) = this.checkNumber(field_name, this.(field_name), this.(upper(field_name)), limits);
                case 4, this.(field_name) = this.checkNumber(field_name, this.(field_name), this.(upper(field_name)), limits, valid_val);
                otherwise, error('Settings checkNumericField called with the wrong number of parameters');
            end
        end
    end

    % =========================================================================
    %%  TEST PARAMETERS VALIDITY
    % =========================================================================

    methods (Access = 'public')
        function check(this)
            % Check the validity of the fields
            %
            % SYNTAX
            %   this.check();

            this.checkNumericField('log_default_mode', [0 1]);
            this.checkLogicalField('log_color_mode');
            this.checkLogicalField('flag_export_transparent');

            if isdeployed
                this.log_color_mode = false;
            end
        end
    end

    % =========================================================================
    %%  GETTERS
    % =========================================================================
    methods
        function out = getLogMode(this)
            out = this.log_default_mode;
        end

        function out = isLogColorMode(this)
            out = logical(this.log_color_mode);
        end

        function out = getGUIMode(this)
            out = this.gui_default_mode;
        end

        function out = getGUIModeExport(this)
            out = this.gui_default_export_mode;
        end

        function out = isExportTransparent(this)
            out = logical(this.flag_export_transparent);
        end
    end

    %%  SETTERS
    % =========================================================================
    methods
        function out = setLogMode(this, val)
            this.log_default_mode = val;
        end

        function out = setLogColorMode(this, val)
            this.log_color_mode = logical(val);
        end

        function out = setGUIMode(this, val)
            this.gui_default_mode = val;
        end

        function out = setGUIModeExport(this, val)
            this.gui_default_export_mode = val;
        end

        function out = setExportTransparent(this, val)
            this.flag_export_transparent = logical(val);
        end
    end

end


function definput=arg_thresh(definput)

  definput.flags.iofun={'hard','soft','wiener'};
  definput.flags.outclass={'full','sparse'};


function varargout = pulldown(varargin)

%PULLDOWN Standalone pulldown menus with title.
%   [A, B, C, ...] = PULLDOWN(TITLE, X, Y, Z, ...) will invoke simple
%   pulldown menus with title TITLE, and lists of choices in cell arrays,
%   X, Y, Z, ..., and provides the choice index for list X in A, the choice
%   index for list Y in B, etc.
%
%   CHOICES = PULLDOWN(TITLE, X, Y, Z, ...) does the same thing, except
%   CHOICES is a numerical array containing all the individual choice
%   indices from X, Y, Z, etc.
%
%   [...] = PULLDOWN(..., V) does the same thing, but will initialize the
%   pulldown menus to the values in vector V.  V may have fewer or more
%   choices than the number of pulldown menus.
%
%   Examples
%   --------
%       [letter, number] = pulldown('Test', {'A', 'B', 'C'}, num2cell(1:5))
%
%       choices = pulldown('Mission', {'117', '116', '115', '121', '114'})
%
%       % initialize to choices 'B' and 4
%       choices = pulldown('Test', {'A', 'B', 'C'}, num2cell(1:5), [2 4])
%
%   See also UICONTROL, UIWAIT, GET, SET, VARARGIN.
%
%   Version 1.2 Kevin Crosby

% DATE      VER  NAME          DESCRIPTION
% 01-23-07  1.0  K. Crosby     First Release.  Promoted from sub-function.
% 07-11-07  1.1  K. Crosby     Added initial settings for pulldown.
% 07-23-07  1.2  K. Crosby     Changed to modal.

% Contact: Kevin.L.Crosby@gmail.com

m = zeros(1, nargin-1); % maximums
n = 0; % number of pulldowns
while n < nargin-1 && iscell(varargin{n+2})
  arg = n + 2;
  n = n + 1;
  m(n) = length(varargin{arg});
  if ~iscellstr(varargin{arg})
    varargin{arg} = cellstr(num2str(cell2mat(varargin{arg})'))';
  end % if ~iscellstr(varargin{arg})
end % while n
m(n+1:end) = [];

u = ones(1, n); % defaults
k = nargin - n - 1; % number of default overrides
switch k
  case 0
    % do nothing
  case 1
    l = length(varargin{end});
    u(1:min(n, l)) = varargin{end}(1:min(n, l));
  otherwise
    u(1:min(n, k)) = [varargin{n+2:end}];
end % switch k

% ensure within bounds
u = round(u); % ensure integers
u(u < 1) = 1; % force minimum to be 1
u(u > m) = m(u > m); % force maximum to be m

h = 1 / (n+1);
x = .15;      % figure width
y = .02*(n+1); % figure height

popup = zeros(1, n);

fig = figure('MenuBar', 'none', 'Resize', 'off', 'CloseRequestFcn', '', ...
  'Name', varargin{1}, 'NumberTitle', 'off', 'ToolBar', 'none', ...
  'WindowStyle', 'modal', ...
  'Units', 'normalized', 'Position', [.5-x/2 .5-y/2 x y]);
for p = 1:n
  popup(p) = uicontrol('Parent', fig, 'Style', 'popupmenu', ...
    'Units', 'normalized', 'Position', [0 (n+1-p)*h 1 h], ...
    'String', varargin{p+1}, 'Value', u(p), 'Callback', {@callbackfcn, p});
end
uicontrol('Parent', fig, 'Style', 'pushbutton', ...
  'Units', 'normalized', 'Position', [0 0 1 h], ...
  'String', 'OK', 'Callback', 'closereq');
set(0, 'UserData', u); % default choices
uiwait(fig);
choices = get(0, 'UserData');

% the following was added while promoting to a function
switch nargout
  case {0, 1}
    varargout = num2cell(choices, 2);
  otherwise
    varargout = num2cell(choices);
end % switch nargout

% call back function
function callbackfcn(src, eventdata, p) % src, eventdata, varargin ...

if isempty(eventdata) || ~isstruct(eventdata)
  eventdata.Key = '';
end % isempty(eventdata) || ~isstruct(eventdata)

u = get(0, 'UserData');
u(p) = get(src, 'Value');
set(0, 'UserData', u);


function args = hlp_struct2varargin(struc,varargin)
% Convert a struct into a sequence of name-value pairs; inverse to hlp_varargin2struct.
% Args = hlp_struct2varargin(Struct,Options...)
%
% In:
%   Struct: a 1x1 structure array
%   Options: optional name-value pairs;
%            'suppress': suppress the output names listed in the following cell array
%            'rewrite' : rewrite some names in the struct, if present (executed after suppress and before restrict)
%                        specified as a cell array of {oldname,newname,oldname,newname, ....}
%            'restrict': restrict the output names to those listed in the following cell array
%
% Out:
%  Args: a list of name-value pairs
%
% Examples:
%   % in the given cell array of name-value pairs, remove occurrences of myarg1 and myarg2 parameters
%   hlp_struct2varargin(options,'suppress',{'myarg1','myarg2'});
%
%   % rewrite any occurrences of a 'channel' parameter into 'chns', and likewise for samplerate
%   hlp_struct2varargin(options,'rewrite',{'channels','chns','samplerate','srate'});
%
%   % restrict the given name-value pairs to those whose name is either test, arg1, or arg2.
%   hlp_struct2varargin(options,'restrict',{'test','arg1','arg2'});
%
% See also:
%   hlp_varargin2struct
%
%                               Christian Kothe, Swartz Center for Computational Neuroscience, UCSD
%                               2010-03-28

% Copyright (C) Christian Kothe, SCCN, 2010, christian@sccn.ucsd.edu
%
% This program is free software; you can redistribute it and/or modify it under the terms of the GNU
% General Public License as published by the Free Software Foundation; either version 2 of the
% License, or (at your option) any later version.
%
% This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
% even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
% General Public License for more details.
%
% You should have received a copy of the GNU General Public License along with this program; if not,
% write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
% USA

if isempty(struc)
    args = {};
    return;
end

opts = hlp_varargin2struct(varargin,'restrict',[],'suppress',[],'rewrite',[]);

fields = fieldnames(struc)';
data = struct2cell(struc)';
% suppress some of the original field names
if ~isempty(opts.suppress)
    [fields,I] = setdiff(fields,opts.suppress);
    data = data(I);
end
% rewrite some of the original field names into new field names
for c=1:2:length(opts.rewrite)
    fields(strcmp(fields,opts.rewrite{c})) = opts.rewrite(c+1); end
% restrict to a subset of old/new field names
if ~isempty(opts.restrict)
    [fields,I,J] = intersect(fields,opts.restrict);  %#ok<NASGU>
    data = data(I);
end

args = vertcat(fields,data);
args = args(:)';


function value = p08_i4 ( action, name, value )

%*****************************************************************************80
%
%% P08_I4 sets or gets I4 parameters for problem 08.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    18 March 2007
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, string ACTION,
%    'D' to set a parameter to its default value.
%    'S' to set a parameter,
%    'G' to get a parameter,
%    'I' to increment a parameter.
%
%    Input, string NAME, the name of the variable.
%    '#' is the number of calls to the integrand routine.
%
%    Input/output, integer VALUE.
%    * If ACTION = 'I', then VALUE is an input quantity, and is the
%      new value to be added to NAME.
%    * If ACTION = 'S', then VALUE is an input quantity, and is the
%      new value to be assigned to NAME.
%    * If ACTION = 'G', then VALUE is an output quantity, and is the
%      current value of NAME.
%
  persistent calls;

  if ( action == 'D' | action == 'd' )

    if ( name == '#' | name == '*' )
      calls = 0;
    end

  elseif ( action == 'G' | action == 'g' )

    if ( name == '#' )
      value = calls;
    else
      fprintf ( 1, '\n' );
      fprintf ( 1, 'P08_I4 - Fatal error!\n' );
      fprintf ( 1, '  Unrecognized name = "%s".\n', name );
      error ( 'P08_I4 - Fatal error!' );
    end

  elseif ( action == 'I' | action == 'i' )

    if ( name == '#' )
      calls = calls + value;
    else
      fprintf ( 1, '\n' );
      fprintf ( 1, 'P08_I4 - Fatal error!\n' );
      fprintf ( 1, '  Unrecognized name = "%s".\n', name );
      error ( 'P08_I4 - Fatal error!' );
    end

  elseif ( action == 'S' | action == 's' )

    if ( name == '#' )
      calls = value;
    else
      fprintf ( 1, '\n' );
      fprintf ( 1, 'P08_I4 - Fatal error!\n' );
      fprintf ( 1, '  Unrecognized name = "%s".\n', name );
      error ( 'P08_I4 - Fatal error!' );
    end

  else

    fprintf ( 1, '\n' );
    fprintf ( 1, 'P08_I4 - Fatal error!\n' );
    fprintf ( 1, '  Unrecognized action = "%s".\n', action );
    error ( 'P08_I4 - Fatal error!' );

  end

  return
end


function RelFiles = buildReleaseZIP( WorkingDir, zipFileName, MainFile, RelFiles, RuntimeDependencies )
% Creates a ZIP file of all release dependencies for a main file
%
% Syntax:	BUILDRELEASEZIP( WORKINGDIR, ZIPFILENAME, MAINFILE, RUNTIMEDEPENDENCIES )
%
% Inputs:
% 	WorkingDir          - The working directory of the project to release.
% 	zipFileName         - The name the final ZIP file.
% 	MainFile            - The path to the file for which to determine the
%                         dependencies and consequently build the release
%                         ZIP.
%   RelFiles            - A list of files to release (the main file's
%                         dependencies are added to this list).
% 	RuntimeDependencies - If true, dependencies are found during a profiled
%                         runtime.
%
% Outputs:
% 	RelFiles            - The final list of released files.
%
% Example:
%     wrkdir = 'C:\myProject\';
%     zipFname = 'myProject_release';
%     mainFile = 'C:\myProject\examples\fullyfledgedexample.m';
%     buildReleaseZIP( wrkdir, zipFname, mainFile, {}, true )
%
% See also: requiredFilesAndProducts, zip

% Author: Jacob Donley
% University of Wollongong
% Email: jrd089@uowmail.edu.au
% Copyright: Jacob Donley 2017
% Date: 09 August 2017
% Version: 1.1 (09 August 2017)
% Version: 1.0 (07 May 2017)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Get all dependencies of the main file
flist = matlab.codetools.requiredFilesAndProducts( MainFile );                      % Full dependency file list

%% Get dependencies of the main file during runtime (includes anonymous functions and dynamic function handles)
% The MATLAB profiler is used here and can take quite some time if the main file is slow to run
if RuntimeDependencies
    profile on;                                                                        % Turn the profiler on
    [p,f]=fileparts(MainFile);
    func = strrep(strrep([p filesep f],filesep,'.'),'+','');                           % Deal with class folders and create function handle
    evalin('caller',[func ';']);                                                       % Assume file runs without arguments and run
    p = profile('info');                                                               % Stop the profiler after execution and get the profiler information
    flist = [flist {p.FunctionTable.FileName}];                                        % Append the runtime functions to the list
    dbs=dbstack;                                                                       % Get history of called functions for this runtime
    flist(contains(flist,{dbs.name})) = [];                                            % Remove references to this function and parents
end

%% Only keep files in working directory
RelFiles = unique([RelFiles(:)' flist(contains(flist,WorkingDir))]);                    % Release files

%% Copy to temp directory so zipping retains folder structure
newdirs = unique(cellfun(@fileparts, strrep(RelFiles, WorkingDir, ''),'un',0));     % Determine structure
tmpdir = [tempname filesep];                                                        % Get temp directory
cellfun(@(a) mkdir( [tmpdir a] ), newdirs);                                         % Create temp folder structure
cellfun(@(a,b) copyfile(a,[tmpdir b]), RelFiles, strrep(RelFiles, WorkingDir, '')); % Copy

%% Zip the entire folder
zip(zipFileName, tmpdir, WorkingDir);

%% Clean up
rmdir(tmpdir,'s');                                                                  % Delete all the temporarily copied files

end