function [revisedModel,gapfilledReactions,replacedReactions]=debugModel(model,testResults,inputDataFolder,infoFilePath,microbeID,biomassReaction)
% This function runs a suite of debugging functions on a refined
% reconstruction produced by the DEMETER pipeline. Tests
% are performed whether or not the models can produce biomass aerobically
% and anaerobically, and whether or not unrealistically high ATP is
% produced on a complex medium.
%
% USAGE:
%
%   [revisedModel,gapfilledReactions,replacedReactions]=debugModel(model,testResults,inputDataFolder,infoFilePath,microbeID,biomassReaction)
%
% INPUTS
% model:                 COBRA model structure
% testResults:           Structure with results of test run
% inputDataFolder:       Folder with input tables with experimental data
%                        and databases that inform the refinement process
% infoFilePath           File with information on reconstructions to refine
% microbeID:             ID of the reconstructed microbe that serves as
%                        the reconstruction name and to identify it in
%                        input tables
% biomassReaction:       Reaction ID of the biomass objective function
%
% OUTPUT
% revisedModel:          Gapfilled COBRA model structure
% gapfilledReactions:    Reactions gapfilled to enable flux
% replacedReactions:     Reactions replaced because they were causing
%                        futile cycles
%
% .. Author:
%       - Almut Heinken, 09/2020

gapfilledReactions = {};
cntGF=1;
replacedReactions = {};

tol=0.0000001;

model=changeObjective(model,biomassReaction);

% implement complex medium
constraints = readtable('ComplexMedium.txt', 'Delimiter', 'tab');
constraints=table2cell(constraints);
constraints=cellstr(string(constraints));

% Load reaction and metabolite database
database=loadVMHDatabase;

% create a temporary summary file of the performed refinement
summary=struct;
summary.condGF={};
summary.targetGF={};
summary.relaxGF={};

% rebuild model
model = rebuildModel(model,database,biomassReaction);

[AerobicGrowth, AnaerobicGrowth] = testGrowth(model, biomassReaction);
if AnaerobicGrowth(1,1) < tol
    % find reactions that are preventing the model from growing
    % anaerobically
    % first gapfilling specialized for anaerobic growth
    [model,oxGapfillRxns,anaerGrowthOK] = anaerobicGrowthGapfill(model, biomassReaction, database);
    if ~isempty(oxGapfillRxns)
        summary.condGF=union(summary.condGF,oxGapfillRxns);

        gapfilledReactions{cntGF,1}=microbeID;
        gapfilledReactions{cntGF,2}='Enabling anaerobic growth';
        gapfilledReactions{cntGF,3}='Condition-specific gapfilling';
        gapfilledReactions(cntGF,4:length(oxGapfillRxns)+3)=oxGapfillRxns;
        cntGF=cntGF+1;
    end
    % then less targeted gapfilling
    model=changeRxnBounds(model,'EX_o2(e)',0,'l');
    [model,condGF,targetGF,relaxGF] = runGapfillingFunctions(model,biomassReaction,biomassReaction,'max',database,1);
    model=changeRxnBounds(model,'EX_o2(e)',-10,'l');
    % export the gapfilled reactions
    if ~isempty(condGF)
        summary.condGF=union(summary.condGF,condGF);

        gapfilledReactions{cntGF,1}=microbeID;
        gapfilledReactions{cntGF,2}='Enabling anaerobic growth';
        gapfilledReactions{cntGF,3}='Condition-specific gapfilling';
        gapfilledReactions(cntGF,4:length(condGF)+3)=condGF;
        cntGF=cntGF+1;
    end
    if ~isempty(targetGF)
        summary.targetGF=union(summary.targetGF,targetGF);

        gapfilledReactions{cntGF,1}=microbeID;
        gapfilledReactions{cntGF,2}='Enabling anaerobic growth';
        gapfilledReactions{cntGF,3}='Targeted gapfilling';
        gapfilledReactions(cntGF,4:length(targetGF)+3)=targetGF;
        cntGF=cntGF+1;
    end
    if ~isempty(relaxGF)
        summary.relaxGF=union(summary.relaxGF,relaxGF);

        gapfilledReactions{cntGF,1}=microbeID;
        gapfilledReactions{cntGF,2}='Enabling anaerobic growth';
        gapfilledReactions{cntGF,3}='Gapfilling based on relaxFBA';
        gapfilledReactions(cntGF,4:length(relaxGF)+3)=relaxGF;
        cntGF=cntGF+1;
    end
end

[AerobicGrowth, AnaerobicGrowth] = testGrowth(model, biomassReaction);
if AerobicGrowth(1,2) < tol
    % identify blocked reactions on complex medium
    model=useDiet(model,constraints);
    [model,condGF,targetGF,relaxGF] = runGapfillingFunctions(model,biomassReaction,biomassReaction,'max',database);
    % export the gapfilled reactions
    if ~isempty(condGF)
        summary.condGF=union(summary.condGF,condGF);

        gapfilledReactions{cntGF,1}=microbeID;
        gapfilledReactions{cntGF,2}='Growth on complex medium';
        gapfilledReactions{cntGF,3}='Condition-specific gapfilling';
        gapfilledReactions(cntGF,4:length(condGF)+3)=condGF;
        cntGF=cntGF+1;
    end
    if ~isempty(targetGF)
        summary.targetGF=union(summary.targetGF,targetGF);

        gapfilledReactions{cntGF,1}=microbeID;
        gapfilledReactions{cntGF,2}='Growth on complex medium';
        gapfilledReactions{cntGF,3}='Targeted gapfilling';
        gapfilledReactions(cntGF,4:length(targetGF)+3)=targetGF;
        cntGF=cntGF+1;
    end
    if ~isempty(relaxGF)
        summary.relaxGF=union(summary.relaxGF,relaxGF);

        gapfilledReactions{cntGF,1}=microbeID;
        gapfilledReactions{cntGF,2}='Growth on complex medium';
        gapfilledReactions{cntGF,3}='Gapfilling based on relaxFBA';
        gapfilledReactions(cntGF,4:length(relaxGF)+3)=relaxGF;
        cntGF=cntGF+1;
    end
end

% identify blocked biomass precursors on defined medium for the organism
[growsOnDefinedMedium,constrainedModel,~] = testGrowthOnDefinedMedia(model, microbeID, biomassReaction, inputDataFolder);
if growsOnDefinedMedium == 0
    % find reactions that are preventing the model from growing
    [model,condGF,targetGF,relaxGF] = runGapfillingFunctions(constrainedModel,biomassReaction,biomassReaction,'max',database,1);
    % export the gapfilled reactions
    if ~isempty(condGF)
        summary.condGF=union(summary.condGF,condGF);

        gapfilledReactions{cntGF,1}=microbeID;
        gapfilledReactions{cntGF,2}='Growth on defined medium';
        gapfilledReactions{cntGF,3}='Condition-specific gapfilling';
        gapfilledReactions(cntGF,4:length(condGF)+3)=condGF;
        cntGF=cntGF+1;
    end
    if ~isempty(targetGF)
        summary.targetGF=union(summary.targetGF,targetGF);

        gapfilledReactions{cntGF,1}=microbeID;
        gapfilledReactions{cntGF,2}='Growth on defined medium';
        gapfilledReactions{cntGF,3}='Targeted gapfilling';
        gapfilledReactions(cntGF,4:length(targetGF)+3)=targetGF;
        cntGF=cntGF+1;
    end
    if ~isempty(relaxGF)
        summary.relaxGF=union(summary.relaxGF,relaxGF);

        gapfilledReactions{cntGF,1}=microbeID;
        gapfilledReactions{cntGF,2}='Growth on defined medium';
        gapfilledReactions{cntGF,3}='Gapfilling based on relaxFBA';
        gapfilledReactions(cntGF,4:length(relaxGF)+3)=relaxGF;
        cntGF=cntGF+1;
    end
end

% if there are any false negative predictions
% find what reactions should be added to enable agreement with experimental
% data

fields=fieldnames(testResults);

for i=1:length(fields)
    % define if objective should be maximized or minimized
    if any(contains(fields{i},{'Carbon_sources','Metabolite_uptake','Drug_metabolism'}))
        osenseStr = 'min';
    elseif any(contains(fields{i},{'Fermentation_products','Secretion_products','Bile_acid_biosynthesis','PutrefactionPathways'}))
        osenseStr = 'max';
    end
    FNlist = testResults.(fields{i});
    if size(FNlist,1)>1
        FNs = FNlist(find(strcmp(FNlist(:,1),microbeID)),2:end);
        FNs = FNs(~cellfun(@isempty, FNs));
        if ~isempty(FNs)
            for j=1:length(FNs)
                metExch=['EX_' database.metabolites{find(strcmp(database.metabolites(:,2),FNs{j})),1} '(e)'];
                if contains(fields{i},'PutrefactionPathways')
                    % reaction ID itself provided
                    metExch = FNs{j};
                end
                % find reactions that could be gap-filled to enable flux
                try
                    [model,condGF,targetGF,relaxGF] = runGapfillingFunctions(model,metExch,biomassReaction,osenseStr,database);
                catch
                    condGF = {};
                    targetGF = {};
                    relaxGF = {};
                end
                % export the gapfilled reactions
                if ~isempty(condGF)
                    summary.condGF=union(summary.condGF,condGF);

                    gapfilledReactions{cntGF,1}=microbeID;
                    gapfilledReactions{cntGF,2}=FNs{j};
                    gapfilledReactions{cntGF,3}='Condition-specific gapfilling';
                    gapfilledReactions(cntGF,4:length(condGF)+3)=condGF;
                    cntGF=cntGF+1;
                end
                if ~isempty(targetGF)
                    summary.targetGF=union(summary.targetGF,targetGF);

                    gapfilledReactions{cntGF,1}=microbeID;
                    gapfilledReactions{cntGF,2}=FNs{j};
                    gapfilledReactions{cntGF,3}='Targeted gapfilling';
                    gapfilledReactions(cntGF,4:length(targetGF)+3)=targetGF;
                    cntGF=cntGF+1;
                end
                if ~isempty(relaxGF)
                    summary.relaxGF=union(summary.relaxGF,relaxGF);

                    gapfilledReactions{cntGF,1}=microbeID;
                    gapfilledReactions{cntGF,2}=FNs{j};
                    gapfilledReactions{cntGF,3}='Gapfilling based on relaxFBA';
                    gapfilledReactions(cntGF,4:length(relaxGF)+3)=relaxGF;
                    cntGF=cntGF+1;
                end
            end
        end
    end
end

% rebuild periplasmatic space if applies
infoFile = readInputTableForPipeline(infoFilePath);
[model] = createPeriplasmaticSpace(model,microbeID,infoFile);

% remove futile cycles if any exist
[model, deletedRxns, addedRxns] = removeFutileCycles(model, biomassReaction, database);
replacedReactions{1,1}=microbeID;
replacedReactions{1,2}='Futile cycle correction';
replacedReactions{1,3}='To replace';
replacedReactions(1,4:length(deletedRxns)+3)=deletedRxns;

% if any futile cycles remain
[atpFluxAerobic, atpFluxAnaerobic] = testATP(model);
if atpFluxAerobic > 200 || atpFluxAnaerobic > 150
    % let us try if running removeFutileCycles again will work
    [model, deletedRxns, addedRxns] = removeFutileCycles(model, biomassReaction, database);
    replacedReactions{1,1}=microbeID;
    replacedReactions{1,2}='Futile cycle correction';
    replacedReactions{1,3}='To replace';
    replacedReactions(1,4:length(deletedRxns)+3)=deletedRxns;
end

% add the gap-filling to model.comments field
for i=1:length(model.rxns)
    if strcmp(model.grRules{i},'demeterGapfill')
        model.grRules{i}=strrep(model.grRules{i},'demeterGapfill','');
        if ~isempty(find(strcmp(summary.condGF,model.rxns{i})))
            model.comments{i}='Added by DEMETER to enable flux with VMH-consistent constraints.';
        elseif ~isempty(find(strcmp(summary.targetGF,model.rxns{i})))
            model.comments{i}='Added by DEMETER during targeted gapfilling to enable production of required metabolites.';
        elseif ~isempty(find(strcmp(summary.relaxGF,model.rxns{i})))
            model.comments{i}='Added by DEMETER based on relaxFBA. Low confidence level.';
        end
    end
end

% rebuild and export the model
revisedModel = rebuildModel(model,database);
revisedModel = changeObjective(revisedModel,biomassReaction);
% limit flux through sink reactions
revisedModel = changeRxnBounds(revisedModel,revisedModel.rxns(find(strncmp(revisedModel.rxns,'sink_',5)),1),-1,'l');

end


function [n, nm, nl, ts, names, m] = nex_marker(filename, varname)
% nex_marker(filename, varname): Read a marker variable from a .nex file
%
% [n, nm, nl, ts, names, m] = nex_marker(filename, varname)
%
% INPUT:
%   filename - if empty string, will use File Open dialog
%   varname - variable name
%
%           continuous (a/d) data come in fragments. Each fragment has a timestamp
%           and a number of a/d data points. The timestamp corresponds to
%           the time of recording of the first a/d value in this fragment.
%           All the data values stored in the vector d.
% OUTPUT:
%   n - number of markers
%   nm - number of fields in each marker
%   nl - number of characters in each marker field
%   ts - array of marker timestamps (in seconds)
%   names - names of marker fields ([nm 64] character array)
%   m - character array of marker values [n nl nm]

% original from Plexon, download from http://www.plexoninc.com (8/4/02)
% modifications by Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    FieldTrip is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

n = 0;
nm = 0;
nl = 0;
ts = 0;
m = 0;
names = 0;

if(nargin ~= 2)
   disp('2 input arguments are required')
   return
end

if(ischar(filename) == 0)
   disp('input arguments should be character arrays')
   return
end

if(ischar(varname) == 0)
   disp('input arguments should be character arrays')
   return
end

if(length(filename) == 0)
   [fname, pathname] = uigetfile('*.nex', 'Select a Nex file');
    filename = strcat(pathname, fname);
end

fid = fopen(filename, 'r', 'ieee-le');
if(fid == -1)
    disp('cannot open file');
   return
end

disp(strcat('file = ', filename));
magic = fread(fid, 1, 'int32');
version = fread(fid, 1, 'int32');
comment = fread(fid, 256, 'char');
freq = fread(fid, 1, 'double');
tbeg = fread(fid, 1, 'int32');
tend = fread(fid, 1, 'int32');
nvar = fread(fid, 1, 'int32');
fseek(fid, 260, 'cof');
name = zeros(1, 64);
found = 0;
for i=1:nvar
    type = fread(fid, 1, 'int32');
    var_version = fread(fid, 1, 'int32');
    name = fread(fid, [1 64], 'char');
    offset = fread(fid, 1, 'int32');
    n = fread(fid, 1, 'int32');
    dummy = fread(fid, 32, 'char');
    adfreq = fread(fid, 1, 'double');
    adtomv = fread(fid, 1, 'double');
    npw = fread(fid, 1, 'int32');
    nm = fread(fid, 1, 'int32');
    nl = fread(fid, 1, 'int32');
    dummy = fread(fid, 68, 'char');
    name = char(name);
    name = deblank(name);
    k = strcmp(name, deblank(varname));
    if(k == 1)
        if type ~= 6
            disp(sprintf('%s is not a marker variable', deblank(varname)));
            return;
        end
        found = 1;
        fseek(fid, offset, 'bof');
        ts = fread(fid, [1 n], 'int32');
        names = zeros(1,64);
        m = zeros(n, nl, nm);
        for j=1:nm
            names(j, :) = fread(fid, [1 64], 'char');
            for p = 1:n
                m(p, :, j) = fread(fid, [1 nl], 'char');
            end
        end
        break
    end
end

fclose(fid);

if found == 0
    disp('did not find variable in the file');
else
    names = char(names);
    m = char(m);
    ts = ts/freq;
    disp(strcat('number of markers = ', num2str(n)));
end


function crossfeed(varargin)

%CROSSFEED Checks for crossfeeding and duplicate data channels
% CROSSFEED Reads a short snippet of data from an entire network and cross
% correlates each trace against all others (without time shifting). Events
% are then clustered and a directory of png images is written out showing
% raw and filtered versions of the waveforms in each cluster. These images
% can be used to check for data problems across the network. The image
% directory is named FIG_yyyymmdd_HHMMSS according to the start time.
%
% CROSSFEED(STARTTIME) specify a start time in Matlab date format
%
% CROSSFEED(STARTTIME,DURATION) specify a trace duration in seconds.
%
% CROSSFEED(STARTTIME,DURATION,THRESHOLD) specify a cross correlation
% threshold for clustering.
%
% CROSSFEED(STARTTIME,DURATION,THRESHOLD,NETWORK) specify a network to
% examine.
%
% Using CROSSFEED with no arguments is equivalent to:
%    CROSSFEED( floor(now)-0.5 , 30 , 0.7 , 'AV' )
%
% Note that "now" is returned in local time. So the default data examined
% is 8 to 9 hours old, assuming AKST/AKDT.
%
% -- CAVEATS --
% This function is hardwired to run in the UAF Seis Lab assuming one day
% archive databases that contain waveforms and station-related tables (to
% get network affiliations)

% READ ARGUMENTS AND SET DEFAULT VALUES
if numel(varargin)>=1
    startTime = varargin{1};
else
     startTime = floor(now)-0.5;
end

if numel(varargin)>=2
    duration = varargin{2}/86400;
else
     duration = 30/86400;
end

if numel(varargin)>=3
    threshold = varargin{3};
else
     threshold = 0.7;
end

if numel(varargin)==4
    networkName = varargin{4};
else
     networkName = 'AV';
end

% GET CHANNEL LIST FROM DATABASE
dbWfdisc = ['/aerun/sum/db/archive/archive_' datestr(startTime,'yyyy') '/archive_' datestr(startTime,'yyyy') '_' datestr(startTime,'mm') '_' datestr(startTime,'dd')];
disp(['Reading STA_CHANs for network ' networkName ' from database: ']);
disp([ '   ' dbWfdisc ' ...']);

db = dbopen(dbWfdisc,'r');
db = dblookup(db,'','wfdisc','','');
db1 = dblookup(db,'','affiliation','','');
db = dbjoin(db,db1);
db = dbsubset(db,['net=="' networkName '"']);
[wfdisc.sta wfdisc.chan wfdisc.net] = dbgetv(db,'sta','chan','net');
dbclose(db);

% GET LIST OF UNIQUE STA CHANs
staChanNetList = strcat(wfdisc.sta,'#',wfdisc.chan,'#',wfdisc.net);
staChanNetList = unique(staChanNetList);
for n = 1:numel(staChanNetList)
    tmp = regexp(staChanNetList(n), '#', 'split');
    scnl(n) = scnlobject(tmp{1}{1},tmp{1}{2},tmp{1}{3},'');
end

% READ IN WAVEFORMS
disp(['Loading ' num2str(numel(scnl)) ' waveforms for this time period ...']);
disp(['   start time: ' datestr(startTime)]);
disp(['   duration:   ' num2str(duration*86400) ' seconds']);
ds = datasource('antelope',dbWfdisc);
w = waveform(ds,scnl,startTime,startTime+duration);
w = demean(w);
w = fillgaps(w,0);
w = detrend(w);

% PREP WAVEFORMS AND CROSS CORRELATE
c = correlation(w);
c = detrend(c);
c = demean(c);
c = taper(c);
c = butter(c,[2 12]);
%c = xcorr(c);

% DO ZERO LAG XCORR
D = get(c,'DATA');
wcoeff = 1./sqrt(sum(D.*D));
%wcoeff(find(wcoeff==Inf)) = 0;
corr = zeros(size(D,2));
lag = zeros(size(D,2));
for col = 1:size(D,2)
    for row = 1:size(D,2)
        corr(col,row) = sum(D(:,col).*D(:,row)) .* wcoeff(col) .* wcoeff(row);
    end
end
f = find(corr<0);
corr(f) = 0;
f = find(isnan(corr));
corr(f) = 0;
c = set(c,'corr',corr);
c = set(c,'lag',lag);
c = linkage(c);
c = cluster(c,threshold);

% MAKE FIGURE DIRECTORY
dirName = [networkName '_' datestr(startTime,'yyyy') datestr(startTime,'mm') datestr(startTime,'dd') '_' datestr(startTime,'HH') datestr(startTime,'MM') datestr(startTime,'SS')];
if ~exist(dirName,'dir')
    disp(['Creating new directory: ' dirName]);
    mkdir(dirName);
end

% PLOT TRACE CLUSTERS
index = find(c,'BIG',2);
clust = get(c,'CLUST');
disp('--> dbpick selection strings: ');
for n = 1:max(clust(index))
    f = find(clust==n);
    figure('Color','w','Position',[50 50 800 1100]);
    set(gcf,'DefaultAxesFontSize',14)

    % RAW TRACE FIGURE

    wOrig = set(w(f),'UNITS','normalized scale');
    wOrigCrop = extract(wOrig,'TIME',startTime+5/86400,startTime+25/86400);
    wOrigCrop = demean(detrend(wOrigCrop));
    for i = 1:numel(wOrigCrop)
       wOrigCrop(i) = 0.25 * wOrigCrop(i) ./ std(wOrigCrop(i)) + i;
    end
    wOrigCrop = set(wOrigCrop,'UNITS','normalized scale');

    subplot(2,1,1)
    plot(wOrigCrop);
    legend(wOrigCrop);
    xlim([0 20]);
    ylim([0 numel(wOrig)+1]);
    text(19.3,0.05 *(numel(wOrig)+1),['correlation threshold: ' num2str(threshold)],'HorizontalAlignment','Right','FontSize',12);
    text(19.3,0.10 *(numel(wOrig)+1),['trace duration: ' num2str(duration*86400) 's'],'HorizontalAlignment','Right','FontSize',12);
    text(19.3,0.15 *(numel(wOrig)+1),'Raw traces','HorizontalAlignment','Right','FontSize',12,'FontWeight','Bold');

    % PLOT FILTERED CROPPED TRACE
    subplot(2,1,2)
    c2 = subset(c,f);
    wFilt = waveform(c2);
    wFilt = set(wFilt,'UNITS','normalized scale');
    wFiltCrop = extract(wFilt,'TIME',startTime+10/86400,startTime+13/86400);
    for i = 1:numel(wOrig)
       wFiltCrop(i) = 0.25 * wFiltCrop(i) ./ std(wFiltCrop(i));
    end
    plot(wFiltCrop,'Linewidth',1);
    legend(wFiltCrop);
    xlim([0 3]);
    ylim([-0.8 0.8]);
    text(2.9,-0.75,'Filtered on 2-12 Hz','HorizontalAlignment','Right','FontSize',12,'FontWeight','Bold');

    % WRITE OUT FIGURES
    subSta = get(wFilt,'STATION');
    subChan = get(wFilt,'CHANNEL');
    scList = [];
    fileName = [dirName '/overlays_' subSta{1} '_' subChan{1} '-' subSta{2} '_' subChan{2} '.png'];
    set(gcf, 'paperorientation', 'portrait');
    set(gcf, 'paperposition', [.25 .25 8 11] );
    print('-dpng',fileName);
    %
    close all

    % WRITE DBPICK TEXT
    scList = [];
    for i = 1:numel(subSta)
        scList = [scList subSta{i} ':' subChan{i} ','];
    end
    scList = scList(1:end-1);
    disp(['sc ' scList]);
end
disp(['ts ' datestr(startTime,'yyyy/mm/dd HH:MM:SS')]);


function F = in_fread_edf(sFile, sfid, SamplesBounds, ChannelsRange)
% IN_FREAD_EDF:  Read a block of recordings from a EDF/BDF file
%
% USAGE:  F = in_fread_edf(sFile, sfid, SamplesBounds, ChannelsRange)
%         F = in_fread_edf(sFile, sfid, SamplesBounds)               : Read all channels
%         F = in_fread_edf(sFile, sfid)                              : Read all channels, all the times

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

%% ===== PARSE INPUTS =====
nChannels      = sFile.header.nsignal;
iChanAnnot     = find(strcmpi({sFile.header.signal.label}, 'EDF Annotations'));
iBadChan       = find(sFile.channelflag == -1);
iChanSignal    = setdiff(1:nChannels, iChanAnnot);
iChanWrongRate = find([sFile.header.signal(iChanSignal).sfreq] ~= max([sFile.header.signal(setdiff(iChanSignal,iBadChan)).sfreq]));
iChanSkip      = union(iChanAnnot, iChanWrongRate);
% By default: Read all the channels
if (nargin < 4) || isempty(ChannelsRange)
    ChannelsRange = [1, nChannels];
end
% By default: Read all the file, at the main file frequency
if (nargin < 3) || isempty(SamplesBounds)
    SamplesBounds = round(sFile.prop.times .* sFile.prop.sfreq);
end

% Block of times/channels to extract
nReadChannels = double(ChannelsRange(2) - ChannelsRange(1) + 1);
% Read annotations instead of real data ?
isAnnotOnly = ~isempty(iChanAnnot) && (ChannelsRange(1) == ChannelsRange(2)) && ismember(ChannelsRange(1), iChanAnnot);
isBdfStatus = strcmpi(sFile.format, 'EEG-BDF') && (ChannelsRange(1) == ChannelsRange(2)) && strcmpi(sFile.header.signal(ChannelsRange(1)).label, 'Status');
% Data channels to read
if isAnnotOnly
    % Read only on annotation channel
    iChanF = 1;
else
    % Remove all the annotation channels from the list of channels to read
    iChanF = setdiff(ChannelsRange(1):ChannelsRange(2), iChanSkip) - ChannelsRange(1) + 1;
%     if any(diff(iChanF) ~= 1)
%         error('All the data channels to read from the file must be contiguous (EDF Annotation channels must be at the end of the list).');
%     end
    if any(diff(iChanF) ~= 1)
        iChanLast = find(diff(iChanF) ~= 1, 1);
        iChanF = iChanF(1:iChanLast);
    else
        iChanLast = length(iChanF);
    end
    ChannelsRange = [iChanF(1), iChanF(iChanLast)] + ChannelsRange(1) - 1;
end
% Cannot read channels with different sampling rates at the same time
if (ChannelsRange(1) ~= ChannelsRange(2))
    allFreq = [sFile.header.signal(ChannelsRange(1):ChannelsRange(2)).nsamples];
    if any(allFreq ~= allFreq(1))
        error(['Cannot read channels with mixed sampling rates at the same time.' 10 ...
               'Mark as bad channels with different sampling rates than EEG.' 10 ...
               '(right-click on data file > Good/bad channels > Edit good/bad channels)']);
    end
end
% Number of time samples to read
nTimes = round(sFile.header.reclen * sFile.header.signal(ChannelsRange(1)).sfreq);
iTimes = SamplesBounds(1):SamplesBounds(2);

%% ===== READ ALL NEEDED EPOCHS =====
% Detect which epochs are necessary for the range of data selected
epochRange = floor(SamplesBounds ./ nTimes);
epochsToRead = epochRange(1) : epochRange(2);
% Initialize block of data to read
if isAnnotOnly
    F = zeros(nReadChannels, 2 * length(iTimes));
else
    F = zeros(nReadChannels, length(iTimes));
end
% Marker that we increment when we add data to F
iF = 1;
% Read all the needed epochs
for i = 1:length(epochsToRead)
    % Find the samples to read from this epoch
    BoundsEpoch = nTimes * epochsToRead(i) + [0, nTimes-1];
    BoundsRead  = [max(BoundsEpoch(1), iTimes(1)), ...
                   min(BoundsEpoch(2), iTimes(end))];
    iTimeRead = BoundsRead(1):BoundsRead(2);
    % Convert this samples into indices in this very epoch
    iTimeRead = iTimeRead - nTimes * epochsToRead(i);
    % New indices to read
    if isAnnotOnly
        iNewF = iF:(iF + 2*length(iTimeRead) - 1);
    else
        iNewF = iF:(iF + length(iTimeRead) - 1);
    end
    % Read epoch (full or partial)
    F(iChanF,iNewF) = edf_read_epoch(sFile, sfid, epochsToRead(i), iTimeRead, ChannelsRange, isAnnotOnly, isBdfStatus);
    % Increment marker
    iF = iF + length(iTimeRead);
end

end

%% ===== READ ONE EPOCH =====
function F = edf_read_epoch(sFile, sfid, iEpoch, iTimes, ChannelsRange, isAnnotOnly, isBdfStatus)
    % ===== COMPUTE OFFSETS =====
    nTimes        = sFile.header.reclen * [sFile.header.signal.sfreq];
    nReadTimes    = length(iTimes);
    nReadChannels = double(ChannelsRange(2) - ChannelsRange(1) + 1);
    iChannels     = ChannelsRange(1):ChannelsRange(2);
    % Check that all the channels selected have the same freq rate
    if any(nTimes(iChannels) ~= nTimes(iChannels(1)))
        error('Cannot read at the same signals with different sampling frequency.');
    end
    % Size of one value
    if strcmpi(sFile.format, 'EEG-BDF')
        % BDF: int24 => 3 bytes
        bytesPerVal = 3;
        % Reading status or regular channel
        if isBdfStatus
            dataClass = 'ubit24';
        else
            dataClass = 'bit24';
        end
    else
        % EDF: int16 => 2 bytes
        bytesPerVal = 2;
        dataClass = 'int16';
        isBdfStatus = 0;
    end
    % Offset of the beginning of the recordings in the file
    offsetHeader    = round(sFile.header.hdrlen);
    % Offset of epoch
    offsetEpoch     = round(iEpoch * sum(nTimes) * bytesPerVal);
    % Channel offset
    offsetChannel   = round(sum(nTimes(1:ChannelsRange(1)-1)) * bytesPerVal);
    % Time offset at the beginning and end of each channel block
    offsetTimeStart = round(iTimes(1) * bytesPerVal);
    offsetTimeEnd   = round((nTimes(ChannelsRange(1)) - iTimes(end) - 1) * bytesPerVal);
    % ALL THE "ROUND" CALLS WERE ADDED AFTER DISCOVERING THAT THERE WERE SOMETIMES ROUNDING ERRORS IN THE MULTIPLICATIONS

    % Where to start reading in the file ?
    % => After the header, the number of skipped epochs, channels and time samples
    offsetStart = offsetHeader + offsetEpoch + offsetChannel + offsetTimeStart;
    % Number of time samples to skip after each channel
    offsetSkip = offsetTimeStart + offsetTimeEnd;

    % ===== READ DATA BLOCK =====
    % Position file at the beginning of the trial
    fseek(sfid, offsetStart, 'bof');
    % Read annotation data (char)
    if isAnnotOnly
        dataClass = 'uint8';
        nReadTimes = bytesPerVal * nReadTimes;  % 1 byte instead of 2
    end
    % Read trial data
    % => WARNING: CALL TO FREAD WITH SKIP=0 DOES NOT WORK PROPERLY
    if (offsetSkip == 0)
        F = fread(sfid, [nReadTimes, nReadChannels], dataClass)';
    elseif (bytesPerVal == 2)
        precision = sprintf('%d*%s', nReadTimes, dataClass);
        F = fread(sfid, [nReadTimes, nReadChannels], precision, offsetSkip)';
    % => WARNING: READING USING ubit24 SOMETIMES DOESNT WORK => DOING IT MANUALLY
    elseif (bytesPerVal == 3)
        % Reading each bit independently
        precision = sprintf('%d*%s', 3*nReadTimes, 'uint8');
        F = fread(sfid, [3*nReadTimes, nReadChannels], precision, offsetSkip)';
        % Grouping the 3 bits together
        F = F(:,1:3:end) + F(:,2:3:end)*256 + F(:,3:3:end)*256*256;
        % 2-Complement (negative value indicated by most significant bit)
        if strcmpi(dataClass, 'bit24')
            iNeg = (F >= 256*256*128);
            F(iNeg) = F(iNeg) - 256*256*256;
        end
    end
    % Check that data block was fully read
    if (numel(F) < nReadTimes * nReadChannels)
        % Number of full time samples that were read
        nTimeTrunc = max(0, floor(numel(F) / nReadChannels) - 1);
        % Error message
        disp(sprintf('EDF> ERROR: File is truncated (%d time samples were read instead of %d). Padding with zeros...', nTimeTrunc, nReadTimes));
        % Pad data with zeros
        Ftmp = zeros(nReadTimes, nReadChannels);
        F = F';
        Ftmp(1:numel(F)) = F(:);
        F = Ftmp';
    end

    % Processing for BDF status file
    if isBdfStatus
        % Mask to keep only the first 16 bits (Triggers bits)
        % Bit 16    : High when new Epoch is started
        % Bit 17-19 : Speed bits 0 1 2
        % Bit 20 	: High when CMS is within range
        % Bit 21 	: Speed bit 3
        % Bit 22 	: High when battery is low
        % Bit 23    : High if ActiveTwo MK2
        F = bitand(F, hex2dec('00FFFF'));
    % Processing for real data
    elseif ~isAnnotOnly
        % Convert to double
        F = double(F);
        % Apply gains
        F = bst_bsxfun(@rdivide, F, [sFile.header.signal(iChannels).gain]');
% IN THEORY: THIS OFFSET SECTION IS USEFUL, BUT IN PRACTICE IT LOOKS LIKE THE VALUES IN ALL THE FILES ARE CENTERED ON ZERO
%         % Add offset
%         if isfield(sFile.header.signal, 'offset') && ~isempty(sFile.header.signal(1).offset)
%             % ...
%         end
    end
end


function CPD = tabular_CPD(bnet, self, varargin)
% TABULAR_CPD Make a multinomial conditional prob. distrib. (CPT)
%
% CPD = tabular_CPD(bnet, node) creates a random CPT.
%
% The following arguments can be specified [default in brackets]
%
% CPT - specifies the params ['rnd']
%   - T means use table T; it will be reshaped to the size of node's family.
%   - 'rnd' creates rnd params (drawn from uniform)
%   - 'unif' creates a uniform distribution
% adjustable - 0 means don't adjust the parameters during learning [1]
% prior_type - defines type of prior ['none']
%  - 'none' means do ML estimation
%  - 'dirichlet' means add pseudo-counts to every cell
%  - 'entropic' means use a prior P(theta) propto exp(-H(theta)) (see Brand)
% dirichlet_weight - equivalent sample size (ess) of the dirichlet prior [1]
% dirichlet_type - defines the type of Dirichlet prior ['BDeu']
%  - 'unif' means put dirichlet_weight in every cell
%  - 'BDeu' means we put 'dirichlet_weight/(r q)' in every cell
%    where r = self_sz and q = prod(parent_sz) (see Heckerman)
% trim - 1 means trim redundant params (rows in CPT) when using entropic prior [0]
% entropic_pcases - list of assignments to the parents nodes when we should use
%      the entropic prior; all other cases will be estimated using ML [1:psz]
% sparse - 1 means use 1D sparse array to represent CPT [0]
%
% e.g., tabular_CPD(bnet, i, 'CPT', T)
% e.g., tabular_CPD(bnet, i, 'CPT', 'unif', 'dirichlet_weight', 2, 'dirichlet_type', 'unif')
%
% REFERENCES
% M. Brand - "Structure learning in conditional probability models via an entropic  prior
%   and parameter extinction", Neural Computation 11 (1999): 1155--1182
% M. Brand - "Pattern discovery via entropy minimization" [covers annealing]
%   AI & Statistics 1999. Equation numbers refer to this paper, which is available from
%   www.merl.com/reports/docs/TR98-21.pdf
% D. Heckerman, D. Geiger and M. Chickering,
%   "Learning Bayesian networks: the combination of knowledge and statistical data",
%   Microsoft Research Tech Report, 1994

if nargin==0
  % This occurs if we are trying to load an object from a file.
  CPD = init_fields;
  CPD = class(CPD, 'tabular_CPD', discrete_CPD(0, []));
  return;
elseif isa(bnet, 'tabular_CPD')
  % This might occur if we are copying an object.
  CPD = bnet;
  return;
end
CPD = init_fields;

ns = bnet.node_sizes;
ps = parents(bnet.dag, self);
fam_sz = ns([ps self]);
psz = prod(ns(ps));
CPD.sizes = fam_sz;
CPD.leftright = 0;
CPD.sparse = 0;

% set defaults
CPD.CPT = mk_stochastic(myrand(fam_sz));
CPD.adjustable = 1;
CPD.prior_type = 'none';
dirichlet_type = 'BDeu';
dirichlet_weight = 1;
CPD.trim = 0;
selfprob = 0.1;
CPD.entropic_pcases = 1:psz;

% extract optional args
args = varargin;
% check for old syntax CPD(bnet, i, CPT) as opposed to CPD(bnet, i, 'CPT', CPT)
if ~isempty(args) & ~isstr(args{1})
  CPD.CPT = myreshape(args{1}, fam_sz);
  args = [];
end

for i=1:2:length(args)
  switch args{i},
   case 'CPT',
    T = args{i+1};
    if ischar(T)
      switch T
       case 'unif', CPD.CPT = mk_stochastic(myones(fam_sz));
       case 'rnd',  CPD.CPT = mk_stochastic(myrand(fam_sz));
       otherwise,   error(['invalid CPT ' T]);
      end
    else
      CPD.CPT = myreshape(T, fam_sz);
    end
   case 'prior_type', CPD.prior_type = args{i+1};
   case 'dirichlet_type', dirichlet_type = args{i+1};
   case 'dirichlet_weight', dirichlet_weight = args{i+1};
   case 'adjustable', CPD.adjustable = args{i+1};
   case 'clamped', CPD.adjustable = ~args{i+1};
   case 'trim', CPD.trim = args{i+1};
   case 'entropic_pcases', CPD.entropic_pcases = args{i+1};
   case 'sparse', CPD.sparse = args{i+1};
   otherwise, error(['invalid argument name: ' args{i}]);
  end
end

switch CPD.prior_type
 case 'dirichlet',
  switch dirichlet_type
   case 'unif', CPD.dirichlet = dirichlet_weight * myones(fam_sz);
   case 'BDeu',  CPD.dirichlet = (dirichlet_weight/psz) * mk_stochastic(myones(fam_sz));
   otherwise, error(['invalid dirichlet_type ' dirichlet_type])
  end
 case {'entropic', 'none'}
  CPD.dirichlet = [];
 otherwise, error(['invalid prior_type ' prior_type])
end

% fields to do with learning
if ~CPD.adjustable
  CPD.counts = [];
  CPD.nparams = 0;
  CPD.nsamples = [];
else
  %CPD.counts = zeros(size(CPD.CPT));
  CPD.counts = zeros(prod(size(CPD.CPT)), 1);
  psz = fam_sz(1:end-1);
  ss = fam_sz(end);
  if CPD.leftright
    % For each of the Qps contexts, we specify Q elements on the diagoanl
    CPD.nparams = Qps * Q;
  else
    % sum-to-1 constraint reduces the effective arity of the node by 1
    CPD.nparams = prod([psz ss-1]);
  end
  CPD.nsamples = 0;
end

CPD.trimmed_trans = [];
fam_sz = CPD.sizes;

%psz = prod(fam_sz(1:end-1));
%ssz = fam_sz(end);
%CPD.trimmed_trans = zeros(psz, ssz); % must declare before reading

%sparse CPT
if CPD.sparse
   CPD.CPT = sparse(CPD.CPT(:));
end

CPD = class(CPD, 'tabular_CPD', discrete_CPD(~CPD.adjustable, fam_sz));

%%%%%%%%%%%

function CPD = init_fields()
% This ensures we define the fields in the same order
% no matter whether we load an object from a file,
% or create it from scratch. (Matlab requires this.)

CPD.CPT = [];
CPD.sizes = [];
CPD.prior_type = [];
CPD.dirichlet = [];
CPD.adjustable = [];
CPD.counts = [];
CPD.nparams = [];
CPD.nsamples = [];
CPD.trim = [];
CPD.trimmed_trans = [];
CPD.leftright = [];
CPD.entropic_pcases = [];
CPD.sparse = [];


function [out] = analyseThermoConstrainedModel(model, cumNormProbCutoff, printLevel, resultsBaseFileName)
% USAGE:
%
%    [out] = analyseThermoConstrainedModel(model, cumNormProbCutoff, printLevel, resultsBaseFileName)
%
% INPUTS:
%    model:                  structure with fields:
%
%                              * .DfGt0 - `m x 1` array of estimated standard transformed Gibbs
%                                energies of formation.
%                              * .DrGt0 - `n x 1` array of estimated standard transformed
%                                reaction Gibbs energies.
%                              * .DfGtMin - `m x 1` array of estimated lower bounds on
%                                transformed Gibbs energies of formation.
%                              * .DfGtMax - `m x 1` array of estimated upper bounds on
%                                transformed Gibbs energies of formation.
%                              * .DrGtMin - `n x 1` array of estimated lower bounds on
%                                transformed reaction Gibbs energies.
%                              * .DrGtMax - `n x 1` array of estimated upper bounds on
%                                transformed reaction Gibbs energies.
%                              * .quantDir - `n x 1` array indicating quantitatively assigned
%                                reaction directionality. 1 for reactions that are
%                                irreversible in the forward direction, -1 for
%                                reactions that are irreversible in the reverse
%                                direction, and 0 for reversible reactions.
%    cumNormProbCutoff:      default = 0.2
%    printLevel:             verbose level, default = 1
%    resultsBaseFileName:
%
% OUTPUT:
%    out:

out=[];

if ~exist('printLevel','var')
    printLevel=1;
end
if ~exist('cumNormProbCutoff','var')
    cumNormProbCutoff=0.2;
else
    if cumNormProbCutoff<0
        error('cumNormProbCutoff cannot be less than zero')
    end
end

if 1
    %statistics of directions
    fprintf('%s\n','directionalityStats...');
    model=directionalityStats(model,cumNormProbCutoff,printLevel);
end
%OUTPUT
% model.directions    a structue of boolean vectors with different directionality
%               assignments where some vectors contain subsets of others
%
% qualtiative -> quantiative changed reaction directions
%   .directions.forwardForward
%   .directions.forwardReverse
%   .directions.forwardReversible
%   .directions.reversibleFwd
%   .directions.reversibleRev
%   .directions.reversibleReversible
%   .directions.tightened
%
% subsets of qualtiatively forward  -> quantiatively reversible
%   .directions.forwardReversible_bydGt0
%   .directions.forwardReversible_bydGt0LHS
%   .directions.forwardReversible_bydGt0Mid
%   .directions.forwardReversible_bydGt0RHS
%
%   .directions.forwardReversible_byConc_zero_fixed_DrG0
%   .directions.forwardReversible_byConc_negative_fixed_DrG0
%   .directions.forwardReversible_byConc_positive_fixed_DrG0
%   .directions.forwardReversible_byConc_negative_uncertain_DrG0
%   .directions.forwardReversible_byConc_positive_uncertain_DrG0

if 1
    %report on directionality changes
    fprintf('%s\n','directionalityChangeReport...');
    directionalityChangeReport(model,cumNormProbCutoff,printLevel,resultsBaseFileName)
end

% if 0
%     fprintf('\n%s\n','...standardGibbsFormationEnergyStats');
%     [nKeq,nGC,nNone]=standardGibbsFormationEnergyStats(model,figures);
% end

if 0
    %pie charts with proportions of reaction directionalities and changes in
    %directionality
    fprintf('%s\n','directionalityStatFigures...');
    directionalityStatsFigures(model.directions,resultsBaseFileName)
end

%figures of reaction directionality changes
%qualitatively forward now quantiatiavely reversible
if any(model.directions.forward2Reversible)
    fprintf('%s\n','forwardReversibleFigures...');
    if 0
        forwardReversibleFigures(model)
    else
        thorStandard=0;
        forwardReversibleFiguresCC(model,model.directions,thorStandard)
    end
end

return

%create a vertical errorbar figure of the qualitatively forward transport reactions
%that are quantitatively reversible, whether from group contribution or
%Keq, that then need to be assigned to be forward, to limit the growth rate
% i.e. abc transporters or reactions involving protons
if any(model.directions.forwardReversible & model.transportRxnBool)
     forwardTransportQuantReverseFigures(model)
end

%vertical errorbar of qualitatively forward but quantitatively reverse
%forward, probably reverse
%   .directions.forwardReversible_bydGt0RHS
%   .directions.forwardReversible_byConc_positive_fixed_DrG0
%   .directions.forwardReversible_byConc_positive_uncertain_DrG0
fwdProbReverse=model.directions.forwardReversible_bydGt0RHS | ...
    model.directions.forwardReversible_byConc_positive_uncertain_DrG0| ...
    model.directions.forwardReversible_byConc_positive_fixed_DrG0;
if any(fwdProbReverse) && 0
    forwardProbablyReverseFigures(model,directions)
end


classdef ThreeDimKspWindTunnelDragCoeffModel < AbstractDragCoefficientModel
    %ThreeDimKspWindTunnelDragCoeffModel Summary of this class goes here
    %   Detailed explanation goes here

    properties
        slices(1,:) ThreeDimKWTDragCoefficientModelSlice

        speed(1,:) double
        altitude(:,1) double
        aoa(1,:) double
        data double

        gi
    end

    properties(Constant)
        enum = DragCoefficientModelEnum.ThreeDimWindTunnel
    end

    methods
        function obj = ThreeDimKspWindTunnelDragCoeffModel(dataSlices)
            obj.slices = dataSlices;
            obj.sortSlicesByAoA();
        end

        function CdA = getDragCoeff(obj, ut, rVect, vVect, bodyInfo, mass, altitude, pressure, density, vVectECEFMag, totalAoA, aoa, sideslip)
            arguments
                obj(1,1) ThreeDimKspWindTunnelDragCoeffModel
                ut(1,1) double
                rVect(3,1) double
                vVect(3,1) double
                bodyInfo(1,1) KSPTOT_BodyInfo
                mass(1,1) double
                altitude(1,1) double
                pressure(1,1) double
                density(1,1) double
                vVectECEFMag(1,1) double
                totalAoA(1,1) double = 0;
                aoa(1,1) double = 0;
                sideslip(1,1) double = 0;
            end

            CdA = obj.gi(altitude, vVectECEFMag, totalAoA);
            CdA = max([CdA, 0]);
        end

        function addSlice(obj, newSlice)
            obj.slices(end+1) = newSlice;
            obj.sortSlicesByAoA();
        end

        function removeSlice(obj, slice)
            obj.slices(obj.slices == slice) = [];
            obj.sortSlicesByAoA();
        end

        function tf = usesTotalAoA(obj)
            tf = true;
        end

        function tf = usesAoaAndSideslip(obj)
            tf = false;
        end

        function createGriddedInterpFromSlices(obj)
            obj.sortSlicesByAoA();

            obj.speed = obj.slices(1).twoDSlice.speed;
            obj.altitude = obj.slices(1).twoDSlice.altitude;
            obj.aoa = [obj.slices.aoa];

            obj.data = [];
            for(i=1:length(obj.slices)) %#ok<*NO4LP>
                slice = obj.slices(i);
                obj.data(:,:,i) = slice.twoDSlice.data;
            end

            gridVecs = {obj.altitude, obj.speed, obj.aoa};
            obj.gi = griddedInterpolant(gridVecs, obj.data, 'makima', 'nearest');
        end

        function sortSlicesByAoA(obj)
            AoAs = [obj.slices.aoa];
            [~,I] = sort(AoAs);
            obj.slices = obj.slices(I);
        end

        function [sliceGoodTf, messages] = getStatusOfSlices(obj)
            sliceGoodTf = true(1, length(obj.slices));
            messages = cell(1, length(obj.slices));

            AoAs = [obj.slices.aoa];

            for(i=1:length(obj.slices))
                slice = obj.slices(i);

                [thisSliceGoodTf, sliceMessages] = slice.getSliceStatus();
                sliceGoodTf(i) = sliceGoodTf(i) && thisSliceGoodTf;
                messages{i} = horzcat(messages{i}, sliceMessages);

                if(length(obj.slices) == 1)
                    sliceGoodTf(i) = false;
                    messages{i}(end+1) = 'There must be at least two AoA data sets.';
                end

                if(i >= 2)
                    subAoAs = AoAs;
                    subAoAs(i) = [];
                    if(any(AoAs(i) == subAoAs))
                        sliceGoodTf(i) = false;
                        messages{i}(end+1) = 'Duplicate angle of attack data sets are not allowed.';
                    end

                    slice1 = obj.slices(1);
                    twoDModelSlice1 = slice1.twoDSlice;

                    altitudeSlice1 = twoDModelSlice1.altitude;
                    speedSlice1 = twoDModelSlice1.speed;

                    twoDModel = slice.twoDSlice;
                    altitudeSliceI = twoDModel.altitude;
                    speedSliceI = twoDModel.speed;

                    if(numel(altitudeSliceI) == numel(altitudeSlice1))
                        bool = altitudeSliceI(:) - altitudeSlice1(:) ~= 0;
                        if(any(bool))
                            sliceGoodTf(i) = false;
                            messages{i}(end+1) = "The altitude axis points do not match those of the first AoA data set.  All axis points must be identical in all data sets.";
                        end
                    else
                        sliceGoodTf(i) = false;
                        messages{i}(end+1) = "There are not the same number of altitude axis points of those of the first AoA data set.  All axis points must be identical in all data sets.";
                    end

                    if(numel(speedSliceI) == numel(speedSlice1))
                        bool = speedSliceI(:) - speedSlice1(:) ~= 0;
                        if(any(bool))
                            sliceGoodTf(i) = false;
                            messages{i}(end+1) = "The speed axis points do not match those of the first AoA data set.  All axis points must be identical in all data sets.";
                        end
                    else
                        sliceGoodTf(i) = false;
                        messages{i}(end+1) = "There are not the same number of speed axis points of those of the first AoA data set.  All axis points must be identical in all data sets.";
                    end
                end

                if(sliceGoodTf(i) == true)
                    messages{i} = "No warnings or errors found.";
                end
            end
        end

        function [listBoxStr, slices] = getListBoxStrs(obj)
            for(i=1:length(obj.slices))
                listBoxStr(i) = string(obj.slices(i).getListboxStr()); %#ok<AGROW>
            end

            slices = obj.slices;
        end

        function useTf = openEditDialog(obj, lvdData)
            out = AppDesignerGUIOutput({false});
            lvd_Edit3DKerbalWindTunnelDragPropertiesGUI_App(obj, lvdData, out);
            useTf = out.output{1};
        end
    end

    methods(Static)
        function obj = loadobj(obj)
            if(isempty(obj.slices))
                slice1 = ThreeDimKspWindTunnelDragCoeffModel.produceDataSlice(0, '');
                obj.addSlice(slice1);

                slice2 = ThreeDimKspWindTunnelDragCoeffModel.produceDataSlice(deg2rad(1), '');
                obj.addSlice(slice2);
            end

            try
                obj.createGriddedInterpFromSlices();
            catch ME
                % nothing to do here
            end
        end

        function slice = produceDataSlice(aoa, sliceDataFile)
            slice = ThreeDimKWTDragCoefficientModelSlice(aoa, sliceDataFile);
        end

        function dragCoeffModel = createDefault()
            slice1 = ThreeDimKspWindTunnelDragCoeffModel.produceDataSlice(0, '');
            slice2 = ThreeDimKspWindTunnelDragCoeffModel.produceDataSlice(deg2rad(1), '');
            slices = [slice1, slice2];

            dragCoeffModel = ThreeDimKspWindTunnelDragCoeffModel(slices);
        end
    end
end


function ft_realtime_fmriproxy(cfg)

% FT_REALTIME_FMRIPROXY simulates an fMRI acquisition system by writing volumes in a
% cycle of about 2 seconds. The voxel data is written as a column vector with X*Y*Z
% channels, where X and Y are the readout and phase-encoding resolution, and Z is the
% number of slices. The voxel data consists of a ellipsoid (a bit like a head) with
% added lateralized activation (20 scan cycle) and noise.
%
% This function also writes out events of type='scan' and value='pulse' when the
% simulated scanner initiates a scan, and value='ready' when a hypothetical
% processing pipeline is finished with that scan, just after writing out the volume
% data itself. There is an artificial delay of 1.3*TR between the two events.
%
% Use as
%   ft_realtime_fmriproxy(cfg)
%
% The target to write the data to is configured as
%   cfg.target.datafile      = string, target destination for the data (default = 'buffer://localhost:1972')
%
% You can also select a resolution of the simulated volumes (default = [64,64,20]) like
%   cfg.voxels = [64 64 32]
% and the repetition time (TR, default = 0.08*number of slices) in seconds using
%   cfg.TR = 2.0
%
% To stop this realtime function, you have to press Ctrl-C
%
% See also FT_REALTIME_SIGNALPROXY, FT_REALTIME_SIGNALVIEWER

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

% set the defaults
if isempty(cfg) || ~isfield(cfg, 'target') || ~isfield(cfg.target, 'datafile')
  cfg.target.datafile = 'buffer://localhost:1972';
end
cfg.target.dataformat = [];

if isfield(cfg, 'voxels')
   voxels = cfg.voxels(1:3);
else
   voxels = [64 64 20];
end

if isfield(cfg,'TR')
   TR = cfg.TR;
   Tslice = TR / voxels(3);
else
   Tslice = 0.08;
   TR = Tslice * voxels(3);
end

nifti = [];
nifti.dim = voxels;
nifti.pixdim = [3.5 3.5 3.5]; % size of voxel in mm
nifti.slice_duration = Tslice;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% create a FieldTrip compatible header structure
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
hdr = [];
hdr.nChans = prod(voxels);
hdr.nSamples = 0;
hdr.Fs = 1/TR;
hdr.nSamplesPre        = 0;
hdr.nTrials            = 1;
hdr.nifti_1 = encode_nifti1(nifti);

% prepare two event structure that will go along with every sample/scan
evp = [];
evp.type = 'scan';
evp.value = 'pulse';
evp.offset = 0;
evp.duration = 0;
evp.sample = 0;

evr = evp;
evr.value = 'ready';

% assume acquisition + processing takes 30% of a TR cycle
TP = 0.3 * TR;

% make some fake MRI data
% Sphere = volume containing a spherical "head"
% Left/Right = volume of same size, adds a bit of fake activation
x = linspace(-1,1,voxels(1));
y = linspace(-1,1,voxels(2));
z = linspace(-1,1,voxels(3));
[X,Y,Z] = meshgrid(x,y,z);

R = sqrt(X.^2 + Y.^2 + Z.^2);
Sphere = single(1200 * (1./(1 + exp(40*(R-0.8)))));
R = sqrt((X-0.4).^2 + Y.^2 + Z.^2);
Left  = single(60 * (1./(1 + exp(40*(R-0.4)))));
R = sqrt((X+0.4).^2 + Y.^2 + Z.^2);
Right = single(60 * (1./(1 + exp(40*(R-0.4)))));

stopwatch = tic;

% write header to FieldTrip buffer
ft_write_data(cfg.target.datafile, single([]), 'header', hdr, 'dataformat', cfg.target.dataformat, 'append', false);

numPulse = 0;
numScan = 0;

while true
	% simulate acquisition of pixeldata
	t0 = toc(stopwatch);

	% write 'scan pulse' event
	numPulse = numPulse + 1;
	evp.sample = numPulse;
	fprintf('Sending pulse: %3i, clock time = %f\n', numPulse, t0);
	ft_write_event(cfg.target.datafile, evp);

	if numPulse > 1
		% fake acquisition + processing
		lateral = 0.5 + 0.5*sin(2*pi*numScan/20);

		Noise = single(30*randn(voxels));
		scan = single(Sphere + lateral*Left + (1-lateral)*Right + Noise);

		tp = toc(stopwatch);
		pause(TP - (tp-t0));

		ft_write_data(cfg.target.datafile, scan(:), 'append', true);
		% write 'scan ready' event
		numScan = numScan + 1;
		evr.sample = numScan;
		fprintf('Scans written: %3i, clock time = %f\n', numScan, toc(stopwatch));
		ft_write_event(cfg.target.datafile, evr);
	end

	tr = toc(stopwatch);
	pause(numPulse*TR - tr);
end


function [TruePositives, FalseNegatives] = testFermentationProducts(model, microbeID, biomassReaction, database, inputDataFolder)
% Performs an FVA and reports those fermentation products (exchange reactions)
% that can be secreted by the model and should be secreted according to
% data (true positives) and those fermentation products that cannot be secreted by
% the model but should be secreted according to in vitro data (false
% negatives).
%
% INPUT
% model             COBRA model structure
% microbeID         Microbe ID in carbon source data file
% biomassReaction   Biomass objective functions (low flux through BOF
%                   required in analysis)
% database          Structure containing rBioNet reaction and metabolite
%                   database
% inputDataFolder   Folder with experimental data and database files
%                   to load
%
% OUTPUT
% TruePositives     Cell array of strings listing all fermentation products
%                   (exchange reactions) that can be secreted by the model
%                   and in in vitro data.
% FalseNegatives    Cell array of strings listing all fermentation products
%                   (exchange reactions) that cannot be secreted by the model
%                   but should be secreted according to in vitro data.
%
% .. Author:
%       Stefania Magnusdottir, Nov 2017
%       Almut Heinken, Jan 2018-reduced number of reactions minimized and
%                      maximized to speed up the computation
%                      March 2022 - changed code to string-matching to make it
%                      more robust

global CBT_LP_SOLVER
if isempty(CBT_LP_SOLVER)
    initCobraToolbox
end

% read fermentation product table
dataTable = readInputTableForPipeline([inputDataFolder filesep 'FermentationTable.txt']);

% remove the reference columns
dataTable(:,find(strncmp(dataTable(1,:),'Ref',3))) = [];

corrRxns = {'Acetate kinase (acetate producer or consumer)','EX_ac(e)';'Bifid shunt','EX_ac(e)';'Acetogen pathway','EX_ac(e)';'Formate producer','EX_for(e)';'D-lactate producer','EX_lac_D(e)';'L-lactate producer','EX_lac_L(e)';'Ethanol producer or consumer','EX_etoh(e)';'Succinate producer','EX_succ(e)';'Propionate from succinate','EX_ppa(e)';'Propionate from propane-1,2-diol','EX_ppa(e)';'Propionate from lactate (acrylate pathway)','EX_ppa(e)';'Propionate from threonine','EX_ppa(e)';'Wood-Werkman cycle','EX_ppa(e)';'Butyrate via butyryl-CoA: acetate CoA transferase','EX_but(e)';'Butyrate via butyrate kinase','EX_but(e)';'Butyrate from lysine via butyrate-acetoacetate CoA-transferase','EX_but(e)';'Butyrate from glutarate or glutamate','EX_but(e)';'Butyrate from 4-hydroxybutyrate or succinate','EX_but(e)';'Hydrogen from ferredoxin oxidoreductase','EX_h2(e)';'Hydrogen from formate hydrogen lyase','EX_h2(e)';'Methanogenesis','EX_ch4(e)';'Sulfate reducer','EX_h2s(e)';'Isobutyrate producer','EX_isobut(e)';'Isovalerate producer','EX_isoval(e)';'Acetoin producer','EX_actn_R(e)';'2,3-butanediol producer','EX_btd_RR(e)';'Indole producer','EX_indole(e)';'Phenylacetate producer','EX_pac(e)';'Butanol producer','EX_btoh(e)';'Valerate producer','EX_M03134(e)'};

TruePositives = {};  % true positives (uptake in vitro and in silico)
FalseNegatives = {};  % false negatives (uptake in vitro not in silico)

% find microbe index in fermentation table
mInd = find(strcmp(dataTable(:,1), microbeID));
if isempty(mInd)
    warning(['Microbe "', microbeID, '" not found in fermentation product data file.'])
else
    % perform FVA to identify uptake metabolites
    % set BOF
    if ~any(ismember(model.rxns, biomassReaction)) || nargin < 3
        error(['Biomass reaction "', biomassReaction, '" not found in model.'])
    end
    model = changeObjective(model, biomassReaction);
    % set a low lower bound for biomass
    %     model = changeRxnBounds(model, biomassReaction, 1e-3, 'l');
    % list exchange reactions
    exchanges = model.rxns(strncmp('EX_', model.rxns, 3));
    % open all exchanges
    model = changeRxnBounds(model, exchanges, -1000, 'l');
    model = changeRxnBounds(model, exchanges, 1000, 'u');

    % get the reactions to test
    rxns = {};
    for i=2:size(dataTable,2)
        if contains(version,'(R202') % for Matlab R2020a and newer
            if dataTable{mInd,i}==1
                findCorrRxns = find(strcmp(corrRxns(:,1),dataTable{1,i}));
                rxns = union(rxns,corrRxns(findCorrRxns,2:end));
            end
        else
            if strcmp(dataTable{mInd,i},'1')
                findCorrRxns = find(strcmp(corrRxns(:,1),dataTable{1,i}));
                rxns = union(rxns,corrRxns(findCorrRxns,2:end));
            end
        end
    end

    % flux variability analysis on reactions of interest
    rxns = unique(rxns);
    rxns = rxns(~cellfun('isempty', rxns));
    rxnsInModel=intersect(rxns,model.rxns);
    if ~isempty(rxnsInModel)
        currentDir=pwd;
        try
            [minFlux, maxFlux, ~, ~] = fastFVA(model, 0, 'max', 'ibm_cplex', ...
                rxnsInModel, 'S');
        catch
            warning('fastFVA could not run, so fluxVariability is instead used. Consider installing fastFVA for shorter computation times.');
            cd(currentDir)
            [minFlux, maxFlux] = fluxVariability(model, 0, 'max', rxnsInModel);
        end

        % active flux
        flux = rxnsInModel(maxFlux > 1e-6);
    else
        flux = {};
    end

    % which reaction should carry flux according to in vitro data
    for i=2:size(dataTable,2)
        rxn={};
        if contains(version,'(R202') % for Matlab R2020a and newer
            if dataTable{mInd,i}==1
                rxn = corrRxns{find(strcmp(corrRxns(:,1),dataTable{1,i})),2};
            end
        else
            if strcmp(dataTable{mInd,i},'1')
                rxn = corrRxns{find(strcmp(corrRxns(:,1),dataTable{1,i})),2};
            end
        end
        if ~isempty(rxn)
            % add any that are not in model/not carrying flux to the false negatives
            if ~isempty(intersect(rxn,flux))
                TruePositives = union(TruePositives,rxn);
            else
                FalseNegatives=union(FalseNegatives,rxn);
            end
        end
    end
end

% replace reaction IDs with metabolite names
if ~isempty(TruePositives)
    TruePositives = TruePositives(~cellfun(@isempty, TruePositives));
    TruePositives=strrep(TruePositives,'EX_','');
    TruePositives=strrep(TruePositives,'(e)','');

    for i=1:length(TruePositives)
        TruePositives{i}=database.metabolites{find(strcmp(database.metabolites(:,1),TruePositives{i})),2};
    end
end

% warn about false negatives
if ~isempty(FalseNegatives)
    FalseNegatives = FalseNegatives(~cellfun(@isempty, FalseNegatives));
    FalseNegatives=strrep(FalseNegatives,'EX_','');
    FalseNegatives=strrep(FalseNegatives,'(e)','');
    for i = 1:length(FalseNegatives)
        FalseNegatives{i}=database.metabolites{find(strcmp(database.metabolites(:,1),FalseNegatives{i})),2};
    end
end

end


function test_pull1643

% MEM 8gb
% WALLTIME 00:40:00
% DEPENDENCY ft_connectivity_plm ft_connectivityanalysis

% create some dummy data
ntrl = 100;
nsmp = 1000;
fs   = 1000;
nchan = 3;
for k = 1:ntrl
  trial{1,k} = randn(nchan,nsmp);
  time{1,k}  = (0:999)/fs;
end
for k = 1:nchan
  label{k,1} = sprintf('chan%02d',k);
end
data.trial = trial;
data.time  = time;
data.label = label;

cfg = [];
cfg.method = 'plm';
plm = ft_connectivityanalysis(cfg,data);

% So far the test code only 'checks' whether it runs through, no checks are
% done on the validity of the output structure (in terms of how it looks
% w.r.t. fields etc., nor on the validity of the numeric results)


function opto = snirf2opto(probe, measurementList)

% SNIRF2OPTO converts the SNIRF probe and measurementList structures to a FieldTrip
% optode structure.
%
% See https://github.com/fNIRS/snirf/blob/master/snirf_specification.md
%
% The FieldTrip optode structure is defined in FT_DATATYPE_SENS
%
% See also OPTO2HOMER, BTI2GRAD, CTF2GRAD, FIF2GRAD, ITAB2GRAD, MNE2GRAD, NETMEG2GRAD, YOKOGAWA2GRAD, FT_DATATYPE_SENS

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

nSrc = length(probe.sourceLabels);
nDet = length(probe.detectorLabels);

opto = [];
opto.optolabel = cat(1, probe.sourceLabels(:), probe.detectorLabels(:)); % ensure that they are columns

if ~isempty(probe.sourcePos3D)
  % use the 3D positions if available
  opto.optopos = cat(1, probe.sourcePos3D, probe.detectorPos3D);
else
  % use the 2D positions
  opto.optopos = cat(1, probe.sourcePos2D, probe.detectorPos2D);
  opto.optopos(:,3) = 0; % convert the positions to 3D
end

opto.optotype = cat(1, repmat({'transmitter'}, nSrc, 1), repmat({'receiver'}, nDet, 1));
opto.wavelength = probe.wavelengths(:)';

M = length(measurementList);    % number of channels
N = nSrc+nDet;                  % number of optodes
K = numel(probe.wavelengths);   % number of wavelengths

opto.label = cell(M,1);
for i=1:M
  tx = measurementList(i).sourceIndex;                        % transmitter
  rx = measurementList(i).detectorIndex;                      % receiver
  wl = probe.wavelengths(measurementList(i).wavelengthIndex); % wavelength in nm
  opto.label{i} = sprintf('%s-%s [%dnm]', probe.sourceLabels{tx}, probe.detectorLabels{rx}, round(wl));
end

% the following specifies for each of the M channels at which wavelength each of the
% N optodes transmits (positive integer from 1 to K), or receives (negative ingeger
% from 1 to K), or does not contribute at all (zeros)

opto.tra = zeros(M, N);
for i=1:M
  tx = measurementList(i).sourceIndex;
  rx = measurementList(i).detectorIndex + nSrc; % the transmitters are first in the list of optodes
  opto.tra(i, tx) = +measurementList(i).wavelengthIndex;
  opto.tra(i, rx) = -measurementList(i).wavelengthIndex;
end


addpath('testImage_Video/')
addpath('model/')
%for pic
load('cars_meta.mat');
load('cifar10NetRCNN.mat') %for detect
load('AlexNet_New.mat');%for recognition

%input image:
frame=imread('6.jpg');
%crop:
frame=imresize(frame,[480 640]);
outputImage=frame;
   % DetectRCNN(frame,cifar10NetRCNN);
   [bboxes, ~, ~] = detect(cifar10NetRCNN, frame);

   if  ~isempty(bboxes)
   size_array=size(bboxes);
   length=size_array(1);
   for i=1:length
       box=bboxes(i,:);
       frame_=imcrop(frame,box);
       frame_=imresize(frame_,[227 227]);
       type_num=classify(AlexNet_New,frame_);
       outputImage=insertObjectAnnotation(outputImage, 'rectangle', box, class_names{type_num},'LineWidth',3);
   end
   end
   imshow(outputImage);


function loadAlignment
% function loadAlignment
%
% loads bestrotvol.mat, converts it into a 4x4 homog transform matrix
% and incorporates it into mrSESSION.alignment.
%
% 9/17/98 rmk
% 4/29/99 BTB: Removed Inpts and Volpts fields, added tests for mrSESSION
%    field and coords.mat & coranal.mat files.
% 8/2001 djh: updated to mrLoadRet-3.0

global HOMEDIR mrSESSION

pathStr = fullfile(HOMEDIR,'bestrotvol.mat');
if ~exist(pathStr,'file')
    myErrorDlg('No bestrotvol file. Run mrAlign3.');
end
load(pathStr);

if isfield(mrSESSION, 'alignment')
    RESP = questdlg(['mrSESSION has an ''alignment'' field '...
            '(created when rotation was last saved).  '...
            'It was used to build any coords.mat and coranal.mat '...
            'files you may have in Volume, Gray, and Flat directories.'...
            'Permanently replace it with new alignment info?  '], ...
        'Question', 'Yes', 'No', 'Yes');
    if strcmp(RESP, 'Yes')
        rmfield(mrSESSION, 'alignment');
    else
        hmsgbox = msgbox(['Although you have updated the file bestrotvol.mat, ',...
                'you have NOT updated mrSESSION.alignment, nor deleted any old ', ...
                'coords.mat files from Volume, Gray, or Flat directories.'], 'Warning','warn');
        drawnow
        return
    end
end

% If we've made it this far, go ahead an replace mrSESSION.alignment field.
% 4x4 homog transform
mrSESSION.alignment = inplane2VolXform(rot,trans,scaleFac);
saveSession
disp('mrSESSION.alignment has been updated and saved.');

%%% Delete (after confirmation) old files that were built using previous alignment.
cleanAllFlats
cleanGray
cleanVolume


%
%      This HDR Toolbox demo creates an HDR radiance map:
%	   1) Read a stack of RAW images
%	   2) Read exposure values from the EXIF
%	   4) Build the radiance map using the stack and stack_exposure
%	   5) Save the radiance map in .hdr format
%	   6) Show the tone mapped version of the radiance map
%
%       Author: Francesco Banterle
%       Copyright 2015 (c)
%

clear all;

disp('Note: this demo does not have RAW files due to room limits in the repository');
disp('please add a folder with RAW files in the variable ''name_folder''.');
name_folder = ''; %Insert your raw stack folder
format = ''; %Insert your raw image format

disp('1) Read a stack of LDR images');
% negative number for saturation -> will be computed from the stack.
% With raw images this usually leads to artefacts in the highlights,
% so it is recommended to check the correct white level from image metadata
stack = ReadRAWStack(name_folder, format, -1);

disp('2) Read exposure values from the exif');
stack_exposure = ReadRAWStackInfo(name_folder, format);

disp('4) Build the radiance map using the stack and stack_exposure');
% Raw images are inherently linear, no need for CRF
imgHDR = BuildHDR(stack, stack_exposure, 'linear', [], 'Deb97', 'log');

disp('5) Save the radiance map in the .hdr format');
hdrimwrite(imgHDR, 'demos/output/stack_hdr_image_raw.exr');

disp('6) Show the tone mapped version of the radiance map with gamma encoding');
h = figure(2);
set(h, 'Name', 'Tone mapped version of the built HDR image');
imgTMO = GammaTMO(ReinhardTMO(imgHDR, 0.18), 2.2, 0, 1);
imwrite(imgTMO, 'demos/output/stack_hdr_image_tmo.png');


function [ons_secs, sqpar, verbose] = tapas_physio_crop_scanphysevents_to_acq_window(ons_secs, sqpar, verbose)
% cropping of ons_secs into acquired scan/presentation session, augmenting
% sqpar by scan-timing parameters from SCANPHYSLOG-file
%
% USAGE
%   function [ons_secs, sqpar] = ...
%       tapas_physio_crop_scanphysevents_to_acq_window(ons_secs, sqpar)
%
% IN
%   ons_secs    - onsets of all physlog events in seconds
%               .spulse     = onsets of slice scan acquisition
%               .cpulse     = onsets of cardiac R-wave peaks
%               .r          = time series of respiration
%               .svolpulse  = onsets of volume scan acquisition
%               .t          = time vector of logfile rows
%
%   sqpar     - sequence timing parameters
%           .Nslices        - number of slices per volume in fMRI scan
%           .NslicesPerBeat - usually equals Nslices, unless you trigger
%                             with the heart beat
%           .TR             - repetition time in seconds
%           .Ndummies       - number of dummy volumes
%           .Nscans         - number of full volumes saved (volumes in nifti file,
%                             usually rows in your design matrix)
%            onset_slice    - slice whose scan onset determines the adjustment of the
%                             regressor timing to a particular slice for the whole volume
%
% OUT
%   ons_secs    - input ons_secs cropped to acquisition window
%                 .raw - uncropped ons_secs-structure as input into this
%                 function
%   sqpar       - augmented input, also contains
%           .maxscan        - acquired volumes during session running
%           .Nvols_paradigm - acquired volumes during paradigm running
%           .meanTR         - mean repetition time (secs) over session
%
% Lars Kasper, August 2011
% Copyright (C) 2013 Institute for Biomedical Engineering, ETH/Uni Zurich.
%
% This file is part of the PhysIO toolbox, which is released under the terms of the GNU General Public
% Licence (GPL), version 3. You can redistribute it and/or modify it under the terms of the GPL
% (either version 3 or, at your option, any later version). For further details, see the file
% COPYING or <http://www.gnu.org/licenses/>.

%% parameter settings
    Nscans          = sqpar.Nscans;
    Ndummies        = sqpar.Ndummies;
    NslicesPerBeat  = sqpar.NslicesPerBeat;
    Nslices         = sqpar.Nslices;

    spulse          = ons_secs.spulse;
    svolpulse       = ons_secs.svolpulse;
    cpulse          = ons_secs.cpulse;
    c               = ons_secs.c;
    r               = ons_secs.r;
    c_is_reliable = ons_secs.c_is_reliable;
    r_is_reliable = ons_secs.r_is_reliable;
    fr              = ons_secs.fr;
    t               = ons_secs.t;
    ons_secs.raw    = ons_secs;

%% cut after end of paradigm window
maxscan = Nscans + Ndummies;
tmax    = ons_secs.spulse_per_vol{maxscan}(end);

tstart  = ons_secs.spulse_per_vol{1}(1);
tend    = ons_secs.spulse_per_vol{maxscan}(end);

spulse((maxscan*Nslices+1):end) = [];

% 1st heartbeat should be right before first scan, others cut
% last heartbeat should be right after last scan; rest cut

acqwindow   = sort([find(cpulse<=tend & cpulse>=tstart); ...
                find(cpulse<tstart,1,'last'); find(cpulse>tend,1,'first')]);

if ~isempty(cpulse), cpulse      = cpulse(acqwindow); end;

% same for respiratory signal
acqwindow   = sort([find(t<=tend & t>=tstart); ...
                find(t<tstart,1,'last'); find(t>tend,1,'first')]);

if ~isempty(r),  r      = r(acqwindow); end;
if ~isempty(r_is_reliable), r_is_reliable = r_is_reliable(acqwindow); end;
if ~isempty(c_is_reliable), c_is_reliable = c_is_reliable(acqwindow); end;
if ~isempty(fr), fr     = fr(acqwindow); end;
if ~isempty(c),  c      = c(acqwindow); end;

ons_secs.t  = t(acqwindow);

% necessary vector for t1correction, all volume excitations needed
sqpar.t        = intersect(spulse,svolpulse);
if maxscan<max(size(sqpar.t))
    sqpar.t    = sqpar.t(1:maxscan*Nslices/NslicesPerBeat); %cut what's not needed
end
sqpar.maxscan = length(sqpar.t); %counts each excitation

% Counts only real repetitions of a slice
sqpar.Nvols_paradigm   = (length(sqpar.t) - ...
    Ndummies*Nslices / NslicesPerBeat)/Nslices*NslicesPerBeat;

% Mean TR of time paradigm is running, excluding dummies
lastmin1        = min([length(svolpulse)-1,sqpar.maxscan]);
sqpar.meanTR    = mean( diff(svolpulse((sqpar.Ndummies:Nslices/NslicesPerBeat:lastmin1)+1)));

formatstr = ['    maxscan (incl. dummies) = %d \n    ', ...
    'tmin (1st scan start (1st dummy))= %6.2f s\n    ',...
    'tmin (1st scan start (after dummies))= %6.2f s\n    ', ...
    'tmax = %6.2f s \n    ', ...
    'mean TR = %6.2f s\n'];

verbose = tapas_physio_log(sprintf(formatstr, sqpar.maxscan, spulse(1), ...
    spulse(1+Ndummies*Nslices), tmax, sqpar.meanTR), verbose);

%% prepare output variable

ons_secs.c          = c;
ons_secs.r          = r;
ons_secs.c_is_reliable = c_is_reliable;
ons_secs.r_is_reliable = r_is_reliable;
ons_secs.fr         = fr;
ons_secs.spulse     = spulse;
ons_secs.cpulse     = cpulse;
ons_secs.svolpulse  = svolpulse;

end


%% demo script of getting tightly cropped faces on IJB-B test set.

% data_dir is the root directory of your IJBB data
% code_dir is the root directory of your detector code, e.g. '/mtcnn_face',
% including standard caffe, the scripts and dependent libraries supporting MTCNN
% (configuration details can be found in https://github.com/kpzhang93/MTCNN_face_detection_alignment)
% e.g. /mtcnn_face/caffe, /mtcnn_face/mtcnn, /mtcnn_face/pdollar_toolbox.
% det_tight_data_dir is the root directory for saving crops. Please create the directory "$data_dir/debug"
% in case of converting image format.

% The coordinates of target facial regions are provided by IJB-B.
% For convenience, we concatenate the files including meta information of faces
% (e.g. ijbb_1N_gallery_S1.csv, ijbb_1N_gallery_S2.csv) and adding the term "FACEID" for face indexing ([1:face_num]).

data_dir = YOUR_IJBB_ROOT_DIR;
code_dir = YOUR_CODE_DIR;
table_path = DATA_CSV_PATH;
det_tight_data_dir = YOUR_TARGET_DIR;

database = readtable(table_path);
imglist = database.FILENAME;
x = database.FACE_X;
y = database.FACE_Y;
width = database.FACE_WIDTH;
height = database.FACE_HEIGHT;
faceid = database.FACEID; %index the target faces
%path of toolbox
pdollar_toolbox_path = fullfile(code_dir, 'pdollar_toolbox';
code_path = fullfile(code_dir, 'mtcnn/code/codes/MTCNNv2');%code/codes/MTCNNv2 is the path strcture used in MTCNN's github code.
addpath(genpath(pdollar_toolbox_path));
addpath(code_path);
caffe_model_path = fullfile(code_path, '/model');
%use cpu
%caffe.set_mode_cpu();
gpu_id = 0;
caffe.set_mode_gpu();
caffe.set_device(gpu_id);

%three steps's threshold
threshold=[0.6 0.7 0.7];
%scale factor
factor=0.709;
det_tight_ratio = 0.3;
%load caffe models
prototxt_dir =fullfile(caffe_model_path,'/det1.prototxt');
model_dir = fullfile(caffe_model_path,'/det1.caffemodel');
PNet=caffe.Net(prototxt_dir, model_dir,'test');
prototxt_dir = fullfile(caffe_model_path,'/det2.prototxt');
model_dir = fullfile(caffe_model_path,'/det2.caffemodel');
RNet=caffe.Net(prototxt_dir, model_dir,'test');
prototxt_dir = fullfile(caffe_model_path,'/det3.prototxt');
model_dir = fullfile(caffe_model_path,'/det3.caffemodel');
ONet=caffe.Net(prototxt_dir, model_dir,'test');
prototxt_dir =  fullfile(caffe_model_path,'/det4.prototxt');
model_dir =  fullfile(caffe_model_path,'/det4.caffemodel');
LNet=caffe.Net(prototxt_dir, model_dir,'test');

% images detector failed on
non_match_gt = zeros(length(imglist),1);
for i = 1: length(imglist)
    i
    img_name = imglist{i};
    img_path = fullfile(data_dir, img_name);
    try
 	img = imread(img_path);
    catch
        f1 = fullfile(data_dir, ['debug/', img_name]);
        f2 = fullfile(data_dir, ['debug/1_', img_name]);
        copyfile(img_path, f1);
        system(sprintf('convert -colorspace RGB %s %s', f1, f2));
        img = imread(f2);
    end
    gt_box = [x(i), y(i), x(i) + width(i), y(i) + height(i)];
    % crop a loose candidate region
    center_gt = [x(i) + 0.5 * width(i), y(i) + 0.5 * height(i)];
    extent_gt = 1.4;
    candidate_region = img(max(1, round(center_gt(2) - height(i)* extent_gt /2)): min(size(img,1), ...
        round(center_gt(2) + height(i) * extent_gt /2)), max(1, round(center_gt(1) - width(i) * extent_gt /2)): ...
        min(size(img,2), round(center_gt(1) + width(i) * extent_gt /2)),:);
    img = candidate_region;
    %we recommend you to set minsize as x * short side
    if size(img,3) == 1
        t_img = repmat(img, 1,1,3);
        img = t_img;
    end
    minl = min([size(img,1) size(img,2)]);
    minsize = fix(minl*0.1);
    tic
    [boundingboxes points]=detect_face(img,minsize,PNet,RNet,ONet,LNet,threshold,false,factor);
    toc
    numbox=size(boundingboxes,1);
    scal_ratio_min = 112/128;
    if numbox == 0
        non_match_gt(i) = 1;
        fprintf('%d image cannot match gt\n',i);
    else
      % select the bb close to the center
      bb_center = [(boundingboxes(:,1) + boundingboxes(:,3))/2, (boundingboxes(:,2) + boundingboxes(:,4))/2];
      bb_gt_dist = abs(bb_center - repmat([size(img,2)/2, size(img,1)/2], size(bb_center,1),1));
      [min_dist, region_id]= min(sum(bb_gt_dist, 2));
      org_coord = boundingboxes(region_id, 1:2);
      center_point = [(boundingboxes(region_id,1) + boundingboxes(region_id, 3))/2, ...,
              (boundingboxes(region_id,2) + boundingboxes(region_id,4))/2];
      bb_size = boundingboxes(region_id, 3:4) - org_coord;
      scal_ratio = max(scal_ratio_min, bb_size(1)/bb_size(2));
      tight_bb_w_h = bb_size(2) * [scal_ratio, 1] * (1 + det_tight_ratio);
      org_tight_region = img(max(1, round(center_point(2) - tight_bb_w_h(2)/2)): ...
              min(size(img,1), round(center_point(2) + tight_bb_w_h(2)/2)), ...
              max(1, round(center_point(1) - tight_bb_w_h(1)/2)): ...
              min(size(img,2), round(center_point(1) + tight_bb_w_h(1)/2)),:);
      imwrite(org_tight_region, fullfile(det_tight_data_dir, [num2str(faceid(i)), '.jpg']));
    end
end

% dealing with empty files
extend_ratio = 0.1;
non_ind = find(non_match_gt);
for i = 1:numel(non_ind)
    i
    img_name = imglist{non_ind(i)};
    img_path = fullfile(data_dir, img_name);
    img = imread(img_path);
    %cropping with GT_0.1
    bb_width = width(non_ind(i)) * (1 + extend_ratio);
    bb_height = height(non_ind(i)) * (1 + extend_ratio);
    xy = [x(non_ind(i)), y(non_ind(i))];
    centrepoint = [round(xy(1) + bb_width/2) round(xy(2) + bb_height/2)];
    x1 = centrepoint(1) - round(bb_width / 2);
    y1 = centrepoint(2) - round(bb_height / 2);
    x2 = centrepoint(1) + round(bb_width / 2);
    y2 = centrepoint(2) + round(bb_height / 2);

    x1 = max(1, x1);
    y1 = max(1, y1);
    x2 = min(x2, size(img,2));
    y2 = min(y2, size(img,1));

    crop = img(y1:y2, x1:x2, :);
    imwrite(crop, fullfile(det_tight_data_dir, [num2str(faceid(non_ind(i))), '.jpg']));
end


function performBatchRFvasari_LGG(pathRF,nTrees,seed,matlabPATH)
% -------------------------------------------------------------------------
% function performBatchRFvasari_LGG(pathRF,nTrees,seed,matlabPATH)
% -------------------------------------------------------------------------
% DESCRIPTION:
% This function performs prediction estimation using out-of-bag estimates
% for random forests constructed using (VASARI) features.
% -------------------------------------------------------------------------
% INPUTS:
% 1. pathRF: Full path to the RANDOM_FORESTS results folder.
%            --> Ex: '/myProject/WORKSPACE/RANDOM_FORESTS'
% 2. nTrees: Number of decision-trees in the random forests.
%            --> Ex: 500
% 3. seed: Numerical number to use as seed for random-forest construction.
%          --> Ex: 54288
% 6. matlabPATH: Full path to the MATLAB executable on the system.
%                --> 'matlab' if a symbolic link to the matlab executable
%                     was previously created.
% -------------------------------------------------------------------------
% OUTPUTS: Final out-of-bag prediction estimation results saved in a table
% in 'pathRF'.
% -------------------------------------------------------------------------
% AUTHOR(S): Martin Vallieres <mart.vallieres@gmail.com>
% -------------------------------------------------------------------------
% HISTORY:
% - Creation: January 2017
%--------------------------------------------------------------------------
% STATEMENT:
% This file is part of <https://github.com/mvallieres/radiomics/>,
% a package providing MATLAB programming tools for radiomics analysis.
% --> Copyright (C) 2015-2017  Martin Vallieres
%
%    This package 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 package 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 package.  If not, see <http://www.gnu.org/licenses/>.
% -------------------------------------------------------------------------

startpath = pwd;

cd(pathRF), load('dataStruct')
nameOutcomes = fieldnames(dataStruct); nOutcomes = numel(nameOutcomes);

time = 30; testCost = 0.25:0.25:5;
mkdir('batchLog_RFvasari'), cd('batchLog_RFvasari'), pathBatch = pwd;

% PRODUCE BATCH COMPUTATIONS
save('workspace','nTrees','dataStruct','nameOutcomes','seed','testCost'), batch = 0;
for o = 1:nOutcomes
    batch = batch + 1;
    nameScript = ['batch',num2str(batch),'_script.m'];
    fid = fopen(nameScript,'w');
    fprintf(fid,'load(''workspace'')\n');
    fprintf(fid,['nameOutcome = nameOutcomes{',num2str(o),'};\n']);
    fprintf(fid,['nVas = dataStruct.(nameOutcome).nVas;\n']);
    fprintf(fid,['nText = dataStruct.(nameOutcome).nText;\n']);
    fprintf(fid,['X = dataStruct.(nameOutcome).data(:,1:nVas);\n']);
    fprintf(fid,['Y = dataStruct.(nameOutcome).outcome;\n']);
    fprintf(fid,['cat = dataStruct.(nameOutcome).categories(1:nVas);\n']);
    fprintf(fid,['[auc,sensitivity,specificity,accuracy] = predictionEstimationRF_LGG(X,Y,logical(cat),nTrees,seed,testCost);\n']);
    fprintf(fid,['save(''result_batch',num2str(batch),''',''auc'',''sensitivity'',''specificity'',''accuracy'')\n']);
    fprintf(fid,['system(''touch batch',num2str(batch),'_end'');\n']);
    fprintf(fid,'clear all');
    fclose(fid);
    system([matlabPATH,' -nojvm -nodisplay -nodesktop -nosplash < ',nameScript,' >& ',nameScript(1:end-1),'log &']);
end

% WAITING LOOP
waitBatch(pathBatch,time,nOutcomes)
delete('workspace.mat')

% GROUP RESULTS
cd(pathBatch)
batch = 0; AUC = zeros(nOutcomes,1); Sensitivity = zeros(nOutcomes,1); Specificity = zeros(nOutcomes,1); Accuracy = zeros(nOutcomes,1);
for o = 1:nOutcomes
    batch = batch + 1;
    load(['result_batch',num2str(batch)]) % Variable 'auc', 'sensitivity', 'specificity', 'accuracy' gets out of here.
    AUC(o) = auc; Sensitivity(o) = sensitivity; Specificity(o) = specificity; Accuracy(o) = accuracy;
    delete(['result_batch',num2str(batch),'.mat'])
    clear auc sensitivity specificity accuracy
end
tab = table(AUC,Sensitivity,Specificity,Accuracy,'RowNames',nameOutcomes);
cd(pathRF), save('tableSummary_Vasari','tab')

cd(startpath)
end


function out=mrdivide(x,y)

precisionx=0;   precisiony=0;
if isa(x,'mp')
 precisionx=x(1).precision;
end
if isa(y,'mp')
 precisiony=y(1).precision;
end
precision=max(precisionx,precisiony);
ex=numel(x);
ey=numel(y);

if ey==1
 out=x./y;
else
 error('full mrdivide hasn''t been written for mp objects yet')
end


function [meshes, fnames] = meshImportFreesurferSurfaces(subjid, hemi, surfaces)
% Import and save freesurfer surfaces in vistasoft form
% [meshes, fnames] = meshImportFreesurferSurfaces(subjid, [hemi], [surfaces], [vw])
%
% Freesurfer autosegmentation produces at least 4 meshes per subject:
% white, pial, inflated, and sphere. The white mesh is closest to what
% vistasoft BuildMesh produces, and we take this to be the base mesh,
% defining the initVertices for all meshes. We then create and one or more
% meshes for one or both hemispheres, and save them as .mat files in
% vistasoft format.
%
% Inputs:
%   subjid: Either the subjid recognized by freesurfer, or the path to the
%               freesurfer subject directory.
%   hemi:   'l', 'r', or 'b' (left, right, or both). [Default = 'b']
%   surfaces: cell array with one or more of 'white', 'pial', 'sphere',
%               'inflated'
%                [Default = {'white' 'pial' 'sphere' 'inflated'};]
%
% Outputs:
%   meshes: structured array with vistasoft compatible meshes
%   fnames: paths to the created meshes
%
% Example:
%   % Install Ernie freesurfer directory and ernie vistasession
%   forceOverwrite = false;
%   fssubjectsdir = getenv('SUBJECTS_DIR');
%   mrtInstallSampleData('anatomy/freesurfer', 'ernie', fssubjectsdir, forceOverwrite);
%   erniePRF = mrtInstallSampleData('functional', 'erniePRF', [], forceOverwrite);
%   cd(erniePRF);
%   % Create and save meshes
%   [meshes, fnames] = meshImportFreesurferSurfaces('ernie');

if notDefined('subjid'), help(mfilename); error('subjid is required'); end
if notDefined('hemi'), hemi = 'b'; end
if notDefined('surfaces'), surfaces = {'white' 'pial' 'sphere' 'inflated'}; end

% open a hidden view to determine location to store meshes
%vw = initHiddenInplane;

% Check for subject directory
if exist(subjid, 'dir')
    subjdir = subjid;
else
    fsPath = getenv('SUBJECTS_DIR');
    if ~exist(fsPath, 'dir'), error('Freesurfer subjects directory not found'); end
    subjdir = fullfile(fsPath, subjid);
end

if ~exist(subjdir, 'dir')
    error('Subject freesurfer directory %s not found', subjdir);
end

switch lower(hemi(1))
    case 'l', hs.fs = 'lh'; hs.vista = 'Left';
    case 'r', hs.fs = 'rh'; hs.vista = 'Right';
    case 'b'
        hs(1).fs = 'lh'; hs(1).vista = 'Left';
        hs(2).fs = 'rh'; hs(2).vista = 'Right';
    otherwise
        error('Hemi %s not recognized', hemi);
end

nummeshes = length(hs) * length(surfaces);
fnames = cell(1, nummeshes);

for h = 1:length(hs)

    % Read in the white mesh for this hemifield. This is the base mesh for
    % the hemifield. All other meshes will have the same initVertices, the
    % same curvature values (for coloration), and the same faces

    fsSurface  = fullfile(subjdir, 'surf', sprintf('%s.%s', hs(h).fs, 'white'));
    msh0         = fs_meshFromSurface(fsSurface);
    msh0.name    = sprintf('%s_white', hs(h).vista);

    % intialize the structured array meshes for output. This will contain
    % all the mesh structs created
    if h==1, meshes = msh0; end

    % Loop over surfaces for this hemisphere
    for ii = 1:length(surfaces)

        % Create this mesh and store it temporarily in the variable msh1
        fsSurface   = fullfile(subjdir, 'surf', sprintf('%s.%s', hs(h).fs, surfaces{ii}));
        msh1         = fs_meshFromSurface(fsSurface);
        msh1.name    = sprintf('%s_%s', hs(h).vista, surfaces{ii});

        % The mesh we will save is called msh, and combines values from the
        % white mesh (msh0) and the current mesh (msh1)
        msh = msh0;
        msh.vertices = msh1.vertices;
        msh.name = msh1.name;

        % Previously, we looked up the mesh directory stored in the mrVista
        %   view structure.
        % savepth = viewGet(vw, 'MeshDir', hs(h).vista);
        savepth = fullfile('./3DAnatomy', hs(h).vista, '3DMeshes');
        if ~exist(savepth, 'dir'), mkdir(savepth); end
        fname = fullfile(savepth, msh.name);
        save(fname, 'msh')

        % save for output
        meshnum = length(surfaces) * (h-1) + ii;
        meshes(meshnum) = msh;
        fnames{meshnum} = fname;

    end

end


function organizeExperiments_Radiomics(pathFeatures,pathLR,pathCR,outcomes,timeToEvent,nPatient,nonTextName,fSetNames,paramSEP,baselineSEP,freedomSEP,textType,textName)

startpath = pwd;

% INITIALIZATION
roi = 'GTVtot'; % Using "GTVtot" ROI for all experiments
permTrain = randperm(nPatient.HGJ + nPatient.CHUS); permTest = randperm(nPatient.HMR + nPatient.CHUM); % Random permutation of instances.
nNonText = numel(nonTextName);
nFset = numel(fSetNames);
nameOutcomes = fieldnames(outcomes.HGJ); nOutcomes = numel(nameOutcomes);
nTextType = numel(textType);

% DATA ORGANIZATION
training = struct; testing = struct;
for o = 1:nOutcomes

    % Organizing non-texture features
    cd(pathFeatures)
    train1 = load(['nonText_HGJ_',roi]); train1 = struct2cell(train1); train1 = train1{1};
    train2 = load(['nonText_CHUS_',roi]); train2 = struct2cell(train2); train2 = train2{1};
    test1 = load(['nonText_HMR_',roi]); test1 = struct2cell(test1); test1 = test1{1};
    test2 = load(['nonText_CHUM_',roi]); test2 = struct2cell(test2); test2 = test2{1};
    for i = 1:nNonText
        training.(nameOutcomes{o}).nonText.(nonTextName{i}).Data = [train1.(nonTextName{i}).Data;train2.(nonTextName{i}).Data];
        testing.(nameOutcomes{o}).nonText.(nonTextName{i}).Data = [test1.(nonTextName{i}).Data;test2.(nonTextName{i}).Data];
        training.(nameOutcomes{o}).nonText.(nonTextName{i}).Data = training.(nameOutcomes{o}).nonText.(nonTextName{i}).Data(permTrain);
        testing.(nameOutcomes{o}).nonText.(nonTextName{i}).Data = testing.(nameOutcomes{o}).nonText.(nonTextName{i}).Data(permTest);
    end

    % Organizing texture features
    for f = 1:nFset
        if strcmp(fSetNames{f},'PET') % Using only PET
            train1 = load(['text_HGJ_PT_',roi]); train1 = struct2cell(train1); train1 = train1{1};
            train2 = load(['text_CHUS_PT_',roi]); train2 = struct2cell(train2); train2 = train2{1};
            test1 = load(['text_HMR_PT_',roi]); test1 = struct2cell(test1); test1 = test1{1};
            test2 = load(['text_CHUM_PT_',roi]); test2 = struct2cell(test2); test2 = test2{1};
            train1 = {train1}; train2 = {train2};
            test1 = {test1}; test2 = {test2};
            training.(nameOutcomes{o}).text.(fSetNames{f}).param = paramSEP;
            training.(nameOutcomes{o}).text.(fSetNames{f}).baseline = baselineSEP;
            training.(nameOutcomes{o}).text.(fSetNames{f}).freedom = freedomSEP;
            training.(nameOutcomes{o}).text.(fSetNames{f}).paramName = {'Scale','Quant.algo','Ng'};
            textCellName = {'PT'};
        elseif strcmp(fSetNames{f},'CT') % Using only CT
            train1 = load(['text_HGJ_CT_',roi]); train1 = struct2cell(train1); train1 = train1{1};
            train2 = load(['text_CHUS_CT_',roi]); train2 = struct2cell(train2); train2 = train2{1};
            test1 = load(['text_HMR_CT_',roi]); test1 = struct2cell(test1); test1 = test1{1};
            test2 = load(['text_CHUM_CT_',roi]); test2 = struct2cell(test2); test2 = test2{1};
            train1 = {train1}; train2 = {train2};
            test1 = {test1}; test2 = {test2};
            training.(nameOutcomes{o}).text.(fSetNames{f}).param = paramSEP;
            training.(nameOutcomes{o}).text.(fSetNames{f}).baseline = baselineSEP;
            training.(nameOutcomes{o}).text.(fSetNames{f}).freedom = freedomSEP;
            training.(nameOutcomes{o}).text.(fSetNames{f}).paramName = {'Scale','Quant.algo','Ng'};
            textCellName = {'CT'};
        elseif strcmp(fSetNames{f},'PETCT') % Using both PET and CT
            train1_1 = load(['text_HGJ_PT_',roi]); train1_1 = struct2cell(train1_1); train1_1 = train1_1{1};
            train1_2 = load(['text_HGJ_CT_',roi]); train1_2 = struct2cell(train1_2); train1_2 = train1_2{1};
            train2_1 = load(['text_CHUS_PT_',roi]); train2_1 = struct2cell(train2_1); train2_1 = train2_1{1};
            train2_2 = load(['text_CHUS_CT_',roi]); train2_2 = struct2cell(train2_2); train2_2 = train2_2{1};
            test1_1 = load(['text_HMR_PT_',roi]); test1_1 = struct2cell(test1_1); test1_1 = test1_1{1};
            test1_2 = load(['text_HMR_CT_',roi]); test1_2 = struct2cell(test1_2); test1_2 = test1_2{1};
            test2_1 = load(['text_CHUM_PT_',roi]); test2_1 = struct2cell(test2_1); test2_1 = test2_1{1};
            test2_2 = load(['text_CHUM_CT_',roi]); test2_2 = struct2cell(test2_2); test2_2 = test2_2{1};
            train1 = {train1_1,train1_2}; train2 = {train2_1,train2_2};
            test1 = {test1_1,test1_2}; test2 = {test2_1,test2_2};
            training.(nameOutcomes{o}).text.(fSetNames{f}).param = paramSEP;
            training.(nameOutcomes{o}).text.(fSetNames{f}).baseline = baselineSEP;
            training.(nameOutcomes{o}).text.(fSetNames{f}).freedom = freedomSEP;
            training.(nameOutcomes{o}).text.(fSetNames{f}).paramName = {'Scale','Quant.algo','Ng'};
            textCellName = {'PT','CT'};
        end

        for i = 1:numel(train1)
            sizeParam = size(train1{1});
            training.(nameOutcomes{o}).text.(fSetNames{f}).(textCellName{i}) = cell(sizeParam);
            testing.(nameOutcomes{o}).text.(fSetNames{f}).(textCellName{i}) = cell(sizeParam);
            for n = 1:numel(train1{1})
                for type = 1:nTextType
                    for text = 1:numel(textName{type})
                        training.(nameOutcomes{o}).text.(fSetNames{f}).(textCellName{i}){n}.(textType{type}).(textName{type}{text}).Data = [train1{i}{n}.(textType{type}).(textName{type}{text}).Data;train2{i}{n}.(textType{type}).(textName{type}{text}).Data];
                        testing.(nameOutcomes{o}).text.(fSetNames{f}).(textCellName{i}){n}.(textType{type}).(textName{type}{text}).Data = [test1{i}{n}.(textType{type}).(textName{type}{text}).Data;test2{i}{n}.(textType{type}).(textName{type}{text}).Data];
                        training.(nameOutcomes{o}).text.(fSetNames{f}).(textCellName{i}){n}.(textType{type}).(textName{type}{text}).Data = training.(nameOutcomes{o}).text.(fSetNames{f}).(textCellName{i}){n}.(textType{type}).(textName{type}{text}).Data(permTrain);
                        testing.(nameOutcomes{o}).text.(fSetNames{f}).(textCellName{i}){n}.(textType{type}).(textName{type}{text}).Data = testing.(nameOutcomes{o}).text.(fSetNames{f}).(textCellName{i}){n}.(textType{type}).(textName{type}{text}).Data(permTest);
                    end
                end
            end
        end
    end
end

% Organizing outcomes
for o = 1:nOutcomes
    training.(nameOutcomes{o}).outcome = [outcomes.HGJ.(nameOutcomes{o});outcomes.CHUS.(nameOutcomes{o})];
    testing.(nameOutcomes{o}).outcome = [outcomes.HMR.(nameOutcomes{o});outcomes.CHUM.(nameOutcomes{o})];
    training.(nameOutcomes{o}).timeToEvent = [timeToEvent.HGJ.(nameOutcomes{o});timeToEvent.CHUS.(nameOutcomes{o})];
    testing.(nameOutcomes{o}).timeToEvent = [timeToEvent.HMR.(nameOutcomes{o});timeToEvent.CHUM.(nameOutcomes{o})];
    training.(nameOutcomes{o}).outcome = training.(nameOutcomes{o}).outcome(permTrain);
    testing.(nameOutcomes{o}).outcome = testing.(nameOutcomes{o}).outcome(permTest);
    training.(nameOutcomes{o}).timeToEvent = training.(nameOutcomes{o}).timeToEvent(permTrain);
    testing.(nameOutcomes{o}).timeToEvent = testing.(nameOutcomes{o}).timeToEvent(permTest);
end

cd(pathLR), save('training','training'), save('testing','testing'), save('permTrain','permTrain'), save('permTest','permTest')
cd(pathCR), save('training','training'), save('testing','testing'), save('permTrain','permTrain'), save('permTest','permTest')

cd(startpath)
end