%   FUZZY RULE BASE COMPRESSION SOFTWARE
%
%   This software implements advanced inference in fuzzy rule based systems
%   by filtration of non-monotonic rules. This software is for mandani type
%   system only.
%
%
%   Neelamugilan Gobalakrishnan
%   2006
%   $Revision:1.0 $ $Date: 15/03/2006 $

clc
clear all

disp('***********************************************************');
disp('*                                                         *');
disp('*         FUZZY RULE BASE COMPRESSION SOFTWARE            *');
disp('*                                                         *');
disp('*     This software implements advanced inference in      *');
disp('*     fuzzy rule based systems by filtration of non-      *');
disp('*     monotonic rules.                                    *');
disp('*                                                         *');
disp('***********************************************************');

fprintf('\n\n');

condStatus = 'start'; %Condition status for choice while loop

while strcmp(condStatus,'start') | strcmp(condStatus,'cont')
    clear choise
    fprintf('Enter 1 to create a new system\n');
    fprintf('Enter 2 to load a system (*.fis)\n\n');
    fprintf('Please enter your choice : ');
    choise = input('');
    if choise == 1 % Create a new system
        fprintf('\n');
        systemName = input('Enter the name of this system : ','s');
        % Call createinttable function to create integer table
        [sys intTable] = createinttable(systemName);
        % Arranged the rules into groups
        [groupedTable outNum] = arrangerules(sys,intTable);

        % Get the initial values for each inputs
        for i=1:size(sys.input, 2)
            fprintf('\nEnter the initial %s : ', sys.input(i).name);
            initialVal(i) = input('');
        end

        displayOpt = 'on'; % Option to display the compressed rules
        % Find dominant rules for provided initial values
        compressRules = finddomrules(sys,intTable,outNum,initialVal,displayOpt);

        newSysName = input('Please enter the name for compressed system : ','s');
        fprintf('\n');
        % Create the system with compressed rules
        createfis(newSysName,sys,compressRules);

        % Simulates the fuzzy inference system
        a = readfis(newSysName);
        outResult = evalfis([initialVal],a);

        fprintf('\n');
        fprintf('The %s value for \n', sys.output(outNum).name);

        for i=1:size(sys.input, 2)
            fprintf('%s(%.2f) \n',sys.input(i).name ,initialVal(i));
        end

        fprintf('= %.2f', outResult);
        fprintf('\n\n');

        view_surface = input('Do u want to view the output surface (yes/no) : ','s');

        % Display the output surface
        while strcmp(view_surface,'yes')
            fprintf('\nEnter 1 to view the original system, output surface\n');
            fprintf('Enter 2 to view the compressed system, output surface\n\n');
            fprintf('Please enter your option : ');
            option = input('');
            if option == 1
                % Create output surface for the original system
                createoutsurf(sys,systemName,intTable,outNum,option);
                fprintf('\nDo u want to view the output surface with\n');
                view_surface = input('new points or compressed system? (yes/no) : ','s');
            elseif option == 2
                % Create output surface for the compressed system
                createoutsurf(sys,newSysName,intTable,outNum,option);
                fprintf('\nDo u want to view the output surface with\n');
                view_surface = input('new points or original system? (yes/no) : ','s');
            else
                disp('Invalid value has been entered! ');
                fprintf('\n');
                view_surface = 'yes';
            end
        end
        condStatus = 'stop'; % choice while loop stopping condition
        % Load a system
    elseif choise == 2
        [fileName,pathName]=uigetfile('*.fis','Read FIS');
        if isequal(fileName,0) || isequal(pathName,0)
          % If fileName is zero, "cancel" was hit, or there was an error.
          errorStr='No file was loaded';
          return
        end
        systemName = [pathName fileName];

        sys = readfis(systemName);

        % Create the integer table
        for i=1:size(sys.rule,2)
            intTable(i,:) = [sys.rule(i).antecedent sys.rule(i).consequent];
        end

        fprintf('\n');
        disp('-------------------------------------------------------------');
        fprintf(' ');
        disp('                    Complete integer table                   ');
        fprintf(' ');
        disp('-------------------------------------------------------------');

        fprintf('\n');
        disp('-------------------------------------------------------------');
        fprintf(' ');

        for i=1:size(sys.input,2)
            fprintf('Input%d ', i);
        end

        for i=1:size(sys.output,2)
            fprintf('Output%d ', i);
        end

        fprintf('\n');
        disp('-------------------------------------------------------------');
        fprintf('\n');
        disp(intTable);
        disp('-------------------------------------------------------------');
        fprintf('\n');

        % Arranged the rules into groups
        [groupedTable outNum] = arrangerules(sys,intTable);

        % Get the initial values for each inputs
        for i=1:size(sys.input, 2)
            fprintf('\nEnter the initial %s : ', sys.input(i).name);
            initialVal(i) = input('');
        end

        displayOpt = 'on'; % Option to display the compressed rules
        % Find dominant rules for provided initial values
        compressRules = finddomrules(sys,intTable,outNum,initialVal,displayOpt);

        fprintf('\n');
        newSysName = input('Please enter the name for compressed system : ','s');
        % Create the system with compressed rules
        createfis(newSysName,sys,compressRules);

        % Simulates the fuzzy inference system
        a = readfis(newSysName);
        outResult = evalfis([initialVal],a);

        fprintf('\n');
        fprintf('The %s value for \n', sys.output(outNum).name);

        for i=1:size(sys.input, 2)
            fprintf('%s(%.2f) \n',sys.input(i).name ,initialVal(i));
        end

        fprintf('= %.2f', outResult);
        fprintf('\n\n');

        view_surface = input('Do u want to view the output surface (yes/no) : ','s');

        % Display the output surface
        while strcmp(view_surface,'yes')
            fprintf('\nEnter 1 to view the original system, output surface\n');
            fprintf('Enter 2 to view the compressed system, output surface\n\n');
            fprintf('Please enter your option : ');
            option = input('');
            if option == 1
                % Create output surface for the original system
                createoutsurf(sys,systemName,groupedTable,outNum,option);
                fprintf('\nDo u want to view the output surface with\n');
                view_surface = input('new points or compressed system? (yes/no) : ','s');
            elseif option == 2
                % Create output surface for the compressed system
                createoutsurf(sys,newSysName,groupedTable,outNum,option);
                fprintf('\nDo u want to view the output surface with\n');
                view_surface = input('new points or original system? (yes/no) : ','s');
            else
                fprintf('\n');
                disp('Invalid value has been entered! ');
                view_surface = 'yes';
            end
        end
        condStatus = 'stop'; % choice while loop stopping condition
    else
        disp('Invalid value has been entered! ');
        condStatus = 'cont'; % Choice while loop continue condition
    end
end


function eventLog = ma_executeDVManeuver(maneuverEvent, initialState, eventNum, celBodyData)
%ma_executeDVManeuver Summary of this function goes here
%   Detailed explanation goes here

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Do the maneuver according to its sub-type
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    type = maneuverEvent.maneuverType;
    value = maneuverEvent.maneuverValue;
    thruster = maneuverEvent.thruster;
    switch type
        case 'dv_inertial'
            eventLog = ma_executeDVManeuver_dv_inertial(value, thruster, initialState, eventNum);
        case 'dv_orbit'
            eventLog = ma_executeDVManeuver_dv_orbit(value, thruster, initialState, eventNum);
        case 'finite_inertial'
            eventLog = ma_executeDVManeuver_finite_inertial(value, thruster, initialState, eventNum, celBodyData);
        case 'finite_steered'
            eventLog = ma_executeDVManeuver_finite_steered(value, thruster, initialState, eventNum, celBodyData);
        case 'dv_inertial_spherical'
            eventLog = ma_executeDVManeuver_dv_inertial_spherical(value, thruster, initialState, eventNum);
        case 'dv_orbit_spherical'
            eventLog = ma_executeDVManeuver_dv_orbit_spherical(value, thruster, initialState, eventNum);
        case 'finite_inertial_spherical'
            eventLog = ma_executeDVManeuver_finite_inertial_spherical(value, thruster, initialState, eventNum, celBodyData);
        case 'finite_steered_spherical'
            eventLog = ma_executeDVManeuver_finite_steered_spherical(value, thruster, initialState, eventNum, celBodyData);
        case 'circularize'
            eventLog = ma_executeDVManeuver_circularize(thruster, initialState, eventNum, celBodyData);
        otherwise
            error(['Did not recongize maneuver of type ', type]);
    end
end


function title = p06_title ( )

%*****************************************************************************80
%
%% P06_TITLE returns the title of problem p06.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    05 August 2012
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Output, string TITLE, the title of the problem.
%
  title = 'Discontinuous';

  return
end


function varargout = panel_cluster(varargin)
% PANEL_CLUSTER: Create a panel to add/remove/edit clusters of sensors attached to a given 3DViz figure.
%
% USAGE:  bstPanelNew = panel_cluster('CreatePanel')
%                       panel_cluster('UpdatePanel')
%                       panel_cluster('CurrentFigureChanged_Callback')
%
% @=============================================================================
% 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, 2009-2023

eval(macro_method);
end

%% ===== CREATE PANEL =====
function bstPanelNew = CreatePanel() %#ok<DEFNU>
    panelName = 'Cluster';
    % Java initializations
    import java.awt.*;
    import javax.swing.*;
    import org.brainstorm.icon.*;
    % Create tools panel
    jPanelNew = gui_component('Panel');
    TB_DIM = java_scaled('dimension',25,25);
    % Font size for the lists
    fontSize = round(11 * bst_get('InterfaceScaling') / 100);

    % ===== TOOLBAR =====
    jMenuBar = gui_component('MenuBar', jPanelNew, BorderLayout.NORTH);
        jToolbar = gui_component('Toolbar', jMenuBar);
        jToolbar.setPreferredSize(TB_DIM);
        jToolbar.setOpaque(0);
        % First buttons
        gui_component('ToolbarButton', jToolbar,[],[], IconLoader.ICON_NEW_SEL, 'Create new cluster: mouse selection.
Use sensors selected in any time series or topography figure.', @(h,ev)CreateNewCluster('Selection'));
        gui_component('ToolbarButton', jToolbar,[],[], IconLoader.ICON_NEW_IND, 'Create new cluster: list of indices/names.
Type the list of sensors indices, names or types you want in the new cluster.', @(h,ev)CreateNewCluster('Indices'));
        gui_component('ToolbarButton', jToolbar,[],[], IconLoader.ICON_TS_DISPLAY, 'Display cluster time series    [ENTER]', @(h,ev)view_clusters());

        % === MENU EDIT ===
        jMenu = gui_component('Menu', jMenuBar, [], 'Edit', IconLoader.ICON_MENU, [], [], 11);
        jMenu.setBorder(BorderFactory.createEmptyBorder(0,2,0,2));
            % Set color
            gui_component('MenuItem', jMenu, [], 'Set color', IconLoader.ICON_COLOR_SELECTION, [], @(h,ev)bst_call(@EditClusterColor));
            % Menu: Set cluster function
            jMenuTs = gui_component('Menu', jMenu, [], 'Set function', IconLoader.ICON_PROPERTIES, [], []);
                gui_component('MenuItem', jMenuTs, [], 'Mean',    [], [], @(h,ev)SetClusterFunction('Mean'));
                gui_component('MenuItem', jMenuTs, [], 'Mean+Std',    [], [], @(h,ev)SetClusterFunction('Mean+Std'));
                gui_component('MenuItem', jMenuTs, [], 'Mean+StdErr',    [], [], @(h,ev)SetClusterFunction('Mean+StdErr'));
                gui_component('MenuItem', jMenuTs, [], 'PCA',     [], [], @(h,ev)SetClusterFunction('PCA'));
                gui_component('MenuItem', jMenuTs, [], 'FastPCA', [], [], @(h,ev)SetClusterFunction('FastPCA'));
                gui_component('MenuItem', jMenuTs, [], 'Max',     [], [], @(h,ev)SetClusterFunction('Max'));
                gui_component('MenuItem', jMenuTs, [], 'Power',   [], [], @(h,ev)SetClusterFunction('Power'));
                gui_component('MenuItem', jMenuTs, [], 'All',     [], [], @(h,ev)SetClusterFunction('All'));
            jMenu.addSeparator();
            gui_component('MenuItem', jMenu, [], 'Rename   [Double-click]', IconLoader.ICON_EDIT,    [], @(h,ev)EditClusterLabel);
            gui_component('MenuItem', jMenu, [], 'Remove   [DEL]',          IconLoader.ICON_DELETE,  [], @(h,ev)RemoveClusters);
            gui_component('MenuItem', jMenu, [], 'Deselect all  [ESC]',     IconLoader.ICON_RELOAD,  [], @(h,ev)SetSelectedClusters(0));
            jMenu.addSeparator();
            gui_component('MenuItem', jMenu, [], 'Export to Matlab', IconLoader.ICON_MATLAB_EXPORT, [], @(h,ev)bst_call(@ExportClustersToMatlab));
            gui_component('MenuItem', jMenu, [], 'Import from Matlab', IconLoader.ICON_MATLAB_IMPORT, [], @(h,ev)bst_call(@ImportClustersFromMatlab));
            jMenu.addSeparator();
            gui_component('MenuItem', jMenu, [], 'Copy to other folders', IconLoader.ICON_COPY,  [], @(h,ev)CopyClusters('AllConditions'));
            gui_component('MenuItem', jMenu, [], 'Copy to other subjects', IconLoader.ICON_COPY,  [], @(h,ev)CopyClusters('AllSubjects'));

    % ===== PANEL MAIN =====
    jPanelMain = java_create('javax.swing.JPanel');
    jPanelMain.setLayout(BoxLayout(jPanelMain, BoxLayout.Y_AXIS));
    jPanelMain.setBorder(BorderFactory.createEmptyBorder(7,7,7,7));
        % ===== FIRST PART =====
        jPanelFirstPart = gui_component('Panel');
        jPanelFirstPart.setPreferredSize(java_scaled('dimension', 100,180));
        jPanelFirstPart.setMaximumSize(java_scaled('dimension', 500,180));
            % ===== Vertical Toolbar =====
            jToolbar2 = gui_component('Toolbar', jPanelFirstPart, BorderLayout.EAST);
            jToolbar2.setOrientation(jToolbar2.VERTICAL);
                gui_component('ToolbarButton', jToolbar2,[],[], IconLoader.ICON_FOLDER_OPEN, 'Load clusters file', @LoadClusters);
                gui_component('ToolbarButton', jToolbar2,[],[], IconLoader.ICON_SAVE, 'Save clusters file', @SaveClusters);

            % ===== Clusters list =====
             jPanelList = gui_component('Panel');
                jBorder = java_scaled('titledborder', 'Available clusters');
                jPanelList.setBorder(jBorder);

                jListClusters = java_create('org.brainstorm.list.BstClusterList');
                jListClusters.setCellRenderer(java_create('org.brainstorm.list.BstClusterListRenderer', 'I', fontSize));
                java_setcb(jListClusters, 'ValueChangedCallback', @ListValueChanged_Callback, ...
                                          'KeyTypedCallback',     @ListKeyTyped_Callback, ...
                                          'MouseClickedCallback', @ListClick_Callback);
                jPanelScrollList = java_create('javax.swing.JScrollPane');
                jPanelScrollList.getLayout.getViewport.setView(jListClusters);
                jPanelScrollList.setBorder([]);
                jPanelList.add(jPanelScrollList);
            jPanelFirstPart.add(jPanelList, BorderLayout.CENTER);
        jPanelMain.add(jPanelFirstPart);

        % ===== Clusters options panel =====
        jPanelOptions = gui_river([0,3], [0,5,10,3], 'Options');
            % Add extra space when not on a Mac
            if strncmp(computer,'MAC',3)
                strSpace = '';
            else
                strSpace = '   ';
            end
            % OPTIONS : Cluster size in number of sensors
            gui_component('Label', jPanelOptions, [], 'Number of sensors:');
            jLabelClusterSize = gui_component('Label', jPanelOptions, 'hfill', '');
            % OPTIONS : Overlay clusters/conditions
            gui_component('Label', jPanelOptions, 'br', ['Overlay:' strSpace]);
            jCheckOverlayClusters   = gui_component('CheckBox', jPanelOptions, [], 'Cluster', {Insets(0,0,0,0)});
            jCheckOverlayConditions = gui_component('CheckBox', jPanelOptions, [], 'Files',   {Insets(0,0,0,0)});
        panelPrefSize = jPanelOptions.getPreferredSize();
        jPanelOptions.setMaximumSize(Dimension(32000, panelPrefSize.getHeight()));
        jPanelMain.add(jPanelOptions);
        jPanelMain.add(Box.createVerticalGlue());
    jPanelNew.add(jPanelMain);

    % Create the BstPanel object that is returned by the function
    % => constructor BstPanel(jHandle, panelName, sControls)
    bstPanelNew = BstPanel(panelName, ...
                           jPanelNew, ...
                           struct('jPanelList',       jPanelList, ...
                                  'jPanelOptions',    jPanelOptions, ...
                                  'jMenuBar',         jMenuBar, ...
                                  'jToolbar',         jToolbar, ...
                                  'jToolbar2',        jToolbar2, ...
                                  'jLabelClusterSize',       jLabelClusterSize, ...
                                  'jCheckOverlayClusters',   jCheckOverlayClusters, ...
                                  'jCheckOverlayConditions', jCheckOverlayConditions, ...
                                  'jListClusters',           jListClusters));
end

%% =================================================================================
%  === CONTROLS CALLBACKS  =========================================================
%  =================================================================================

%% ===== LIST SELECTION CHANGED CALLBACK =====
function ListValueChanged_Callback(h, ev)
    if ~ev.getValueIsAdjusting()
        % Update number of sensors
        UpdateProperties();
        % Update mouse selection in all the Datasets
        UpdateChannelSelection();
    end
end

%% ===== LIST KEY TYPED CALLBACK =====
function ListKeyTyped_Callback(h, ev)
    switch(uint8(ev.getKeyChar()))
        % DELETE
        case {ev.VK_DELETE, ev.VK_BACK_SPACE}
            RemoveClusters();
        case ev.VK_ENTER
            tree_view_clusters();
        case ev.VK_ESCAPE
            SetSelectedClusters([]);
    end
end

%% ===== LIST CLICK CALLBACK =====
function ListClick_Callback(h, ev)
    % If DOUBLE CLICK
    if (ev.getClickCount() == 2)
        % Rename selection
        EditClusterLabel();
    end
end

%% =================================================================================
%  === EXTERNAL PANEL CALLBACKS  ===================================================
%  =================================================================================
%% ===== CURRENT FIGURE CHANGED =====
function CurrentFigureChanged_Callback(hFig)
    % Update list of clusters in the panel
    UpdatePanel();
    % Update sensor selection
    UpdateChannelSelection();
end

%% ===== UPDATE CALLBACK =====
function UpdatePanel()
    % Get panel controls
    ctrl = bst_get('PanelControls', 'Cluster');
    if isempty(ctrl)
        return;
    end
    % Update clusters JList
    UpdateClustersList();
end

%% ===== UPDATE CLUSTERS LIST =====
function UpdateClustersList()
    import org.brainstorm.list.*;
    % Get "Cluster" panel controls
    ctrl = bst_get('PanelControls', 'Cluster');
    if isempty(ctrl)
        return;
    end
    % Get clusters
    sClusters = GetClusters();

    % Remove temporarily the list callback
    callbackBak = java_getcb(ctrl.jListClusters, 'ValueChangedCallback');
    java_setcb(ctrl.jListClusters, 'ValueChangedCallback', []);
    % Get selected clusters
    SelNames = {};
    selValues = ctrl.jListClusters.getSelectedValues();
    for i = 1:length(selValues)
        SelNames{end+1} = char(selValues(i));
    end
    if ~isempty(SelNames) && ~isempty(sClusters)
        iSelected = find(ismember({sClusters.Label}, SelNames));
    else
        iSelected = [];
    end
    % Create a new empty list
    listModel = java_create('javax.swing.DefaultListModel');
    % Get font with which the list is rendered
    fontSize = round(11 * bst_get('InterfaceScaling') / 100);
    jFont = java.awt.Font('Dialog', java.awt.Font.PLAIN, fontSize);
    tk = java.awt.Toolkit.getDefaultToolkit();
    % Add an item in list for each cluster
    Wmax = 0;
    for i = 1:length(sClusters)
        itemType  = sClusters(i).Function;
        itemText  = sClusters(i).Label;
        itemColor = sClusters(i).Color;
        listModel.addElement(BstListItem(itemType, [], itemText, i, itemColor(1), itemColor(2), itemColor(3)));
        % Get longest string
        W = tk.getFontMetrics(jFont).stringWidth(sClusters(i).Label);
        if (W > Wmax)
            Wmax = W;
        end
    end
    % Update list model
    ctrl.jListClusters.setModel(listModel);
    % Update cell rederer based on longest channel name
    ctrl.jListClusters.setCellRenderer(java_create('org.brainstorm.list.BstClusterListRenderer', 'II', fontSize, Wmax + 28));
    % Select previously selected clusters
    if ~isempty(iSelected)
        ctrl.jListClusters.setSelectedIndices(iSelected - 1);
    end
    % Reset cluster comments
    ctrl.jLabelClusterSize.setText('');

    % Restore callback
    drawnow;
    java_setcb(ctrl.jListClusters, 'ValueChangedCallback', callbackBak);
end

%% ===== UPDATE CLUSTER PROPERTIES DISPLAY =====
function UpdateProperties()
    % Get panel controls
    ctrl = bst_get('PanelControls', 'Cluster');
    if isempty(ctrl)
        return;
    end
    % Get selected clusters
    sClusters = GetSelectedClusters();
    % Add all the selected clusters to compute
    nbSensors = 0;
    for i = 1:length(sClusters)
        nbSensors = nbSensors + length(sClusters(i).Sensors);
    end
    % Format results
    if (nbSensors == 0)
        strSize = '';
    else
        strSize = sprintf(' [ %d ]', nbSensors);
    end
    % Update panel
    ctrl.jLabelClusterSize.setText(strSize);
end

%% ===== UPDATE CHANNEL SELECTION =====
function UpdateChannelSelection()
    % Get selected clusters
    [sClusters, iClusters] = GetSelectedClusters();
    if isempty(sClusters)
        return;
    end
    % Join all the clusters contents
    SensorsNames = unique([sClusters.Sensors]);
    % Update sensors list
    bst_figures('SetSelectedRows', SensorsNames, 0);
end

%% ===== GET CLUSTERS =====
% USAGE:  [sClusters, iDSall, iFigall, hFigall] = panel_cluster('GetClusters')
function [sClusters, iDSall, iFigall, hFigall] = GetClusters()
    global GlobalData;
    % Get current figure
    [hFigall,iFigall,iDSall] = bst_figures('GetCurrentFigure');
    % Check if there are electrodes defined for this file
    if isempty(hFigall) || isempty(GlobalData.DataSet(iDSall).Clusters) || isempty(GlobalData.DataSet(iDSall).Clusters)
        sClusters = [];
        return;
    end
    % Return all the available electrodes
    sClusters = GlobalData.DataSet(iDSall).Clusters;
    ChannelFile = GlobalData.DataSet(iDSall).ChannelFile;
    % Get all the figures that share this channel file
    for iDS = 1:length(GlobalData.DataSet)
        % Skip if not the correct channel file
        if ~file_compare(GlobalData.DataSet(iDS).ChannelFile, ChannelFile)
            continue;
        end
        % Get all the figures
        for iFig = 1:length(GlobalData.DataSet(iDS).Figure)
            if ((iDS ~= iDSall(1)) || (iFig ~= iFigall(1))) && ismember(GlobalData.DataSet(iDS).Figure(iFig).Id.Type, {'DataTimeSeries', '3DViz', 'Topography'})
                iDSall(end+1) = iDS;
                iFigall(end+1) = iFig;
                hFigall(end+1) = GlobalData.DataSet(iDS).Figure(iFig).hFigure;
            end
        end
    end
end

%% ===== SET CLUSTER =====
% USAGE:  iClusters = SetClusters(iClusters=[], sClusters)
%         iClusters = SetClusters('Add', sClusters)
function iClusters = SetClusters(iClusters, sClusters)
    global GlobalData;
    % Parse input
    isAdd = ~isempty(iClusters) && ischar(iClusters) && strcmpi(iClusters, 'Add');
    % Get dataset
    [sClustersOld, iDSall] = GetClusters();
    % If there is no selected dataset
    if isempty(iDSall)
        return;
    end
    % Perform operations only once per dataset
    iDSall = unique(iDSall);
    for iDS = iDSall
        % Reset clusters list
        if isempty(sClusters)
            GlobalData.DataSet(iDS).Clusters(iClusters) = [];
        % Set clusters
        else
            % Add new clusters
            if isAdd
                iClusters = length(GlobalData.DataSet(iDS).Clusters) + (1:length(sClusters));
                % Add clusters
                for i = 1:length(sClusters)
                    % Default cluster name
                    if isempty(sClusters(i).Label)
                        sClusters(i).Label = sprintf('c%d', iClusters(i));
                    end
                    % Make new cluster names unique
                    if ~isempty(GlobalData.DataSet(iDS).Clusters)
                        sClusters(i).Label = file_unique(sClusters(i).Label, {GlobalData.DataSet(iDS).Clusters.Label, sClusters(1:i-1).Label});
                    end
                end
            end
            % Set clusters in global structure
            if isempty(GlobalData.DataSet(iDS).Clusters)
                GlobalData.DataSet(iDS).Clusters = sClusters;
            else
                GlobalData.DataSet(iDS).Clusters(iClusters) = sClusters;
            end
        end
        % Add color if not defined yet
        for i = 1:length(GlobalData.DataSet(iDS).Clusters)
            if isempty(GlobalData.DataSet(iDS).Clusters(i).Color)
                ColorTable = panel_scout('GetScoutsColorTable');
                iColor = mod(i-1, length(ColorTable)) + 1;
                GlobalData.DataSet(iDS).Clusters(i).Color = ColorTable(iColor,:);
            end
        end
    end
    % Mark channel file as modified (only in first dataset)
    GlobalData.DataSet(iDSall(1)).isChannelModified = 1;
end

%% ===== GET SELECTED CLUSTERS =====
% NB: Returned indices are indices in Clusters array
function [sSelClusters, iSelClusters] = GetSelectedClusters()
    sSelClusters = [];
    iSelClusters = [];
    % Get panel controls
    ctrl = bst_get('PanelControls', 'Cluster');
    if isempty(ctrl)
        return;
    end
    % Get current clusters
    sClusters = GetClusters();
    if isempty(sClusters)
        return
    end
    % Get JList selected indices
    iSelClusters = uint16(ctrl.jListClusters.getSelectedIndices())' + 1;
    if isempty(iSelClusters)
        return
    end
    sSelClusters = sClusters(iSelClusters);
end

%% ===== SET SELECTED CLUSTERS =====
% WARNING: Input indices are references in the Clusters array, not in the JList
function SetSelectedClusters(iSelClusters, isUpdateMouseSel)
    if (nargin < 2) || isempty(isUpdateMouseSel)
        isUpdateMouseSel = 1;
    end
    % === CHECK FOR MODIFICATIONS ===
    iSelClusters = iSelClusters - 1;
    % Get panel controls
    ctrl = bst_get('PanelControls', 'Cluster');
    if isempty(ctrl)
        return;
    end
    % Get previous selection
    iPrevItems = ctrl.jListClusters.getSelectedIndices();
    % If selection did not change: exit
    if isequal(iPrevItems, iSelClusters) || (isempty(iPrevItems) && isequal(iSelClusters, -1))
        return
    end

    % === UPDATE SELECTION ===
    % Temporality disables JList selection callback
    jListCallback_bak = java_getcb(ctrl.jListClusters, 'ValueChangedCallback');
    java_setcb(ctrl.jListClusters, 'ValueChangedCallback', []);
    % Select items in JList
    if isempty(iSelClusters)
        ctrl.jListClusters.setSelectedIndices(-1);
    else
        ctrl.jListClusters.setSelectedIndices(iSelClusters);
    end
    % Restore JList callback
    java_setcb(ctrl.jListClusters, 'ValueChangedCallback', jListCallback_bak);
    % Update panel "Clusters" fields
    UpdateProperties();
    if isUpdateMouseSel
        % Update selected channels for all figures
        UpdateChannelSelection();
    end
end

%% ===== GET CLUSTER DISPLAY TYPE =====
% ClustersOptions:
%    |- overlayClusters   : {0, 1}
%    |- overlayConditions : {0, 1}
function ClustersOptions = GetClusterOptions() %#ok<DEFNU>
    % Get panel controls
    ctrl = bst_get('PanelControls', 'Cluster');
    if isempty(ctrl)
        ClustersOptions = [];
        return;
    end
    % Get current clusters
    sClusters = GetClusters();
    % If at least one of the cluster functions is "All", ignore the overlay checkboxes
    if any(strcmpi({sClusters.Function}, 'All'))
        ClustersOptions.overlayClusters   = 0;
        ClustersOptions.overlayConditions = 0;
    else
        % Overlay
        ClustersOptions.overlayClusters   = ctrl.jCheckOverlayClusters.isSelected();
        ClustersOptions.overlayConditions = ctrl.jCheckOverlayConditions.isSelected();
    end
end

%% ===== SET CLUSTER DISPLAY TYPE =====
function SetClusterOptions(overlayClusters, overlayConditions) %#ok<DEFNU>
    % Overlay
    if ~isempty(overlayClusters)
        ctrl.jCheckOverlayClusters.setSelected(overlayClusters);
    end
    if ~isempty(overlayConditions)
        ctrl.jCheckOverlayConditions.setSelected(overlayConditions);
    end
end

%% ===== SET CLUSTER FUNCTION =====
% USAGE:  SetClusterFunction(Function)
function SetClusterFunction(Function)
    % Get select clusters
    [sClusters, iClusters] = GetSelectedClusters();
    if isempty(iClusters)
        return
    end
    % Set function
    [sClusters.Function] = deal(Function);
    % Save clusters
    SetClusters(iClusters, sClusters);
    % Update JList
    UpdateClustersList();
    % Select edited clusters (selection was lost during update)
    SetSelectedClusters(iClusters);
end

%% ===== GET CHANNELS IN CLUSTER =====
function [iChannel, Modality] = GetChannelsInCluster(sCluster, Channel, ChannelFlag)
    % Get channels
    iChannel = zeros(1, length(sCluster.Sensors));
    for ic = 1:length(iChannel)
        tmp = find(strcmpi(sCluster.Sensors{ic}, {Channel.Name}));
        if isempty(tmp)
            iChannel = [];
            bst_error(['Sensor "' sCluster.Sensors{ic} '" does not exist.'], 'Clusters', 0);
            return;
        end
        iChannel(ic) = tmp;
    end
    % Check cluster homogeneity
    Modality = unique({Channel(iChannel).Type});
    if (length(Modality) > 1)
        iChannel = [];
        bst_error('Sensors in a cluster must be of the same type.', 'Clusters', 0);
        return;
    end
    Modality = Modality{1};
    % Remove channels that are "BAD"
    iBadChannels = find(ChannelFlag == -1);
    iChannel = setdiff(iChannel, iBadChannels);
    % Check if there are still some channels in the cluster
    if isempty(iChannel)
        iChannel = [];
        bst_error('All the channels in the cluster are BAD.', 'Clusters', 0);
        return;
    end
end

%% ===== CREATE NEW CLUSTER =====
% Usage:  [sCluster, iCluster] = CreateNewCluster(Sensors)      : Cell array of sensors names
%         [sCluster, iCluster] = CreateNewCluster('Selection')  : Get the current sensors selected by the user with his mouse
%         [sCluster, iCluster] = CreateNewCluster('Indices')    : Ask user to type the list of sensors indices
function [sCluster, iCluster] = CreateNewCluster(Sensors)
    global GlobalData;
    % Update current figure
    bst_figures('CheckCurrentFigure');

    % === GET SENSORS NAMES ===
    if ischar(Sensors)
        % Get selected figure
        [hFig,iFig,iDS] = bst_figures('GetCurrentFigure');
        if isempty(hFig)
            bst_error('No figure available.', 'Create new cluster', 0);
            return
        end

        % SELECTION: Get mouse selected sensors
        if strcmpi(Sensors, 'Selection')
            % Get the mouse selected sensors (generic interface)
            [SelChan, iSelChan] = bst_figures('GetSelectedChannels', iDS);
            % Selected sensors = intersection(mouse selection, figure sensors)
            iSensors = intersect(iSelChan, GlobalData.DataSet(iDS).Figure(iFig).SelectedChannels);
            % If no selection in current figure: error
            if isempty(iSensors)
                bst_error('Please select at least one sensor in the current figure.', 'Create new cluster', 0);
                return;
            end
        % INDICES: Ask user to give a list of sensors indices
        elseif strcmpi(Sensors, 'Indices')
            % Ask user to enter manually the indices of the sensors
            res = java_dialog('input', ['Enter the list of channels separated with commas.' 10 10 ...
                                        'Channels can be selected by:' 10 ...
                                        '     1) Types: "MEG, Misc"' 10 ...
                                        '     2) Names: "EEG021, EEG023"' 10 ...
                                        '     3) Indices: "5, 12, 13"' 10 10], 'Create cluster');
            if isempty(res)
                return;
            end
            % Get the channels indices
            iSensors = channel_find(GlobalData.DataSet(iDS).Channel, res);
            % If invalid sensors list
            if isempty(iSensors)
                bst_error('Invalid sensors selection.', 'Create new cluster', 0);
                return;
            end
            % Order sensors names and make unique
            iSensors = unique(iSensors);
        end
        % Check that the sensors are all of the same type
        uniqueType = unique({GlobalData.DataSet(iDS).Channel(iSensors).Type});
        if (length(uniqueType) > 1)
            java_dialog('warning', ['Warning: Sensors in a cluster must be of the same type.' 10 10 ...
                                    'Different types in this selection: ' uniqueType{1}, ', ' uniqueType{2}, 10 10], 'Create cluster');
            return
        end
        % Get the names of the mouse selected sensors
        Sensors = {GlobalData.DataSet(iDS).Channel(iSensors).Name};
    end

    % === NEW CLUSTER ===
    % New cluster structure
    sCluster  = db_template('Cluster');
    % Store current cluster sensors list
    sCluster.Sensors = Sensors;
    % Add clusters
    iCluster = SetClusters('Add', sCluster);

    % Update clusters list
    UpdateClustersList();
    % Select new cluster
    SetSelectedClusters(iCluster);
end

%% ===============================================================================
%  ====== CLUSTERS OPERATIONS ====================================================
%  ===============================================================================
%% ===== REMOVE CLUSTERS =====
% Usage : RemoveClusters()  : remove the clusters selected in the JList
function RemoveClusters()
    global GlobalData;
    % Get dataset
    [sClusters, iDSall, iFigall] = GetClusters();
    if isempty(iDSall)
        return;
    end
    % Get selected clusters
    [sClusters, iClusters] = GetSelectedClusters();
    % Check whether a cluster is selected
    if isempty(sClusters)
        java_dialog('warning', 'No cluster selected.', 'Remove cluster');
        return
    end
    % Ask for confirmation
    if (length(sClusters) == 1)
        strConfirm = ['Delete cluster "' sClusters(1).Label '"?'];
    else
        strConfirm = ['Delete ' num2str(length(sClusters)) ' clusters?'];
    end
    if ~java_dialog('confirm', strConfirm)
        return;
    end
    % Remove clusters definitions from global data structure
    for iDS = unique(iDSall)
        GlobalData.DataSet(iDS).Clusters(iClusters) = [];
        GlobalData.DataSet(iDS).isChannelModified = 1;
    end
    % Update Clusters list
    UpdateClustersList();
    % Reset list of selected sensors
    bst_figures('SetSelectedRows', [], 0);
end

%% ===== EDIT CLUSTER LABEL =====
% Usage : EditClusterLabel()  : Interactive edition of cluster name
function EditClusterLabel()
    % Get all clusters
    sClustersAll = GetClusters();
    % Get selected clusters
    [sCluster, iCluster] = GetSelectedClusters();
    % Warning message if no cluster selected
    if isempty(sCluster)
        java_dialog('warning', 'No cluster selected.', 'Rename selected cluster');
        return;
    % If more than one cluster selected: keep only the first one
    elseif (length(sCluster) > 1)
        iCluster = iCluster(1);
        sCluster = sCluster(1);
        SetSelectedClusters(iCluster);
    end
    % Ask user for a new Cluster Label
    newLabel = java_dialog('input', sprintf('Please enter a new label for cluster "%s":', sCluster.Label), ...
                           'Rename selected cluster', [], sCluster.Label);
    if isempty(newLabel) || strcmpi(newLabel, sCluster.Label)
        return
    elseif ~isempty(sClustersAll) && any(strcmpi({sClustersAll.Label}, newLabel))
        java_dialog('warning', 'Cluster name already exists.', 'Rename selected cluster');
        return;
    end

    % Update cluster definition
    sCluster.Label = newLabel;
    SetClusters(iCluster, sCluster);
    % Update JList
    UpdateClustersList();
    % Select edited clusters (selection was lost during update)
    SetSelectedClusters(iCluster);
end

%% ===== EDIT CLUSTER COLOR =====
function EditClusterColor(newColor)
    % Get selected clusters
    [sSelClusters, iSelClusters] = GetSelectedClusters();
    if isempty(iSelClusters)
        java_dialog('warning', 'No cluster selected.', 'Edit cluster color');
        return
    end
    % If color is not specified in argument : ask it to user
    if (nargin < 1)
        newColor = java_dialog('color');
        if (length(newColor) ~= 3) || all(sSelClusters(1).Color(:) == newColor(:))
            return
        end
    end
    % Update cluster color
    for i = 1:length(sSelClusters)
        sSelClusters(i).Color = newColor;
    end
    % Save clusters
    SetClusters(iSelClusters, sSelClusters);
    % Update clusters list
    UpdateClustersList();
end

%% ===== SAVE CLUSTERS =====
function SaveClusters(varargin)
    % Get selected clusters
    sClusters = GetSelectedClusters();
    if isempty(sClusters)
        return
    end
    % Default folder name
    ClusterDir = GetDefaultClusterDir();
    % Build a default file name
    ClusterFile = bst_fullfile(ClusterDir, ['cluster', sprintf('_%s', sClusters.Label), '.mat']);
    % Get filename where to store the filename
    ClusterFile = java_getfile( 'save', 'Save selected clusters', ClusterFile, ...
                                'single', 'files', ...
                                {{'_cluster'}, 'Brainstorm sensor clusters (*cluster*.mat)', 'BST'}, 1);
    if isempty(ClusterFile)
        return;
    end
    % Make sure that filename contains the 'cluster' tag
    if isempty(strfind(ClusterFile, '_cluster')) && isempty(strfind(ClusterFile, 'cluster_'))
        [filePath, fileBase, fileExt] = bst_fileparts(ClusterFile);
        ClusterFile = bst_fullfile(filePath, ['cluster_' fileBase fileExt]);
    end
    % Save file
    FileMat.Clusters = sClusters;
    bst_save(ClusterFile, FileMat, 'v7');
end

%% ===== GET DEFAULT CLUSTER DIR =====
function ClusterDir = GetDefaultClusterDir()
    global GlobalData;
    % Get subject of current figure
    [hFig,iFig,iDS] = bst_figures('GetCurrentFigure');
    if ~isempty(iDS) && ~isempty(GlobalData.DataSet(iDS).SubjectFile)
        SubjectFile = GlobalData.DataSet(iDS).SubjectFile;
    % Get current subject in the database
    else
        sSubject = bst_get('Subject');
        if ~isempty(sSubject)
            SubjectFile = sSubject.FileName;
        else
            SubjectFile = [];
        end
    end
    % Get subject anatomy folder
    if ~isempty(SubjectFile)
        ClusterDir = bst_fileparts(file_fullpath(SubjectFile));
    else
        ClusterDir = '';
    end
end

%% ===== LOAD CLUSTER =====
function LoadClusters(varargin)
    % Get default cluster folder
    ClusterDir = GetDefaultClusterDir();
    % Ask user which are the files to be loaded
    [ClusterFiles, FileFormat] = java_getfile(...
        'open', 'Import clusters', ClusterDir, ...
        'multiple', 'files', ...
        bst_get('FileFilters', 'clusterin'), 1);
    if isempty(ClusterFiles)
        return
    end

    % ==== CREATE AND DISPLAY ====
    iNewClusters = [];
    bst_progress('start', 'Load clusters', 'Load cluster file');
    % Load all files selected by user
    for iFile = 1:length(ClusterFiles)
        % Load clusters
        sClusters = in_clusters(ClusterFiles{iFile}, FileFormat);
        % Add to current clusters
        iNewClusters = [iNewClusters, SetClusters('Add', sClusters)];
    end
    % Update cluster list
    UpdateClustersList();
    % Select first cluster
    if isempty(iNewClusters)
        SetSelectedClusters(iNewClusters(1));
    end
    bst_progress('stop');
end

%% ===== COPY CLUSTERS =====
function CopyClusters(Target)
    global GlobalData;
    % Get loaded clusters
    [sClusters, iDSall] = GetSelectedClusters();
    if isempty(sClusters)
        bst_error('No clusters selected.', 'Copy clusters', 0);
        return
    end
    % Copy clusters
    db_set_clusters(GlobalData.DataSet(iDSall(1)).ChannelFile, Target, sClusters);
end

%% ===== EXPORT CLUSTERS TO MATLAB =====
function ExportClustersToMatlab()
    % Get selected clusters
    sClusters = GetSelectedClusters();
    % If nothing selected, take all clusters
    if isempty(sClusters)
        sClusters = GetClusters();
    end
    % If nothing: exit
    if isempty(sClusters)
        return;
    end
    % Export to the base workspace
    export_matlab(sClusters, []);
    % Display in the command window the selected clusters
    disp([10 'List of sensors for each cluster:']);
    for i = 1:length(sClusters)
        disp(['   ' sClusters(i).Label ': ' sprintf('%s ', sClusters(i).Sensors{:})]);
    end
    disp(' ');
end

%% ===== IMPORT CLUSTERS FROM MATLAB =====
function ImportClustersFromMatlab()
    % Export to the base workspace
    sClusters = in_matlab_var([], 'struct');
    if isempty(sClusters)
        return;
    end
    % Check structure
    sTemplate = db_template('cluster');
    if isempty(sClusters) || ~isequal(fieldnames(sClusters), fieldnames(sTemplate))
        bst_error('Invalid clusters structure.', 'Import from Matlab', 0);
        return;
    end
    % Save new cluster
    iNewCluster = SetClusters('Add', sClusters);
    % Update "Clusters Manager" panel
    UpdateClustersList();
    % Select last cluster in list (new cluster)
    SetSelectedClusters(iNewCluster);
end


%% Subfunction: get a parameter in CSA series ASC header: MrPhoenixProtocol
function val = asc_header(s, key, dft)
if nargin>2, val = dft; else, val = []; end
csa = 'CSASeriesHeaderInfo';
if ~isfield(s, csa) % in case of multiframe
    try s.(csa) = s.SharedFunctionalGroupsSequence.Item_1.(csa).Item_1; end
end
if isfield(s, 'Private_0029_1020') && isa(s.Private_0029_1020, 'uint8')
    str = char(s.Private_0029_1020(:)');
    str = regexp(str, 'ASCCONV BEGIN(.*)ASCCONV END', 'tokens', 'once');
    if isempty(str), return; end
    str = str{1};
elseif isfield(s, 'MrPhoenixProtocol') % X20A
    str = s.MrPhoenixProtocol;
elseif ~isfield(s, csa), return; % non-siemens
elseif isfield(s.(csa), 'MrPhoenixProtocol') % most Siemens dicom
    str = s.(csa).MrPhoenixProtocol;
elseif isfield(s.(csa), 'MrProtocol') % older version dicom
    str = s.(csa).MrProtocol;
else, return;
end

% tSequenceFileName  = ""%SiemensSeq%\gre_field_mapping""
expr = ['\n' regexptranslate('escape', key) '\s*=\s*(.*?)\n'];
str = regexp(str, expr, 'tokens', 'once');
if isempty(str), return; end
str = strtrim(str{1});

if strncmp(str, '""', 2) % str parameter
    val = str(3:end-2);
elseif strncmp(str, '"', 1) % str parameter for version like 2004A
    val = str(2:end-1);
elseif strncmp(str, '0x', 2) % hex parameter, convert to decimal
    val = sscanf(str(3:end), '%x', 1);
else % decimal
    val = sscanf(str, '%g', 1);
end


function stuff = get(scnl, prop_name)
   %GET for the scnl object
   %  result = get(scnlobject, property), where PROPERTY is one of the
   %  following:
   %    'network', 'station', 'location', 'channel'
   %    'channeltag'
   %
   % If the results of a single SCNL are requested, then a string is returned.
   % Otherwise, a cell of values will be returned.

   lower_prop_name = lower(prop_name);
   if ~strcmp(lower_prop_name,prop_name)
      warning('SCNLOBJECT:get:propertyWarning',...
              'Use lowercase ''%s'' property name for consistency',lower(prop_name));
      prop_name = lower_prop_name;
   end

   switch prop_name

      case{'station','channel','network','location'}
         thesetags = [scnl.tag];
         stuff = {thesetags.(prop_name)};
      case{'nscl_string'}
         for n = numel(scnl): -1 : 1
            thistag = scnl(n).tag;
            stuff(n) = {[ thistag.network, '_', thistag.station, '_',...
               thistag.channel, '_', thistag.location ]};
         end
      case {'channeltag'}
         stuff = [scnl.tag];
         return
      otherwise
         error('SCNLOBJECT:get:UnrecognizedProperty',...
            'Unrecognized property name : %s',  prop_name);
   end

   %if a single scnl, then return the string representation, else return a
   %cell of strings.
   if numel(stuff) == 1
      stuff = stuff{1};
   else
      stuff = reshape(stuff,size(scnl));
   end;
end


 function y = mtimes_block(ob, x, iblock, nblock)
%function y = mtimes_block(ob, x, iblock, nblock)
%	y = G(i'th block) * x	or y = G'(i'th block) * x
%	in either case the project data will be "small"
%	iblock is 1,...,nblock

% support 'exists' option for seeing if this routine is available
if nargin == 2 & ischar(x) & streq(x, 'exists')
	y = 1;
	return
end

if nargin ~= 4, ir_usage, end

if nblock ~= 1 | iblock ~= 1
	error 'no block for mtimes_block'
end
y = ob * x;


function varargout=specsubm(varargin)
%
% For calling details please see v_specsubm.m
%
% This dummy routine is included for backward compatibility only
% and will be removed in a future release of voicebox. Please use
% v_specsubm.m in future and/or update with v_voicebox_update.m
%
%      Copyright (C) Mike Brookes 2018
%      Version: $Id: specsubm.m 10863 2018-09-21 15:39:23Z dmb $
%
if nargout
    varargout=cell(1,nargout);
    [varargout{:}]=v_specsubm(varargin{:});
else
    v_specsubm(varargin{:});
end


function UseCards(object, ~, inventory, main)

res = get(0, 'ScreenSize');
%% FIGURE WINDOW
handle = ...
    figure('Name', 'Use Cards', ...
           'Units', 'pixels', ...
           'MenuBar', 'none', ...
           'NumberTitle', 'off', ...
           'Position', [res(3:4)/3, 150, 200]);

%% RADIO BUTTONS
str = {'3 x Infantry','3 x Cavallery','3 x Artillery','One of each'};
r = zeros(1,4);
for i = 1:4
    r(i) = uicontrol('style', 'radiobutton', ...
                     'Parent', handle, ...
                     'Units', 'normalized', ...
                     'String', str{i}, ...
                     'FontSize', 12, ...
                     'BackgroundColor', get(handle, 'Color'), ...
                     'Position', [0.1, 0.85 - 0.2 * (i-1), 0.8, 0.1], ...
                     'CallBack', @RadioCallback);
end

%% USE BUTTON
uicontrol('style', 'pushbutton', ...
          'Parent', handle, ...
          'Units', 'normalized', ...
          'String', 'Use', ...
          'FontSize', 12, ...
          'FontWeight', 'bold', ...
          'BackgroundColor', get(handle, 'Color'), ...
          'Position', [0.3, 0.06, 0.4, 0.1], ...
          'CallBack', {@UseBtnCallback, inventory, r, object})

uiwait(handle) %10
uiresume(main); %9

function RadioCallback(button, ~)

r = findobj('Parent', get(button, 'Parent'), 'style', 'radiobutton');
for i = find(r ~= button)
    set(r(i), 'Value', 0);
end

function UseBtnCallback(button, ~, inventory, r, object)

for i = 1:4
    if get(r(i),'Value')
        break
    end
end

%% CHECK INVENTORY IF THIS OPTION IS AVAILABLE
N = [sum(inventory == 1), sum(inventory == 2), sum(inventory == 3)];
ok = ( (i < 4) && sum(inventory == i) >= 3 ) || ( (i == 4) && all(N) );

if ~ok
    uiwait(msgbox('You don''t have these cards.'))
    return
end

set(object, 'UserData', i);
uiresume(get(button, 'Parent')) %10
delete(get(button, 'Parent'))


function pickStatrStopNodes(src,evt,hAxis,axisType,~)

global airwayStateS

% set(src,'Marker','o','MarkerSize',12,'MarkerEdgeColor',[0,0,0])
xV = get(src,'XData');
yV = get(src,'YData');
zV = get(src,'ZData');
%x = mean(xV);
%y = mean(yV);
%z = mean(zV);
distV = (xV-evt.IntersectionPoint(1)).^2 + (yV-evt.IntersectionPoint(2)).^2 + ...
    (zV-evt.IntersectionPoint(3)).^2;
[minDist,indMin] = min(distV);
if minDist > 0.5
    return;
end
x = xV(indMin(1));
y = yV(indMin(1));
z = zV(indMin(1));

if strcmpi(axisType,'base')
    if airwayStateS.selectBaseStartNode % size(airwayStateS.baseSegmentStartNodes,1) > size(airwayStateS.baseSegmentEndNodes,1)
        %airwayStateS.baseSegmentEndNodes(end+1,:) = [x,y,z];
        airwayStateS.baseSegmentS(airwayStateS.currentSegment).startNode = indMin(1);
        airwayStateS.selectBaseStartNode = 0;
        set(airwayStateS.hBaseStart,'XData',x,'YData',y,'zData',z,'visible','on')
        %set(airwayStateS.hBaseEnd,'visible','off')
%         airwayStateS.basehEndNodes = [airwayStateS.basehEndNodes,...
%             plot3(x,y,z,'parent',hAxis,'marker','o','MarkerFaceColor','r',...
%             'markerSize',8,'hittest','off')];
    else
        %airwayStateS.baseSegmentStartNodes(end+1,:) = [x,y,z];
        %set(airwayStateS.hBaseEnd,'XData',x,'YData',y,'zData',z,'visible','on')
        airwayStateS.baseSegmentS(airwayStateS.currentSegment).endNode = indMin(1);
        airwayGraph = airwayStateS.baseTreeS.airwayGraph;
        startNode = airwayStateS.baseSegmentS(airwayStateS.currentSegment).startNode;
        endNode = airwayStateS.baseSegmentS(airwayStateS.currentSegment).endNode;
        allNodesV = shortestpath(airwayGraph,startNode,endNode);
        airwayStateS.baseSegmentS(airwayStateS.currentSegment).allNodes = allNodesV;
        xyzM = airwayStateS.baseTreeS.nodeXyzM(allNodesV,:);
        if ishandle(airwayStateS.baseSegmentS(airwayStateS.currentSegment).hPlot)
            delete(airwayStateS.baseSegmentS(airwayStateS.currentSegment).hPlot)
        end
        airwayStateS.baseSegmentS(airwayStateS.currentSegment).hPlot = ...
            plot3(airwayStateS.hAxisBase,xyzM(:,1),xyzM(:,2),xyzM(:,3),'linewidth',5,...
            'color',[0,0,1,1],'hittest','off');
        airwayStateS.selectBaseStartNode = 1;
        set(airwayStateS.hBaseStart,'visible','off')
        airwaySegmentCallback('SEGMENT_CLICKED')
%         airwayStateS.basehStartNodes = [airwayStateS.basehStartNodes,...
%             plot3(x,y,z,'parent',hAxis,'marker','o','MarkerFaceColor','g',...
%             'markerSize',8,'hittest','off')];
    end
else
    if airwayStateS.selectFollowupStartNode  %size(airwayStateS.followupSegmentStartNodes,1) > size(airwayStateS.followupSegmentEndNodes,1)
        %airwayStateS.followupSegmentEndNodes(end+1,:) = [x,y,z];
        airwayStateS.followupSegmentS(airwayStateS.currentSegment).startNode = indMin(1);
        airwayStateS.selectFollowupStartNode = 0;
        set(airwayStateS.hFollowupStart,'XData',x,'YData',y,'zData',z,'visible','on')
        %set(airwayStateS.hFollowupEnd,'visible','off')
%         airwayStateS.followuphEndNodes = [airwayStateS.followuphEndNodes,...
%             plot3(x,y,z,'parent',hAxis,'marker','o','MarkerFaceColor','r',...
%             'markerSize',8,'hittest','off')];
    else
        %         airwayStateS.followupSegmentStartNodes(end+1,:) = [x,y,z];
        airwayStateS.followupSegmentS(airwayStateS.currentSegment).endNode = indMin(1);
        airwayGraph = airwayStateS.followupTreeS.airwayGraph;
        startNode = airwayStateS.followupSegmentS(airwayStateS.currentSegment).startNode;
        endNode = airwayStateS.followupSegmentS(airwayStateS.currentSegment).endNode;
        allNodesV = shortestpath(airwayGraph,startNode,endNode);
        airwayStateS.followupSegmentS(airwayStateS.currentSegment).allNodes = allNodesV;
        %set(airwayStateS.hFollowupEnd,'XData',x,'YData',y,'zData',z,'visible','on')
        xyzM = airwayStateS.followupTreeS.nodeXyzM(allNodesV,:);
        if ishandle(airwayStateS.followupSegmentS(airwayStateS.currentSegment).hPlot)
            delete(airwayStateS.followupSegmentS(airwayStateS.currentSegment).hPlot)
        end
        airwayStateS.followupSegmentS(airwayStateS.currentSegment).hPlot = ...
            plot3(airwayStateS.hAxisFollowup,xyzM(:,1),xyzM(:,2),xyzM(:,3),'linewidth',5,...
            'color',[0,0,1,1],'hittest','off');
        airwayStateS.selectFollowupStartNode = 1;
        set(airwayStateS.hFollowupStart,'visible','off')
        airwaySegmentCallback('SEGMENT_CLICKED')

%         airwayStateS.followuphStartNodes = [airwayStateS.followuphStartNodes,...
%             plot3(x,y,z,'parent',hAxis,'marker','o','MarkerFaceColor','g',...
%             'markerSize',8,'hittest','off')];
    end
end

end


function [sameReactions,similarReactions] = mapKBaseToVMHReactions(translatedRxns)
% Part of the DEMETER pipeline. This functions aids in translating
% reactions from KBase to VMH nomenclature. Requires running the function
% propagateKBaseMetTranslationToRxns beforehand to translate metabolite
% IDs, which will then allow matching translated reactions to reactions
% that already exist in the VMH (Virtual Metabolic Human) database.
%
% USAGE:
%
%           [sameReactions,similarReactions] = mapKBaseToVMHReactions(translatedRxns)
%
% INPUTS
% translatedRxns:       Table with untranslated KBase reactions but
%                       translated metabolite IDs
%
% OUTPUT
% sameReactions:        Table with translated KBase reactions that already
%                       exist in the VMH database with corresponding IDs
% similarReactions:     Table with translated KBase reactions for which a
%                       reaction with the same formula but irreversible in
%                       VMH and reversible in KBase (or vice versa) exists
%
% .. Authors:
%       - Almut Heinken, 01/2021

sameReactions={'KBase_reaction','VMH_reaction','KBase_Formula','VMH_Formula'};
similarReactions={'KBase_reaction','VMH_reaction','KBase_Formula','VMH_Formula'};

% get VMH reaction database
database=loadVMHDatabase;

parsedVMHFormulas={};
cnt=1;

% parse the VMH reaction database
for i=1:size(database.reactions,1)
    % break down the formula
    [metaboliteList, stoichCoeffList, revFlag] = parseRxnFormula(database.reactions{i,3});
    % now put the formula back together, ensuring it is written the same
    % way as translated KBase formulas
    if revFlag==1
        revChar=' <=> ';
    elseif revFlag==0
        revChar=' -> ';
    end
    rxnForm='';
    % parse substrates
    substrates=find(stoichCoeffList<0);
    if ~isempty(substrates)
        for j=1:length(substrates)
            if j<length(substrates)
                rxnForm=[rxnForm num2str(abs(stoichCoeffList(substrates(j)))) ' ' metaboliteList{substrates(j)} ' + ' ];
            else
                rxnForm=[rxnForm num2str(abs(stoichCoeffList(substrates(j)))) ' ' metaboliteList{substrates(j)} revChar ];
            end
        end
    else
      % catch cases of exchange reactions
      rxnForm=[rxnForm revChar];
    end

    % parse products
    products=find(stoichCoeffList>0);
    if ~isempty(products)
        for j=1:length(products)
            if j<length(products)
                rxnForm=[rxnForm num2str(abs(stoichCoeffList(products(j)))) ' ' metaboliteList{products(j)} ' + ' ];
            else
                rxnForm=[rxnForm num2str(abs(stoichCoeffList(products(j)))) ' ' metaboliteList{products(j)}];
            end
        end
    end

    parsedVMHFormulas{cnt,1}=database.reactions{i,1};
    parsedVMHFormulas{cnt,2}=rxnForm;
    parsedVMHFormulas{cnt,3}=revFlag;
    cnt=cnt+1;
    if revFlag==1
        % also catch the reaction being written the other way around
        rxnForm=strsplit(rxnForm,' <=> ');
        parsedVMHFormulas{cnt,1}=database.reactions{i,1};
        parsedVMHFormulas{cnt,2}=[rxnForm{2} ' <=> ' rxnForm{1}];
        parsedVMHFormulas{cnt,3}=revFlag;
        cnt=cnt+1;
    end
end

% parse KBase reactions to translate in the same manner
parsedKBaseFormulas={};
cnt=1;

for i=1:size(translatedRxns,1)
    % break down the formula
    [metaboliteList, stoichCoeffList, revFlag] = parseRxnFormula(translatedRxns{i,3});
    % now put the formula back together, ensuring it is written the same
    % way as translated VMH formulas
    if revFlag==1
        revChar=' <=> ';
    elseif revFlag==0
        revChar=' -> ';
    end
    rxnForm='';
    % parse substrates
    substrates=find(stoichCoeffList<0);
    if ~isempty(substrates)
        for j=1:length(substrates)
            if j<length(substrates)
                rxnForm=[rxnForm num2str(abs(stoichCoeffList(substrates(j)))) ' ' metaboliteList{substrates(j)} ' + ' ];
            else
                rxnForm=[rxnForm num2str(abs(stoichCoeffList(substrates(j)))) ' ' metaboliteList{substrates(j)} revChar ];
            end
        end
    else
      % catch cases of exchange reactions
      rxnForm=[rxnForm revChar];
    end

    % parse products
    products=find(stoichCoeffList>0);
    if ~isempty(products)
        for j=1:length(products)
            if j<length(products)
                rxnForm=[rxnForm num2str(abs(stoichCoeffList(products(j)))) ' ' metaboliteList{products(j)} ' + ' ];
            else
                rxnForm=[rxnForm num2str(abs(stoichCoeffList(products(j)))) ' ' metaboliteList{products(j)}];
            end
        end
    end

    parsedKBaseFormulas{cnt,1}=translatedRxns{i,1};
    parsedKBaseFormulas{cnt,2}=rxnForm;
    parsedKBaseFormulas{cnt,3}=revFlag;
    cnt=cnt+1;
    % also catch if irreversible versions of this reaction exist in the VMH
    % database -> may consider translation
    if revFlag==1
        % also catch the reaction being written the other way around
        rxnForm=strsplit(rxnForm,' <=> ');
        parsedKBaseFormulas{cnt,1}=translatedRxns{i,1};
        parsedKBaseFormulas{cnt,2}=[rxnForm{1} ' -> ' rxnForm{2}];
        parsedKBaseFormulas{cnt,3}=revFlag;
        cnt=cnt+1;
        parsedKBaseFormulas{cnt,1}=translatedRxns{i,1};
        parsedKBaseFormulas{cnt,2}=[rxnForm{2} ' -> ' rxnForm{1}];
        parsedKBaseFormulas{cnt,3}=revFlag;
        cnt=cnt+1;
    end
end

% now find the overlap between the VMH database formulas and translated
% KBase reactions
[C,IA,IB]=intersect(parsedVMHFormulas(:,2),parsedKBaseFormulas(:,2));

% now get the overlap. If the reversibility flags agree, they are the same
% reaction, otherwise, they are similar (but translation should be
% considered).

for i=1:length(C)
    VMHform=database.reactions{find(strcmp(database.reactions(:,1),parsedVMHFormulas{IA(i),1})),3};
    KBaseform=translatedRxns{find(strcmp(translatedRxns(:,1),parsedKBaseFormulas{IB(i),1})),3};

    toAdd={parsedKBaseFormulas{IB(i),1},parsedVMHFormulas{IA(i),1},KBaseform,VMHform};
    % if they are the same reaction
    if parsedVMHFormulas{IA(i),3} == parsedKBaseFormulas{IB(i),3}
        sameReactions(size(sameReactions,1)+1,:)=toAdd;
    else
        similarReactions(size(similarReactions,1)+1,:)=toAdd;
    end
end

% remove similar reactions that are already in same reactions
[C,IA]=intersect(similarReactions(:,1),sameReactions(:,1),'stable');
similarReactions(IA(2:end),:)=[];

end


%% ppval_extrapVal
% Below is a demonstration of the features of the |ppval_extrapVal| function

%%
clear; close all; clc;

%% Syntax
% |v=ppval_extrapVal(pp,xx,interpLim,extrapVal);|

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


function varargout = process_generate_canonical( varargin )
% PROCESS_GENERATE_CANONICAL: Generate SPM canonical surface.
%
% USAGE:     OutputFiles = process_generate_canonical('Run',     sProcess, sInputs)
%         [isOk, errMsg] = process_generate_canonical('Compute', iSubject, iAnatomy=[default], Resolution)
%                          process_generate_canonical('ComputeInteractive', iSubject, iAnatomy=[default], Resolution)

% @=============================================================================
% 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, 2017-2023

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
    % Description the process
    sProcess.Comment     = 'Generate SPM canonical surfaces';
    sProcess.Category    = 'Custom';
    sProcess.SubGroup    = {'Import', 'Import anatomy'};
    sProcess.Index       = 12;
    sProcess.Description = 'https://github.com/neurodebian/spm12/blob/master/spm_eeg_inv_mesh.m';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'import'};
    sProcess.OutputTypes = {'import'};
    sProcess.nInputs     = 1;
    sProcess.nMinFiles   = 0;
    sProcess.isSeparator = 1;
    % Option: Subject name
    sProcess.options.subjectname.Comment = 'Subject name:';
    sProcess.options.subjectname.Type    = 'subjectname';
    sProcess.options.subjectname.Value   = '';
    % Option: Cortex resolution
    sProcess.options.resolution.Comment = {'5124', '8196', '20484', 'Cortex surface resolution:'};
    sProcess.options.resolution.Type    = 'radio_line';
    sProcess.options.resolution.Value   = 3;
end

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

%% ===== RUN =====
function OutputFiles = Run(sProcess, sInputs) %#ok<DEFNU>
    OutputFiles = {};
    % Get resolution
    Resolution = sProcess.options.resolution.Value;
    % 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, [], Resolution, 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 CANONICAL SURFACES =====
function [isOk, errMsg] = Compute(iSubject, iAnatomy, Resolution, isInteractive)
    isOk = 0;
    % Initialize SPM
    [isInstalled, errMsg] = bst_plugin('Install', 'spm12', isInteractive);
    if ~isInstalled
        return;
    end
    bst_plugin('SetProgressLogo', 'spm12');

    % ===== 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

    % ===== VERIFY FIDUCIALS IN MRI =====
    % Load MRI file
    MriFileBst = sSubject.Anatomy(iAnatomy).FileName;
    sMri = in_mri_bst(MriFileBst);
    % 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 transformation to get default positions.';
        % Compute MNI normalization
        [sMri, errNorm] = bst_normalize_mni(MriFileBst);
        % Handle errors
        if ~isempty(errNorm)
            errMsg = [errMsg 10 'Error trying to compute the MNI transformation: ' 10 errNorm 10 ...
                'The surfaces will not be properly aligned with the MRI.'];
        end
    end

    % ===== CALL SPM FUNCTIONS =====
    % Empty temporary folder, otherwise it reuses previous files in the folder
    TmpDir = bst_get('BrainstormTmpDir', 0, 'spmcanonical');
    % Save MRI in .nii format
    NiiFile = bst_fullfile(TmpDir, 'spm_canonical.nii');
    out_mri_nii(sMri, NiiFile);
    % Call SPM function
    switch (Resolution)
        case {1,2,3}
            spmMesh = spm_eeg_inv_mesh(NiiFile, Resolution);
        case 4
            spmMesh = ImaGIN_spm_eeg_inv_mesh(NiiFile, Resolution);
        otherwise
            error('Unsupported resolution.');
    end
    % ===== READ OUTPUT SURFACES =====
    % Read transformation from temporary .nii
    niiMri = in_mri_nii(NiiFile, 0, 0, 0);
    % Create surfaces
    sHead   = CreateSurface(sMri, niiMri, export(gifti(spmMesh.tess_scalp),'patch'),  'spm_head');
    sOuter  = CreateSurface(sMri, niiMri, export(gifti(spmMesh.tess_oskull),'patch'), 'spm_outerskull');
    sInner  = CreateSurface(sMri, niiMri, export(gifti(spmMesh.tess_iskull),'patch'), 'spm_innerskull');
    sCortex = CreateSurface(sMri, niiMri, export(gifti(spmMesh.tess_ctx),'patch'),    'spm_cortex');

    % ===== SAVE NEW SURFACES =====
    % Create output filenames
    SurfaceDir    = bst_fileparts(file_fullpath(MriFileBst));
    SpmHeadFile   = file_unique(bst_fullfile(SurfaceDir, sprintf('tess_head_spm_%dV.mat', length(sHead.Vertices))));
    SpmOuterFile  = file_unique(bst_fullfile(SurfaceDir, sprintf('tess_outerskull_spm_%dV.mat', length(sOuter.Vertices))));
    SpmInnerFile  = file_unique(bst_fullfile(SurfaceDir, sprintf('tess_innerskull_spm_%dV.mat', length(sInner.Vertices))));
    SpmCortexFile = file_unique(bst_fullfile(SurfaceDir, sprintf('tess_cortex_spm_%dV.mat', length(sCortex.Vertices))));
    % Save head
    bst_save(SpmHeadFile, sHead, 'v7');
    db_add_surface(iSubject, SpmHeadFile, sHead.Comment);
    % Save outerskull
    bst_save(SpmOuterFile, sOuter, 'v7');
    db_add_surface(iSubject, SpmOuterFile, sOuter.Comment);
    % Save innerskull
    bst_save(SpmInnerFile, sInner, 'v7');
    db_add_surface(iSubject, SpmInnerFile, sInner.Comment);
    % Save cortex
    bst_save(SpmCortexFile, sCortex, 'v7');
    db_add_surface(iSubject, SpmCortexFile, sCortex.Comment);

    % Delete the temporary files
    file_delete(TmpDir, 1, 1);
    % Remove logo
    bst_plugin('SetProgressLogo', []);
    isOk = 1;
end

%% ===== COMPUTE/INTERACTIVE =====
function ComputeInteractive(iSubject, iAnatomy, Resolution) %#ok<DEFNU>
    % Get inputs
    if (nargin < 2) || isempty(iAnatomy)
        iAnatomy = [];
    end
    if (nargin < 3) || isempty(Resolution)
        strRes = java_dialog('question', 'Cortex surface resolution:', 'SPM canonical mesh', [], {'5124', '8196', '20484'}, '20484');
        if isempty(strRes)
            return;
        end
        switch (strRes)
            case '5124',  Resolution = 1;
            case '8196',  Resolution = 2;
            case '20484', Resolution = 3;
        end
    end
    % Open progress bar
    bst_progress('start', 'SPM', 'Generating canonical surfaces...');
    % Compute surfaces
    [isOk, errMsg] = Compute(iSubject, iAnatomy, Resolution, 1);
    % Error handling
    if ~isOk
        bst_error(errMsg, 'SPM canonical surfaces', 0);
    elseif ~isempty(errMsg)
        java_dialog('msgbox', ['Warning: ' errMsg]);
    end
    % Close progress bar
    bst_progress('stop');
end

%% ===== CREATE SURFACE =====
function sTess = CreateSurface(sMri, niiMri, gii, Comment)
    % Transformation to apply to the surface
    ras2vox = inv(niiMri.Header.nifti.vox2ras);
    % Create surfaces structure
    sTess = db_template('SurfaceMat');
    sTess.Comment  = sprintf('%s_%dV', Comment, length(gii.vertices));
    sTess.Vertices = bst_bsxfun(@plus, ras2vox(1:3,1:3)*gii.vertices', ras2vox(1:3,4))';
    sTess.Vertices = bst_bsxfun(@plus, sTess.Vertices, [1 1 1]);
    sTess.Vertices = cs_convert(sMri, 'voxel', 'scs', sTess.Vertices);
    sTess.Faces    = gii.faces(:,[2 1 3]);
    sTess = bst_history('add', sTess, 'spm', ['Canonical surface generated with: ' spm('version')]);
end


%% unpadeven
% Below is a demonstration of the features of the |unpadeven| function

%%
clear; close all; clc;

%% Syntax
% |[M]=unpadeven(M_pad,L_even_dim);|

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


function ds_info=gen_dataset_info(ds_config)

if ds_config.use_custom_data

    ds_info=gen_ds_info_custom_data(ds_config, ds_config.class_info);

else

    cache_dir=ds_config.ds_info_cache_dir;
    cache_file=fullfile(cache_dir, 'my_dataset_info.mat');

    if ~my_check_file(cache_file)
        ds_info=ds_config.gen_ds_info_fn(ds_config);
        my_save_file(cache_file, ds_info);
    else
        ds_info=my_load_file(cache_file);
        ds_info=ds_info.data_obj;
    end

end

end

function ds_info=gen_ds_info_custom_data(ds_config, class_info)

    ds_info=[];

    img_dir=ds_config.img_data_dir;
    img_files=my_list_file(img_dir)';
    img_num=length(img_files);
    ds_info.img_idxes=uint32(1:img_num);
    ds_info.img_num=img_num;
    ds_info.train_idxes=uint32([]);
    ds_info.test_idxes=ds_info.img_idxes;

    img_names=cell(img_num, 1);
    for img_idx=1:img_num
        one_name=img_files{img_idx};
        [~, one_name]=fileparts(one_name);
        img_names{img_idx}=one_name;
    end

    ds_info.img_files=img_files;
    ds_info.img_names=img_names;
    ds_info.mask_files=[];
    ds_info.train_idxes=[];
    ds_info.test_idxes=uint32(1:img_num)';
    ds_info.name=ds_config.ds_name;

    ds_info.data_dir_idxes_img=repmat(uint8(1), [img_num, 1]);
    ds_info.data_dir_idxes_mask=[];
    ds_info.data_dirs={img_dir};

    ds_info.class_info=class_info;

end


function [TableChecks, Table_csources, CSourcesTestedRxns, TestSolutionNameOpenSinks,TestSolutionNameClosedSinks] = performSanityChecksonRecon(model,resultsFileName,ExtraCellCompIn,ExtraCellCompOut,runSingleGeneDeletion)
% This function performs various quality control and quality assurance
% tests.
% [TableChecks, Table_csources, CSourcesTestedRxns, TestSolutionNameOpenSinks,TestSolutionNameClosedSinks] = performSanityChecksonRecon(model,resultsFileName,ExtraCellCompIn,ExtraCellCompOut,runSingleGeneDeletion)
%
% INPUT
% model                         model structure
% resultsFileName               File name of the generated output file
% ExtraCellCompIn               [e] compartment by default, if extracellular
%                               uptake compartment is named differently, it
%                               can be specified here
% ExtraCellCompOut              [e] compartment by default, if extracellular
%                               secretion compartment is named differently, it
%                               can be specified here
% runSingleGeneDeletion         if 0 (default): function does not run single gene deletion otw choose 1
%
% OUTPUT
% TableChecks                   Table overview of the performed tests and
%                               their outcomes
% Table_csources                Table the test results for ATP yield from
%                               various carbon scources under aerobic and anaerobic conditions
% CSourcesTestedRxns            List of reactions active when testing the ATP
%                               yield from the various carbon sources

% TestSolutionNameOpenSinks     List of results when testing for 460 metabolic
%                               functions with all sinks open
% TestSolutionNameClosedSinks   List of results when testing for 460 metabolic
%                               functions with all sinks closed
%
% Ines Thiele 2016-2019

global resultsPath
resultsPath = which('MethodSection3.mlx');
resultsPath = strrep(resultsPath,'MethodSection3.mlx',['Results' filesep]);

if ~exist([resultsPath 'OrganChecks'],'dir')
    mkdir([resultsPath 'OrganChecks'])
end

if ~exist('ExtraCellCompIn','var')
    ExtraCellCompIn = '[e]'; % [e] compartment by default
end
if ~exist('ExtraCellCompOut','var')
    ExtraCellCompOut = '[e]'; % [e] compartment by default
end

if ~exist('runSingleGeneDeletion','var')
    runSingleGeneDeletion = 0; % do not run single gene deletion by default
end

global saveDiary
if saveDiary
    %save each diary to PSCM/Results/OrganChecks/
    global resultsPath
    resultsPath = which('MethodSection3.mlx');
    resultsPath = strrep(resultsPath,'MethodSection3.mlx',['Results' filesep]);
    resultsFileName=[resultsFileName '_diary'];
    diary([resultsPath 'OrganChecks' filesep resultsFileName])
end

% if 1
%     changeCobraSolver('tomlab_cplex','lp');
%     changeCobraSolver('tomlab_cplex','qp');
% end
cnt = 1;
tol = 1e-6;

model.rxns(find(ismember(model.rxns,'ATPM')))={'DM_atp_c_'};
model.rxns(find(ismember(model.rxns,'ATPhyd')))={'DM_atp_c_'};
% adds DM_atp to model if not exist

if isempty(strmatch('DM_atp_c_',model.rxns,'exact'))
    [model, rxnIDexists] = addReaction(model,'DM_atp_c_', 'reactionFormula', 'h2o[c] + atp[c]  -> adp[c] + h[c] + pi[c] ');
   % [model, rxnIDexists] = addReaction(model,'DM_atp_c_', 'reactionFormula', '1 atp[c] ->');
end

model.rxns(find(ismember(model.rxns,'EX_biomass_reaction')))={'biomass_reaction'};
model.rxns(find(ismember(model.rxns,'EX_biomass_maintenance')))={'biomass_maintenance'};
model.rxns(find(ismember(model.rxns,'EX_biomass_maintenance_noTrTr')))={'biomass_maintenance_noTrTr'};
model.lb(find(ismember(model.rxns,'biomass_reaction')))=0;
model.lb(find(ismember(model.rxns,'biomass_maintenance_noTrTr')))=0;
model.lb(find(ismember(model.rxns,'biomass_maintenance')))=0;

TestSolutionNameOpenSinks ='';
TestSolutionNameClosedSinks = '';

model.rxns = regexprep(model.rxns,'\(','\[');
model.rxns = regexprep(model.rxns,'\)','\]');
% vanilla leak test

if 1
    modelClosed = model;
    LeakTestRecon;
    TableChecks{cnt,1} = 'fastLeakTest 1';
    if length(LeakMets)>0
        warning('model leaks metabolites!')
        TableChecks{cnt,2} = 'Model leaks metabolites!';
    else
        TableChecks{cnt,2} = 'Leak free!';
    end
    cnt = cnt + 1;
end

if 0
    % test if something leaks when demand are added
    modelClosed = model;
    modelexchanges1 = strmatch('Ex_',modelClosed.rxns);
    modelexchanges4 = strmatch('EX_',modelClosed.rxns);
    modelexchanges2 = strmatch('DM_',modelClosed.rxns);
    modelexchanges3 = strmatch('sink_',modelClosed.rxns);
    selExc = (find( full((sum(abs(modelClosed.S)==1,1) ==1) & (sum(modelClosed.S~=0) == 1))))';

    modelexchanges = unique([modelexchanges1;modelexchanges2;modelexchanges3;modelexchanges4;selExc]);
    modelClosed.lb(find(ismember(modelClosed.rxns,modelClosed.rxns(modelexchanges))))=0;
    [modelClosed,rxnNames] = addDemandReaction(modelClosed,modelClosed.mets);

    [LeakRxns,modelTested] = fastLeakTest(modelClosed,modelClosed.rxns(selExc));

    TableChecks{cnt,1} = 'fastLeakTest 2 - add demand reactions for each metabolite in the model';
    if length(LeakMets)>0
        TableChecks{cnt,2} = 'Model leaks metabolites when demand reactions are added!';
    else
        TableChecks{cnt,2} = 'Leak free when demand reactions are added!';
    end
    cnt = cnt + 1;
end

if 1
    %%model produces energy from water!
    modelClosed = model;

    modelexchanges1 = strmatch('Ex_',modelClosed.rxns);
    modelexchanges4 = strmatch('EX_',modelClosed.rxns);
    modelexchanges2 = strmatch('DM_',modelClosed.rxns);
    modelexchanges3 = strmatch('sink_',modelClosed.rxns);
    selExc = (find( full((sum(abs(modelClosed.S)==1,1) ==1) & (sum(modelClosed.S~=0) == 1))))';

    modelexchanges = unique([modelexchanges1;modelexchanges2;modelexchanges3;modelexchanges4;selExc]);
    modelClosed.lb(find(ismember(modelClosed.rxns,modelClosed.rxns(modelexchanges))))=0;
    modelClosedATP = changeObjective(modelClosed,'DM_atp_c_');
    modelClosedATP = changeRxnBounds(modelClosedATP,'DM_atp_c_',0,'l');
    modelClosedATP = changeRxnBounds(modelClosedATP,'DM_atp_c_',1000,'u');
    modelClosedATP = changeRxnBounds(modelClosedATP,strcat('EX_h2o',ExtraCellCompIn),-1,'l');
    FBA3=optimizeCbModel(modelClosedATP);
    TableChecks{cnt,1} = 'Exchanges, sinks, and demands have  lb = 0, except h2o';
    if abs(FBA3.f) > 1e-6
        TableChecks{cnt,2} = 'model produces energy from water!';
    else
        TableChecks{cnt,2} = 'model DOES NOT produce energy from water!';
    end
    cnt = cnt + 1;
end
%% model produces energy from water and oxygen!
if 1
    modelClosed = model;
    modelexchanges1 = strmatch('Ex_',modelClosed.rxns);
    modelexchanges4 = strmatch('EX_',modelClosed.rxns);
    modelexchanges2 = strmatch('DM_',modelClosed.rxns);
    modelexchanges3 = strmatch('sink_',modelClosed.rxns);
    selExc = (find( full((sum(abs(modelClosed.S)==1,1) ==1) & (sum(modelClosed.S~=0) == 1))))';

    modelexchanges = unique([modelexchanges1;modelexchanges2;modelexchanges3;modelexchanges4;selExc]);
    modelClosed.lb(find(ismember(modelClosed.rxns,modelClosed.rxns(modelexchanges))))=0;
    modelClosedATP = changeObjective(modelClosed,'DM_atp_c_');
    modelClosedATP = changeRxnBounds(modelClosedATP,'DM_atp_c_',0,'l');
    modelClosedATP = changeRxnBounds(modelClosedATP,'DM_atp_c_',1000,'u');
    modelClosedATP = changeRxnBounds(modelClosedATP,strcat('EX_h2o',ExtraCellCompIn),-1,'l');
    modelClosedATP = changeRxnBounds(modelClosedATP,strcat('EX_o2',ExtraCellCompIn),-1,'l');

    FBA6=optimizeCbModel(modelClosedATP);
    TableChecks{cnt,1} = 'Exchanges, sinks, and demands have  lb = 0, except h2o and o2';
    if abs(FBA6.f) > 1e-6
        TableChecks{cnt,2} = 'model produces energy from water and oxygen!';
    else
        TableChecks{cnt,2} = 'model DOES NOT produce energy from water and oxygen!';
    end
    cnt = cnt + 1;
end
%% model produces matter when atp demand is reversed!
if 1
    modelClosed = model;
    modelexchanges1 = strmatch('Ex_',modelClosed.rxns);
    modelexchanges4 = strmatch('EX_',modelClosed.rxns);
    modelexchanges2 = strmatch('DM_',modelClosed.rxns);
    modelexchanges3 = strmatch('sink_',modelClosed.rxns);
    selExc = (find( full((sum(abs(modelClosed.S)==1,1) ==1) & (sum(modelClosed.S~=0) == 1))))';

    modelexchanges = unique([modelexchanges1;modelexchanges2;modelexchanges3;modelexchanges4;selExc]);
    modelClosed.lb(find(ismember(modelClosed.rxns,modelClosed.rxns(modelexchanges))))=0;

    modelClosed = changeObjective(modelClosed,'DM_atp_c_');
    modelClosed.lb(find(ismember(modelClosed.rxns,'DM_atp_c_'))) = -1000;
    modelClosed.ub(selExc)=1000;
    FBA = optimizeCbModel(modelClosed,'min');
    TableChecks{cnt,1} = 'Exchanges, sinks, and demands have  lb = 0, allow DM_atp_c_ to be reversible';
    if abs(FBA.f) > 1e-6
        TableChecks{cnt,2} = 'model produces matter when atp demand is reversed!';
    else
        TableChecks{cnt,2} = 'model DOES NOT produce matter when atp demand is reversed!';
    end
    cnt = cnt + 1;
end
%% model has flux through h[m] demand !
if 1
    modelClosed = model;
    modelexchanges1 = strmatch('Ex_',modelClosed.rxns);
    modelexchanges4 = strmatch('EX_',modelClosed.rxns);
    modelexchanges2 = strmatch('DM_',modelClosed.rxns);
    modelexchanges3 = strmatch('sink_',modelClosed.rxns);
    selExc = (find( full((sum(abs(modelClosed.S)==1,1) ==1) & (sum(modelClosed.S~=0) == 1))))';

    modelexchanges = unique([modelexchanges1;modelexchanges2;modelexchanges3;modelexchanges4;selExc]);
    modelClosed.lb(find(ismember(modelClosed.rxns,modelClosed.rxns(modelexchanges))))=0;
    modelClosed = addDemandReaction(modelClosed,'h[m]');
    modelClosed = changeObjective(modelClosed,'DM_h[m]');
    modelClosed.ub(find(ismember(modelClosed.rxns,'DM_h[m]'))) = 1000;
    modelClosed.ub(selExc)=1000;
    FBA = optimizeCbModel(modelClosed,'max');
    TableChecks{cnt,1} = 'Exchanges, sinks, and demands have  lb = 0, test flux through DM_h[m] (max)';
    if abs(FBA.f) > 1e-6
        TableChecks{cnt,2} = 'model has flux through h[m] demand (max)!';
    else
        TableChecks{cnt,2} = 'model has NO flux through h[m] demand (max)!';
    end
    cnt = cnt + 1;
end
if 0
    modelClosed = model;
    modelexchanges1 = strmatch('Ex_',modelClosed.rxns);
    modelexchanges4 = strmatch('EX_',modelClosed.rxns);
    modelexchanges2 = strmatch('DM_',modelClosed.rxns);
    modelexchanges3 = strmatch('sink_',modelClosed.rxns);
    selExc = (find( full((sum(abs(modelClosed.S)==1,1) ==1) & (sum(modelClosed.S~=0) == 1))))';

    modelexchanges = unique([modelexchanges1;modelexchanges2;modelexchanges3;modelexchanges4;selExc]);
    modelClosed.lb(find(ismember(modelClosed.rxns,modelClosed.rxns(modelexchanges))))=0;
    modelClosed = addDemandReaction(modelClosed,'h[m]');
    modelClosed = changeObjective(modelClosed,'DM_h[m]');
    modelClosed.ub(find(ismember(modelClosed.rxns,'DM_h[m]'))) = 1000;
    modelClosed.lb(find(ismember(modelClosed.rxns,'DM_h[m]'))) = -1000;
    modelClosed.ub(selExc)=1000;
    FBA = optimizeCbModel(modelClosed,'min');
    TableChecks{cnt,1} = 'Exchanges, sinks, and demands have  lb = 0, test flux through DM_h[m] (min)';
    if abs(FBA.f) > 1e-6
        TableChecks{cnt,2} = 'model has flux through h[m] demand (min)!';
    else
        TableChecks{cnt,2} = 'model has NO flux through h[m] demand (min)!';
    end
    cnt = cnt + 1;
end
%% model has flux through h[c] demand !
if 1
    modelClosed = model;
    modelexchanges1 = strmatch('Ex_',modelClosed.rxns);
    modelexchanges4 = strmatch('EX_',modelClosed.rxns);
    modelexchanges2 = strmatch('DM_',modelClosed.rxns);
    modelexchanges3 = strmatch('sink_',modelClosed.rxns);
    selExc = (find( full((sum(abs(modelClosed.S)==1,1) ==1) & (sum(modelClosed.S~=0) == 1))))';

    modelexchanges = unique([modelexchanges1;modelexchanges2;modelexchanges3;modelexchanges4;selExc]);
    modelClosed.lb(find(ismember(modelClosed.rxns,modelClosed.rxns(modelexchanges))))=0;
    modelClosed = addDemandReaction(modelClosed,'h[c]');
    modelClosed = changeObjective(modelClosed,'DM_h[c]');
    modelClosed.ub(find(ismember(modelClosed.rxns,'DM_h[c]'))) = 1000;
    modelClosed.ub(selExc)=1000;
    FBA = optimizeCbModel(modelClosed,'max');
    TableChecks{cnt,1} = 'Exchanges, sinks, and demands have  lb = 0, test flux through DM_h[c] (max)';
    if abs(FBA.f) > 1e-6
        TableChecks{cnt,2} = 'model has flux through h[c] demand (max)!';
    else
        TableChecks{cnt,2} = 'model has NO flux through h[c] demand (max)!';
    end
    cnt = cnt + 1;
end
if 1
    modelClosed = model;
    modelexchanges1 = strmatch('Ex_',modelClosed.rxns);
    modelexchanges4 = strmatch('EX_',modelClosed.rxns);
    modelexchanges2 = strmatch('DM_',modelClosed.rxns);
    modelexchanges3 = strmatch('sink_',modelClosed.rxns);
    selExc = (find( full((sum(abs(modelClosed.S)==1,1) ==1) & (sum(modelClosed.S~=0) == 1))))';

    modelexchanges = unique([modelexchanges1;modelexchanges2;modelexchanges3;modelexchanges4;selExc]);
    modelClosed.lb(find(ismember(modelClosed.rxns,modelClosed.rxns(modelexchanges))))=0;
    modelClosed = addDemandReaction(modelClosed,'h[c]');
    modelClosed = changeObjective(modelClosed,'DM_h[c]');
    modelClosed.ub(find(ismember(modelClosed.rxns,'DM_h[c]'))) = 1000;
    modelClosed.lb(find(ismember(modelClosed.rxns,'DM_h[c]'))) = -1000;
    modelClosed.ub(selExc)=1000;
    modelClosed.ub(find(ismember(modelClosed.rxns,strcat('EX_h',ExtraCellCompOut)))) = 0;
    FBA = optimizeCbModel(modelClosed,'min');
    TableChecks{cnt,1} = 'Exchanges, sinks, and demands have  lb = 0, ub of EX_h[e] = 0, test flux through DM_h[c] (min)';
    if abs(FBA.f) > 1e-6
        TableChecks{cnt,2} = 'model has flux through h[c] demand (min)!';
    else
        TableChecks{cnt,2} = 'model has NO flux through h[c] demand (min)!';
    end
    cnt = cnt + 1;
end
%% model produces too much atp demand from glc -- old remove
if 0
    modelClosed = model;
    modelexchanges1 = strmatch('Ex_',modelClosed.rxns);
    modelexchanges4 = strmatch('EX_',modelClosed.rxns);
    modelexchanges2 = strmatch('DM_',modelClosed.rxns);
    modelexchanges3 = strmatch('sink_',modelClosed.rxns);
    selExc = (find( full((sum(abs(modelClosed.S)==1,1) ==1) & (sum(modelClosed.S~=0) == 1))))';

    modelexchanges = unique([modelexchanges1;modelexchanges2;modelexchanges3;modelexchanges4;selExc]);
    modelClosed.lb(find(ismember(modelClosed.rxns,modelClosed.rxns(modelexchanges))))=0;
    modelClosed.c = zeros(length(modelClosed.rxns),1);
    modelClosed = changeObjective(modelClosed,'DM_atp_c_');
    modelClosed.lb(find(ismember(modelClosed.rxns,strcat('EX_o2',ExtraCellCompIn)))) = -1000;
    modelClosed.lb(find(ismember(modelClosed.rxns,strcat('EX_h2o',ExtraCellCompIn)))) = -1000;
    modelClosed.ub(find(ismember(modelClosed.rxns,strcat('EX_h2o',ExtraCellCompOut)))) = 1000;
    modelClosed.ub(find(ismember(modelClosed.rxns,strcat('EX_co2',ExtraCellCompOut)))) = 1000;
    %modelClosed.lb(find(ismember(modelClosed.rxns,'EX_pi[e]'))) = -1000;
    %modelClosed = addExchangeRxn(modelClosed,{'glc_D[e]'});
    modelClosed.lb(find(ismember(modelClosed.rxns,strcat('EX_glc_D',ExtraCellCompIn)))) = -1;
    modelClosed.ub(find(ismember(modelClosed.rxns,strcat('EX_glc_D',ExtraCellCompIn)))) = -1;

    modelClosed.ub(selExc)=1000;
    %    FBA = optimizeCbModel(modelClosed,'max',1e-6);
    FBA = optimizeCbModel(modelClosed,'max');

    TableChecks{cnt,1} = 'ATP yield ';
    if abs(FBA.f) > 1e-6
        FBA.f
        %  modelClosed = changeObjective(modelClosed,'ATPS4m');
        %  FBA = optimizeCbModel(modelClosed,'max',1e-6);
        FBA.x(find(ismember(modelClosed.rxns,'ATPS4m')))
        % prepare table

        warning('model produces too much atp demand from glc!');
    else
        warning('model DOES NOT produce too much atp demand from glc!');
    end

end

TableChecks{cnt,1} = 'Test metabolic objective functions with open sinks';
if 1 % perform test function
    [TestSolution,TestSolutionNameOpenSinks, TestedRxnsSinks, PercSinks] = Test4HumanFctExtv5(model,'all');
    TableChecks{cnt,2} = strcat('Done. See variable TestSolutionNameOpenSinks for results. The model passes ', num2str(length(find(abs(TestSolution)>tol))),' out of ', num2str(length(TestSolution)), 'tests');
else
    TableChecks{cnt,2} = 'Not performed.';
end
cnt =  cnt + 1;

TableChecks{cnt,1} = 'Test metabolic objective functions with closed sinks (lb)';
if 0 % perform test functions
    [TestSolution,TestSolutionNameClosedSinks, TestedRxnsClosedSinks, PercClosedSinks] = Test4HumanFctExtv5(model,'all',0);
    TableChecks{cnt,2} = strcat('Done. See variable TestSolutionNameClosedSinks for results. The model passes ', num2str(length(find(abs(TestSolution)>tol))),' out of ', num2str(length(TestSolution)), 'tests');
else
    TableChecks{cnt,2} = 'Not performed.';
end
cnt =  cnt + 1;

TableChecks{cnt,1} = 'Compute ATP yield';
if 1 % test ATP yield
    [Table_csources, CSourcesTestedRxns, Perc] = testATPYieldFromCsources(model,[],ExtraCellCompIn,ExtraCellCompOut);
    TableChecks{cnt,2} = 'Done. See variable Table_csources for results.';
else
    TableChecks{cnt,2} = 'Not performed.';
    CSourcesTestedRxns = '';
end
cnt = cnt + 1;

TableChecks{cnt,1} = 'Check duplicated reactions';
if 0
    method='FR';
    removeFlag=0;
    [modelOut,removedRxnInd, keptRxnInd] = checkDuplicateRxn(model,method,removeFlag);
    if isempty(removedRxnInd)
        TableChecks{cnt,2} = 'No duplicated reactions in model.';
    else
        TableChecks{cnt,2} = 'Duplicated reactions in model.';
    end
else
    TableChecks{cnt,2} = 'Not performed.';
end
cnt = cnt + 1;

TableChecks{cnt,1} = 'Check empty columns in rxnGeneMat';
if 1
    E = find(sum(model.rxnGeneMat)==0);
    if isempty(E)
        TableChecks{cnt,2} = 'No empty columns in rxnGeneMat.';
    else
        TableChecks{cnt,2} = 'Empty columns in rxnGeneMat.';
    end
else
    TableChecks{cnt,2} = 'Not performed.';
end
cnt = cnt + 1;

TableChecks{cnt,1} = 'Check that demand reactions have a lb >= 0';
if 1
    DMlb = find(model.lb(strmatch('DM_',model.rxns))<0);
    if isempty(DMlb)
        TableChecks{cnt,2} = 'No demand reaction can have flux in backward direction.';
    else
        TableChecks{cnt,2} = 'Demand reaction can have flux in backward direction.';
    end
else
    TableChecks{cnt,2} = 'Not performed.';
end
cnt = cnt + 1;

TableChecks{cnt,1} = 'Check consistency of model.rev with model.lb';
if 0
    Rev = setdiff(find(model.lb<0), find(model.rev==1));
    if isempty(Rev)
        TableChecks{cnt,2} = 'model.rev and model.lb are consistent.';
    else
        TableChecks{cnt,2} = 'model.rev and model.lb are NOT consistent.';
    end
else
    TableChecks{cnt,2} = 'Not performed.';
end
cnt = cnt + 1;

TableChecks{cnt,1} = 'Check whether singleGeneDeletion runs smoothly';
if runSingleGeneDeletion == 1
    try
        [grRatio,grRateKO,grRateWT,hasEffect,delRxns,fluxSolution] = singleGeneDeletion(model);
        TableChecks{cnt,2} = 'singleGeneDeletion finished without problems.';
    catch
        TableChecks{cnt,2} = 'There are problems with singleGeneDeletion.';
    end
else
    TableChecks{cnt,2} = 'Not performed.';
end
cnt = cnt + 1;

TableChecks{cnt,1} = 'Check for flux consistency';
if 0
    param.epsilon=getCobraSolverParams('LP', 'feasTol')*100;
    param.modeFlag=0;
    %param.method='null_fastcc';
    %param.method='fastcc';
    printLevel = 1;
    [fluxConsistentMetBool,fluxConsistentRxnBool,fluxInConsistentMetBool,fluxInConsistentRxnBool,model] = findFluxConsistentSubset(model,param,printLevel)
    if isempty(find(fluxInConsistentRxnBool))
        TableChecks{cnt,2} = 'Model is flux consistent.';
    else
        TableChecks{cnt,2} = 'Model is NOT flux consistent';
    end
else
    TableChecks{cnt,2} = 'Not performed.';
end
cnt = cnt + 1;

if 0
save([resultsPath 'OrganChecks' filesep resultsFileName,'.mat'],'TableChecks', 'Table_csources', 'CSourcesTestedRxns', 'TestSolutionNameOpenSinks','TestSolutionNameClosedSinks');
end
if saveDiary
    diary off;
end


classdef TestStylization
    %TestStylization

    methods (Static)
        function test_1
            img = imread(fullfile(mexopencv.root(),'test','lena.jpg'));
            out = cv.stylization(img);
            validateattributes(out, {class(img)}, {'size',size(img)});
        end

        function test_2
            img = imread(fullfile(mexopencv.root(),'test','lena.jpg'));
            out = cv.stylization(img, 'SigmaS',60 ,'SigmaR',0.45);
            validateattributes(out, {class(img)}, {'size',size(img)});
        end

        function test_error_argnum
            try
                cv.stylization();
                throw('UnitTest:Fail');
            catch e
                assert(strcmp(e.identifier,'mexopencv:error'));
            end
        end
    end

end


function finalStruct = parseKspConfigFile(fileTxt)
    pos1 = strfind(fileTxt,'{');

    pairs = NaN(length(pos1),2);
    for(i=1:length(pos1))
        pos2 = getbracketpairpos(fileTxt,pos1(i));
        pairs(i,:) = [pos1(i), pos2];
    end

    structs = {};
    structsStartEnd = [];
    for(i=1:size(pairs,1))
        pair = pairs(i,:);

        internalPairs = pairs(pairs(:,1) > pair(1) & pairs(:,2) < pair(2),:);

        if(not(isempty(internalPairs)))
            endInd = min(internalPairs(:,1));
        else
            endInd = pair(2)-1;
        end
        startInd = pair(1)+1;
        subText = fileTxt(startInd:endInd);

        priorText = fileTxt(1:pair(1)-1);
        tokens = regexp(priorText,'(\S)+\s*$','tokens');
        if(not(isempty(tokens)))
            className = matlab.lang.makeValidName(tokens{1}{1});
        else
            className = '';
        end

        subTextLines = strtrim(strsplit(subText,'\n'));
        s = struct('ClassName',className);
        for(j=1:length(subTextLines))
            line = subTextLines{j};
            tokens = regexp(line,'(\w+)\s*\=\s*([ \S]+)','tokens');

            if(not(isempty(tokens)))
                token = tokens{1};
                name = matlab.lang.makeValidName(token{1});

                dataStr = attemptStrEval(token{2});
                data = str2double(dataStr);
                if(isnan(data))
                    data = dataStr;
                end

                if(isfield(s,name))
                    if(not(iscell(s.(name))))
                        s.(name) = {s.(name)};
                    end
                    s.(name){end+1} = data;
                else
                    s.(name) = data;
                end
            end
        end

        if(not(isempty(fields(s))))
            structs{end+1} = s; %#ok<AGROW>
            structsStartEnd(end+1,:) = pair; %#ok<AGROW>
        end
    end

    width = structsStartEnd(:,2) - structsStartEnd(:,1);
    [~,I] = sort(width);
    structsStartEnd = structsStartEnd(I,:);
    structs = structs(I);

    for(i=1:length(structsStartEnd))
        structStartEnd = structsStartEnd(i,:);

        greaterStructs = structsStartEnd(structsStartEnd(:,1) < structStartEnd(1) & ...
                                         structsStartEnd(:,2) > structStartEnd(2), :);

        if(not(isempty(greaterStructs)))
            [~,I] = min(greaterStructs(:,2) - greaterStructs(:,1));
            greaterStruct = greaterStructs(I,:);

            [~, index] = ismember(greaterStruct, structsStartEnd, 'rows');

            if(not(isfield(structs{index},structs{i}.ClassName)))
                structs{index}.(structs{i}.ClassName) = structs{i};
            else
                if(not(iscell(structs{index}.(structs{i}.ClassName))))
                    structs{index}.(structs{i}.ClassName) = {structs{index}.(structs{i}.ClassName)};
                end

                structs{index}.(structs{i}.ClassName){end+1} = structs{i};
            end
        end
    end

    finalStruct = structs{end};
end