function ROI = itkClass2roi(ni, varargin)
% Convert a layer from a nifti segmentation file (created by itkGray) into
% a mrVista Gray ROI
%
% ROI = itkClass2roi(ni, varargin)
%
%   INPUTS:
%       ni: a matrix, a nifti struct, or a path to a nifti file
%       Optional Inputs:
%           spath: a directory in which to save the ROI (if no path, then
%                 don't save; just return the ROI struct)
%           color: a 3-vector or color char (rbgcymkw)
%           comments: string for ROI.comments
%           name: string for ROI.name
%           layer: integer layer number in input matrix to be converted to
%                   ROI (Note that an ITK gray segmentation file can have
%                   any number of layers. We may want to convert only one
%                   of them to an ROI.)
%   OUTPUT:
%       ROI: a mrVista gray-view ROI struct
%
% see nifti2mrVistaAnat.m, itkClasses2roi.m
%
% Example:
%
%   ni =   '3DAnatomy/t1_class.nii';
%   fname = 'LeftWhiteMatter'
%   col = 'm';
%   ROI = itkClass2roi(ni, 'name', fname, 'color', col, 'layer', 2);
%
% April, 2009: JW

error('Obsolete. Use nifti2ROI instead.')

%--------------------------------------------------------------
% variable check
if nargin > 1
    for ii = 1:2:length(varargin)
        switch lower(varargin{ii})
            case {'spath'}
                spath = varargin{ii+1};
            case {'color', 'roicolor'}
                ROI.color = varargin{ii+1} / 255;
            case 'comments'
                ROI.comments = varargin{ii+1};
            case {'name', 'roiname'}
                ROI.name = varargin{ii+1};
            case {'layer'}
                layer = varargin{ii+1};
            case{'roi'}
                ROI = varargin{ii+1};
            otherwise
                warning('Unknown input arg: %s', varargin{ii}); %#ok<WNTAG>
        end
    end
end
%--------------------------------------------------------------

% We assume that the input data structure comes from an ITK-generated
% nifti, which must be permuted for our standard mrVista format
data = nifti2mrVistaAnat(ni);

% Find the indices in the 3d image that have nonzero values
if exist('layer', 'var')
    % if we know the layer, just get voxels with that value
    ROIinds = find(ismember(data,layer));
else
    % otherwise get all the non-zero voxels in the image
    ROIinds = find(data);
end

% Get the XYZ coordinates of these indices
[x y z] = ind2sub(size(data), ROIinds );

% Define a mrVista Gray ROI using these coordinates
ROI.coords   = single([x y z]');
ROI.created  = datestr(now);
ROI.modified = datestr(now);
ROI.viewType = 'Gray';

% Add optional or default ROI fields
if ~isfield(ROI, 'name'),       ROI.name = 'MyROI'; end
if ~isfield(ROI, 'color'),      ROI.color   = 'b'; end
if ~isfield(ROI, 'comments'),   ROI.comments = sprintf('%s: created by anat2roi', ROI.name); end

% If requested, save
if exist('spath', 'var'),
    save(fullfile(spath, ROI.name), 'ROI');
    fprintf('[%s] Saving ROI %s in %s\n', mfilename, ROI.name, spath);
end

% Done
return


function [names, ids, dat, descrips] = concatenate(D, varargin)
% Concatenates Subject-level and Event-level data across all subjects
%
% :Usage:
% ::
%
%    [names ids dat] = concatenate(D, [optional inputs])
%
% :Inputs:
%
%   **D:**
%        canlab_dataset object
%
% :Optional Inputs:
%   **a logical array**
%       a vector of 1/0 values to use as wh_keep
%
%
% :Outputs:
%
%   **names:**
%        cell array of variable names
%
%   **ids:**
%        subject IDs matching data rows in dat
%
%   **dat:**
%        subjects*events x variables matrix
%           - subject number, event number are included
%           - all subject-level and event-level data are included
%           - this format appropriate for, e.g., SAS/HLM
%
%   **descrips:**
%        cell array of variable descriptions
%
% :Examples:
% ::
%
%    [names, ids, flatdat] = concatenate(D);
%    id_numbers = flatdat(:, 1);
%
%    wh_subjs = true(size(D.Subj_Level.id));
%    wh_subjs([13 18 19]) = false;
%    [names, ids, dat] = concatenate(D, wh_subjs);
%

% ..
%     Author and copyright information:
%
%     Copyright (C) 2013 Tor Wager
%
%     This program is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or
%     (at your option) any later version.
%
%     This program is distributed in the hope that it will be useful,
%     but WITHOUT ANY WARRANTY; without even the implied warranty of
%     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%     GNU General Public License for more details.
%
%     You should have received a copy of the GNU General Public License
%     along with this program.  If not, see <http://www.gnu.org/licenses/>.
% ..
%

wh_ids = true(length(D.Subj_Level.id), 1); % select subjects
if length(varargin) > 0, wh_ids = logical(varargin{1}); end
whfind = find(wh_ids);

names = {'subj_num' D.Subj_Level.names{:} 'Event_number' D.Event_Level.names{:}};
descrips = {'subj_num' D.Subj_Level.descrip{:} 'Event_number' D.Event_Level.descrip{:}};

n = length(D.Subj_Level.id(wh_ids));

x = D.Event_Level.data(wh_ids);

for i = 1:n

    e = size(D.Event_Level.data{whfind(i)}, 1);

    ids{i} = repmat(D.Subj_Level.id{whfind(i)}, e, 1);

    %           subj ID                     subj level data              trialID  event data
    x{i} = [repmat(i, e, 1) repmat(D.Subj_Level.data(whfind(i), :), e, 1) (1:e)' x{i}];

end

ids = strvcat(ids{:});

dat = cat(1, x{:});

end % function


function scanFileNameC = scan2imageOut(planC,scanNumV,tmpDirPath,reorientFlag,extn,dataType)

if ~exist('tmpDirPath','var') || isempty(tmpDirPath) || ~exist(tmpDirPath,'dir')
    tmpDirPath = fullfile(getCERRPath, 'ImageRegistration', 'tmpFiles');
end

if ~exist('reorientFlag','var') || isempty(reorientFlag)
    reorientFlag = 1;
end

if ~exist('extn','var') || isempty(extn)
    extn = 'nii';
end

if ~exist('dataType','var')
    dataType = [];
end

for i = 1:numel(scanNumV)
    scanNum = scanNumV(i);
    [scanUniqName, ~] = genScanUniqName(planC,scanNum);
    [affineMat,scan3M_RAS,voxel_size] = getPlanCAffineMat(planC, scanNum, reorientFlag);
    [~,orientationStr,~] = returnViewerAxisLabels(planC,scanNum);
    if ~isempty(dataType)
        eval(['scan3M_RAS = ' dataType '(scan3M_RAS);']);
    end
    qOffset = affineMat(1:3,end)';
    if strcmpi(extn,'nii')
        scanFileName = fullfile(tmpDirPath, ['scan_' num2str(scanNum) '_' scanUniqName '.nii']);
        niiC{i} = vol2nii(scan3M_RAS,affineMat,qOffset,voxel_size,[],scanFileName);
    elseif strcmpi(extn,'nrrd')
        scanFileName = fullfile(tmpDirPath, ['scan_' num2str(scanNum) '_' scanUniqName '.nrrd']);
        vol2nrrd(scan3M_RAS,affineMat,qOffset,voxel_size,orientationStr,scanFileName);
    end
    scanFileNameC{i} = scanFileName;
end


function [translatedModel] = translateDraftReconstruction(model)
% This function translates reaction and metabolite IDs in KBase draft
% reconstructions to VMH nomenclature so the test suite can be performed on
% them. It does not make changes to the reaction content otherwise.
%
% USAGE: [translatedModel] = translateDraftReconstruction(model)
%
% INPUT
% model                     COBRA model structure generated by KBase
%                           reconstruction pipeline
% OUTPUT
% translatedModel:          COBRA model structure with metabolites and
%                           reactions translated to VMH identifiers (but
%                           not ran through refinement pipeline)
%
% AUTHOR
%       - Almut Heinken 09/2020

tol=0.0000001;

% Load metabolite and reaction translation tables
database=loadVMHDatabase;

translateRxns = readtable('ReactionTranslationTable.txt', 'Delimiter', '\t');
translateRxns = [translateRxns.Properties.VariableDescriptions;table2cell(translateRxns)];
translateRxns(find(~strncmp(translateRxns(:,1),'EX_',3)),:)=[];
translateMets = readtable('MetaboliteTranslationTable.txt', 'Delimiter', '\t');
translateMets = [translateMets.Properties.VariableDescriptions;table2cell(translateMets)];

model.mets=strrep(model.mets,'[c0]','[c]');
model.mets=strrep(model.mets,'[e0]','[e]');
model.mets=strrep(model.mets,'_c0','[c]');
model.mets=strrep(model.mets,'_e0','[e]');

for k=1:length(translateMets)
    model.mets = strrep(model.mets,translateMets{k,1}, translateMets{k,2});
end

% special case: two biomass reactions in model (bug in KBase?)
if length(intersect(model.rxns,{'bio1','bio2'}))==2
    model=removeRxns(model,'bio2');
end

% add some reactions that need to be in every reconstruction
essentialRxns={'DM_atp_c_','sink_PGPm1[c]'};

for i=1:length(essentialRxns)
    model = addReaction(model, essentialRxns{i}, 'reactionFormula', database.reactions{find(ismember(database.reactions(:, 1), essentialRxns{i})), 3});
end

% translate while keeping track of original reaction
rxns = strrep(model.rxns, '_c0', '');
rxns = strrep(rxns, '_e0', '');
%     rxns = strrep(rxns, 'R_', '');

for k=1:length(translateRxns)
    rxns = strrep(rxns,translateRxns{k,1}, translateRxns{k,2});
end

% rebuild model to get reactions in order-will get "reactions not
% sorted" error otherwise
[rxnsSorted,oldInd]=sort(rxns);

newModel=struct;
newModel.rxns={};
newModel.rxnNames={};
newModel.genes={};
newModel.grRules={};
newModel.rules={};
newModel.subSystems={};
newModel.lb=zeros(1);
newModel.ub=zeros(1);
newModel.S=[];
newModel.mets={};
newModel.metFormulas={};
newModel.metCharges=[];

for k=1:length(rxnsSorted)
    form=printRxnFormula(model,model.rxns{oldInd(k),1});
    name=model.rxnNames{oldInd(k)};
    lb=model.lb(oldInd(k));
    ub=model.ub(oldInd(k));
    geneRule=model.grRules{oldInd(k)};
    newModel=addReaction(newModel,rxnsSorted{k},'reactionName',name,'reactionFormula',...
        form{1},'lowerBound',lb,'upperBound',ub,'geneRule',geneRule,'subSystem','');
end
model=newModel;

% add metabolite formulas and charges
for k=1:length(model.mets)
    met=strrep(model.mets{k},'[c]','');
    met=strrep(met,'[e]','');
    if ~isempty(find(strcmp(database.metabolites(:,1),met)))
        model.metFormulas{k,1}=database.metabolites{find(strcmp(database.metabolites(:,1),met)),4};
        model.metCharges(k,1)=str2double(database.metabolites{find(strcmp(database.metabolites(:,1),met)),5});
    else
        model.metFormulas{k,1}='X';
        model.metCharges(k,1)=0;
    end
end
model=convertOldStyleModel(model);
model=changeObjective(model,model.rxns{find(strncmp(model.rxns,'bio',3))});

% Delete sink for petidoglycan if not needed
modelTest = removeRxns(model, 'sink_PGPm1[c]');
FBA = optimizeCbModel(modelTest, 'max');
if FBA.f>tol
    model= modelTest;
end

translatedModel=model;

end


classdef DeclusterWindowingMethods < int32
    % methods used for declustering a catalog
    % for a complete list
    % >> enumeration('DeclusterWindowingMethods')
    enumeration
        GardinerKnopoff1974     (1) % Gardener & Knopoff, 1974
        GruenthalPersCom        (2) % Gruenthal pers. communication
        Urhammer1986            (3) % Urhammer, 1986
        % Gruenthal1985           (4) % Gruenthal, 1985 (from Figure)
        % ModifiedYoungs1987Max   (5) % Modified Youngs, 1987 Maximum window
        % ModifiedYoungs1987Min   (6) % Modified Youngs, 1987 Minimum window
    end

    methods(Static)
        function s = description(val)

            switch DeclusterWindowingMethods(val)
                case DeclusterWindowingMethods.GardinerKnopoff1974
                    s = "Gardener & Knopoff, 1974";
                case DeclusterWindowingMethods.GruenthalPersCom
                    s = "Gruenthal pers. communication";
                case DeclusterWindowingMethods.Urhammer1986
                    s = "Urhammer, 1986";
            end
        end
    end
end


function [D,val] = spm_eeg_inv_check(varargin)
% Checks that the EEG/MEG .mat file structure is loaded properly and that
% the particular inversion of interest has been specified
%
% FORMAT [D,val] = spm_eeg_inv_check(D,[val])
% Input:
% S              - data structure or its filename
% val            - model of interest (usually 1)
% Output:
% D              - data structure
% val            - model of interest D.val
%__________________________________________________________________________
% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging

% Jeremie Mattout, Karl Friston
% $Id: spm_eeg_inv_check.m 2861 2009-03-11 18:41:03Z guillaume $

% Check - prompt for file if necessary
%--------------------------------------------------------------------------
if nargin == 0
    [D, sts] = spm_select(1, 'mat', 'Select EEG/MEG mat file');
    if ~sts, D = []; val = 0; return; end
else
    D = varargin{1};
end

D = spm_eeg_load(D);

% Check for inversion
%--------------------------------------------------------------------------
if ~isfield(D,'inv')
    val = 0;
    return
end

% Set val = 1 if only one model
%--------------------------------------------------------------------------
if length(D.inv) == 1
    val   = 1;
    D.val = val;
    return
end

% Check - val
%--------------------------------------------------------------------------
try
    val        = varargin{2};
catch
    try
        val    = D.val;
    catch
        prompt = sprintf('which model (1 to %i)',length(D.inv));
        val    = inputdlg(prompt,'Source reconstruction',1,{'1'});
        val    = eval(val{1});
    end
end
D.val = val;


RELATIONS.COOCCUR = 0;
RELATIONS.SUPPORT1 = -1; % row supports column
RELATIONS.SUPPORT2 = -2; % column supports row
RELATIONS.ATTACH = -3;
RELATIONS.SIDEBYSIDE = -4;
RELATIONS.SYMSUR_START = 10;

NODETYPES.COOCCUR = 2;
NODETYPES.SURROUND = 3;
NODETYPES.SUPPORT = 1;
NODETYPES.ROOM = 4;
NODETYPES.WALL = 5;

%% file locations
data_folder = ['..', filesep, '0-data'];
labelset_folder = [data_folder, filesep, 'labelsets'];

data_filename = [data_folder, filesep,'1-graphs',filesep,'bedroom_graph.mat']; % the dataset generated by step1.
labelsetname = [labelset_folder, filesep, 'bedroomlabelset.mat']; % coarse-grained labels
output_folder = [data_folder, filesep, '2-hierarchies'];
output_filename = [output_folder, filesep, 'bedroom_hierarchies.mat'];


classdef nme_shunt_acp_nln < mp.nme_shunt_acp & mp.nme_wrapper_ac_nln

%   MATPOWER
%   Copyright (c) 2019-2020, Power Systems Engineering Research Center (PSERC)
%   by Ray Zimmerman, PSERC Cornell
%
%   This file is part of MATPOWER.
%   Covered by the 3-clause BSD License (see LICENSE file for details).
%   See https://matpower.org for more info.

    properties
        nme_class = @mp.nme_shunt_acp;
    end

    methods
        function obj = nme_shunt_acp_nln()
            obj@mp.nme_shunt_acp();
            obj.nme_wrapper_ac_nln_init();
        end

        function build_params(obj, nm, dm)
            build_params@mp.nme_shunt_acp(obj, nm, dm);
            obj.build_nln_params(nm, dm);
        end

        function nk = count(obj, dm)
            obj.nme.count(dm);
            nk = count@mp.nme_shunt_acp(obj, dm);
        end
    end     %% methods
end         %% classdef


function [] = exportInputsMustOrder2ToGAMS(model, minFluxesW, maxFluxesW, constrOpt, excludedRxns, mustSetFirstOrder, inputFolder)
% This function exports all the inputs needed to run the GAMS functions to
% find second order Must Sets (`MustUU`, `MustLL`, `MustUL`). The inputs will
% be stored in inputFolder. Some inputs will be exported using GDXMRW and
% others will be exported as simple .txt files.
%
% USAGE:
%
%    exportInputsMustOrder2ToGAMS(model, minFluxesW, maxFluxesW, constrOpt, excludedRxns, mustSetFirstOrder, inputFolder)
%
% INPUTS:
%    model:                   (structure) a metabolic model with at least
%                             the following fields:
%
%                               * .rxns - Reaction IDs in the model
%                               * .mets - Metabolite IDs in the model
%                               * .S -    Stoichiometric matrix (sparse)
%                               * .b -    RHS of `Sv = b` (usually zeros)
%                               * .c -    Objective coefficients
%                               * .lb -   Lower bounds for fluxes
%                               * .ub -   Upper bounds for fluxes
%    minFluxesW:              (double array of size `n_rxns x 1`) minimum
%                             fluxes for each reaction in the model for
%                             wild-type strain. This can be obtained by
%                             running the function `FVAOptForce` e.g.:
%                             `minFluxesW = [-90; -56];`
%    maxFluxesW:              (double array of size `n_rxns x 1`) maximum
%                             fluxes for each reaction in the model for
%                             wild-type strain. This can be obtained by
%                             running the function `FVAOptForce` e.g.:
%                             `maxFluxesW = [90; 56];`
%    constrOpt:               (structure) Structure containing additional
%                             contraints. Include here only reactions
%                             whose flux is fixed, i.e., reactions whose
%                             lower and upper bounds have the same value.
%                             Do not include here reactions whose lower
%                             and upper bounds have different values. Such
%                             contraints should be defined in the lower
%                             and upper bounds of the model. The structure
%                             has the following fields:
%
%                               * .rxnList - Reaction list (cell array)
%                               * .values -  Values for constrained reactions
%                                 (double array). E.g.: `struct('rxnList', {{'EX_gluc', 'R75', 'EX_suc'}}, 'values', [-100, 0, 155.5]');`
%    excludedRxns:            (cell array) Reactions to be excluded to the
%                             `MustUL` set. This could be used to avoid
%                             finding transporters or exchange reactions
%                             in the set. Default: empty.
%    mustSetFirstOrder:       (cell array) Reactions that belong to `MustU`
%                             and `MustL` (first order sets). Default: empty.
%    inputFolder:             (string) Folder where inputs for GAMS
%                             function will be stored
%
% OUTPUTS:
%    Reactions.txt:           (file) File containing the
%                             identifiers for reactions
%    Metabolites.txt:         (file) File containing the
%                             identifiers for metabolites
%    Constrains.txt:          (file) File containing the
%                             identifiers for constrained reactions
%    Excluded.txt:            (file) File containing the
%                             identifiers for excluded reactions. These
%                             reactions will not be considered in when
%                             running `findMustXX.gms` (`XX = UU` or `LL` or `UL`
%                             depending on the case)
%    MustSetFirstOrder.txt:   (file) File containing the
%                             identifiers for reactions in `MustL` and
%                             `MustU`
%    MtoG.gdx:                (file) File containing the
%                             parameters which will be read by GAMS
%                             (lower bounds, upper bounds, stoichiometrix
%                             matrix `S`, minimum and maximun fluxes for
%                             each reaction in the previous step of FVA,
%                             and the values for contrained reactions)
%
% .. Author: - Sebastian Mendoza, May 30th 2017, Center for Mathematical Modeling, University of Chile, snmendoz@uc.cl

if nargin < 8  % inputs handling
    error('Optforce: All inputs must be specified when using exportInputsMustOrder2ToGAMS');
end

%Create a temporaty folder for inputs
if ~exist(inputFolder, 'dir')
   mkdir(inputFolder);
end
current = pwd;
cd(inputFolder);

%Export Sets
exportSetToGAMS(model.rxns, 'Reactions.txt')
exportSetToGAMS(model.mets, 'Metabolites.txt')
exportSetToGAMS(constrOpt.rxnList, 'Constraints.txt')
exportSetToGAMS(excludedRxns, 'Excluded.txt')
exportSetToGAMS(mustSetFirstOrder, 'MustSetFirstOrder.txt')

%Export parameters
s.name = 's';
s.val = full(model.S);
s.type = 'parameter';
s.form = 'full';
s.uels = {model.mets',model.rxns'};

basemin.name = 'basemin';
basemin.val = minFluxesW;
basemin.type = 'parameter';
basemin.form = 'full';
basemin.uels = model.rxns';

basemax.name = 'basemax';
basemax.val = maxFluxesW;
basemax.type = 'parameter';
basemax.form = 'full';
basemax.uels = model.rxns';

lb.name = 'lb';
lb.val = model.lb';
lb.type = 'parameter';
lb.form = 'full';
lb.uels = model.rxns';

ub.name = 'ub';
ub.val = model.ub';
ub.type = 'parameter';
ub.form = 'full';
ub.uels = model.rxns';

b.name = 'b';
b.val = constrOpt.values;
b.type = 'parameter';
b.form = 'full';
b.uels = {'EX_gluc','R75','EX_suc'};

%Using GDXMRW to export inputs
fileName = ['MtoG' setType];
wgdx(fileName, s, basemin, basemax, lb, ub, b)

cd(current);

end


function gotcha_pr2cphd( directory_name, output_file, pass, pol, azimuth_range )
%GOTCHA_PR2CPHD Converts a dataset from the AFRL 2D/3D SAR Volumetric
%public release dataset to CPHD file format.
%
% Usage: gotcha_pr2cphd( directory_name, output_file, pass, pol, azimuth_range)
%
% Inputs: directory_name: Data root directory for the GOTCHA public release
%                         dataset.  Assumes all files are kept in the same
%                         directory structure that the dataset is
%                         distributed with.
%         output_file:    Name of CPHD file to save.
%         pass:           What pass to image (1-8).  Default = 1.
%         pol:            What polarization to image ('HH','HV','VH','VV').
%                         Default = 'HH'.
%         azimuth_range:  Two-element vector of the first and last azimuth
%                         angle (in degrees) to convert to CPHD format.
%                         Default = [1 360] (all available angles).
%
% This function is just a wrapper to CONVERT_TO_CPHD, but allows for
% simpler selection of the pass, polarization, and azimuth range desired.
%
% Author: Wade Schwartzkopf, NGA/IDT
%
% //////////////////////////////////////////
% /// CLASSIFICATION: UNCLASSIFIED       ///
% //////////////////////////////////////////

% Setup default parameters
if nargin<3
    pass = 1;
end
if nargin<4
    pol = 'HH';
end
if nargin<5
    azimuth_range = [1 360];
end

% Calculate which pulse numbers go with which files in directory for
% desired azimuth range.
last_pulse = 0;
pulse_ranges = zeros(azimuth_range(2),2); % First and last pulse number in every file
for az = 1:azimuth_range(end)
    fname = fullfile(directory_name, sprintf('pass%d',pass),pol,...
        sprintf('data_3dsar_pass%d_az%03d_%s.mat',pass,az,pol));
    load(fname); % Loads data into a structure named 'data'
    num_pulses = size(data.fp,2);
    pulse_ranges(az,:) = last_pulse + [1 num_pulses];
    last_pulse = last_pulse + num_pulses;
end
first_pulse = pulse_ranges(azimuth_range(1),1);
last_pulse = pulse_ranges(azimuth_range(2),2);

convert_to_cphdx(fname,output_file,'pulse_indices',first_pulse:last_pulse);
% convert_to_cphd30(fname,output_file,first_pulse:last_pulse); % Old way

end

% //////////////////////////////////////////
% /// CLASSIFICATION: UNCLASSIFIED       ///
% //////////////////////////////////////////


function [vhdr] = read_brainvision_vhdr(filename)

% READ_BRAINVISION_VHDR reads the known items from the BrainVision EEG
% header file and returns them in a structure.
%
% Use as
%   vhdr = read_brainvision_vhdr(filename)
%
% See also READ_BRAINVISION_EEG, READ_BRAINVISION_VMRK

% Copyright (C) 2003-2019, Robert Oostenveld
%
% 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$

vhdr.DataFile         = read_ini(filename, 'DataFile=', '%s');
vhdr.MarkerFile       = read_ini(filename, 'MarkerFile=', '%s');
vhdr.DataFormat       = read_ini(filename, 'DataFormat=', '%s');
vhdr.DataOrientation  = read_ini(filename, 'DataOrientation=', '%s');
vhdr.BinaryFormat     = read_ini(filename, 'BinaryFormat=', '%s');
vhdr.NumberOfChannels = read_ini(filename, 'NumberOfChannels=', '%d');
vhdr.SamplingInterval = read_ini(filename, 'SamplingInterval=', '%f'); % microseconds

if ~isempty(vhdr.NumberOfChannels)
  % only show the warning once
  ft_warning once FieldTrip:fileio:UnknownResolution

  for i=1:vhdr.NumberOfChannels
    chan_str  = sprintf('Ch%d=', i);
    chan_info = read_ini(filename, chan_str, '%s');
    t = tokenize(chan_info, ',');
    vhdr.label{i} = t{1};
    vhdr.reference{i} = t{2};
    resolution = str2num(t{3}); % in microvolt
    if ~isempty(resolution)
      vhdr.resolution(i) = resolution;
    else
      ft_warning('FieldTrip:fileio:UnknownResolution', 'unknown resolution (i.e. recording units) for channel %d in "%s"', i, filename);
      vhdr.resolution(i) = 1;
    end
  end

  % switch the warning state back to the default
  ft_warning on FieldTrip:fileio:UnknownResolution
end

% compute the sampling rate in Hz
vhdr.Fs = 1e6/(vhdr.SamplingInterval);

% the number of samples is unkown to start with
vhdr.nSamples = Inf;

% confirm the names of the .vmrk and .eeg files as specified in the .vhdr file
[p, f, x] = fileparts(filename);
dataFile = fullfile(p, vhdr.DataFile);
[dum, dum, dataExt] = fileparts(vhdr.DataFile);

if ~isempty(vhdr.MarkerFile)
  markerFile = fullfile(p, vhdr.MarkerFile);
  [dum, dum, markerExt] = fileparts(vhdr.MarkerFile);
else
  % it is allowed to have a BrainVision dataset with only header and data, but no marker file
  markerFile = '';
  markerExt = '';
end

% in case they cannot be found, we can try whether they exist with the same name as the vhdr file
% this can happen if the researcher renamed the three files but not the header information
dataFileSameName = fullfile(p,[f dataExt]);
markerFileSameName = fullfile(p,[f markerExt]);

info = dir(dataFile);
if isempty(info)
  info = dir(filename);
  if ~isempty(info)
    ft_notice('could not find "%s" as specified in the .vhdr file, using "%s" instead', dataFile, dataFileSameName);
    vhdr.DataFile = [f dataExt]; % without full path
    dataFile = dataFileSameName; % with full path
  else
    ft_error('cannot determine the location of the data file "%s"', dataFile);
  end
else
  if ~strcmp(dataFile, dataFileSameName)
    ft_notice('could not find "%s" as specified in the .vhdr file, using "%s" instead', dataFile, dataFileSameName);
  end
end

if ~isempty(markerFile)
  info = dir(markerFile);
  if isempty(info)
    info = dir(markerFileSameName);
    if ~isempty(info)
      ft_notice('could not find "%s" as specified in the .vhdr file, using "%s" instead', markerFile, markerFileSameName);
      vhdr.MarkerFile = [f markerExt]; % without full path
      markerFile = markerFileSameName; % with full path
    else
      ft_error('cannot determine the location of the marker file "%s"', markerFile);
    end
  else
    if ~strcmp(markerFile, markerFileSameName)
      ft_notice('could not find "%s" as specified in the .vhdr file, using "%s" instead', markerFile, markerFileSameName);
    end
  end
end

% determine the number of samples by looking at the binary file
if strcmpi(vhdr.DataFormat, 'binary')
  % the data file is supposed to be located in the same directory as the header file
  % but that might be on another location than the present working directory

  info = dir(dataFile);
  switch lower(vhdr.BinaryFormat)
    case 'int_16'
      vhdr.nSamples = info.bytes./(vhdr.NumberOfChannels*2);
    case 'int_32'
      vhdr.nSamples = info.bytes./(vhdr.NumberOfChannels*4);
    case 'ieee_float_32'
      vhdr.nSamples = info.bytes./(vhdr.NumberOfChannels*4);
  end

elseif strcmpi(vhdr.DataFormat, 'ascii')
  vhdr.skipLines = 0;
  vhdr.skipColumns = 0;

  % Read ascii info from header (if available).
  dataPoints    = read_ini(filename, 'DataPoints=', '%d');
  skipLines     = read_ini(filename, 'SkipLines=', '%d');
  skipColumns   = read_ini(filename, 'SkipColumns=', '%d');
  decimalSymbol = read_ini(filename, 'DecimalSymbol=', '%s'); % This is not used in reading dataset yet

  if ~isempty(dataPoints); vhdr.nSamples = dataPoints; end
  if ~isempty(skipLines); vhdr.skipLines = skipLines; end
  if ~isempty(skipColumns); vhdr.skipColumns = skipColumns; end
  if ~isempty(decimalSymbol); vhdr.decimalSymbol = decimalSymbol; end

  if isempty(dataPoints) && strcmpi(vhdr.DataOrientation, 'vectorized')
    % this is a very inefficient fileformat to read data from, it looks like this:
    % Fp1   -2.129 -2.404 -18.646 -15.319 -4.081 -14.702 -23.590 -8.650 -3.957
    % AF3   -24.023 -23.265 -30.677 -17.053 -24.889 -35.008 -21.444 -15.896 -12.050
    % F7    -10.553 -10.288 -19.467 -15.278 -21.123 -25.066 -14.363 -10.774 -15.396
    % F3    -28.696 -26.314 -35.005 -27.244 -31.401 -39.445 -30.411 -20.194 -16.488
    % FC1   -35.627 -29.906 -38.013 -33.426 -40.532 -49.079 -38.047 -26.693 -22.852
    % ...
    fid = fopen_or_error(dataFile, 'rt');
    tline = fgetl(fid); % read the complete first line
    fclose(fid);
    t = tokenize(tline, ' ', true); % cut the line into pieces
    vhdr.nSamples = length(t) - 1;  % the first element is the channel label
  end
end

if isinf(vhdr.nSamples)
  ft_warning('cannot determine number of samples for this sub-fileformat');
end

% the number of trials is unkown, assume continuous data
vhdr.nTrials = 1;
vhdr.nSamplesPre = 0;

% ensure that the labels are in a column
vhdr.label = vhdr.label(:);
vhdr.reference = vhdr.reference(:);
vhdr.resolution = vhdr.resolution(:);

% read in impedance values
vhdr.impedances.channels = [];
vhdr.impedances.reference = [];
vhdr.impedances.ground = NaN;
vhdr.impedances.refChan = [];

fid = fopen_or_error(filename, 'rt');
while ~feof(fid)
  tline = fgetl(fid);
  if tline == -1
    break; % the end-of-file was reached with an empty line
  end
  if startsWith(tline, 'Impedance [')
    chanCounter = 0;
    refCounter = 0;
    impCounter = 0;
    while chanCounter<vhdr.NumberOfChannels && ~feof(fid)
      chan_info = fgetl(fid);
      if ~isempty(chan_info)
        impCounter = impCounter+1;
        [chanName,impedances] = strtok(chan_info,':');
        spaceList = strfind(chanName,' ');
        if ~isempty(spaceList)
          chanName = chanName(spaceList(end)+1:end);
        end
        if strfind(chanName,'REF_') == 1 % for situation where there is more than one reference
          refCounter = refCounter+1;
          vhdr.impedances.refChan(refCounter) = impCounter;
          if ~isempty(impedances)
            vhdr.impedances.reference(refCounter) = str2double(impedances(2:end));
          else
            vhdr.impedances.reference(refCounter) = NaN;
          end
        elseif strcmpi(chanName,'ref') % single reference
          refCounter = refCounter+1;
          vhdr.impedances.refChan(refCounter) = impCounter;
          if ~isempty(impedances)
            vhdr.impedances.reference(refCounter) = str2double(impedances(2:end));
          else
            vhdr.impedances.reference(refCounter) = NaN;
          end
        else
          chanCounter = chanCounter+1;
          if ~isempty(impedances)
            vhdr.impedances.channels(chanCounter,1) = str2double(impedances(2:end));
          else
            vhdr.impedances.channels(chanCounter,1) = NaN;
          end
        end
      end
    end
    if ~feof(fid)
      tline='';
      while ~feof(fid) && isempty(tline)
        tline = fgetl(fid);
      end
      if ~isempty(tline)
        if strcmp(tline(1:4),'Ref:')
          refCounter = refCounter+1;
          [chanName,impedances] = strtok(tline,':');
          if ~isempty(impedances)
            vhdr.impedances.reference(refCounter) = str2double(impedances(2:end));
          else
            vhdr.impedances.reference(refCounter) = NaN;
          end
        end
        if strcmpi(tline(1:4),'gnd:')
          [chanName,impedances] = strtok(tline,':');
          vhdr.impedances.ground = str2double(impedances(2:end));
        end
      end
    end
    if ~feof(fid)
      tline='';
      while ~feof(fid) && isempty(tline)
        tline = fgetl(fid);
      end
      if ~isempty(tline)
        if strcmpi(tline(1:4),'gnd:')
          [chanName,impedances] = strtok(tline,':');
          vhdr.impedances.ground = str2double(impedances(2:end));
        end
      end
    end
  end
end
fclose(fid);


function hh = rotateXLabels( ax, angle, varargin )
%rotateXLabels: rotate any xticklabels
%
%   hh = rotateXLabels(ax,angle) rotates all XLabels on axes AX by an angle
%   ANGLE (in degrees). Handles to the resulting text objects are returned
%   in HH.
%
%   hh = rotateXLabels(ax,angle,param,value,...) also allows one or more
%   optional parameters to be specified. Possible parameters are:
%     'MaxStringLength'   The maximum length of label to show (default inf)
%
%   Examples:
%   >> bar( hsv(5)+0.05 )
%   >> days = {'Monday','Tuesday','Wednesday','Thursday','Friday'};
%   >> set( gca(), 'XTickLabel', days )
%   >> rotateXLabels( gca(), 45 )
%
%   See also: GCA, BAR

%   Copyright 2006-2013 The MathWorks Ltd.

error( nargchk( 2, inf, nargin ) );
if ~isnumeric( angle ) || ~isscalar( angle )
    error( 'RotateXLabels:BadAngle', 'Parameter ANGLE must be a scalar angle in degrees' )
end
angle = mod( angle, 360 );

[maxStringLength] = parseInputs( varargin{:} );

% Get the existing label texts and clear them
[vals, labels] = findAndClearExistingLabels( ax, maxStringLength );

% Create the new label texts
h = createNewLabels( ax, vals, labels, angle );

% Reposition the axes itself to leave space for the new labels
repositionAxes( ax );

% If an X-label is present, move it too
repositionXLabel( ax );

% Store angle
setappdata( ax, 'RotateXLabelsAngle', angle );

% Only send outputs if requested
if nargout
    hh = h;
end

%-------------------------------------------------------------------------%
    function [maxStringLength] = parseInputs( varargin )
        % Parse optional inputs
        maxStringLength = inf;
        if nargin > 0
            params = varargin(1:2:end);
            values = varargin(2:2:end);
            if numel( params ) ~= numel( values )
                error( 'RotateXLabels:BadSyntax', 'Optional arguments must be specified as parameter-value pairs.' );
            end
            if any( ~cellfun( 'isclass', params, 'char' ) )
                error( 'RotateXLabels:BadSyntax', 'Optional argument names must be specified as strings.' );
            end
            for pp=1:numel( params )
                switch upper( params{pp} )
                    case 'MAXSTRINGLENGTH'
                        maxStringLength = values{pp};

                    otherwise
                        error( 'RotateXLabels:BadParam', 'Optional parameter ''%s'' not recognised.', params{pp} );
                end
            end
        end
    end % parseInputs
%-------------------------------------------------------------------------%
    function [vals,labels] = findAndClearExistingLabels( ax, maxStringLength )
        % Get the current tick positions so that we can place our new labels
        vals = get( ax, 'XTick' );

        % Now determine the labels. We look first at for previously rotated labels
        % since if there are some the actual labels will be empty.
        ex = findall( ax, 'Tag', 'RotatedXTickLabel' );
        if isempty( ex )
            % Store the positions and labels
            labels = get( ax, 'XTickLabel' );
            if isempty( labels )
                % No labels!
                return
            else
                if ~iscell(labels)
                    labels = cellstr(labels);
                end
            end
            % Clear existing labels so that xlabel is in the right position
            set( ax, 'XTickLabel', {}, 'XTickMode', 'Manual' );
            setappdata( ax, 'OriginalXTickLabels', labels );
        else
            % Labels have already been rotated, so capture them
            labels = getappdata( ax, 'OriginalXTickLabels' );
            set(ex, 'DeleteFcn', []);
            delete(ex);
        end
        % Limit the length, if requested
        if isfinite( maxStringLength )
            for ll=1:numel( labels )
                if length( labels{ll} ) > maxStringLength
                    labels{ll} = labels{ll}(1:maxStringLength);
                end
            end
        end

    end % findAndClearExistingLabels
%-------------------------------------------------------------------------%
    function restoreDefaultLabels( ax )
        % Restore the default axis behavior
        removeListeners( ax );

        % Try to restore the tick marks and labels
        set( ax, 'XTickMode', 'auto', 'XTickLabelMode', 'auto' );
        rmappdata( ax, 'OriginalXTickLabels' );

        % Try to restore the axes position
        if isappdata( ax, 'OriginalAxesPosition' )
            set( ax, 'Position', getappdata( ax, 'OriginalAxesPosition' ) );
            rmappdata( ax, 'OriginalAxesPosition' );
        end
    end
%-------------------------------------------------------------------------%
    function textLabels = createNewLabels( ax, vals, labels, angle )
        % Work out the ticklabel positions
        zlim = get(ax,'ZLim');
        z = zlim(1);

        % We want to work in normalised coords, but this doesn't print
        % correctly. Instead we have to work in data units even though it
        % makes positioning hard.
        ylim = get( ax, 'YLim' );
        if strcmpi( get( ax, 'XAxisLocation' ), 'Top' )
            y = ylim(2);
        else
            y = ylim(1);
        end

        % Now create new text objects in similar positions.
        textLabels = -1*ones( numel( vals ), 1 );
        for ll=1:numel(vals)
            textLabels(ll) = text( ...
                'Units', 'Data', ...
                'Position', [vals(ll), y, z], ...
                'String', labels{ll}, ...
                'Parent', ax, ...
                'Clipping', 'off', ...
                'Rotation', angle, ...
                'Tag', 'RotatedXTickLabel', ...
                'UserData', vals(ll));
        end
        % So that we can respond to CLA and CLOSE, attach a delete
        % callback. We only attach it to one label to save massive numbers
        % of callbacks during axes shut-down.
        set(textLabels(end), 'DeleteFcn', @onTextLabelDeleted);

        % Now copy font properties into the texts
        updateFont();
        % Update the alignment of the text
        updateAlignment();

    end % createNewLabels

%-------------------------------------------------------------------------%
    function repositionAxes( ax )
        % Reposition the axes so that there's room for the labels
        % Note that we only do this if the OuterPosition is the thing being
        % controlled
        if ~strcmpi( get( ax, 'ActivePositionProperty' ), 'OuterPosition' )
            return;
        end

        % Work out the maximum height required for the labels
        labelHeight = getLabelHeight(ax);

        % Remove listeners while we mess around with things, otherwise we'll
        % trigger redraws recursively
        removeListeners( ax );

        % Change to normalized units for the position calculation
        oldUnits = get( ax, 'Units' );
        set( ax, 'Units', 'Normalized' );

        % Not sure why, but the extent seems to be proportional to the height of the axes.
        % Correct that now.
        set( ax, 'ActivePositionProperty', 'Position' );
        pos = get( ax, 'Position' );
        axesHeight = pos(4);
        % Make sure we don't adjust away the axes entirely!
        heightAdjust = min( (axesHeight*0.9), labelHeight*axesHeight );

        % Move the axes
        if isappdata( ax, 'OriginalAxesPosition' )
            pos = getappdata( ax, 'OriginalAxesPosition' );
        else
            pos = get(ax,'Position');
            setappdata( ax, 'OriginalAxesPosition', pos );
        end
        if strcmpi( get( ax, 'XAxisLocation' ), 'Bottom' )
            % Move it up and reduce the height
            set( ax, 'Position', pos+[0 heightAdjust 0 -heightAdjust] )
        else
            % Just reduce the height
            set( ax, 'Position', pos+[0 0 0 -heightAdjust] )
        end
        set( ax, 'Units', oldUnits );
        set( ax, 'ActivePositionProperty', 'OuterPosition' );

        % Make sure we find out if axes properties are changed
        addListeners( ax );

    end % repositionAxes

%-------------------------------------------------------------------------%
    function repositionXLabel( ax )
        % Try to work out where to put the xlabel
        removeListeners( ax );
        labelHeight = getLabelHeight(ax);

        % Use the new max extent to move the xlabel. We may also need to
        % move the title
        xlab = get(ax,'XLabel');
        titleh = get( ax, 'Title' );
        set( [xlab,titleh], 'Units', 'Normalized' );
        if strcmpi( get( ax, 'XAxisLocation' ), 'Top' )
            titleExtent = get( xlab, 'Extent' );
            set( xlab, 'Position', [0.5 1+labelHeight-titleExtent(4) 0] );
            set( titleh, 'Position', [0.5 1+labelHeight 0] );
        else
            set( xlab, 'Position', [0.5 -labelHeight 0] );
            set( titleh, 'Position', [0.5 1 0] );
        end
        addListeners( ax );
    end % repositionXLabel

%-------------------------------------------------------------------------%
    function height = getLabelHeight(ax)
        height = 0;
        textLabels = findall( ax, 'Tag', 'RotatedXTickLabel' );
        if isempty(textLabels)
            return;
        end
        oldUnits = get( textLabels(1), 'Units' );
        set( textLabels, 'Units', 'Normalized' );
        for ll=1:numel(vals)
            ext = get( textLabels(ll), 'Extent' );
            if ext(4) > height
                height = ext(4);
            end
        end
        set( textLabels, 'Units', oldUnits );
    end % getLabelExtent

%-------------------------------------------------------------------------%
    function updateFont()
        % Update the rotated text fonts when the axes font changes
        properties = {
            'FontName'
            'FontSize'
            'FontAngle'
            'FontWeight'
            'FontUnits'
            };
        propertyValues = get( ax, properties );
        textLabels = findall( ax, 'Tag', 'RotatedXTickLabel' );
        set( textLabels, properties, propertyValues );
    end % updateFont

    function updateAlignment()
        textLabels = findall( ax, 'Tag', 'RotatedXTickLabel' );
        angle = get( textLabels(1), 'Rotation' );
        % Depending on the angle, we may need to change the alignment. We change
        % alignments within 5 degrees of each 90 degree orientation.
        if strcmpi( get( ax, 'XAxisLocation' ), 'Top' )
            if 0 <= angle && angle < 5
                set( textLabels, 'HorizontalAlignment', 'Center', 'VerticalAlignment', 'Bottom' );
            elseif 5 <= angle && angle < 85
                set( textLabels, 'HorizontalAlignment', 'Left', 'VerticalAlignment', 'Bottom' );
            elseif 85 <= angle && angle < 95
                set( textLabels, 'HorizontalAlignment', 'Left', 'VerticalAlignment', 'Middle' );
            elseif 95 <= angle && angle < 175
                set( textLabels, 'HorizontalAlignment', 'Left', 'VerticalAlignment', 'Top' );
            elseif 175 <= angle && angle < 185
                set( textLabels, 'HorizontalAlignment', 'Center', 'VerticalAlignment', 'Top' );
            elseif 185 <= angle && angle < 265
                set( textLabels, 'HorizontalAlignment', 'Right', 'VerticalAlignment', 'Top' );
            elseif 265 <= angle && angle < 275
                set( textLabels, 'HorizontalAlignment', 'Right', 'VerticalAlignment', 'Middle' );
            elseif 275 <= angle && angle < 355
                set( textLabels, 'HorizontalAlignment', 'Right', 'VerticalAlignment', 'Bottom' );
            else % 355-360
                set( textLabels, 'HorizontalAlignment', 'Center', 'VerticalAlignment', 'Bottom' );
            end
        else
            if 0 <= angle && angle < 5
                set( textLabels, 'HorizontalAlignment', 'Center', 'VerticalAlignment', 'Top' );
            elseif 5 <= angle && angle < 85
                set( textLabels, 'HorizontalAlignment', 'Right', 'VerticalAlignment', 'Top' );
            elseif 85 <= angle && angle < 95
                set( textLabels, 'HorizontalAlignment', 'Right', 'VerticalAlignment', 'Middle' );
            elseif 95 <= angle && angle < 175
                set( textLabels, 'HorizontalAlignment', 'Right', 'VerticalAlignment', 'Bottom' );
            elseif 175 <= angle && angle < 185
                set( textLabels, 'HorizontalAlignment', 'Center', 'VerticalAlignment', 'Bottom' );
            elseif 185 <= angle && angle < 265
                set( textLabels, 'HorizontalAlignment', 'Left', 'VerticalAlignment', 'Bottom' );
            elseif 265 <= angle && angle < 275
                set( textLabels, 'HorizontalAlignment', 'Left', 'VerticalAlignment', 'Middle' );
            elseif 275 <= angle && angle < 355
                set( textLabels, 'HorizontalAlignment', 'Left', 'VerticalAlignment', 'Top' );
            else % 355-360
                set( textLabels, 'HorizontalAlignment', 'Center', 'VerticalAlignment', 'Top' );
            end
        end
    end

%-------------------------------------------------------------------------%
    function onAxesFontChanged( ~, ~ )
        updateFont();
        repositionAxes( ax );
        repositionXLabel( ax );
    end % onAxesFontChanged

%-------------------------------------------------------------------------%
    function onAxesPositionChanged( ~, ~ )
        % We need to accept the new position, so remove the appdata before
        % redrawing
        if isappdata( ax, 'OriginalAxesPosition' )
            rmappdata( ax, 'OriginalAxesPosition' );
        end
        if isappdata( ax, 'OriginalXLabelPosition' )
            rmappdata( ax, 'OriginalXLabelPosition' );
        end
        repositionAxes( ax );
        repositionXLabel( ax );
    end % onAxesPositionChanged

%-------------------------------------------------------------------------%
    function onXAxisLocationChanged( ~, ~ )
        updateAlignment();
        repositionAxes( ax );
        repositionXLabel( ax );
    end % onXAxisLocationChanged

%-------------------------------------------------------------------------%
    function onAxesLimitsChanged( ~, ~ )
        % The limits have moved, so make sure the labels are still ok
        textLabels = findall( ax, 'Tag', 'RotatedXTickLabel' );
        xlim = get( ax, 'XLim' );
        ylim = get( ax, 'YLim' );
        if strcmpi( get( ax, 'XAxisLocation'), 'Bottom' )
            pos = [0 ylim(1)];
        else
            pos = [0 ylim(2)];
        end
        for tt=1:numel( textLabels )
            xval = get( textLabels(tt), 'UserData' );
            pos(1) = xval;
            % If the tick is off the edge, make it invisible
            if xval<xlim(1) || xval>xlim(2)
                set( textLabels(tt), 'Visible', 'off', 'Position', pos )
            elseif ~strcmpi( get( textLabels(tt), 'Visible' ), 'on' )
                set( textLabels(tt), 'Visible', 'on', 'Position', pos )
            else
                % Just set the position
                set( textLabels(tt), 'Position', pos );
            end
        end

        repositionXLabel( ax );
    end % onAxesPositionChanged

%-------------------------------------------------------------------------%
    function onTextLabelDeleted( ~, ~ )
        % The final text label has been deleted. This is likely from a
        % "cla" or "close" call, so we should remove all of our dirty
        % hacks.
        restoreDefaultLabels(ax);
    end

%-------------------------------------------------------------------------%
    function addListeners( ax )
        % Create listeners. We store the array of listeners in the axes to make
        % sure that they have the same life-span as the axes they are listening to.
        axh = handle( ax );
        listeners = [
            handle.listener( axh, findprop( axh, 'FontName' ), 'PropertyPostSet', @onAxesFontChanged )
            handle.listener( axh, findprop( axh, 'FontSize' ), 'PropertyPostSet', @onAxesFontChanged )
            handle.listener( axh, findprop( axh, 'FontWeight' ), 'PropertyPostSet', @onAxesFontChanged )
            handle.listener( axh, findprop( axh, 'FontAngle' ), 'PropertyPostSet', @onAxesFontChanged )
            handle.listener( axh, findprop( axh, 'FontUnits' ), 'PropertyPostSet', @onAxesFontChanged )
            handle.listener( axh, findprop( axh, 'OuterPosition' ), 'PropertyPostSet', @onAxesPositionChanged )
            handle.listener( axh, findprop( axh, 'XLim' ), 'PropertyPostSet', @onAxesLimitsChanged )
            handle.listener( axh, findprop( axh, 'YLim' ), 'PropertyPostSet', @onAxesLimitsChanged )
            handle.listener( axh, findprop( axh, 'XAxisLocation' ), 'PropertyPostSet', @onXAxisLocationChanged )
            ];
        setappdata( ax, 'RotateXLabelsListeners', listeners );
    end % addListeners

%-------------------------------------------------------------------------%
    function removeListeners( ax )
        % Rempove any property listeners whilst we are fiddling with the axes
        if isappdata( ax, 'RotateXLabelsListeners' )
            delete( getappdata( ax, 'RotateXLabelsListeners' ) );
            rmappdata( ax, 'RotateXLabelsListeners' );
        end
    end % removeListeners

end % EOF


function r = isfinite(a)
%ISFINITE     Array of 1's for finite components
%
%   r = isfinite(a)
%

% written  05/21/09     S.M. Rump
%

  r = reshape(all(isfinite(a.t)),a.size);


function varargout = process_segment_brainsuite( varargin )
% PROCESS_SEGMENT_BRAINSUITE: Run the segmentation of a T1 MRI with BrainSuite.
%
% USAGE:     OutputFiles = process_segment_brainsuite('Run',     sProcess, sInputs)
%         [isOk, errMsg] = process_segment_brainsuite('Compute', iSubject, iAnatomy=[default], nVertices, isInteractive)
%                          process_segment_brainsuite('ComputeInteractive', iSubject, iAnatomy)

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2021-2023

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
    % Description the process
    sProcess.Comment     = 'Segment MRI with BrainSuite';
    sProcess.Category    = 'Custom';
    sProcess.SubGroup    = {'Import', 'Import anatomy'};
    sProcess.Index       = 32;
    sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/SegBrainSuite';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'import'};
    sProcess.OutputTypes = {'import'};
    sProcess.nInputs     = 1;
    sProcess.nMinFiles   = 0;
    % Title
    sProcess.options.doc.Comment = ['BrainSuite must be installed on the computer,
' ...
                                    'the command "bse" must be available in the system path.
' ...
                                    'Website: http://brainsuite.org

'];
    sProcess.options.doc.Type    = 'label';
    % Option: Subject name
    sProcess.options.subjectname.Comment = 'Subject name:';
    sProcess.options.subjectname.Type    = 'subjectname';
    sProcess.options.subjectname.Value   = '';
    % Option: Number of vertices
    sProcess.options.nvertices.Comment = 'Number of vertices (cortex): ';
    sProcess.options.nvertices.Type    = 'value';
    sProcess.options.nvertices.Value   = {15000, '', 0};
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess) %#ok<DEFNU>
    Comment = sProcess.Comment;
end

%% ===== RUN =====
function OutputFiles = Run(sProcess, sInputs) %#ok<DEFNU>
    OutputFiles = {};
    % Number of vertices
    nVertices = sProcess.options.nvertices.Value{1};
    if isempty(nVertices) || (nVertices < 50)
        bst_report('Error', sProcess, [], 'Invalid number of vertices.');
        return
    end
    % Get subject name
    SubjectName = file_standardize(sProcess.options.subjectname.Value);
    if isempty(SubjectName)
        bst_report('Error', sProcess, [], 'Subject name is empty.');
        return;
    end
    % Get subject
    [sSubject, iSubject] = bst_get('Subject', SubjectName);
    if isempty(iSubject)
        bst_report('Error', sProcess, [], ['Subject "' SubjectName '" does not exist.']);
        return
    end
    % Call processing function
    [isOk, errMsg] = Compute(iSubject, [], nVertices, 0);
    % Handling errors
    if ~isOk
        bst_report('Error', sProcess, [], errMsg);
    elseif ~isempty(errMsg)
        bst_report('Warning', sProcess, [], errMsg);
    end
    % Return an empty structure
    OutputFiles = {'import'};
end

%% ===== COMPUTE BRAINSUITE SEGMENTATION =====
function [isOk, errMsg] = Compute(iSubject, iAnatomy, nVertices, isInteractive)
    errMsg = '';
    isOk = 0;

    % ===== INSTALL BRAINSUITE =====
    bst_progress('text', 'Testing BrainSuite installation...');
    % Get BrainSuite path from Brainstorm preferences
    BsDir = bst_get('BrainSuiteDir');
    % Get executable names
    if ispc
        cse_exe = fullfile(BsDir, 'bin', 'cortical_extraction.cmd');
        svreg_exe = fullfile(BsDir, 'svreg', 'bin', 'svreg.exe');
    else
        cse_exe = fullfile(BsDir, 'bin', 'cortical_extraction.sh');
        svreg_exe = fullfile(BsDir, 'svreg', 'bin', 'svreg.sh');
    end
    % Check BrainSuite installation
    if isempty(BsDir) || ~file_exist(cse_exe)
        errMsg = ['BrainSuite is not installed on your computer.' 10 ...
                      'Download it from http://brainsuite.org and install it.' 10 ...
                      'Then set its installation folder in the Brainstorm options (File > Edit preferences)'];
        return
    end

    % ===== GET SUBJECT =====
    % Get subject
    [sSubject, iSubject] = bst_get('Subject', iSubject);
    if isempty(sSubject)
        errMsg = 'Subject does not exist.';
        return
    end
    % Check if a MRI is available for the subject
    if isempty(sSubject.Anatomy)
        errMsg = ['No MRI available for subject "' SubjectName '".'];
        return
    end
    % Get default MRI if not specified
    if isempty(iAnatomy)
        iAnatomy = sSubject.iAnatomy;
    end

    % ===== DELETE EXISTING SURFACES =====
    % Confirm with user that the existing surfaces will be removed
    if isInteractive && ~isempty(sSubject.Surface)
        isDel = java_dialog('confirm', ['Warning: There are already surfaces in this subject.' 10 ...
            'Running BrainSuite will remove all the existing surfaces.' 10 10 ...
            'Delete the existing files?' 10 10], 'BrainSuite segmentation');
        if ~isDel
            errMsg = 'Process aborted by user.';
            return;
        end
    end

    % ===== VERIFY FIDUCIALS IN MRI =====
    % Load MRI file
    T1FileBst = sSubject.Anatomy(iAnatomy).FileName;
    sMri = in_mri_bst(T1FileBst);
    % If the SCS transformation is not defined: compute MNI transformation to get a default one
    if isempty(sMri) || ~isfield(sMri, 'SCS') || ~isfield(sMri.SCS, 'NAS') || ~isfield(sMri.SCS, 'LPA') || ~isfield(sMri.SCS, 'RPA') || (length(sMri.SCS.NAS)~=3) || (length(sMri.SCS.LPA)~=3) || (length(sMri.SCS.RPA)~=3) || ~isfield(sMri.SCS, 'R') || isempty(sMri.SCS.R) || ~isfield(sMri.SCS, 'T') || isempty(sMri.SCS.T)
        % Issue warning
        errMsg = 'Missing NAS/LPA/RPA: Computing the MNI normalization to get default positions.';
        % Compute MNI normalization
        [sMri, errNorm] = bst_normalize_mni(T1FileBst);
        % Handle errors
        if ~isempty(errNorm)
            errMsg = [errMsg 10 'Error trying to compute the MNI normalization: ' 10 errNorm 10 ...
                'Missing fiducials: the surfaces cannot be aligned with the MRI.'];
        end
    end

    % ===== SAVE MRI AS NII =====
    bst_progress('text', 'Saving temporary files...');
    % Create temporay folder for BrainSuite output
    TmpDir = bst_get('BrainstormTmpDir', 0, 'brainsuite');
    % Save MRI in .nii format
    subjid = strrep(sSubject.Name, '@', '');
    NiiFile = bst_fullfile(TmpDir, [subjid, '.nii']);
    out_mri_nii(sMri, NiiFile);
    % If a "world transformation" was not available in the MRI in the database, it was set to a default when saving to .nii
    % Let's reload this file to get the transformation matrix, it will be used when importing the results
    if ~isfield(sMri, 'InitTransf') || isempty(sMri.InitTransf) || isempty(find(strcmpi(sMri.InitTransf(:,1), 'vox2ras')))
        % Load again the file, with the default vox2ras transformation
        [tmp, vox2ras] = in_mri_nii(NiiFile, 0, 0, 0);
        % Prepare the history of transformations
        if ~isfield(sMri, 'InitTransf') || isempty(sMri.InitTransf)
            sMri.InitTransf = cell(0,2);
        end
        % Add this transformation in the MRI
        sMri.InitTransf(end+1,[1 2]) = {'vox2ras', vox2ras};
        % Save modification on hard drive
        bst_save(file_fullpath(T1FileBst), sMri, 'v7');
    end

    % ===== 1. CORTICATL EXTRACTION =====
    bst_progress('text', '1/2: Cortical extraction...    (see command window)');
    strCall = ['"' cse_exe '" "' NiiFile(1:end-4) '"'];
    disp(['BST> System call: ' strCall]);
    status = system(strCall)
    % Error handling
    if (status ~= 0)
        errMsg = ['BrainSuite failed at step 1/2 (Cortical extraction).', 10, 'Check the Matlab command window for more information.'];
        return
    end

    % ===== 2. SVREG =====
    bst_progress('text', '2/2: SVREG...    (see command window)');
    AtlasPath = fullfile(BsDir, 'svreg', 'BrainSuiteAtlas1', 'mri');
    strCall = ['"' svreg_exe '" "' NiiFile(1:end-4) '" "' AtlasPath '"'];
    disp(['BST> System call: ' strCall]);
    status = system(strCall)
    % Error handling
    if (status ~= 0)
        errMsg = ['BrainSuite failed at step 2/2 (SVREG).', 10, 'Check the Matlab command window for more information.'];
        return
    end

    % ===== 3. SKULL EXTRACTION =====
    % cmd=[skullfinder_exe,' -i ', subbasename,'.nii.gz -o ',subbasename,'.skull.label.nii.gz -m ',subbasename,'.mask.nii.gz --scalplabel ',subbasename,'.scalp.label.nii.gz -s ',subbasename];
    % '$HOME/BrainSuite21a/bin/skullfinder' -i subject.nii.gz -o subject.skull.label.nii.gz -m subject.mask.nii.gz -s subject
    %
    % Anand 28-Oct-2022: There is a caveat. It seems that due to a bug in the executable, outer skull and scalp surfaces are swapped.
    % For now, we can write a code that swaps subject.scalp.dfs and subject.outer_skull.dfs

    % ===== IMPORT OUTPUT FOLDER =====
    % Import BrainSuite anatomy folder
    isKeepMri = 1;
    isVolumeAtlas = 1;
    errMsg = import_anatomy_bs(iSubject, TmpDir, nVertices, 0, [], isVolumeAtlas, isKeepMri);
    if ~isempty(errMsg)
        return;
    end
    % Delete temporary folder
    file_delete(TmpDir, 1, 1);
    % Return success
    isOk = 1;
end

%% ===== COMPUTE/INTERACTIVE =====
function ComputeInteractive(iSubject, iAnatomy) %#ok<DEFNU>
    % Get inputs
    if (nargin < 2) || isempty(iAnatomy)
        iAnatomy = [];
    end
    % Ask for number of vertices
    nVertices = java_dialog('input', 'Number of vertices on the cortex surface:', 'BrainSuite segmentation', [], '15000');
    if isempty(nVertices)
        return
    end
    nVertices = str2double(nVertices);
    % Open progress bar
    bst_progress('start', 'BrainSuite', 'BrainSuite MRI segmentation...');
    % Run BrainSuite
    isInteractive = 1;
    [isOk, errMsg] = Compute(iSubject, iAnatomy, nVertices, isInteractive);
    % Error handling
    if ~isOk
        bst_error(errMsg, 'BrainSuite MRI segmentation', 0);
    elseif ~isempty(errMsg)
        java_dialog('msgbox', ['Warning: ' errMsg]);
    end
    % Close progress bar
    bst_progress('stop');
end


% forcerow.m
% function to force a vector to be a single row
%
% Brian Birge
% Rev 2.0 - vectorized
% 7/1/98

function[out]=forcerow(in)
len=prod(size(in));
out=reshape(in,[1,len]);


function disp( prob, varargin )
cvx_display( cvx_validate( prob.index_, prob.id_ ), varargin{:} );

% Copyright 2005-2014 CVX Research, Inc.
% See the file LICENSE.txt for full copyright information.
% The command 'cvx_where' will show where this file is located.


%kkresample 'Resample Object Data'
% This MatLab function was automatically generated by a converter (KhorosToMatLab) from the Khoros kresample.pane file
%
% Parameters:
% InputFile: i 'Input ', required: 'Input Object'
% Toggle: center 'Place COA in middle of dimensions (unless overridden below)', default: 0: 'center'
% Integer: wc 'Width Coordinate of COA ', default: 0: 'Width coordinate for the center of action'
% Integer: hc 'Height Coordinate of COA ', default: 0: 'Height coordinate for the center of action'
% Integer: dc 'Depth Coordinate of COA ', default: 0: 'Depth coordinate for the center of action'
% Integer: tc 'Time Coordinate of COA ', default: 0: 'Time coordinate for the center of action'
% Integer: ec 'Elements Coordinate of COA ', default: 0: 'Elements coordinate for the center of action'
% OutputFile: o 'Output', required: 'Resulting output object'
%
% Example: o = kkresample(i, {'i','';'center',0;'wc',0;'hc',0;'dc',0;'tc',0;'ec',0;'o',''})
%
% Khoros helpfile follows below:
%
%  PROGRAM
% kresample - Resample Object Data
%
%  DESCRIPTION
% .I kresample
% is used to magnify or reduce the Input data object (i) by using the
% selected interpolation method (inter).  (At this time, only pixel
% replication is supported.)  Magnification factors greater than one
% magnify the object, and magnification factors between zero and one
% reduce the object.
%
% Pixel replication and subsampling are done on a floating point indexing
% basis, where the data point index is obtained by truncating the cumulative
% floating point index.  Therefore, when the calculated replication factor
% or the subsampling step size is non-integer, the resulting integer step
% size varies during the process.
%
%  "Magnification Factors"
% Magnification factors for each dimension, Width (wmag), Height (hmag)
% Depth (dmag), Time (tmag), and Elements (emag) can be specified.  Magnification
% factors can be any value greater than zero.
%
%  "Center of Action"
% The magnification or reduction is done about a center point that can be
% specified to be anywhere in the object (even outside of the object).
% The correct way to think about this is by analogy to a magnifying glass
% where you place the magnifying glass over the point of interest and then
% choose the lens to get the magnification you want.  The center of action
% coordinates can be specified individually for each dimension (wc, hc, dc,
% tc, ec) or, for convenience, the center of action can be automatically
% computed to be in the middle of all dimensions.  This is accomplished
% by selecting the "center" flag.  Automatic centering can be overridden
% on selected dimensions by explicitly specifying the centers of action
% for those dimensions.
%
%  "Output Object Size"
% The resultant output object size can either be the same as the original
% input object size if "Resize dimensions" is set to "No" (-resize 0), or
% it can be resized to fit the magnified or reduced data set if "Resize
% dimensions" is set to "Yes" (-resize 1).
% If the user chooses not to resize the dimensionality of the output object,
% and the input object is reduced (magnification
% factor < 1.0) then unknown pixels are set to zero.  Likewise, if the
% input object is magnified (magnification factor > 1.0) then input data
% that would be mapped outside of the output object are lost.
%
%  "Data Type"
% .cI $DATAMANIP/repos/shared/man/sections/value_type_1input
% If there is map and value data in the source object, and zero
% order interpolation (pixel replication) is used, the map is transferred
% to the destination object.  If any interpolation algorithm is used,
% the value data is mapped before the operation can begin and the
% destination will not have map data.
%
%  "Failure Modes"
% .cI $DATAMANIP/repos/shared/man/sections/fail_1input
% This program fails if there is mask data and the operation uses anything
% other than zero order interpolation.  In other words, if value data
% is being replicated or subsample on grid, then the mask can be
% correspondingly replicated or subsampled.  (Bilinear interpolation can not
% be performed on mask data.)
% If explicit location or time data are available, the sampling algorithm
% can not resample these data yet, and will fail.  This will be implemented
% later ....
%
%
%
%  EXAMPLES
%
%  "SEE ALSO"
% kshrink, kexpand
%
%  RESTRICTIONS
%
%  REFERENCES
%
%  COPYRIGHT
% Copyright (C) 1993 - 1997, Khoral Research, Inc. ("KRI")  All rights reserved.
%

function varargout = kkresample(varargin)
if nargin ==0
  Inputs={};arglist={'',''};
elseif nargin ==1
  Inputs=varargin{1};arglist={'',''};
elseif nargin ==2
  Inputs=varargin{1}; arglist=varargin{2};
else error('Usage: [out1,..] = kkresample(Inputs,arglist).');
end
if size(arglist,2)~=2
  error('arglist must be of form {''ParameterTag1'',value1;''ParameterTag2'',value2}')
 end
narglist={'i', '__input';'center', 0;'wc', 0;'hc', 0;'dc', 0;'tc', 0;'ec', 0;'o', '__output'};
maxval={0,0,1,1,1,1,1,0};
minval={0,0,1,1,1,1,1,0};
istoggle=[0,1,1,1,1,1,1,0];
was_set=istoggle * 0;
paramtype={'InputFile','Toggle','Integer','Integer','Integer','Integer','Integer','OutputFile'};
% identify the input arrays and assign them to the arguments as stated by the user
if ~iscell(Inputs)
Inputs = {Inputs};
end
NumReqOutputs=1; nextinput=1; nextoutput=1;
  for ii=1:size(arglist,1)
  wasmatched=0;
  for jj=1:size(narglist,1)
   if strcmp(arglist{ii,1},narglist{jj,1})  % a given argument was matched to the possible arguments
     wasmatched = 1;
     was_set(jj) = 1;
     if strcmp(narglist{jj,2}, '__input')
      if (nextinput > length(Inputs))
        error(['Input ' narglist{jj,1} ' has no corresponding input!']);
      end
      narglist{jj,2} = 'OK_in';
      nextinput = nextinput + 1;
     elseif strcmp(narglist{jj,2}, '__output')
      if (nextoutput > nargout)
        error(['Output nr. ' narglist{jj,1} ' is not present in the assignment list of outputs !']);
      end
      if (isempty(arglist{ii,2}))
        narglist{jj,2} = 'OK_out';
      else
        narglist{jj,2} = arglist{ii,2};
      end

      nextoutput = nextoutput + 1;
      if (minval{jj} == 0)
         NumReqOutputs = NumReqOutputs - 1;
      end
     elseif isstr(arglist{ii,2})
      narglist{jj,2} = arglist{ii,2};
     else
        if strcmp(paramtype{jj}, 'Integer') & (round(arglist{ii,2}) ~= arglist{ii,2})
            error(['Argument ' arglist{ii,1} ' is of integer type but non-integer number ' arglist{ii,2} ' was supplied']);
        end
        if (minval{jj} ~= 0 | maxval{jj} ~= 0)
          if (minval{jj} == 1 & maxval{jj} == 1 & arglist{ii,2} < 0)
            error(['Argument ' arglist{ii,1} ' must be bigger or equal to zero!']);
          elseif (minval{jj} == -1 & maxval{jj} == -1 & arglist{ii,2} > 0)
            error(['Argument ' arglist{ii,1} ' must be smaller or equal to zero!']);
          elseif (minval{jj} == 2 & maxval{jj} == 2 & arglist{ii,2} <= 0)
            error(['Argument ' arglist{ii,1} ' must be bigger than zero!']);
          elseif (minval{jj} == -2 & maxval{jj} == -2 & arglist{ii,2} >= 0)
            error(['Argument ' arglist{ii,1} ' must be smaller than zero!']);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} < minval{jj})
            error(['Argument ' arglist{ii,1} ' must be bigger than ' num2str(minval{jj})]);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} > maxval{jj})
            error(['Argument ' arglist{ii,1} ' must be smaller than ' num2str(maxval{jj})]);
          end
        end
     end
     if ~strcmp(narglist{jj,2},'OK_out') &  ~strcmp(narglist{jj,2},'OK_in')
       narglist{jj,2} = arglist{ii,2};
     end
   end
   end
   if (wasmatched == 0 & ~strcmp(arglist{ii,1},''))
        error(['Argument ' arglist{ii,1} ' is not a valid argument for this function']);
   end
end
% match the remaining inputs/outputs to the unused arguments and test for missing required inputs
 for jj=1:size(narglist,1)
     if  strcmp(paramtype{jj}, 'Toggle')
        if (narglist{jj,2} ==0)
          narglist{jj,1} = '';
        end;
        narglist{jj,2} = '';
     end;
     if  ~strcmp(narglist{jj,2},'__input') && ~strcmp(narglist{jj,2},'__output') && istoggle(jj) && ~ was_set(jj)
          narglist{jj,1} = '';
          narglist{jj,2} = '';
     end;
     if strcmp(narglist{jj,2}, '__input')
      if (minval{jj} == 0)  % meaning this input is required
        if (nextinput > size(Inputs))
           error(['Required input ' narglist{jj,1} ' has no corresponding input in the list!']);
        else
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        end
      else  % this is an optional input
        if (nextinput <= length(Inputs))
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end;
     else
     if strcmp(narglist{jj,2}, '__output')
      if (minval{jj} == 0) % this is a required output
        if (nextoutput > nargout & nargout > 1)
           error(['Required output ' narglist{jj,1} ' is not stated in the assignment list!']);
        else
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
          NumReqOutputs = NumReqOutputs-1;
        end
      else % this is an optional output
        if (nargout - nextoutput >= NumReqOutputs)
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end
     end
  end
end
if nargout
   varargout = cell(1,nargout);
else
  varargout = cell(1,1);
end
global KhorosRoot
if exist('KhorosRoot') && ~isempty(KhorosRoot)
w=['"' KhorosRoot];
else
if ispc
  w='"C:\Program Files\dip\khorosBin\';
else
[s,w] = system('which cantata');
w=['"' w(1:end-8)];
end
end
[varargout{:}]=callKhoros([w 'kresample"  '],Inputs,narglist);


% This file is part of the project NILM-Eval (https://github.com/beckel/nilm-eval).
% Licence: GPL 2.0 (http://www.gnu.org/licenses/gpl-2.0.html)
% Copyright: ETH Zurich, 2014
% Author: Romano Cicchetti

function [] = plotExperiment()

    % plots the results of an experiment
    %
    % Parameters:
    % parameter: name of parameter on x-axis (e.g. param1 or event_metrics)
    % lines: name of lines in plot (e.g. param1 or event_metrics)

    % get algorithm and experiment name
    result_settings = ReadYaml('input/result.yaml');
    algorithm = result_settings.algorithm;
    configuration = result_settings.configuration;
    experiment_name = result_settings.experiment;

    % load experiment results
    path_to_experiment = strcat('results/summary/', algorithm, '/', configuration, '/', experiment_name);
    load(strcat(path_to_experiment, '/experiment_result.mat'));

    % get parameter (x-axis on plot) and restriction (some parameters are
    % restricted to specific values, e.g. only household 2 is used)
    parameter = result_settings.parameter;
    restriction = result_settings.restriction;

    % get names and values of the experiment parameters
    yaml_file_of_experiment = strcat('input/autogen/experiments/yaml/', algorithm, '_', configuration, '/', experiment_name, '.yaml');
    experiment = ReadYaml(yaml_file_of_experiment);
    experiment_parameters = {};
    field_names = fieldnames(experiment);
    for field_name = field_names'
        values = experiment.(field_name{1});
        if length(values) > 1
            experiment_parameters{end+1} = field_name{1};
        end
    end
    parameterID = find(strcmpi(parameter,experiment_parameters));
    load(strcat(path_to_experiment, '/usedParameterValues.mat'));
    parameter_values_in_cell = usedParameterValues.(parameter);
    parameter_values = cellfun(@str2double, parameter_values_in_cell);

    % get indexes of the lines in the plot
    lines = usedParameterValues.(result_settings.lines{1});
    res = zeros(length(parameter_values), length(lines));
    result = experiment_result.(result_settings.domain);
    if ~isempty(result_settings.lines{2})
        lines_idx = find(ismember(usedParameterValues.(result_settings.lines{1}), result_settings.lines{2}));
        lines = lines(lines_idx);
    else
        lines_idx = 1:length(lines);
    end

    % for each line, get appropriate index of result (experiment_result)
    % idx(param1, param2, ..., param_n, event/appliance_name, combine_res,
    % event/consumption_metric)
    for i = lines_idx
        idx(1:length(experiment_parameters)) = {':'};

        % set index of specified event/appliance name, event/consumption metric
        % and 'combine_results' (method for combining results of different training periods)
        if strcmpi(result_settings.domain, 'events')
            idx(length(experiment_parameters)+1) = {find(ismember(usedParameterValues.event_names, result_settings.event_name))};
            idx(length(experiment_parameters)+3) = {find(ismember(usedParameterValues.event_metrics, result_settings.metric))};
        else
            idx(length(experiment_parameters)+1) = {find(ismember(usedParameterValues.appliance_names, result_settings.appliance_name))};
            idx(length(experiment_parameters)+3) = {find(ismember(usedParameterValues.appliance_metrics, result_settings.metric))};
        end
        idx(length(experiment_parameters)+2) = {find(ismember(usedParameterValues.combine_results, result_settings.combine_res))};

        % some parameters are restricted to specific values, adapt index
        % accordingly
        for k=1:length(restriction)
            param_restricted = restriction{k};
            param_restricted_name = param_restricted{1};
            param_restricted_values = param_restricted{2};
            param_restricted_idx = find(ismember(usedParameterValues.(param_restricted_name), param_restricted_values));
            index_of_restricted_parameter = getIndexOfParameterName(param_restricted_name, experiment_parameters);
            idx(index_of_restricted_parameter) = {param_restricted_idx};
        end

        % set index of 'lines parameter'
        index_of_lines_parameter = getIndexOfParameterName(result_settings.lines{1}, experiment_parameters);
        idx(index_of_lines_parameter) = {i};

        % get result and store it in the 'res' matrix
        for j = 1:size(result,parameterID)
            idx(parameterID) = {j};
            r = result(idx{:});
            idx2 = r ~= 0 & ~isnan(r);
            res(j,i) = sum(r(idx2)) / nnz(idx2 ~=0);
        end
    end

    % plot the result (res matrix)
    set(0, 'DefaultAxesFontSize',12)
    set(0, 'DefaultTextFontName','Times New Roman');
    figure();
    [parameter_values_sorted, sortIdx] = sort(parameter_values);
     res_sorted = res(sortIdx, :);
    plot(parameter_values_sorted, res_sorted(:,lines_idx));

    % set legend of plot
    if iscellstr(lines)
        legend(lines);
    else
        legend(strcat(result_settings.lines{1}, {' '}, cellstr(num2str(lines'))'));
    end

    % set labels of axises in plot
    xlabel(parameter)
    if strcmpi(result_settings.lines{1}, 'event_metrics') || strcmpi(result_settings.lines{1}, 'appliance_metrics')
        ylabel('performance')
    else
        ylabel(result_settings.metric)
    end

    % set title of plot
    %title(['Performance of ''', algorithm ,''' algorithm with ''', configuration, ''' configuration:'],'FontWeight', 'Bold');

    grid on;

end