%CellState
%
% Author: Jonathan Karr, jkarr@stanford.edu
% Affiliation: Covert Lab, Department of Bioengineering, Stanford University
% Last Updated: 9/12/2010
classdef CellState
    %printing
    methods (Static)
        function run(sim, fileName)
            import edu.stanford.covert.cell.sim.analysis.CellState;
            import edu.stanford.covert.cell.sim.util.PlotUtil;
            import edu.stanford.covert.cell.sim.util.PrintUtil;

            %excel file
            [content, colLabels, indentation] = CellState.state(sim);
            if nargin == 1
                PrintUtil.printToStdIO(content, colLabels, struct('indentation', indentation));
            else
                PrintUtil.printToFile(content, colLabels, [fileName '.xls'], 'State', struct('indentation', indentation));
            end

            [content, colLabels, indentation] = CellState.processes(sim);
            if nargin == 1
                PrintUtil.printToStdIO(content, colLabels, struct('indentation', indentation));
            else
                PrintUtil.printToFile(content, colLabels, [fileName '.xls'], 'Process', struct('indentation', indentation));
            end

            % plots
            if nargin == 1
                CellState.plotStateOnChromosome(sim, PlotUtil.newAxesHandle());
            else
                [axesHandle, figHandle] = PlotUtil.newAxesHandle();

                cla(axesHandle);
                CellState.plotStateOnChromosome(sim, axesHandle);
                saveas(figHandle, [fileName '-OnChromosome.pdf']);

                close(figHandle);
            end
        end

        function [content, colLabels, indentation] = state(sim)
            %import classes
            import edu.stanford.covert.util.ConstantUtil;
            import edu.stanford.covert.cell.sim.Simulation;

            content = cell(0, 4);
            colLabels = {'Quantity', 'Value', 'Units'};

            %% time
            time = sim.state('Time');
            content = [content;{
                0 'Time' time.values(1)/ConstantUtil.secondsPerHour 'h';
                }];

            %% mass
            mass = sim.state('Mass');
            cellDryMass = sum(mass.cellDry);
            content = [content;{
                0 'Mass' sum(mass.cell) 'g';
                    1 'Dry' sum(mass.cellDry) 'g';
                    1 'Metabolite' sum(mass.metaboliteWt) 'g';
                        2 'Percent Dry' sum(mass.metaboliteWt) / cellDryMass * 100 '%';
                    1 'DNA' sum(mass.dnaWt) 'g';
                        2 'Percent Dry' sum(mass.dnaWt) / cellDryMass * 100 '%';
                    1 'RNA' sum(mass.rnaWt) 'g';
                        2 'Percent Dry' sum(mass.rnaWt) / cellDryMass * 100 '%';
                    1 'Protein' sum(mass.proteinWt) 'g';
                        2 'Percent Dry' sum(mass.proteinWt) / cellDryMass * 100 '%';
                    1 'No. cells' sum(mass.cellDry)/mass.cellInitialDryWeight 'cells';
                }];

            %% growth
            s = sim.state('MetabolicReaction');
            time = sim.state('Time');
            content = [content;{
                0 'Growth' s.growth 'cells/s';
                    1 'Relative to expected rate' s.growth / (1 / time.cellCycleLength * log(2) * exp(time.values / time.cellCycleLength * log(2))) [];
                    1 'Doubling Time', s.doublingTime / ConstantUtil.secondsPerHour 'h';
                }];

            %% shape
            s = sim.state('Geometry');
            content = [content;{
                0 'Shape' 'Cyclinder with hemispherical caps' []
                    1 'Width' s.width 'm'
                    1 'Pinched Diameter' s.pinchedDiameter 'm'
                    1 'Cylindrical length' s.cylindricalLength 'm'
                    1 'Total length' s.totalLength 'm'
                    1 'Pinched circumference' s.pinchedCircumference 'm'
                    1 'Volume' s.volume 'L'
                    1 'Surface area' s.surfaceArea' 'm^2'
                    1 'Pinched' s.pinched 'true/false'
                }];

            %% FtsZ ring
            s = sim.state('FtsZRing');
            content = [content;{
                0 'FtsZ ring' [] []
                    1 'No. edges' s.numEdges []
                    1 'No. edges bound by 1 straight FtsZ 9-mer' s.numEdgesOneStraight []
                    1 'No. edges bound by 2 straight FtsZ 9-mers' s.numEdgesTwoStraight []
                    1 'No. edges bound by 2 bent FtsZ 9-mers' s.numEdgesTwoBent []
                    1 'No. edges bound by 1 bent FtsZ 9-mer' s.numResidualBent []
                }];

            %% stimuli
            content = [content;{
                0 'Stimuli' '' []
                }];
            stim = sim.state('Stimulus');
            for i = 1:numel(stim.wholeCellModelIDs)
                value = stim.values(i, stim.values(i, :) ~= 0);
                if numel(value) == 0
                    value = 0;
                elseif numel(value) > 1
                    value = mean(value);
                end
                content = [content;{
                    1 stim.wholeCellModelIDs{i} value []
                    }]; %#ok<AGROW>
            end

            %% metabolites
            c = sim.state('Chromosome');
            s = sim.state('Metabolite');

            freedNTPs = s.counts(s.dntpIndexs, sim.compartment.cytosolIndexs);
            freeNTPs = s.counts(s.ntpIndexs, sim.compartment.cytosolIndexs);
            freeNDPs = s.counts(s.ndpIndexs, sim.compartment.cytosolIndexs);
            freeNMPs = s.counts(s.nmpIndexs, sim.compartment.cytosolIndexs);
            freeAAs = s.counts(s.aminoAcidIndexs, sim.compartment.cytosolIndexs);

            [mindNTPVal, mindNTPIdx] = min(freedNTPs);
            [minNTPVal, minNTPIdx] = min(freeNTPs);
            [minNDPVal, minNDPIdx] = min(freeNDPs);
            [minNMPVal, minNMPIdx] = min(freeNMPs);
            [minAAVal, minAAIdx] = min(freeAAs);

            freeNTPMass = freeNTPs' * s.molecularWeights(s.ntpIndexs) / ConstantUtil.nAvogadro;
            freeAAMass = freeAAs' * s.molecularWeights(s.aminoAcidIndexs) / ConstantUtil.nAvogadro;

            content = [content;{
                0 'Metabolites' [] []
                    1 'Free' sum(s.counts(:)) 'molecules'
                    1 'dNTPs' [] []
                        2 'Free' sum(freedNTPs) 'molecules'
                        2 'Min free' s.wholeCellModelIDs{s.dntpIndexs(mindNTPIdx)} []
                        2 'Min free' mindNTPVal 'molecules'
                        2 'Incorporated into DNA' (1 - sum(freedNTPs) / (sum(freedNTPs) + collapse(c.polymerizedRegions)))*100 '%'
                    1 'NTPs' [] []
                        2 'Free' sum(freeNTPs) 'molecules'
                        2 'Min free' s.wholeCellModelIDs{s.ntpIndexs(minNTPIdx)} []
                        2 'Min free' minNTPVal 'molecules'
                        2 'Incorporated into RNA' (1 - freeNTPMass / (freeNTPMass + sum(sim.state('Mass').rnaWt)))*100 '%'
                    1 'NDPs' [] []
                        2 'Free' sum(freeNDPs) 'molecules'
                        2 'Min free' s.wholeCellModelIDs{s.ndpIndexs(minNDPIdx)} []
                        2 'Min free' minNDPVal 'molecules'
                    1 'NMPs' [] []
                        2 'Free' sum(freeNMPs) 'molecules'
                        2 'Min free' s.wholeCellModelIDs{s.nmpIndexs(minNMPIdx)} []
                        2 'Min free' minNMPVal 'molecules'
                    1 'Amino acids' [] []
                        2 'Free' sum(freeAAs) 'molecules'
                        2 'Min free' s.wholeCellModelIDs{s.aminoAcidIndexs(minAAIdx)} []
                        2 'Min free' minAAVal 'molecules'
                        2 'Incorporated into Protein' (1 - freeAAMass / (freeAAMass + sum(sim.state('Mass').proteinWt)))*100 '%'
                    1 'Energy' [] []
                        2 'ATP' sum(s.counts(s.getIndexs({'ATP'}), :)) 'molecules'
                        2 'ADP' sum(s.counts(s.getIndexs({'ADP'}), :)) 'molecules'
                        2 'AMP' sum(s.counts(s.getIndexs({'AMP'}), :)) 'molecules'
                        2 'PPi' sum(s.counts(s.getIndexs({'PPI'}), :)) 'molecules'
                        2 'Pi'  sum(s.counts(s.getIndexs({'PI'}),  :)) 'molecules'
                        2 'H2O' sum(s.counts(s.getIndexs({'H2O'}), :)) 'molecules'
                        2 'H'   sum(s.counts(s.getIndexs({'H'}),   :)) 'molecules'
                }];

            %% DNA
            c = sim.state('Chromosome');
            pm = sim.state('ProteinMonomer');
            pc = sim.state('ProteinComplex');
            ri = sim.process('ReplicationInitiation');
            dr = sim.process('DNARepair');
            dnaABoxBound = ...
                ri.calculateDnaABoxStatus() == ri.dnaABoxStatus_DnaAATPBound | ...
                ri.calculateDnaABoxStatus() == ri.dnaABoxStatus_DnaAADPBound;
            [polATPs, polADPs] = ri.calculateDnaAR1234Polymerization();
            oriCComplexSize = ri.calculateDnaAR1234ComplexSize(polATPs, polADPs);
            [~, boundMonomers] = find(c.monomerBoundSites);
            [~, boundComplexs] = find(c.complexBoundSites);
            occupiedBases = sum(c.monomerDNAFootprints(boundMonomers) + c.complexDNAFootprints(boundComplexs));

            methylatedRMSites = nnz(c.damagedBases([
                dr.RM_MunI_RecognitionSites(:, dr.RM_MunI_MethylatedPositions(1))   ones(size(dr.RM_MunI_RecognitionSites, 1), 1);
                dr.RM_MunI_RecognitionSites(:, dr.RM_MunI_MethylatedPositions(2)) 2*ones(size(dr.RM_MunI_RecognitionSites, 1), 1);
                dr.RM_MunI_RecognitionSites(:, dr.RM_MunI_MethylatedPositions(1)) 3*ones(size(dr.RM_MunI_RecognitionSites, 1), 1);
                dr.RM_MunI_RecognitionSites(:, dr.RM_MunI_MethylatedPositions(2)) 4*ones(size(dr.RM_MunI_RecognitionSites, 1), 1);
                ])) / 2;
            polymerizedRMSites = sum(c.isRegionPolymerized([
                dr.RM_MunI_RecognitionSites(:, 1)   ones(size(dr.RM_MunI_RecognitionSites, 1), 1);
                dr.RM_MunI_RecognitionSites(:, 1) 2*ones(size(dr.RM_MunI_RecognitionSites, 1), 1);
                dr.RM_MunI_RecognitionSites(:, 1) 3*ones(size(dr.RM_MunI_RecognitionSites, 1), 1);
                dr.RM_MunI_RecognitionSites(:, 1) 4*ones(size(dr.RM_MunI_RecognitionSites, 1), 1);
                ], 6, false)) / 2;

            content = [content;{
                0 'No. chromosomes' collapse(c.polymerizedRegions)/c.sequenceLen [];
                    1 'Bases' collapse(c.polymerizedRegions) 'nt';
                    1 'Linking number' collapse(c.linkingNumbers)/2 'Links';
                    1 'Superhelical density', (collapse(c.linkingNumbers)  - collapse(c.linkingNumbers_minFreeEnergy)) / collapse(c.linkingNumbers_minFreeEnergy) [];
                    1 'Bound proteins' nnz(c.monomerBoundSites) + nnz(c.complexBoundSites) 'molecules'
                        2 'Monomers' nnz(c.monomerBoundSites) 'molecules'
                            3 'GntR' nnz(c.monomerBoundSites == pm.getIndexs({'MG_101_MONOMER'})) 'molecules'
                        2 'Complexes' nnz(c.monomerBoundSites) 'molecules'
                            3 'DnaA' nnz(dnaABoxBound) 'molecules'
                                4 '7-mer' nnz(dnaABoxBound(ri.dnaABoxIndexs_7mer, :)) 'molecules'
                                4 '8-mer' nnz(dnaABoxBound(ri.dnaABoxIndexs_8mer, :)) 'molecules'
                                4 '9-mer' nnz(dnaABoxBound(ri.dnaABoxIndexs_9mer, :)) 'molecules'
                                4 'OriC Complex' oriCComplexSize(1) 'molecules'
                                4 'R1' nnz(dnaABoxBound(ri.dnaABoxIndexs_R12345(1), :)) 'molecules'
                                4 'R2' nnz(dnaABoxBound(ri.dnaABoxIndexs_R12345(2), :)) 'molecules'
                                4 'R3' nnz(dnaABoxBound(ri.dnaABoxIndexs_R12345(3), :)) 'molecules'
                                4 'R4' nnz(dnaABoxBound(ri.dnaABoxIndexs_R12345(3), :)) 'molecules'
                                4 'R5' nnz(dnaABoxBound(ri.dnaABoxIndexs_R12345(5), :)) 'molecules'
                            3 'Gyrase' nnz(c.complexBoundSites == pc.getIndexs({'DNA_GYRASE'})) 'molecules'
                            3 'LuxR' nnz(c.complexBoundSites == pc.getIndexs({'MG_428_DIMER'})) 'molecules'
                            3 'SMC' nnz(c.complexBoundSites == pc.getIndexs({'MG_213_214_298_6MER_ADP'})) 'molecules'
                                4 'Average spacing' collapse(c.polymerizedRegions)/2 / nnz(c.complexBoundSites == pc.getIndexs({'MG_213_214_298_6MER_ADP'})) 'nt'
                        2 'Occupied' occupiedBases 'nt'
                        2 'Occupied' occupiedBases / collapse(c.polymerizedRegions) / 2 * 100 '%'
                    1 'Damage' [] []
                        2 'Gap sites' nnz(c.gapSites) 'nt'
                        2 'Abasic sites' nnz(c.abasicSites) 'nt'
                        2 'Damaged bases' nnz(c.damagedBases) 'nt'
                            3 'm6AD' nnz(c.damagedBases == c.metabolite.m6ADIndexs) 'nt'
                            3 'methylated R/M sites' methylatedRMSites 'nt'
                            3 'methylated R/M sites' methylatedRMSites/polymerizedRMSites*100 '%'
                        2 'Damaged sugar-phosphate' nnz(c.damagedSugarPhosphates) 'nt'
                        2 'Strand breaks' nnz(c.strandBreaks) 'nt'
                        2 'Intrastrand crosslinks' nnz(c.intrastrandCrossLinks) 'nt'
                        2 'Holliday junctions' nnz(c.hollidayJunctions) 'nt'
                }];

            %% RNA
            r = sim.state('Rna');

            matureRNAs = sum(r.counts(r.matureIndexs, :), 2);
            aminoacylatedRNAs = sum(r.counts(r.aminoacylatedIndexs, :), 2);
            content = [content;{
                0 'RNA' sum(r.counts(:)) 'molecules'
                    1 'mRNA' sum(matureRNAs(r.matureMRNAIndexs)) 'molecules'
                        2 'No. species expressed' nnz(r.counts(r.matureIndexs(r.matureMRNAIndexs), :)) []
                    1 'rRNA' sum(matureRNAs(r.matureRRNAIndexs)) 'molecules'
                        2 '5S'  matureRNAs(r.matureRibosomalRRNAIndexs(1)) 'molecules'
                        2 '15S' matureRNAs(r.matureRibosomalRRNAIndexs(2)) 'molecules'
                        2 '23S' matureRNAs(r.matureRibosomalRRNAIndexs(3)) 'molecules'
                    1 'sRNA' sum(matureRNAs(r.matureSRNAIndexs) + aminoacylatedRNAs(r.matureSRNAIndexs)) 'molecules'
                        2 'scRNA'   matureRNAs(r.matureSRNAIndexs(1))+aminoacylatedRNAs(r.matureSRNAIndexs(1)) 'molecules'
                        2 'MG170'   matureRNAs(r.matureSRNAIndexs(2))+aminoacylatedRNAs(r.matureSRNAIndexs(2)) 'molecules'
                        2 'RNase P' matureRNAs(r.matureSRNAIndexs(3))+aminoacylatedRNAs(r.matureSRNAIndexs(3)) 'molecules'
                        2 'tmRNA'   matureRNAs(r.matureSRNAIndexs(4))+aminoacylatedRNAs(r.matureSRNAIndexs(4)) 'molecules'
                            3 'Free' matureRNAs(r.matureSRNAIndexs(4)) 'molecules'
                            4 'Aminoacylated' aminoacylatedRNAs(r.matureSRNAIndexs(4)) 'molecules'
                    1 'tRNA' sum(matureRNAs(r.matureTRNAIndexs) + aminoacylatedRNAs(r.matureTRNAIndexs)) 'molecules'
                        2 'Free' sum(matureRNAs(r.matureTRNAIndexs)) 'molecules'
                        2 'Aminoacylated' sum(aminoacylatedRNAs(r.matureTRNAIndexs)) 'molecules'
                        2 'No. species expressed' nnz(matureRNAs(r.matureTRNAIndexs) + aminoacylatedRNAs(r.matureTRNAIndexs)) 'molecules'
                    1 'Immature' nnz(sim.process('Transcription').transcripts.boundTranscriptionUnits) 'molecules'
                    1 'Aborted' size(sim.state('Transcript').abortedTranscripts, 1) 'molecules'
                }];

            %% Protein
            m = sim.state('ProteinMonomer');
            c = sim.state('ProteinComplex');

            ribosomalMonomerIdxs = sum(c.proteinComplexComposition(sim.gene.mRNAIndexs, c.getIndexs({'RIBOSOME_70S'}), :),3);
            ribosomalMonomers = sum(m.counts(m.matureIndexs(ribosomalMonomerIdxs > 0), :), 2);
            [minRibosomalMonomers, minRibosomalMonomerIdx] = min(ribosomalMonomers);
            minRibosomalMonomerID = m.wholeCellModelIDs{m.matureIndexs(minRibosomalMonomerIdx)};

            [idxs, ~, counts] = find(sum(m.counts(m.inactivatedIndexs, :), 2));
            inactiveMonomers = cell(numel(idxs), 4);
            inactiveMonomers(:, 1) = {2};
            inactiveMonomers(:, 2) = m.wholeCellModelIDs(m.inactivatedIndexs(idxs));
            inactiveMonomers(:, 3) = num2cell(counts);

            [idxs, ~, counts] = find(sum(c.counts(c.inactivatedIndexs, :), 2));
            inactiveComplexs = cell(numel(idxs), 4);
            inactiveComplexs(:, 1) = {2};
            inactiveComplexs(:, 2) = c.wholeCellModelIDs(c.inactivatedIndexs(idxs));
            inactiveComplexs(:, 3) = reshape(num2cell(counts), [], 1);

            content = [content;{
                0 'Protein' sum(m.counts(:)) + sum(c.counts(:)) 'molecules'
                    1 'Monomers' sum(m.counts(:)) 'molecules'
                        2 'Ribosomal proteins' sum(ribosomalMonomers) 'molecules'
                            3 'Min expressed species' minRibosomalMonomers 'molecules'
                            3 'Min expressed species' minRibosomalMonomerID []
                    1 'Complexs' sum(c.counts(:)) 'molecules'
                    1 'Bound' sum(sum(m.counts(m.boundIndexs, :)))+sum(sum(c.counts(c.boundIndexs, :))) 'molecules'
                        2 'Chromosome' nnz(sim.state('Chromosome').monomerBoundSites)+nnz(sim.state('Chromosome').complexBoundSites) 'molecules'
                        2 'RNA polymerase' nnz(sim.process('Transcription').transcripts.boundTranscriptionUnits) 'molecules'
                        2 'Ribosome' nnz(sim.state('Ribosome').boundMRNAs) 'molecules'
                    1 'Immature' nnz(sim.state('Ribosome').boundMRNAs) 'molecules'
                    1 'Inactive' sum(sum(m.counts(m.inactivatedIndexs, :)))+sum(sum(c.counts(c.inactivatedIndexs, :))) 'molecules'
                        };
                        inactiveMonomers;
                        inactiveComplexs;
                        {
                    1 'Damaged' sum(sum(m.counts(m.damagedIndexs, :)))+sum(sum(c.counts(c.damagedIndexs, :))) 'molecules'
                    1 'Aborted' size(sim.state('Polypeptide').abortedPolypeptides, 1) 'molecules'
                    1 'Cytosol' sum(m.counts(:, sim.compartment.cytosolIndexs))+ sum(c.counts(:, sim.compartment.cytosolIndexs)) 'molecules'
                    1 'Membrane' sum(m.counts(:, sim.compartment.membraneIndexs))+ sum(c.counts(:, sim.compartment.membraneIndexs)) 'molecules'
                    1 'Term. Org. Cytosol' sum(m.counts(:, sim.compartment.terminalOrganelleCytosolIndexs))+ sum(c.counts(:, sim.compartment.terminalOrganelleCytosolIndexs)) 'molecules'
                    1 'Term. Org. Membrane' sum(m.counts(:, sim.compartment.terminalOrganelleMembraneIndexs))+ sum(c.counts(:, sim.compartment.terminalOrganelleMembraneIndexs)) 'molecules'
                    1 'Extracellular' sum(m.counts(:, sim.compartment.extracellularIndexs))+ sum(c.counts(:, sim.compartment.extracellularIndexs)) 'molecules'
                }];

            %% DNA Polymerase
            r = sim.process('Replication');
            free = r.enzymes(r.enzymeIndexs_core) + r.enzymeComposition(r.enzymeIndexs_core, :) * r.enzymes;
            bound = r.boundEnzymes(r.enzymeIndexs_core) + r.enzymeComposition(r.enzymeIndexs_core, :) * r.boundEnzymes;
            content = [content;{
                0 'DNA polymerase' free+bound 'molecules'
                    1 'Free' free 'molecules'
                    1 'Bound' bound 'molecules'
                }];

            %% Transcription enzymes
            t = sim.process('Transcription');
            s = sim.state('RNAPolymerase');
            rna = t.rnaPolymerases;
            rnaPolymeraseStateOccupancies = s.stateOccupancies*100;
            content = [content;{
                0 'Transcription enzymes' [] []
                    1 'RNA polymerase' t.enzymes(t.enzymeIndexs_rnaPolymerase)+t.boundEnzymes(t.enzymeIndexs_rnaPolymerase) 'molecules'
                        2 'Active' rnaPolymeraseStateOccupancies(rna.activelyTranscribingIndex) ' %'
                        2 'Specifically bound' rnaPolymeraseStateOccupancies(rna.specificallyBoundIndex) ' %'
                        2 'Non-specifically bound' rnaPolymeraseStateOccupancies(rna.nonSpecificallyBoundIndex) ' %'
                        2 'Free' rnaPolymeraseStateOccupancies(rna.freeIndex) ' %'
                    1 'Initiation/Elongation factors' sum(t.enzymes(t.enzymeIndexs_transcriptionFactors)+t.boundEnzymes(t.enzymeIndexs_transcriptionFactors)) 'molecules'
                        2 'Free' sum(t.enzymes(t.enzymeIndexs_transcriptionFactors)) 'molecules'
                        2 'Bound' sum(t.boundEnzymes(t.enzymeIndexs_transcriptionFactors)) 'molecules'
                }];

            %% tRNA aminoacylation enzymes
            t = sim.process('tRNAAminoacylation');
            totEnzymes = t.enzymes + t.boundEnzymes;
            content = [content;{
                0 'tRNA aminoacylation enzymes' sum(totEnzymes) 'molecules'
                    1 'tRNA synthetases' sum(totEnzymes(t.enzymeIndexs_tRNASynthetases)) 'molecules'
                    1 'tRNA transferases' sum(totEnzymes(t.enzymeIndexs_tRNATransferases)) 'molecules'
                }];

            %% Translation enzymes
            t = sim.process('Translation');
            r = sim.state('Ribosome');
            totEnzymes = t.enzymes + t.boundEnzymes;
            content = [content;{
                0 'Translation enzymes' [] []
                    1 'Ribosome' [] []
                        2 'Ribosome 30S' totEnzymes(t.enzymeIndexs_ribosome30S) 'molecules'
                        2 'Ribosome 30S-IF3' totEnzymes(t.enzymeIndexs_ribosome30SIF3) 'molecules'
                        2 'Ribosome 50S' totEnzymes(t.enzymeIndexs_ribosome50S) 'molecules'
                        2 'Ribosome 70S' totEnzymes(t.enzymeIndexs_ribosome70S) 'molecules'
                            3 'Free' t.enzymes(t.enzymeIndexs_ribosome70S) 'molecules'
                            3 'Bound' t.boundEnzymes(t.enzymeIndexs_ribosome70S) 'molecules'
                            3 'Active' nnz(r.boundMRNAs) 'molecules'
                    1 'Translation factors' sum(totEnzymes(t.enzymeIndexs_translationFactors)) 'molecules'
                        1 'Free' sum(t.enzymes(t.enzymeIndexs_translationFactors)) 'molecules'
                        1 'Bound' sum(t.boundEnzymes(t.enzymeIndexs_translationFactors)) 'molecules'
                }];

            %% FtsZ
            f = sim.process('FtsZPolymerization');
            totEnzymes = f.enzymes + f.boundEnzymes;
            content = [content;{
                0 'FtsZ' [1 1 1 2:9] * totEnzymes 'molecules'
                    1 'Free monomers' totEnzymes(f.enzymeIndexs_FtsZ) 'molecules'
                    1 'GDP 1-mer' totEnzymes(f.enzymeIndexs_FtsZ_GDP) 'molecules'
                    1 'GTP 1-mer' totEnzymes(f.enzymeIndexs_FtsZ_GTP) 'molecules'
                    1 'GTP 2-mer' totEnzymes(f.enzymeIndexs_FtsZ_activated(2)) 'molecules'
                    1 'GTP 3-mer' totEnzymes(f.enzymeIndexs_FtsZ_activated(3)) 'molecules'
                    1 'GTP 4-mer' totEnzymes(f.enzymeIndexs_FtsZ_activated(4)) 'molecules'
                    1 'GTP 5-mer' totEnzymes(f.enzymeIndexs_FtsZ_activated(5)) 'molecules'
                    1 'GTP 6-mer' totEnzymes(f.enzymeIndexs_FtsZ_activated(6)) 'molecules'
                    1 'GTP 7-mer' totEnzymes(f.enzymeIndexs_FtsZ_activated(7)) 'molecules'
                    1 'GTP 8-mer' totEnzymes(f.enzymeIndexs_FtsZ_activated(8)) 'molecules'
                    1 'GTP 9-mer' totEnzymes(f.enzymeIndexs_FtsZ_activated(9)) 'molecules'
                    1 'Bound 9-mer' f.boundEnzymes(f.enzymeIndexs_FtsZ_activated(9)) 'molecules'
                }];

            %% format output
            indentation = cell2mat(content(:, 1));
            content = content(:, 2:end);
        end

        %diagnostics
        function [content, colLabels, indentation] = processes(sim)
            content = cell(0, 4);
            colLabels = {'Quantity', 'Value', 'Units'};

            %% Transcription
            t = sim.process('Transcription');
            s = sim.state('RNAPolymerase');

            %building blocks
            availableNTPs = t.substrates(t.substrateIndexs_ntp);
            rnaPolymeraseMaxNTPIncorporation = s.nActive * t.rnaPolymeraseElongationRate;
            transcriptionFactors = t.enzymes(t.enzymeIndexs_transcriptionFactors);

            %limits
            transcriptionNTPLimit = min(availableNTPs, ...
                availableNTPs / sum(availableNTPs) * rnaPolymeraseMaxNTPIncorporation);
            if abs(rnaPolymeraseMaxNTPIncorporation - sum(transcriptionNTPLimit)) < 1e-6
                transcriptionLimitingFractor = 'Active RNA Polymerases';
            else
                transcriptionLimitingFractor = 'Available NMPs, NTPs';
            end

            %limiting blocks
            [minNTPs, minNTP] = min(availableNTPs);
            minNTP = t.substrateWholeCellModelIDs{t.substrateIndexs_ntp(minNTP)};

            [minTranscriptionFactors, minTranscriptionFactor] = min(transcriptionFactors);
            minTranscriptionFactor = t.enzymeWholeCellModelIDs{t.enzymeIndexs_transcriptionFactors(minTranscriptionFactor)};

            %print
            content = [content;{
                0 'Transcription' [] []
                    1 'Allocated ATP' t.substrates(t.substrateIndexs_atp) 'molecules'
                    1 'Available NTPs' sum(availableNTPs) 'molecules'
                    1 'Min NTP' minNTPs 'molecules'
                    1 'Min NTP' minNTP []
                    1 'Active RNA Polymerases' s.nActive 'molecules'
                    1 'RNA Polymerase Elongation Rate' t.rnaPolymeraseElongationRate 'nt/s'
                    1 'Max RNA Polymerase NTP Incorporation'  rnaPolymeraseMaxNTPIncorporation 'nt/s'
                    1 'Transcription factors' sum(transcriptionFactors) 'molecules'
                    1 'Min transcription factors' minTranscriptionFactors 'molecules'
                    1 'Min transcription factors' minTranscriptionFactor []
                    1 'Max NTP Incorporation' sum(transcriptionNTPLimit) 'nt/s'
                    1 'Limiting Factor' transcriptionLimitingFractor []
             }];

            %% Translation
            t = sim.process('Translation');
            r = sim.state('Ribosome');

            %building blocks
            energy = t.substrates(t.substrateIndexs_gtp);

            aminoAcids = t.substrates(t.substrateIndexs_aminoAcids);
            ribosomeMaxAAIncorporation = r.nActive * t.ribosomeElongationRate;

            tRNAs = t.freeTRNAs + t.aminoacylatedTRNAs;
            translationFactors = t.enzymes(t.enzymeIndexs_translationFactors) + t.boundEnzymes(t.enzymeIndexs_translationFactors);

            %limiting blocks
            [minAminoAcids, minAminoAcid] = min(aminoAcids);
            minAminoAcid = t.substrateWholeCellModelIDs{t.substrateIndexs_aminoAcids(minAminoAcid)};

            [minTRNAs, minTRNA] = min(tRNAs);
            minTRNA = t.freeTRNAWholeCellModelIDs{minTRNA};

            [minTranslationFactors, minTranslationFactor]=min(translationFactors);
            minTranslationFactor = t.enzymeWholeCellModelIDs{t.enzymeIndexs_translationFactors(minTranslationFactor)};

            %limits
            content = [content;{
                0 'Translation' [] []
                    1 'Allocated ATP' energy 'molecules'
                    1 'Available AAs'  sum(aminoAcids) 'molecules'
                    1 'Min Amino Acid' minAminoAcids 'molecules'
                    1 'Min Amino Acid' minAminoAcid []
                    1 'tRNAs' sum(tRNAs) 'molecules'
                    1 'Min tRNAs' minTRNAs 'molecules'
                    1 'Min tRNAs' minTRNA []
                    1 'Active Ribosomes' r.nActive 'molecules'
                    1 'Ribosome Elongation Rate' t.ribosomeElongationRate 'aa/s'
                    1 'Max Ribosome AA Incorporation' ribosomeMaxAAIncorporation 'aa/s'
                    1 'Translation factors' sum(translationFactors) 'molecules'
                    1 'Min translation factors' minTranslationFactors 'molecules'
                    1 'Min translation factors' minTranslationFactor []
                }];

            %% format output
            indentation = cell2mat(content(:, 1));
            content = content(:, 2:end);
        end
    end

    %plotting
    methods (Static)
        function plotStateOnChromosome(sim, axesHandle)
            c = sim.state('Chromosome');

            cla(axesHandle);
            hold on;
            legendHandles = [];

            %base chromosome
            for i = 1:4
                [r, xoffset] = calcRadiusXOffset(i);
                h = plot(axesHandle, xoffset + r*cos(pi/180 * (1:360)), r*sin(pi/180 * (1:360)), 'Color', [0.7 0.7 0.7], 'LineStyle', ':');
            end
            legendHandles = [legendHandles; h];

            %polymerized regions
            for i = 1:4
                [pos, lens] = find(c.polymerizedRegions(:, i));
                [r, xoffset] = calcRadiusXOffset(i);
                for j = 1:size(pos, 1)
                    theta = 2*pi * (pos(j,1) + (0:lens(j)/360:lens(j)))/c.sequenceLen;
                    h = plot(axesHandle, xoffset + r*cos(theta), r*sin(theta), 'Color', [0.7 0.7 0.7], 'LineWidth', 2);
                end
            end
            legendHandles = [legendHandles; h];

            %polymerized transcription units
            for i = 1:2
                for j = 1:numel(c.transcriptionUnitWholeCellModelIDs)
                    if ~c.polymerizedTranscriptionUnits(j, i); continue; end;
                    [r, xoffset] = calcRadiusXOffset(2*(i-1) + c.transcriptionUnitStrands(j));
                    theta = 2*pi * (c.transcriptionUnitStartCoordinates(j) + (0:c.transcriptionUnitLengths(j)/360:c.transcriptionUnitLengths(j))) / c.sequenceLen;
                    h = plot(axesHandle, xoffset + r*cos(theta), r*sin(theta), 'Color', [0 1 0], 'LineWidth', 2);
                end
            end
            legendHandles = [legendHandles; h];

            %polymerized genes
            for i = 1:2
                for j = 1:numel(sim.gene.wholeCellModelIDs)
                    if ~c.polymerizedGenes(j, i); continue; end;
                    [r, xoffset] = calcRadiusXOffset(2*(i-1) + sim.gene.strands(j));
                    theta = 2*pi * (sim.gene.startCoordinates(j) + (0:sim.gene.lengths(j)/360:sim.gene.lengths(j))) / c.sequenceLen;
                    h = plot(axesHandle, xoffset + r*cos(theta), r*sin(theta), 'Color', [0 0 1], 'LineWidth', 2);
                end
            end
            legendHandles = [legendHandles; h];

            %bound proteins
            [monomerPosStrnds, monomers] = find(c.monomerBoundSites);
            [complexPosStrnds, complexs] = find(c.complexBoundSites);
            posStrnds = [monomerPosStrnds; complexPosStrnds];
            lens = [c.monomerDNAFootprints(monomers); c.complexDNAFootprints(complexs)];
            ssStranded = [c.monomerDNAFootprintRegionStrandedness(monomers); c.complexDNAFootprintRegionStrandedness(complexs)] == c.dnaStrandedness_ssDNA;
            for i = 1:size(posStrnds, 1)
                [r, xoffset] = calcRadiusXOffset(posStrnds(i,2));
                theta = 2*pi * (posStrnds(i,1) + (0:lens(i)/360:lens(i))) / c.sequenceLen;
                h = plot(axesHandle, xoffset + r*cos(theta), r*sin(theta), 'Color', [1 1 0], 'LineWidth', 4);
                if ~ssStranded(i)
                    [r, xoffset] = calcRadiusXOffset(posStrnds(i, 2) -iseven(posStrnds(i, 2)) + isodd(posStrnds(i, 2)));
                    plot(axesHandle, xoffset + r*cos(theta), r*sin(theta), 'Color', [1 1 0], 'LineWidth', 4);
                end
            end
            legendHandles = [legendHandles; h];

            %damage
            posStrnds = find(c.getDamagedSites(1,1,1,1,1,1,1));
            for i = 1:size(posStrnds, 1)
                [r, xoffset] = calcRadiusXOffset(posStrnds(i,2));
                theta = 2*pi * posStrnds(i,1) / c.sequenceLen;
                h = plot(axesHandle, xoffset + r*cos(theta), r*sin(theta), 'Color', [1 0 0], 'LineWidth', 4);
            end
            legendHandles = [legendHandles; h];

            %expressed RNAs
            r = sim.state('Rna');
            genes = r.matureRNAGeneComposition * sum(r.counts(r.matureIndexs, :) + r.counts(r.aminoacylatedIndexs, :), 2);
            for i = 1:numel(sim.gene.wholeCellModelIDs)
                if genes(i) == 0; continue; end;

                r = 1 + [0.05;0.25] * iseven(sim.gene.strands(i)) - [0.05;0.25] * isodd(sim.gene.strands(i));
                xoffset = 0;

                theta = 2*pi * (sim.gene.startCoordinates(i) + sim.gene.lengths(i)/2) / c.sequenceLen;
                h = plot(axesHandle, xoffset + r*cos(theta), r*sin(theta), 'Color', [1 0 1], 'LineWidth', 2);
            end
            legendHandles = [legendHandles; h];

            %expressed proteins
            pm = sim.state('ProteinMonomer');
            pc = sim.state('ProteinComplex');

            monomers = ...
                sum(pm.counts(pm.matureIndexs, :) + pm.counts(pm.boundIndexs, :) + pm.counts(pm.inactivatedIndexs, :), 2) + ...
                sum(pc.proteinComplexComposition(sim.gene.mRNAIndexs, :, :), 3) * sum(pc.counts(pc.matureIndexs, :) + pc.counts(pc.boundIndexs, :) + pc.counts(pc.inactivatedIndexs, :), 2);
            for i = 1:numel(monomers)
                if monomers(i) == 0; continue; end;

                r = 1 + [0.05;0.25] * iseven(sim.gene.strands(sim.gene.mRNAIndexs(i))) - [0.05;0.25] * isodd(sim.gene.strands(sim.gene.mRNAIndexs(i)));
                xoffset = 0;

                theta = 2*pi * (sim.gene.startCoordinates(sim.gene.mRNAIndexs(i)) + sim.gene.lengths(sim.gene.mRNAIndexs(i))/2) / c.sequenceLen;
                h = plot(axesHandle, xoffset + r*cos(theta), r*sin(theta), 'Color', [0 1 1], 'LineWidth', 2);
            end
            legendHandles = [legendHandles; h];

            %ribosomes
            ribosome = sim.state('Ribosome');
            for i = 1:numel(ribosome.boundMRNAs)
                if ribosome.boundMRNAs(i) == 0; continue; end;

                r = 1 + [0.05;0.25] * iseven(sim.gene.strands(sim.gene.mRNAIndexs(ribosome.boundMRNAs(i)))) - [0.05;0.25] * isodd(sim.gene.strands(sim.gene.mRNAIndexs(ribosome.boundMRNAs(i))));
                xoffset = 0;

                theta = 2*pi * (sim.gene.startCoordinates(sim.gene.mRNAIndexs(ribosome.boundMRNAs(i))) + ribosome.states(i)) / c.sequenceLen;
                plot(axesHandle, xoffset + r*cos(theta), r*sin(theta), 'Color', [1 1 0], 'LineWidth', 2);
            end

            %legend
            legend(legendHandles, {
                'Chromosome'
                'Polymerized'
                '  Gene'
                '  TU'
                'Bnd Prot'
                'Damage'
                'Exp RNA'
                'Exp Prot'}, ...
                'Orientation', 'Horizontal', ...
                'Location', 'SouthOutside');

            %format
            xlim([-1.4 2.5+1.4]);
            ylim([-1.4 1.4]);

            set(axesHandle, 'DataAspectRatio', [1 1 1])
            set(axesHandle, 'Visible', 'off')
            set(get(axesHandle, 'Parent'), 'Color', [1 1 1]);
            position = get(gca, 'Position');
            scale = 1.35;
            set(gca, 'Position', [position(1:2)-position(3:4)*(scale-1)/2 position(3:4)*scale]);

            function [r, xoffset] = calcRadiusXOffset(strnd)
                r = 1 + 0.05 * iseven(strnd) - 0.05 * isodd(strnd);
                xoffset = 2.5 * floor((strnd-1)/2);
            end
        end
    end
end


function L = le(p1,p2)
%PREAL/LE Overloaded LE (<=) operator for class preal.

global useUnitsFlag

if ~(useUnitsFlag) % If physunits is disabled...
    L=le(double(p1),double(p2)); % ... treat as double.
    return
end

p1=preal(p1);
p2=preal(p2);
tol=0.001;
if isscalar(p1)
    p1=preal(p1.value*ones(size(p2)),p1.units);
elseif isscalar(p2)
    p2=preal(p2.value*ones(size(p1)),p2.units);
elseif ndims(p1)==ndims(p2)&size(p1)==size(p2)
    % do nothing
else
    error('Array dimensions must agree')
end

L=false(size(p1));
for k=1:numel(L)
    if any(abs(p1(k).units-p2(k).units)>tol)
        error('Attempt to compare dimensionally inconsistent preals')
    end
    L(k)=le(p1(k).value,p2(k).value);
end


function im = screenShotRGB(x0,y0,widthSS, heightSS)
%screenShotRGB  Make a screenshot using Java.
%   im = screenShotRGB; make a screen capture of the entire screen, it returns
%   a RGB color image  with 'uint8' class elements.
%   im = screenShotRGB(x0,y0,widthSS, heightSS); makes a screenshot of the
%   rectangle defined by the botton-left point (x0, y0) in the screen
%   coordinates, and its width, and its height.
%
%   For example, make a screen shot of the entire screen, plot and save it to a
%   file:
%     im = screenShotRGB; % make the screenshot
%     imwrite(im,'screenShot.png');
%     figure
%     image(im);
%     axis equal, axis tight
%     axis off
%
%   screenShotRGB use the Java class 'Robot' to make the capture.

%   For more information about the Robot class see
%   http://www.mathworks.com/support/solutions/en/data/1-90E06C/?solution=1-90E06C
%   http://download.oracle.com/javase/1.4.2/docs/api/java/awt/Robot.html#cr
%   eateScreenCapture%28java.awt.Rectangle%29

%   Matlab version by Daniel Mena 12-2009.

trash = java.awt.Toolkit.getDefaultToolkit();
screenSize = trash.getScreenSize();
widthScreen = screenSize.getWidth();
heightScreen = screenSize.getHeight();
if nargin == 0
    x0 = 0;
    y0 = 0;
    widthSS = widthScreen;
    heightSS = heightScreen;
else
% %
%     if (widthSS+x0 > (widthScreen)) || (heightSS+y0 > (heightScreen))
%         error('Rectangle is outside the screen.');
%     end
%     if (x0 > widthScreen) || (y0 > heightScreen)
%         error('Origin of the Rectangle is outside the screen.')
%     end
%     if (x0 < 0) || (y0 < 0)
%         error('x0 and y0 must be  positive.')
%     end
% %     y0=heightScreen-(heightSS+y0);
end
im = zeros(heightSS,widthSS,3,'uint8');
r = java.awt.Robot;
bimg = r.createScreenCapture(java.awt.Rectangle(x0, y0, widthSS, heightSS));
RGBA = bimg.getRGB(0,0,widthSS,heightSS,[],0,widthSS); % RGBA has int32 elements
RGBA = typecast(RGBA, 'uint8');

im(:,:,1) = reshape(RGBA(3:4:end),widthSS,heightSS).';
im(:,:,2) = reshape(RGBA(2:4:end),widthSS,heightSS).';
im(:,:,3) = reshape(RGBA(1:4:end),widthSS,heightSS).';


function obj=set_limit_extra(obj,x_extra,y_extra,z_extra)
%set_limit_extra Add some breathing room around data in plots
%
% Example syntax gramm_object.set_limit_extra([0.05 0.2],0.05)
% first argument is XLim extra, second is YLim extra, extra
% room is expressed as ratio to original limits. It is possible to set
% extra room separately for upper and lower limits by giving a 2x1 array.
% If not, the lower and uupper limit extra will be the same

if nargin<4
    z_extra=0;
end

%If only one element is given we use it for upper and lower extra
if numel(x_extra==1)
    x_extra=[x_extra x_extra];
end
if numel(y_extra==1)
    y_extra=[y_extra y_extra];
end
if numel(z_extra==1)
    z_extra=[z_extra z_extra];
end

for obj_ind=1:numel(obj)
    obj(obj_ind).xlim_extra=x_extra;
    obj(obj_ind).ylim_extra=y_extra;
    obj(obj_ind).zlim_extra=z_extra;
end

end


function x = tsvread(f,v,hdr)
% Load text and numeric data from file
% FORMAT x = tsvread(f,v,hdr)
% f   - filename (can be gzipped) {txt,mat,csv,tsv,json}
% v   - name of field to return if data stored in a structure [default: '']
%       or index of column if data stored as an array
% hdr - detect the presence of a header row for csv/tsv [default: true]
%
% x   - corresponding data array or structure
%__________________________________________________________________________
%
% Based on spm_load.m from SPM12.
%__________________________________________________________________________

% Copyright (C) 2018, Guillaume Flandin, Wellcome Centre for Human Neuroimaging
% Copyright (C) 2018--, BIDS-MATLAB developers

%-Check input arguments
%--------------------------------------------------------------------------
if ~exist(f,'file')
    error('Unable to read file ''%s''',f);
end

if nargin < 2, v = ''; end
if nargin < 3, hdr = true; end % Detect

%-Load the data file
%--------------------------------------------------------------------------
[~,~,ext] = fileparts(f);
switch ext(2:end)
    case 'txt'
        x = load(f,'-ascii');
    case 'mat'
        x  = load(f,'-mat');
    case 'csv'
        % x = csvread(f); % numeric data only
        x = dsvread(f,',',hdr);
    case 'tsv'
        % x = dlmread(f,'\t'); % numeric data only
        x = dsvread(f,'\t',hdr);
    case 'json'
        x = bids.util.jsondecode(f);
    case 'gz'
        fz  = gunzip(f,tempname);
        sts = true;
        try
            x   = tsvread(fz{1});
        catch
            sts = false;
        end
        delete(fz{1});
        rmdir(fileparts(fz{1}));
        if ~sts, error('Cannot load ''%s''.',f); end
    otherwise
        try
            x = load(f);
        catch
            error('Unknown file format.');
        end
end

%-Return relevant subset of the data if required
%--------------------------------------------------------------------------
if isstruct(x)
    if isempty(v)
        fn = fieldnames(x);
        if numel(fn) == 1 && isnumeric(x.(fn{1}))
            x = x.(fn{1});
        end
    else
        if ischar(v)
            try
                x = x.(v);
            catch
                error('Data do not contain array ''%s''.',v);
            end
        else
            fn = fieldnames(x);
            try
                x = x.(fn{v});
            catch
                error('Invalid data index.');
            end
        end
    end
elseif isnumeric(x)
    if isnumeric(v)
        try
            x = x(:,v);
        catch
            error('Invalid data index.');
        end
    elseif ~isempty(v)
        error('Invalid data index.');
    end
end

%==========================================================================
% function x = dsvread(f,delim)
%==========================================================================
function x = dsvread(f,delim,header)
% Read delimiter-separated values file into a structure array
%  * header line of column names will be used if detected
%  * 'n/a' fields are replaced with NaN

%-Input arguments
%--------------------------------------------------------------------------
if nargin < 2, delim = '\t'; end
if nargin < 3, header = true; end % true: detect, false: no
delim = sprintf(delim);
eol   = sprintf('\n');

%-Read file
%--------------------------------------------------------------------------
S   = fileread(f);
if isempty(S), x = []; return; end
if S(end) ~= eol, S = [S eol]; end
S   = regexprep(S,{'\r\n','\r','(\n)\1+'},{'\n','\n','$1'});

%-Get column names from header line (non-numeric first line)
%--------------------------------------------------------------------------
h   = find(S == eol,1);
hdr = S(1:h-1);
var = regexp(hdr,delim,'split');
N   = numel(var);
n1  = isnan(cellfun(@str2double,var));
n2  = cellfun(@(x) strcmpi(x,'NaN'),var);
if header && any(n1 & ~n2)
    hdr     = true;
    try
        var = genvarname(var);
    catch
        var = matlab.lang.makeValidName(var,'ReplacementStyle','hex');
        var = matlab.lang.makeUniqueStrings(var);
    end
    S       = S(h+1:end);
else
    hdr     = false;
    fmt     = ['Var%0' num2str(floor(log10(N))+1) 'd'];
    var     = arrayfun(@(x) sprintf(fmt,x),(1:N)','UniformOutput',false);
end

% remove double delim
try
    while ~isempty(strfind(S,[delim delim]))
        S = strrep(S,[delim delim],delim);
    end
end

%-Parse file
%--------------------------------------------------------------------------
if exist('OCTAVE_VERSION','builtin') % bug #51093
    S = strrep(S,delim,'#');
    delim = '#';
end
if ~isempty(S)
    d = textscan(S,'%s','Delimiter',delim);
else
    d = {[]};
end
if rem(numel(d{1}),N), error('Varying number of delimiters per line.'); end
d = reshape(d{1},N,[])';
allnum = true;
for i=1:numel(var)
    sts = true;
    dd = zeros(size(d,1),1);
    for j=1:size(d,1)
        if strcmp(d{j,i},'n/a')
            dd(j) = NaN;
        else
            dd(j) = str2double(d{j,i}); % i,j considered as complex
            if isnan(dd(j)), sts = false; break; end
        end
    end
    if sts
        x.(var{i}) = dd;
    else
        x.(var{i}) = d(:,i);
        allnum     = false;
    end
end

if ~hdr && allnum
    x = struct2cell(x);
    x = [x{:}];
end


function [collectedEyeData, img, fixMap] = showAllEyeDataForImage2(filename, FixationDB, STIMULI, HASH)

if strcmp(filename(end-2:end), 'jpg')
    stimname = filename(1:end-4);
elseif strcmp(filename(end-2:end), 'mat')
    stimname = filename(1:end-4);
else
    disp('filename not .mat or .jpg');
end

if exist(fullfile(STIMULI, strcat(stimname, '.jpg')))
    img = imread(fullfile(STIMULI, strcat(stimname, '.jpg')));
else
    fprintf('image file not found\n')
    fullfile(STIMULI, strcat(stimname, '.jpg'))
    collectedEyeData = [];
    img = NaN;
    fixMap = NaN;
    return
end

% get the eyetracking data from the FixationDB
load(HASH);
index = hash.(stimname);
collectedEyeData = FixationDB(index).fixationData;
[users{1:length(collectedEyeData)}] = deal(collectedEyeData.user);
fixMap = zeros(size(img, 1), size(img, 2));

for u=1:length(users)

    appropFix = collectedEyeData(u).appropFix;

    % create new image with fixations and save
    for j=1:size(appropFix, 1)
        pt = appropFix(j, :);
        fixMap(pt(2), pt(1))=1;
    end

end


function [varargout] = ft_selectdata(cfg, varargin)

% FT_SELECTDATA makes a selection in the input data along specific data
% dimensions, such as channels, time, frequency, trials, etc. It can also
% be used to average the data along each of the specific dimensions.
%
% Use as
%  [data] = ft_selectdata(cfg, data, ...)
%
% The cfg argument is a configuration structure which can contain
%   cfg.tolerance   = scalar, tolerance value to determine equality of time/frequency bins (default = 1e-5)
%
% For data with trials or subjects as repetitions, you can specify
%   cfg.trials      = 1xN, trial indices to keep, can be 'all'. You can use logical indexing, where false(1,N) removes all the trials
%   cfg.avgoverrpt  = string, can be 'yes' or 'no' (default = 'no')
%
% For data with a channel dimension you can specify
%   cfg.channel     = Nx1 cell-array with selection of channels (default = 'all'), see FT_CHANNELSELECTION
%   cfg.avgoverchan = string, can be 'yes' or 'no' (default = 'no')
%   cfg.nanmean     = string, can be 'yes' or 'no' (default = 'no')
%
% For data with channel combinations you can specify
%   cfg.channelcmb     = Nx2 cell-array with selection of channels (default = 'all'), see FT_CHANNELCOMBINATION
%   cfg.avgoverchancmb = string, can be 'yes' or 'no' (default = 'no')
%
% For data with a time dimension you can specify
%   cfg.latency     = scalar or string, can be 'all', 'minperiod', 'maxperiod', 'prestim', 'poststim', or [beg end], specify time range in seconds
%   cfg.avgovertime = string, can be 'yes' or 'no' (default = 'no')
%   cfg.nanmean     = string, can be 'yes' or 'no' (default = 'no')
%
% For data with a frequency dimension you can specify
%   cfg.frequency   = scalar or string, can be 'all', or [beg end], specify frequency range in Hz
%   cfg.avgoverfreq = string, can be 'yes' or 'no' (default = 'no')
%   cfg.nanmean     = string, can be 'yes' or 'no' (default = 'no')
%
% When you average over a dimension, you can choose whether to keep that dimension in
% the data representation or remove it alltogether.
%   cfg.keeprptdim     = 'yes' or 'no' (default is automatic)
%   cfg.keepchandim    = 'yes' or 'no' (default = 'yes')
%   cfg.keepchancmbdim = 'yes' or 'no' (default = 'yes')
%   cfg.keeptimedim    = 'yes' or 'no' (default = 'yes')
%   cfg.keepfreqdim    = 'yes' or 'no' (default = 'yes')
%
% If multiple input arguments are provided, FT_SELECTDATA will adjust the individual
% inputs such that either the INTERSECTION across inputs is retained (i.e. only the
% channel, time, and frequency points that are shared across all input arguments), or
% that the UNION across inputs is retained (replacing missing data with nans). In
% either case, the order of the channels is made consistent across inputs. The
% behavior can be specified with
%   cfg.select      = string, can be 'intersect' or 'union' (default = 'intersect')
% For raw data structures you cannot make the union.
%
% See also FT_DATATYPE, FT_CHANNELSELECTION, FT_CHANNELCOMBINATION

% Undocumented options
%   cfg.avgoverpos
%   cfg.keepposdim     = 'yes' or 'no' (default = 'yes')

% Copyright (C) 2012-2022, Robert Oostenveld & Jan-Mathijs Schoffelen
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip 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.
%
%    FieldTrip 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 FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

% these are used by the ft_preamble/ft_postamble function and scripts
ft_revision = '$Id$';
ft_nargin   = nargin;
ft_nargout  = nargout;

ft_defaults
ft_preamble init
ft_preamble debug

ft_preamble loadvar varargin
ft_preamble provenance varargin

% determine the characteristics of the input data
dtype = ft_datatype(varargin{1});
for i=2:length(varargin)
  % ensure that all subsequent inputs are of the same type
  ok = ft_datatype(varargin{i}, dtype);
  if ~ok, ft_error('input data should be of the same datatype'); end
end

% this only works with certain data types, it is not meant for descriptive fields such as elec, grad, opto, layout, etc.
assert(~ismember(dtype, {'elec', 'grad', 'opto', 'layout'}), 'invalid input data type "%s"', dtype);

% ensure that the user does not give invalid selection options
cfg = ft_checkconfig(cfg, 'forbidden', {'foi', 'toi'});

cfg = ft_checkconfig(cfg, 'renamed',    {'selmode',    'select'});
cfg = ft_checkconfig(cfg, 'renamed',    {'toilim',     'latency'});
cfg = ft_checkconfig(cfg, 'renamed',    {'foilim',     'frequency'});
cfg = ft_checkconfig(cfg, 'renamed',    {'avgoverroi', 'avgoverpos'});
cfg = ft_checkconfig(cfg, 'renamedval', {'parameter', 'avg.pow', 'pow'});
cfg = ft_checkconfig(cfg, 'renamedval', {'parameter', 'avg.mom', 'mom'});
cfg = ft_checkconfig(cfg, 'renamedval', {'parameter', 'avg.nai', 'nai'});
cfg = ft_checkconfig(cfg, 'renamedval', {'parameter', 'trial.pow', 'pow'});
cfg = ft_checkconfig(cfg, 'renamedval', {'parameter', 'trial.mom', 'mom'});
cfg = ft_checkconfig(cfg, 'renamedval', {'parameter', 'trial.nai', 'nai'});

cfg.tolerance = ft_getopt(cfg, 'tolerance', 1e-5);        % default tolerance for checking equality of time/freq axes
cfg.select    = ft_getopt(cfg, 'select',   'intersect');  % default is to take intersection, alternative 'union'
if isequal(dtype, 'raw') && isequal(cfg.select, 'union')
  ft_error('using cfg.select=''union'' in combination with ''raw'' datatype is not supported');
end

if strcmp(dtype, 'volume') || strcmp(dtype, 'segmentation')
  % it must be a source representation, not a volume representation
  for i=1:length(varargin)
    varargin{i} = ft_checkdata(varargin{i}, 'datatype', 'source');
  end
  dtype = 'source';
else
  % check that the data is according to the latest FieldTrip representation
  for i=1:length(varargin)
    varargin{i} = ft_checkdata(varargin{i});
  end
end

% this function only works for the upcoming (not yet standard) source representation without sub-structures
% update the old-style beamformer source reconstruction to the upcoming representation
if strcmp(dtype, 'source')
  if isfield(varargin{1}, 'avg') && isstruct(varargin{1}.avg)
    restoreavg = fieldnames(varargin{1}.avg);
  else
    restoreavg = {};
  end
  for i=1:length(varargin)
    varargin{i} = ft_datatype_source(varargin{i}, 'version', 'upcoming');
  end
end

cfg.latency = ft_getopt(cfg, 'latency', 'all', 1);
if isnumeric(cfg.latency) && numel(cfg.latency)==2 && cfg.latency(1)==cfg.latency(2)
  % this is better specified by a single number
  cfg.latency = cfg.latency(1);
end

cfg.channel = ft_getopt(cfg, 'channel', 'all', 1);
cfg.trials  = ft_getopt(cfg, 'trials',  'all', 1);

if length(varargin)>1 && ~isequal(cfg.trials, 'all')
  ft_error('it is ambiguous to make a subselection of trials while at the same time concatenating multiple data structures')
end

cfg.frequency = ft_getopt(cfg, 'frequency', 'all', 1);
if isnumeric(cfg.frequency) && numel(cfg.frequency)==2 && cfg.frequency(1)==cfg.frequency(2)
  % this is better specified by a single number
  cfg.frequency = cfg.frequency(1);
end

datfield  = fieldnames(varargin{1});
for i=2:length(varargin)
  % only consider fields that are present in all inputs
  datfield = intersect(datfield, fieldnames(varargin{i}));
end
datfield  = setdiff(datfield, {'label' 'labelcmb'});  % these fields will be used for selection, but are not treated as data fields
datfield  = setdiff(datfield, {'dim'});               % not used for selection, also not treated as data field
datfield  = setdiff(datfield, ignorefields('selectdata'));
orgdim1   = datfield(~cellfun(@isempty, regexp(datfield, 'label$')) & cellfun(@isempty, regexp(datfield, '^csd'))); % xxxlabel, with the exception of csdlabel
datfield  = setdiff(datfield, orgdim1);
datfield  = datfield(:)';
orgdim1   = datfield(~cellfun(@isempty, regexp(datfield, 'dimord$'))); % xxxdimord
datfield  = setdiff(datfield, orgdim1);
datfield  = datfield(:)';

sel = strcmp(datfield, 'cumtapcnt');
if any(sel)
  % move this field to the end, as it is needed to make the selections in the other fields
  datfield(sel) = [];
  datfield = [datfield {'cumtapcnt'}];
end

orgdim2 = cell(size(orgdim1));
for i=1:length(orgdim1)
  orgdim2{i} = varargin{1}.(orgdim1{i});
end

dimord = cell(size(datfield));
for i=1:length(datfield)
  dimord{i} = getdimord(varargin{1}, datfield{i});
end

% do not consider fields of which the dimensions are unknown
% sel = cellfun(@isempty, regexp(dimord, 'unknown'));
% for i=find(~sel)
%   fprintf('not including "%s" in selection\n', datfield{i});
% end
% datfield = datfield(sel);
% dimord   = dimord(sel);

% determine all dimensions that are present in all data fields
dimtok = {};
for i=1:length(datfield)
  dimtok = cat(2, dimtok, tokenize(dimord{i}, '_'));
end
dimtok = unique(dimtok);

hasspike   = any(ismember(dimtok, 'spike'));
haspos     = any(ismember(dimtok, {'pos' '{pos}'}));
haschan    = any(ismember(dimtok, {'chan' '{chan}'}));
haschancmb = any(ismember(dimtok, 'chancmb'));
hasfreq    = any(ismember(dimtok, 'freq'));
hastime    = any(ismember(dimtok, 'time'));
hasrpt     = any(ismember(dimtok, {'rpt' 'subj' '{rpt}'}));
hasrpttap  = any(ismember(dimtok, 'rpttap'));

if hasspike
  % cfg.latency is used to select individual spikes, not to select from a continuously sampled time axis
  hastime = false;
end

clear dimtok

haspos     = haspos     && isfield(varargin{1}, 'pos');
haschan    = haschan    && isfield(varargin{1}, 'label');
haschancmb = haschancmb && isfield(varargin{1}, 'labelcmb');
hasfreq    = hasfreq    && isfield(varargin{1}, 'freq');
hastime    = hastime    && isfield(varargin{1}, 'time');

% do a sanity check on all input arguments
if haspos,     assert(all(cellfun(@isfield, varargin, repmat({'pos'},      size(varargin)))), 'not all input arguments have a "pos" field'); end
if haschan,    assert(all(cellfun(@isfield, varargin, repmat({'label'},    size(varargin)))), 'not all input arguments have a "label" field'); end
if haschancmb, assert(all(cellfun(@isfield, varargin, repmat({'labelcmb'}, size(varargin)))), 'not all input arguments have a "labelcmb" field'); end
if hasfreq,    assert(all(cellfun(@isfield, varargin, repmat({'freq'},     size(varargin)))), 'not all input arguments have a "freq" field'); end
if hastime,    assert(all(cellfun(@isfield, varargin, repmat({'time'},     size(varargin)))), 'not all input arguments have a "time" field'); end

avgoverpos     = istrue(ft_getopt(cfg, 'avgoverpos',  false)); % at some places it is also referred to as roi (region-of-interest)
avgoverchan    = istrue(ft_getopt(cfg, 'avgoverchan', false));
avgoverchancmb = istrue(ft_getopt(cfg, 'avgoverchancmb', false));
avgoverfreq    = istrue(ft_getopt(cfg, 'avgoverfreq', false));
avgovertime    = istrue(ft_getopt(cfg, 'avgovertime', false));
avgoverrpt     = istrue(ft_getopt(cfg, 'avgoverrpt',  false));

% do a sanity check for the averaging options
if avgoverpos,     assert(haspos,     'there are no source positions, so averaging is not possible'); end
if avgoverchan,    assert(haschan,    'there is no channel dimension, so averaging is not possible'); end
if avgoverchancmb, assert(haschancmb, 'there are no channel combinations, so averaging is not possible'); end
if avgoverfreq,    assert(hasfreq,    'there is no frequency dimension, so averaging is not possible'); end
if avgovertime,    assert(hastime,    'there is no time dimension, so averaging over time is not possible'); end
if avgoverrpt,     assert(hasrpt||hasrpttap, 'there are no repetitions, so averaging is not possible'); end

% set averaging function
cfg.nanmean = ft_getopt(cfg, 'nanmean', 'no');
if strcmp(cfg.nanmean, 'yes')
  average = @nanmean;
else
  average = @mean;
end

% by default we keep most of the dimensions in the data structure when averaging over them
keepposdim     = istrue(ft_getopt(cfg, 'keepposdim',  true));
keepchandim    = istrue(ft_getopt(cfg, 'keepchandim', true));
keepchancmbdim = istrue(ft_getopt(cfg, 'keepchancmbdim', true));
keepfreqdim    = istrue(ft_getopt(cfg, 'keepfreqdim', true));
keeptimedim    = istrue(ft_getopt(cfg, 'keeptimedim', true));
keeprptdim     = istrue(ft_getopt(cfg, 'keeprptdim', ~avgoverrpt));

if ~keepposdim,     assert(avgoverpos,     'removing a dimension is only possible when averaging'); end
if ~keepchandim,    assert(avgoverchan,    'removing a dimension is only possible when averaging'); end
if ~keepchancmbdim, assert(avgoverchancmb, 'removing a dimension is only possible when averaging'); end
if ~keepfreqdim,    assert(avgoverfreq,    'removing a dimension is only possible when averaging'); end
if ~keeptimedim,    assert(avgovertime,    'removing a dimension is only possible when averaging'); end
if ~keeprptdim,     assert(avgoverrpt,     'removing a dimension is only possible when averaging'); end

% trim the selection to all inputs, rpt and rpttap are dealt with later
if hasspike,   [selspike,   cfg] = getselection_spike  (cfg, varargin{:}); end
if haspos,     [selpos,     cfg] = getselection_pos    (cfg, varargin{:}, cfg.tolerance, cfg.select); end
if haschan,    [selchan,    cfg] = getselection_chan   (cfg, varargin{:}, cfg.select); end
if haschancmb, [selchancmb, cfg] = getselection_chancmb(cfg, varargin{:}, cfg.select); end
if hasfreq,    [selfreq,    cfg] = getselection_freq   (cfg, varargin{:}, cfg.tolerance, cfg.select); end
if hastime,    [seltime,    cfg] = getselection_time   (cfg, varargin{:}, cfg.tolerance, cfg.select); end

% this is to keep track of all fields that should be retained in the output
keepfield = datfield;

for i=1:numel(varargin)

  for j=1:numel(datfield)
    dimtok = tokenize(dimord{j}, '_');

    % the rpt selection should only work with a single data argument
    % in case tapers were kept, selrpt~=selrpttap, otherwise selrpt==selrpttap
    [selrpt{i}, dum, rptdim{i}, selrpttap{i}] = getselection_rpt(cfg, varargin{i}, dimord{j});

    % check for the presence of each dimension in each datafield
    fieldhasspike   = ismember('spike',   dimtok);
    fieldhaspos     = ismember('pos',     dimtok) || ismember('{pos}', dimtok);
    fieldhaschan    = (ismember('chan',    dimtok) || ismember('{chan}', dimtok)) && isfield(varargin{1}, 'label');
    fieldhaschancmb = ismember('chancmb', dimtok);
    fieldhastime    = ismember('time',    dimtok) && ~hasspike;
    fieldhasfreq    = ismember('freq',    dimtok);
    fieldhasrpt     = ismember('rpt',     dimtok) | ismember('subj', dimtok) | ismember('{rpt}', dimtok);
    fieldhasrpttap  = ismember('rpttap',  dimtok);

    % cfg.latency is used to select individual spikes, not to select from a continuously sampled time axis

    if fieldhasspike,   varargin{i} = makeselection(varargin{i}, datfield{j}, dimtok, find(strcmp(dimtok,'spike')),             selspike{i},   false,           'intersect', average); end
    if fieldhaspos,     varargin{i} = makeselection(varargin{i}, datfield{j}, dimtok, find(ismember(dimtok, {'pos', '{pos}'})), selpos{i},     avgoverpos,      cfg.select, average);  end
    if fieldhaschan,    varargin{i} = makeselection(varargin{i}, datfield{j}, dimtok, find(ismember(dimtok,{'chan' '{chan}'})), selchan{i},    avgoverchan,     cfg.select, average);  end
    if fieldhaschancmb, varargin{i} = makeselection(varargin{i}, datfield{j}, dimtok, find(strcmp(dimtok,'chancmb')),           selchancmb{i}, avgoverchancmb,  cfg.select, average);  end
    if fieldhastime,    varargin{i} = makeselection(varargin{i}, datfield{j}, dimtok, find(strcmp(dimtok,'time')),              seltime{i},    avgovertime,     cfg.select, average);  end
    if fieldhasfreq,    varargin{i} = makeselection(varargin{i}, datfield{j}, dimtok, find(strcmp(dimtok,'freq')),              selfreq{i},    avgoverfreq,     cfg.select, average);  end
    if fieldhasrpt,     varargin{i} = makeselection(varargin{i}, datfield{j}, dimtok, rptdim{i},                                selrpt{i},     avgoverrpt,      'intersect', average); end
    if fieldhasrpttap,  varargin{i} = makeselection(varargin{i}, datfield{j}, dimtok, rptdim{i},                                selrpttap{i},  avgoverrpt,      'intersect', average); end

    % update the fields that should be kept in the structure as a whole
    % and update the dimord for this specific datfield
    keepdim = true(size(dimtok));

    if avgoverchan && ~keepchandim
      keepdim(strcmp(dimtok, 'chan')) = false;
      keepfield = setdiff(keepfield, 'label');
    else
      keepfield = [keepfield 'label'];
    end

    if avgoverchancmb && ~keepchancmbdim
      keepdim(strcmp(dimtok, 'chancmb')) = false;
      keepfield = setdiff(keepfield, 'labelcmb');
    else
      keepfield = [keepfield 'labelcmb'];
    end

    if avgoverfreq && ~keepfreqdim
      keepdim(strcmp(dimtok, 'freq')) = false;
      keepfield = setdiff(keepfield, 'freq');
    else
      keepfield = [keepfield 'freq'];
    end

    if avgovertime && ~keeptimedim
      keepdim(strcmp(dimtok, 'time')) = false;
      keepfield = setdiff(keepfield, 'time');
    else
      keepfield = [keepfield 'time'];
    end

    if avgoverpos && ~keepposdim
      keepdim(strcmp(dimtok, 'pos'))   = false;
      keepdim(strcmp(dimtok, '{pos}')) = false;
      keepdim(strcmp(dimtok, 'dim'))   = false;
      keepfield = setdiff(keepfield, {'pos' '{pos}' 'dim'});
    elseif avgoverpos && keepposdim
      keepfield = setdiff(keepfield, {'dim'}); % this should be removed anyway
    else
      keepfield = [keepfield {'pos' '{pos}' 'dim'}];
    end

    if avgoverrpt && ~keeprptdim
      keepdim(strcmp(dimtok, 'rpt'))    = false;
      keepdim(strcmp(dimtok, 'rpttap')) = false;
      keepdim(strcmp(dimtok, 'subj'))   = false;
    end

    % update the sampleinfo, if possible, and needed
    if strcmp(datfield{j}, 'sampleinfo') && ~isequal(cfg.latency, 'all')
      if iscell(seltime{i}) && numel(seltime{i})==size(varargin{i}.sampleinfo,1)
        for k = 1:numel(seltime{i})
          varargin{i}.sampleinfo(k,:) = varargin{i}.sampleinfo(k,1) - 1 + seltime{i}{k}([1 end]);
        end
      elseif ~iscell(seltime{i}) && ~isempty(seltime{i}) && ~all(isnan(seltime{i}))
        nrpt       = size(varargin{i}.sampleinfo,1);
        seltime{i} = seltime{i}(:)';
        varargin{i}.sampleinfo = varargin{i}.sampleinfo(:,[1 1]) - 1 + repmat(seltime{i}([1 end]),nrpt,1);
      end
    end

    varargin{i}.(datfield{j}) = squeezedim(varargin{i}.(datfield{j}), ~keepdim);

  end % for datfield

  % also update the fields that describe the dimensions, time/freq/pos have been dealt with as data
  if haschan,    varargin{i} = makeselection_chan   (varargin{i}, selchan{i}, avgoverchan); end % update the label field
  if haschancmb, varargin{i} = makeselection_chancmb(varargin{i}, selchancmb{i}, avgoverchancmb); end % update the labelcmb field

end % for varargin

if strcmp(cfg.select, 'union')
  % create the union of the descriptive axes
  if haspos,      varargin = makeunion(varargin, 'pos'); end
  if haschan,     varargin = makeunion(varargin, 'label'); end
  if haschancmb,  varargin = makeunion(varargin, 'labelcmb'); end
  if hastime,     varargin = makeunion(varargin, 'time'); end
  if hasfreq,     varargin = makeunion(varargin, 'freq'); end
end

% remove all fields from the data structure that do not pertain to the selection
sel = strcmp(keepfield, '{pos}'); if any(sel), keepfield(sel) = {'pos'}; end
sel = strcmp(keepfield, 'chan');  if any(sel), keepfield(sel) = {'label'}; end
sel = strcmp(keepfield, 'chancmb');  if any(sel), keepfield(sel) = {'labelcmb'}; end

if avgoverrpt
  % these are invalid after averaging
  keepfield = setdiff(keepfield, {'cumsumcnt' 'cumtapcnt' 'trialinfo' 'sampleinfo'});
end

if avgovertime
  % these are invalid after averaging or making a latency selection
  keepfield = setdiff(keepfield, {'sampleinfo'});
end

for i=1:numel(varargin)
  varargin{i} = keepfields(varargin{i}, [keepfield ignorefields('selectdata')']);
end

% restore the original dimord fields in the data
for i=1:length(orgdim1)
  dimtok = tokenize(orgdim2{i}, '_');

  % using a setdiff may result in double occurrences of chan and pos to
  % disappear, so this causes problems as per bug 2962
  % if ~keeprptdim, dimtok = setdiff(dimtok, {'rpt' 'rpttap' 'subj'}); end
  % if ~keepposdim, dimtok = setdiff(dimtok, {'pos' '{pos}'}); end
  % if ~keepchandim, dimtok = setdiff(dimtok, {'chan'}); end
  % if ~keepfreqdim, dimtok = setdiff(dimtok, {'freq'}); end
  % if ~keeptimedim, dimtok = setdiff(dimtok, {'time'}); end
  if ~keeprptdim, dimtok = dimtok(~ismember(dimtok, {'rpt' 'rpttap' 'subj'})); end
  if ~keepposdim, dimtok = dimtok(~ismember(dimtok, {'pos' '{pos}'}));         end
  if ~keepchandim, dimtok = dimtok(~ismember(dimtok, {'chan'})); end
  if ~keepfreqdim, dimtok = dimtok(~ismember(dimtok, {'freq'})); end
  if ~keeptimedim, dimtok = dimtok(~ismember(dimtok, {'time'})); end

  dimord = sprintf('%s_', dimtok{:});
  dimord = dimord(1:end-1); % remove the trailing _
  for j=1:length(varargin)
    varargin{j}.(orgdim1{i}) = dimord;
  end
end

% restore the source.avg field, this keeps the output reasonably consistent with the
% old-style source representation of the input
if strcmp(dtype, 'source') && ~isempty(restoreavg)
  for i=1:length(varargin)
    varargin{i}.avg = keepfields(varargin{i}, restoreavg);
    varargin{i}     = removefields(varargin{i}, restoreavg);
  end
end

varargout = varargin;

ft_postamble debug

ft_postamble previous varargin
ft_postamble provenance varargout
ft_postamble history varargout
ft_postamble savevar varargout

% the varargout variable can be cleared when written to outputfile
if exist('varargout', 'var') && ft_nargout>numel(varargout)
  % also return the input cfg with the combined selection over all input data structures
  varargout{end+1} = cfg;
end

end % function ft_selectdata

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTIONS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function data = makeselection(data, datfield, dimtok, seldim, selindx, avgoverdim, selmode, average)

if numel(seldim) > 1
  for k = 1:numel(seldim)
    data = makeselection(data, datfield, dimtok, seldim(k), selindx, avgoverdim, selmode, average);
  end
  return;
end

if isnumeric(data.(datfield))
  if isrow(data.(datfield)) && seldim==1
    if length(dimtok)==1
      seldim = 2; % switch row and column
    end
  elseif iscolumn(data.(datfield)) && seldim==2
    if length(dimtok)==1
      seldim = 1; % switch row and column
    end
  end
elseif iscell(data.(datfield))
  if isrow(data.(datfield){1}) && seldim==2
    if length(dimtok)==2
      seldim = 3; % switch row and column
    end
  elseif iscolumn(data.(datfield){1}) && seldim==3
    if length(dimtok)==2
      seldim = 2; % switch row and column
    end
  end
end

% an empty selindx means that nothing(!) should be selected and hence everything should be removed, which is different than keeping everything
% the selindx value of NaN indicates that it is not needed to make a selection

switch selmode
  case 'intersect'
    if iscell(selindx)
      % there are multiple selections in multipe vectors, the selection is in the matrices contained within the cell-array
      for j=1:numel(selindx)
        if ~isempty(selindx{j}) && all(isnan(selindx{j}))
          % no selection needs to be made
        else
          data.(datfield){j} = cellmatselect(data.(datfield){j}, seldim-1, selindx{j}, numel(dimtok)==1);
        end
      end

    else
      % there is a single selection in a single vector
      if ~isempty(selindx) && all(isnan(selindx))
        % no selection needs to be made
      else
        data.(datfield) = cellmatselect(data.(datfield), seldim, selindx, numel(dimtok)==1);
      end
    end

    if avgoverdim
      data.(datfield) = cellmatmean(data.(datfield), seldim, average);
    end

  case 'union'
    if ~isempty(selindx) && all(isnan(selindx))
      % no selection needs to be made
    elseif isequal(seldim,1) && any(strcmp({'time' 'freq'}, datfield))
      % treat this as an exception, because these fields should only be
      % unionized along the second dimension, so here also no selection
      % needs to be made
    else
      tmp = data.(datfield);
      siz = size(tmp);
      siz(seldim) = numel(selindx);
      data.(datfield) = nan(siz);
      sel = isfinite(selindx);
      switch seldim
        case 1
          data.(datfield)(sel,:,:,:,:,:) = tmp(selindx(sel),:,:,:,:,:);
        case 2
          data.(datfield)(:,sel,:,:,:,:) = tmp(:,selindx(sel),:,:,:,:);
        case 3
          data.(datfield)(:,:,sel,:,:,:) = tmp(:,:,selindx(sel),:,:,:);
        case 4
          data.(datfield)(:,:,:,sel,:,:) = tmp(:,:,:,selindx(sel),:,:);
        case 5
          data.(datfield)(:,:,:,:,sel,:) = tmp(:,:,:,:,selindx(sel),:);
        case 6
          data.(datfield)(:,:,:,:,:,sel) = tmp(:,:,:,:,:,selindx(sel));
        otherwise
          ft_error('unsupported dimension (%d) for making a selection for %s', seldim, datfield);
      end
    end

    if avgoverdim
      data.(datfield) = average(data.(datfield), seldim);
    end
end % switch

end % function makeselection

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function data = makeselection_chan(data, selchan, avgoverchan)
if isempty(selchan)
  %error('no channels were selected');
  data.label = {};
elseif avgoverchan && all(isnan(selchan))
  str = sprintf('%s, ', data.label{:});
  str = str(1:end-2);
  str = sprintf('mean(%s)', str);
  data.label = {str};
elseif avgoverchan && ~any(isnan(selchan))
  str = sprintf('%s, ', data.label{selchan});
  str = str(1:end-2);
  str = sprintf('mean(%s)', str);
  data.label = {str};                 % remove the last '+'
elseif all(isfinite(selchan))
  data.label = data.label(selchan);
  data.label = data.label(:);
elseif numel(selchan)==1 && any(~isfinite(selchan))
  % do nothing
elseif numel(selchan)>1  && any(~isfinite(selchan))
  tmp = cell(numel(selchan),1);
  for k = 1:numel(tmp)
    if isfinite(selchan(k))
      tmp{k} = data.label{selchan(k)};
    end
  end
  data.label = tmp;
else
  % this should never happen
  ft_error('cannot figure out how to select channels');
end
end % function makeselection_chan

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function data = makeselection_chancmb(data, selchancmb, avgoverchancmb)
if isempty(selchancmb)
  ft_error('no channel combinations were selected');
elseif avgoverchancmb && all(isnan(selchancmb))
  % naming the channel combinations becomes ambiguous, but should not
  % suggest that the mean was computed prior to combining
  str1 = sprintf('%s, ', data.labelcmb{:,1});
  str1 = str1(1:end-2);
  % str1 = sprintf('mean(%s)', str1);
  str2 = sprintf('%s, ', data.labelcmb{:,2});
  str2 = str2(1:end-2);
  % str2 = sprintf('mean(%s)', str2);
  data.label = {str1, str2};
elseif avgoverchancmb && ~any(isnan(selchancmb))
  % naming the channel combinations becomes ambiguous, but should not
  % suggest that the mean was computed prior to combining
  str1 = sprintf('%s, ', data.labelcmb{selchancmb,1});
  str1 = str1(1:end-2);
  % str1 = sprintf('mean(%s)', str1);
  str2 = sprintf('%s, ', data.labelcmb{selchancmb,2});
  str2 = str2(1:end-2);
  % str2 = sprintf('mean(%s)', str2);
  data.label = {str1, str2};
elseif all(isfinite(selchancmb))
  data.labelcmb = data.labelcmb(selchancmb,:);
elseif numel(selchancmb)==1 && any(~isfinite(selchancmb))
  % do nothing
elseif numel(selchancmb)>1  && any(~isfinite(selchancmb))
  tmp = cell(numel(selchancmb),2);
  for k = 1:size(tmp,1)
    if isfinite(selchan(k))
      tmp{k,1} = data.labelcmb{selchan(k),1};
      tmp{k,2} = data.labelcmb{selchan(k),2};
    end
  end
  data.labelcmb = tmp;
else
  % this should never happen
  ft_error('cannot figure out how to select channelcombinations');
end
end % function makeselection_chancmb

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [chanindx, cfg] = getselection_chan(cfg, varargin)

selmode  = varargin{end};
ndata    = numel(varargin)-1;
varargin = varargin(1:ndata);

% loop over data once to initialize
chanindx = cell(ndata,1);
label    = cell(1,0);

if ndata==1 && (isequal(cfg.channel, 'all') || isequal(cfg.channel, varargin{1}.label))
  % the loop across data arguments, as well as the expensive calls to
  % FT_CHANNELSELECTION can be avoided if there is only a single data
  % argument and if 'all' channels are to be returned in the output
  label = varargin{1}.label(:);

else
  for k = 1:ndata
    selchannel      = cell(0,1);
    selgrad         = [];
    selelec         = [];
    selopto         = [];
    if isfield(varargin{k}, 'grad') && isfield(varargin{k}.grad, 'type')
      % this makes channel selection more robust, e.g. when using wildcards in cfg.channel
      [selgrad, dum] = match_str(varargin{k}.label, varargin{k}.grad.label);
      selchannel     = cat(1, selchannel, ft_channelselection(cfg.channel, varargin{k}.label(selgrad), varargin{k}.grad.type));
    end
    if isfield(varargin{k}, 'elec') && isfield(varargin{k}.elec, 'type')
      % this makes channel selection more robust, e.g. when using wildcards in cfg.channel
      [selelec, dum] = match_str(varargin{k}.label, varargin{k}.elec.label);
      selchannel     = cat(1, selchannel, ft_channelselection(cfg.channel, varargin{k}.label(selelec), varargin{k}.elec.type));
    end
    if isfield(varargin{k}, 'opto') && isfield(varargin{k}.opto, 'type')
      % this makes channel selection more robust, e.g. when using wildcards in cfg.channel
      [selopto, dum] = match_str(varargin{k}.label, varargin{k}.opto.label);
      selchannel     = cat(1, selchannel, ft_channelselection(cfg.channel, varargin{k}.label(selopto), varargin{k}.opto.type));
    end
    selrest    = setdiff((1:numel(varargin{k}.label))', [selgrad; selelec; selopto]);
    selchannel = cat(1, selchannel, ft_channelselection(cfg.channel, varargin{k}.label(selrest)));
    label      = union(label, selchannel);
  end
  label = label(:);   % ensure that this is a column array

  % this call to match_str ensures that that labels are always in the
  % order of the first input argument see bug_2917, but also temporarily keep
  % the labels from the other data structures not present in the first one
  % (in case selmode = 'union')
  [ix, iy] = match_str(varargin{1}.label, label);
  label1   = varargin{1}.label(:); % ensure column array
  label    = [label1(ix); label(setdiff(1:numel(label),iy))];

end % if ndata==1 and all channels are to be returned

indx = nan+zeros(numel(label), ndata);
for k = 1:ndata
  [ix, iy] = match_str(label, varargin{k}.label);
  indx(ix,k) = iy;
end

switch selmode
  case 'intersect'
    sel      = sum(isfinite(indx),2)==ndata;
    indx     = indx(sel,:);
    label    = varargin{1}.label(indx(:,1));
  case 'union'
    % don't do a subselection
  otherwise
    ft_error('invalid value for cfg.select');
end % switch

ok = false(size(indx,1),1);
for k = 1:ndata
  % loop through the columns to preserve the order of the channels, where
  % the order of the input arguments determines the final order
  ix = find(~ok);
  [srt,srtix] = sort(indx(ix,k));
  indx(ix,:)  = indx(ix(srtix),:);
  ok = ok | isfinite(indx(:,k));
end

for k = 1:ndata
  % do a sanity check on double occurrences
  if numel(unique(indx(isfinite(indx(:,k)),k)))<sum(isfinite(indx(:,k)))
    ft_error('the selection of channels across input arguments leads to double occurrences');
  end
  chanindx{k} = indx(:,k);
end

for k = 1:ndata
  if isequal(chanindx{k}, (1:numel(varargin{k}.label))')
    % no actual selection is needed for this data structure
    chanindx{k} = nan;
  end
end

cfg.channel = label;

end % function getselection_chan

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [chancmbindx, cfg] = getselection_chancmb(cfg, varargin)

selmode  = varargin{end};
ndata    = numel(varargin)-1;
varargin = varargin(1:ndata);

chancmbindx = cell(ndata,1);

if ~isfield(cfg, 'channelcmb')
  for k=1:ndata
    % the nan return value specifies that no selection was specified
    chancmbindx{k} = nan;
  end

else

  switch selmode
    case 'intersect'
      haslabel = false(ndata,1);
      for k=1:ndata
        haslabel = isfield(varargin{k}, 'label');
      end
      if all(haslabel)
        for k=1:ndata
          cfg.channelcmb = ft_channelcombination(cfg.channelcmb, varargin{k}.label);
        end
        cfgcmb = cellfun(@sprintf,repmat({'%s_%s'},size(cfg.channelcmb,1),1),cfg.channelcmb(:,1),cfg.channelcmb(:,2),'UniformOutput',false);
      elseif all(~haslabel)
        % the data already has labelcmb, and thus needs a slightly different way to
        % preset the cfg.channelcmb
        chancmb = cellfun(@sprintf,repmat({'%s_%s'},size(varargin{1}.labelcmb,1),1),varargin{1}.labelcmb(:,1),varargin{1}.labelcmb(:,2),'UniformOutput',false);
        for k=2:ndata
          tmp = cellfun(@sprintf,repmat({'%s_%s'},size(varargin{k}.labelcmb,1),1),varargin{k}.labelcmb(:,1),varargin{k}.labelcmb(:,2),'UniformOutput',false);
          chancmb = intersect(chancmb, tmp);
        end
        cfgcmb = unique(chancmb);

        if isequal(cfg.channelcmb, {'all' 'all'})
          % nothing needed here
        else
          cfg.channelcmb = cellfun(@sprintf,repmat({'%s_%s'},size(cfg.channelcmb,1),1),cfg.channelcmb(:,1),cfg.channelcmb(:,2),'UniformOutput',false);

          cfgcmb = intersect(cfg.channelcmb, cfgcmb);
        end

      else
        ft_error('a combination of data with and without label field is not possible');
      end

      for k=1:ndata
        datcmb = cellfun(@sprintf,repmat({'%s_%s'},size(varargin{k}.labelcmb,1),1),varargin{k}.labelcmb(:,1),varargin{k}.labelcmb(:,2),'UniformOutput',false);

        % return the order according to the (joint) configuration, not according to the (individual) data
        % FIXME this should adhere to the general code guidelines, where
        % the order returned will be according to the first data argument!
        [dum, chancmbindx{k}] = match_str(cfgcmb, datcmb);
      end

    case 'union'
      % FIXME this is not yet implemented
      ft_error('union of channel combination is not yet supported');

    otherwise
      ft_error('invalid value for cfg.select');
  end % switch

end

end % function getselection_chancmb

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [spikeindx, cfg] = getselection_spike(cfg, varargin)
% possible specifications are
% cfg.latency = string -> 'all'
% cfg.latency = [beg end]
% cfg.trials  = string -> 'all'
% cfg.trials  = vector with indices

ndata    = numel(varargin);
varargin = varargin(1:ndata);

if isequal(cfg.latency, 'all') && isequal(cfg.trials, 'all')
  spikeindx = cell(1,ndata);
  for i=1:ndata
    spikeindx{i} = num2cell(nan(1, length(varargin{i}.time)));
  end
  return
end

trialbeg = varargin{1}.trialtime(:,1);
trialend = varargin{1}.trialtime(:,2);
for i=2:ndata
  trialbeg = cat(1, trialbeg, varargin{1}.trialtime(:,1));
  trialend = cat(1, trialend, varargin{1}.trialtime(:,2));
end

% convert string into a numeric selection
if ischar(cfg.latency)
  switch cfg.latency
    case 'all'
      cfg.latency = [-inf inf];
    case 'maxperiod'
      cfg.latency = [min(trialbeg) max(trialend)];
    case 'minperiod'
      cfg.latency = [max(trialbeg) min(trialend)];
    case 'prestim'
      cfg.latency = [min(trialbeg) 0];
    case 'poststim'
      cfg.latency = [0 max(trialend)];
    otherwise
      ft_error('incorrect specification of cfg.latency');
  end % switch
end

spikeindx = cell(1,ndata);
for i=1:ndata
  nchan = length(varargin{i}.time);
  spikeindx{i} = cell(1,nchan);
  for j=1:nchan
    selbegtime = varargin{i}.time{j}>=cfg.latency(1);
    selendtime = varargin{i}.time{j}<=cfg.latency(2);
    if isequal(cfg.trials, 'all')
      seltrial = true(size(varargin{i}.trial{j}));
    else
      seltrial = ismember(varargin{i}.trial{j}, cfg.trials);
    end
    spikeindx{i}{j} = find(selbegtime & selendtime & seltrial);
  end
end

end % function getselection_spiketime

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [timeindx, cfg] = getselection_time(cfg, varargin)
% possible specifications are
% cfg.latency = value     -> can be 'all'
% cfg.latency = [beg end]

if ft_datatype(varargin{1}, 'spike')
  ft_error('latency selection in spike data is not supported')
end

selmode  = varargin{end};
tol      = varargin{end-1};
ndata    = numel(varargin)-2;
varargin = varargin(1:ndata);

if isequal(cfg.latency, 'all') && iscell(varargin{1}.time)
  % for raw data this means that all trials should be selected as they are
  % for timelock/freq data it is still needed to make the intersection between data arguments
  timeindx = cell(1,ndata);
  for i=1:ndata
    % the nan return value specifies that no selection was specified
    timeindx{i} = num2cell(nan(1, length(varargin{i}.time)));
  end
  return
end

% if there is a single timelock/freq input, there is one time vector
% if there are multiple timelock/freq inputs, there are multiple time vectors
% if there is a single raw input, there are multiple time vectors
% if there are multiple raw inputs, there are multiple time vectors

% collect all time axes in one large cell-array
alltimecell = {};
if iscell(varargin{1}.time)
  for k = 1:ndata
    alltimecell = [alltimecell varargin{k}.time{:}];
  end
else
  for k = 1:ndata
    alltimecell = [alltimecell {varargin{k}.time}];
  end
end

% the nan return value specifies that no selection was specified
timeindx = repmat({nan}, size(alltimecell));

% loop over data once to determine the union of all time axes
alltimevec = zeros(1,0);
for k = 1:length(alltimecell)
  alltimevec = union(alltimevec, round(alltimecell{k}/tol)*tol);
end

indx = nan(numel(alltimevec), numel(alltimecell));
for k = 1:numel(alltimecell)
  [dum, ix, iy] = intersect(alltimevec, round(alltimecell{k}/tol)*tol);
  indx(ix,k) = iy;
end

if iscell(varargin{1}.time) && ~isequal(cfg.latency, 'minperiod')
  % if the input data arguments are of type 'raw', temporarily set the
  % selmode to union, otherwise the potentially different length trials
  % will be truncated to the shortest epoch, prior to latency selection.
  selmode = 'union';
elseif ischar(cfg.latency) && strcmp(cfg.latency, 'minperiod')
  % enforce intersect
  selmode = 'intersect';
end
switch selmode
  case 'intersect'
    sel        = sum(isfinite(indx),2)==numel(alltimecell);
    indx       = indx(sel,:);
    alltimevec = alltimevec(sel);
  case 'union'
    % don't do a subselection
  otherwise
    ft_error('invalid value for cfg.select');
end

% Note that cfg.toilim handling has been removed, as it was renamed to cfg.latency

% convert a string selection into a numeric selection
if ischar(cfg.latency)
  switch cfg.latency
    case {'all' 'maxperlen' 'maxperiod'}
      cfg.latency = [min(alltimevec) max(alltimevec)];
    case 'prestim'
      cfg.latency = [min(alltimevec) 0];
    case 'poststim'
      cfg.latency = [0 max(alltimevec)];
    case 'minperiod'
      % the time vector has been pruned above
      cfg.latency = [min(alltimevec) max(alltimevec)];
    otherwise
      ft_error('incorrect specification of cfg.latency');
  end % switch
end

% deal with numeric selection
if isempty(cfg.latency)
  for k = 1:numel(alltimecell)
    % FIXME I do not understand this
    % this signifies that all time bins are deselected and should be removed
    timeindx{k} = [];
  end

elseif numel(cfg.latency)==1
  % this single value should be within the time axis of each input data structure
  if numel(alltimevec)>1
    tbin = nearest(alltimevec, cfg.latency, true, true); % determine the numerical tolerance
  else
    tbin = nearest(alltimevec, cfg.latency, true, false); % don't consider tolerance
  end
  cfg.latency = alltimevec(tbin);

  for k = 1:ndata
    timeindx{k} = indx(tbin, k);
  end

elseif numel(cfg.latency)==2
  % the [min max] range can be specifed with +inf or -inf, but should
  % at least partially overlap with the time axis of the input data
  mintime = min(alltimevec);
  maxtime = max(alltimevec);
  if all(cfg.latency<mintime) || all(cfg.latency>maxtime)
    ft_error('the selected time range falls outside the time axis in the data');
  end
  tbeg = nearest(alltimevec, cfg.latency(1), false, false);
  tend = nearest(alltimevec, cfg.latency(2), false, false);
  cfg.latency = alltimevec([tbeg tend]);

  for k = 1:numel(alltimecell)
    timeindx{k} = indx(tbeg:tend, k);
    % if the input data arguments are of type 'raw', the non-finite values
    % need to be removed from the individual cells to ensure correct
    % behavior
    if iscell(varargin{1}.time)
      timeindx{k} = timeindx{k}(isfinite(timeindx{k}));
    end
  end

elseif size(cfg.latency,2)==2
  % this may be used for specification of the computation, not for data selection

else
  ft_error('incorrect specification of cfg.latency');
end

for k = 1:numel(alltimecell)
  if ~iscell(varargin{1}.time)
    if isequal(timeindx{k}(:)', 1:length(alltimecell{k}))
      % no actual selection is needed for this data structure
      timeindx{k} = nan;
    end
  else
    % if the input data arguments are of type 'raw', they need to be
    % handled differently, because the individual trials can be of
    % different length
  end
end

if iscell(varargin{1}.time)
  % split all time axes again over the different input raw data structures
  dum = cell(1,ndata);
  for k = 1:ndata
    sel = 1:length(varargin{k}.time);
    dum{k} = timeindx(sel); % get the first selection
    timeindx(sel) = []; % remove the first selection
  end
  timeindx = dum;
else
  % no splitting is needed, each input data structure has one selection
end

end % function getselection_time

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [freqindx, cfg] = getselection_freq(cfg, varargin)
% possible specifications are
% cfg.frequency = value     -> can be 'all'
% cfg.frequency = [beg end]

selmode  = varargin{end};
tol      = varargin{end-1};
ndata    = numel(varargin)-2;
varargin = varargin(1:ndata);

% loop over data once to initialize
freqindx = cell(ndata,1);
freqaxis = zeros(1,0);
for k = 1:ndata
  % the nan return value specifies that no selection was specified
  freqindx{k} = nan;

  % update the axis along which the frequencies are defined
  freqaxis = union(freqaxis, round(varargin{k}.freq(:)/tol)*tol);
end

indx = nan+zeros(numel(freqaxis), ndata);
for k = 1:ndata
  [dum, ix, iy] = intersect(freqaxis, round(varargin{k}.freq(:)/tol)*tol);
  indx(ix,k) = iy;
end

switch selmode
  case 'intersect'
    sel      = sum(isfinite(indx),2)==ndata;
    indx     = indx(sel,:);
    freqaxis = varargin{1}.freq(indx(:,1));
  case 'union'
    % don't do a subselection
  otherwise
    ft_error('invalid value for cfg.select');
end

if isfield(cfg, 'frequency')
  % deal with string selection
  % some of these do not make sense, but are here for consistency with ft_multiplotER
  if ischar(cfg.frequency)
    if strcmp(cfg.frequency, 'all')
      cfg.frequency = [min(freqaxis) max(freqaxis)];
    elseif strcmp(cfg.frequency, 'maxmin')
      cfg.frequency = [min(freqaxis) max(freqaxis)]; % the same as 'all'
    elseif strcmp(cfg.frequency, 'minzero')
      cfg.frequency = [min(freqaxis) 0];
    elseif strcmp(cfg.frequency, 'maxabs')
      cfg.frequency = [-max(abs(freqaxis)) max(abs(freqaxis))];
    elseif strcmp(cfg.frequency, 'zeromax')
      cfg.frequency = [0 max(freqaxis)];
    elseif strcmp(cfg.frequency, 'zeromax')
      cfg.frequency = [0 max(freqaxis)];
    else
      ft_error('incorrect specification of cfg.frequency');
    end
  end

  % deal with numeric selection
  if isempty(cfg.frequency)
    for k = 1:ndata
      % FIXME I do not understand this
      % this signifies that all frequency bins are deselected and should be removed
      freqindx{k} = [];
    end

  elseif numel(cfg.frequency)==1
    % this single value should be within the frequency axis of each input data structure
    if numel(freqaxis)>1
      fbin = nearest(freqaxis, cfg.frequency, true, true); % determine the numerical tolerance
    else
      fbin = nearest(freqaxis, cfg.frequency, true, false); % don't consider tolerance
    end
    cfg.frequency = freqaxis(fbin);

    for k = 1:ndata
      freqindx{k} = indx(fbin,k);
    end

  elseif numel(cfg.frequency)==2
    % the [min max] range can be specifed with +inf or -inf, but should
    % at least partially overlap with the freq axis of the input data
    minfreq = min(freqaxis);
    maxfreq = max(freqaxis);
    if all(cfg.frequency<minfreq) || all(cfg.frequency>maxfreq)
      ft_error('the selected range falls outside the frequency axis in the data');
    end
    fbeg = nearest(freqaxis, cfg.frequency(1), false, false);
    fend = nearest(freqaxis, cfg.frequency(2), false, false);
    cfg.frequency = freqaxis([fbeg fend]);

    for k = 1:ndata
      freqindx{k} = indx(fbeg:fend,k);
    end

  elseif size(cfg.frequency,2)==2
    % this may be used for specification of the computation, not for data selection

  else
    ft_error('incorrect specification of cfg.frequency');
  end
end % if cfg.frequency

for k = 1:ndata
  if isequal(freqindx{k}, 1:length(varargin{k}.freq))
    % the cfg was updated, but no selection is needed for the data
    freqindx{k} = nan;
  end
end

end % function getselection_freq

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [rptindx, cfg, rptdim, rpttapindx] = getselection_rpt(cfg, varargin)
% this should deal with cfg.trials

dimord   = varargin{end};
ndata    = numel(varargin)-1;
data     = varargin{1:ndata}; % this syntax ensures that it will only work on a single data input

dimtok = tokenize(dimord, '_');
rptdim = find(strcmp(dimtok, '{rpt}') | strcmp(dimtok, 'rpt') | strcmp(dimtok, 'rpttap') | strcmp(dimtok, 'subj'));

if isequal(cfg.trials, 'all')
  rptindx    = nan; % the nan return value specifies that no selection was specified
  rpttapindx = nan; % the nan return value specifies that no selection was specified

elseif isempty(rptdim)
  % FIXME should [] not mean that none of the trials is to be selected?
  rptindx    = nan; % the nan return value specifies that no selection was specified
  rpttapindx = nan; % the nan return value specifies that no selection was specified

else
  rptindx = ft_getopt(cfg, 'trials');

  if islogical(rptindx)
    % convert from booleans to indices
    rptindx = find(rptindx);
  end

  rptindx = unique(sort(rptindx));

  if strcmp(dimtok{rptdim}, 'rpttap') && isfield(data, 'cumtapcnt')
    % there are tapers in the data

    % determine for each taper to which trial it belongs
    nrpt = size(data.cumtapcnt, 1);
    taper = zeros(nrpt, 1);
    sumtapcnt = cumsum([0; data.cumtapcnt(:)]);
    begtapcnt = sumtapcnt(1:end-1)+1;
    endtapcnt = sumtapcnt(2:end);
    for i=1:nrpt
      taper(begtapcnt(i):endtapcnt(i)) = i;
    end
    rpttapindx = find(ismember(taper, rptindx));

  else
    % there are no tapers in the data
    rpttapindx = rptindx;
  end
end

end % function getselection_rpt

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [posindx, cfg] = getselection_pos(cfg, varargin)
% possible specifications are <none>

ndata   = numel(varargin)-2;
tol     = varargin{end-1}; % FIXME this is still ignored
selmode = varargin{end};   % FIXME this is still ignored
data    = varargin(1:ndata);

for i=1:ndata
  if ~isequal(varargin{i}.pos, varargin{1}.pos)
    % FIXME it would be possible here to make a selection based on intersect or union
    ft_error('not yet implemented');
  end
end

if strcmp(cfg.select, 'union')
  % FIXME it would be possible here to make a selection based on intersect or union
  ft_error('not yet implemented');
end

for i=1:ndata
  posindx{i} = nan;    % the nan return value specifies that no selection was specified
end
end % function getselection_pos

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function x = squeezedim(x, dim)
siz = size(x);
for i=(numel(siz)+1):numel(dim)
  % all trailing singleton dimensions have length 1
  siz(i) = 1;
end
if isvector(x) && ~(isrow(x) && dim(1) && numel(x)>1)
  % there is no harm to keep it as it is, unless the data matrix is 1xNx1x1
elseif istable(x)
  % there is no harm to keep it as it is
else
  x = reshape(x, [siz(~dim) 1]);
end
end % function squeezedim

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function x = makeunion(x, field)
old = cellfun(@getfield, x, repmat({field}, size(x)), 'uniformoutput', false);
if iscell(old{1})
  % empty is indicated to represent missing value for a cell-array (label, labelcmb)
  new = old{1};
  for i=2:length(old)
    sel = ~cellfun(@isempty, old{i});
    new(sel) = old{i}(sel);
  end
else
  % nan is indicated to represent missing value for a numeric array (time, freq, pos)
  new = old{1};
  for i=2:length(old)
    sel = ~isnan(old{i});
    new(sel) = old{i}(sel);
  end
end
x = cellfun(@setfield, x, repmat({field}, size(x)), repmat({new}, size(x)), 'uniformoutput', false);
end % function makeunion

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION to make a selextion in data representations like {pos}_ori_time
% FIXME this will fail for {xxx_yyy}_zzz
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function x = cellmatselect(x, seldim, selindx, maybevector)
if nargin<4
  % some fields are a vector with an unspecified singleton dimension, these can be transposed
  % if the singleton dimension represents something explicit, they should not be transposed
  % they might for example represent a single trial, or a single channel
  maybevector = true;
end
if iscell(x)
  if seldim==1
    x = x(selindx);
  else
    for i=1:numel(x)
      if isempty(x{i})
        continue
      end
      switch seldim
        case 2
          if maybevector && isvector(x{i})
            % sometimes the data is 1xN, whereas the dimord describes only the first dimension
            % in this case a row and column vector can be interpreted as equivalent
            x{i} = x{i}(selindx);
          elseif istable(x)
            % multidimensional indexing is not supported
            x{i} = x{i}(selindx,:);
          else
            x{i} = x{i}(selindx,:,:,:,:);
          end
        case 3
          x{i} = x{i}(:,selindx,:,:,:);
        case 4
          x{i} = x{i}(:,:,selindx,:,:);
        case 5
          x{i} = x{i}(:,:,:,selindx,:);
        case 6
          x{i} = x{i}(:,:,:,:,selindx);
        otherwise
          ft_error('unsupported dimension (%d) for making a selection', seldim);
      end % switch
    end % for
  end
else
  switch seldim
    case 1
      if maybevector && isvector(x)
        % sometimes the data is 1xN, whereas the dimord describes only the first dimension
        % in this case a row and column vector can be interpreted as equivalent
        x = x(selindx);
      elseif istable(x)
        % multidimensional indexing is not supported
        x = x(selindx,:);
      else
        x = x(selindx,:,:,:,:,:);
      end
    case 2
      x = x(:,selindx,:,:,:,:);
    case 3
      x = x(:,:,selindx,:,:,:);
    case 4
      x = x(:,:,:,selindx,:,:);
    case 5
      x = x(:,:,:,:,selindx,:);
    case 6
      x = x(:,:,:,:,:,selindx);
    otherwise
      ft_error('unsupported dimension (%d) for making a selection', seldim);
  end
end
end % function cellmatselect

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION to take an average in data representations like {pos}_ori_time
% FIXME this will fail for {xxx_yyy}_zzz
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function x = cellmatmean(x, seldim, average)
if iscell(x)
  if seldim==1
    for i=2:numel(x)
      x{1} = x{1} + x{i};
    end
    x = {x{1}/numel(x)};
  else
    for i=1:numel(x)
      x{i} = average(x{i}, seldim-1);
    end % for
  end
elseif istable(x)
  try
    % try to convert to an array, depending on the table content this might fail
    x = average(table2array(x), seldim);
  catch
    % construct an appropriately sized array with NaN values
    s = size(x);
    s(seldim) = 1;
    x = nan(s);
  end
  % convert back to table
  x = array2table(x);
else
  x = average(x, seldim);
end
end % function cellmatmean