function test_bug1646

% MEM 6gb
% WALLTIME 00:15:00
% DEPENDENCY ft_prepare_mesh ft_datatype_segmentation

% the purpose of this test script is to ensure that the new implementation
% of ft_prepare_mesh, which is a merger between the old ft_prepare_mesh and
% ft_prepare_mesh_new, works as expected on all normal cases. What I have
% done is to search for as many as possible other tets cases and replicated
% them here.

% http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=105 is obsolete
% http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=115 is obsolete
% http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=964 is obsolete

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% it is not supposed to work with a 3-D array as input
try
  cfg = [];
  bnd = ft_prepare_mesh(cfg, randn(30,30,30));
  status = false;
catch
  status = true;
end
if ~status
  error('invalid input was not dealt with')
end

% it is supposed to wotk with a headmodel as input
headmodel = [];
headmodel.r = 12;
headmodel.o = [0 0 4];
headmodel.unit = 'cm';

cfg = [];
cfg.numvertices = 100;
bnd = ft_prepare_mesh(cfg, headmodel);

cfg = [];
bnd = ft_prepare_mesh(cfg, headmodel);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% the following piece of test code relates loosely to
% http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=222
% http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=373
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

cfg = [];
cfg.headshape = dccnpath('/home/common/matlab/fieldtrip/data/ftp/test/ctf/Subject01.shape');
bnd = ft_prepare_mesh(cfg);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% this is the same test data as used for bug 1652
% http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=1652
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

datadir = fullfile(dccnpath('/home/common/matlab/fieldtrip/data/test/bug1646'));

load(fullfile(datadir,'seg1'));
load(fullfile(datadir,'seg2'));
load(fullfile(datadir,'seg3'));
load(fullfile(datadir,'seg4'));
load(fullfile(datadir,'seg5'));
load(fullfile(datadir,'seg6'));

atlas = ft_read_atlas(dccnpath('/home/common/matlab/fieldtrip/template/atlas/afni/TTatlas+tlrc.BRIK'));
mri = ft_read_mri(dccnpath('/home/common/matlab/fieldtrip/data/ftp/tutorial/beamformer/Subject01.mri'));

assert(ft_datatype(seg1, 'segmentation'));
assert(ft_datatype(seg2, 'segmentation'));
assert(ft_datatype(seg3, 'segmentation'));
assert(ft_datatype(seg4, 'segmentation'));
assert(ft_datatype(seg5, 'segmentation'));
assert(ft_datatype(seg6, 'segmentation'));
assert(ft_datatype(atlas, 'segmentation'));
assert(ft_datatype(mri, 'volume'));

cfg = [];
cfg.numvertices = 642;
cfg.tissue = 'brain';
bnd = ft_prepare_mesh(cfg, seg1);
assert(isequal(bnd(1).unit, seg1.unit));

% this is a weird case, because seg2 is not indexed but tpm
% the output depends on the order of the cfg.tissue
cfg = [];
cfg.numvertices = [1000, 100, 10];
cfg.tissue = [1 2 3];
bndA = ft_prepare_mesh(cfg, seg2);
cfg.numvertices = [10, 100, 1000];
cfg.tissue = [3 2 1];
bndB = ft_prepare_mesh(cfg, seg2);
assert(isequal(bndA(1), bndB(3)));

cfg = [];
cfg.numvertices = [1000, 100, 10];
cfg.tissue = {'brain', 'skull', 'scalp'};
bndA = ft_prepare_mesh(cfg, seg2);
assert(isequal(bndA(1).unit, seg2.unit));
assert(size(bndA(1).pos,1)==1000);
assert(size(bndA(2).pos,1)==100);
assert(size(bndA(3).pos,1)==10);
% check that the output has the desired order (as specified in the cfg)
cfg.numvertices = [10, 100, 1000];
cfg.tissue = {'scalp', 'skull', 'brain'};
bndB = ft_prepare_mesh(cfg, seg2);
assert(isequal(bndA(1), bndB(3)));

cfg = [];
cfg.numvertices = [1000, 100, 10];
cfg.tissue = {'brain', 'skull', 'scalp'};
bnd = ft_prepare_mesh(cfg, seg3);
assert(isequal(bnd(1).unit, seg3.unit));
assert(size(bnd(1).pos,1)==1000);
assert(size(bnd(2).pos,1)==100);
assert(size(bnd(3).pos,1)==10);

cfg = [];
cfg.numvertices = [1000, 100, 10];
cfg.tissue = {'brain', 'skull', 'scalp'};
bnd = ft_prepare_mesh(cfg, seg4);
assert(isequal(bnd(1).unit, seg4.unit));
assert(size(bnd(1).pos,1)==1000);
assert(size(bnd(2).pos,1)==100);
assert(size(bnd(3).pos,1)==10);

cfg = [];
cfg.numvertices = [1000, 100, 10];
cfg.tissue = [3 2 1];
bnd = ft_prepare_mesh(cfg, seg5);
assert(isequal(bnd(1).unit, seg5.unit));
assert(size(bnd(1).pos,1)==1000);
assert(size(bnd(2).pos,1)==100);
assert(size(bnd(3).pos,1)==10);

cfg = [];
cfg.numvertices = 642;
cfg.tissue = 'brain';
bnd = ft_prepare_mesh(cfg, seg6);
assert(isequal(bnd(1).unit, seg6.unit));

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% addressing further issues raised in bug1646, on cfg.tissue
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% If the input is an indexed representation and  cfg.tissue is equal to
% setdiff(unique(mri.seg(:)),0) then there is no problem.

% seg5 is indexed segmentation
cfg = [];
cfg.tissue = setdiff(unique(seg5.seg(:)),0);
cfg.numvertices = 1000;
bnd = ft_prepare_mesh(cfg, seg5);

% "If the input is an indexed representation and cfg.tissue is empty, then
% cfg.tissue should be set to unique(mri.seg(:))"

cfg = [];
cfg.numvertices = [1000 1000 1000];
cfg.tissue = [];
bnd = ft_prepare_mesh(cfg, seg5);

cfg = [];
cfg.numvertices = [1000 1000 1000];
bnd = ft_prepare_mesh(cfg, seg5);

cfg = [];
cfg.numvertices = [1000]; % should still be converted to vector Nx3 since seg5 has 3 indices
cfg.tissue = [];
bnd = ft_prepare_mesh(cfg, seg5);

% "If the input is a tpm representation then cfg.tissue should be a string
% pointing to the field."
% Both seg1 and seg2 are tpm, but only seg2 has 'brain'.

cfg = [];
cfg.numvertices = [800];
cfg.tissue = 'brain'; %this is exception in that .brain DNE in seg1
bnd = ft_prepare_mesh(cfg, seg1);

try
  cfg = [];
  cfg.numvertices = [800];
  cfg.tissue = 'randomfieldname';
  bnd = ft_prepare_mesh(cfg, seg1);
  success = true;
catch
  success = false;
end
if success
  error('randomfieldname should not work for tpm type')
end

try
  cfg = [];
  cfg.numvertices = [800];
  cfg.tissue = 'csf'; %this is exception in that .brain DNE in seg1
  bnd = ft_prepare_mesh(cfg, seg2);
  success = true;
catch
  success = false;
end
if success
  error('randomfieldname should not work for tpm type')
end

cfg = [];
cfg.numvertices = [800];
cfg.tissue = 1;
bnd = ft_prepare_mesh(cfg, seg1);

cfg = [];
cfg.numvertices = [800];
cfg.tissue = 1;
bnd = ft_prepare_mesh(cfg, seg2);

try
  cfg = [];
  cfg.numvertices = [800];
  cfg.tissue = 4;
  bnd = ft_prepare_mesh(cfg, seg2);
  success = true;
catch
  success = false;
end
if success
  error('too large of index for cfg.tissue should not work for tpm type')
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% this shares the test data with bug 1651
% http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=1651
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

clear all
load(dccnpath('/home/common/matlab/fieldtrip/data/test/bug1651.mat'));

cfg = [];
cfg.tissue = {'brain', 'skull', 'scalp'};
cfg.numvertices = [1000 2000 3000];
bnd = ft_prepare_mesh(cfg, seg2);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=997
% this one does not state an explicit way of reproducing the problems, it rather refers to 937
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=937
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

load(dccnpath('/home/common/matlab/fieldtrip/data/test/bug937.mat'));

mri = [];
mri.anatomy = bkgrnd;
mri.sphere1 = bkgrnd==1;
mri.sphere2 = bkgrnd==2;
mri.sphere3 = bkgrnd==3;
mri.transform = eye(4);
mri.dim = size(bkgrnd);

mri.anatomy = mri.anatomy>0;

cfg = [];
cfg.tissue = {'sphere1' 'sphere2' 'sphere3'};
cfg.numvertices = 1000;
bnd = ft_prepare_mesh(cfg, mri);


function output = ctranspose(input)

output = cellfun(@ctranspose, input, 'uniformoutput', false);


function gp = gplite_clean(gp)
%GPLITE_CLEAN Remove auxiliary info from lite GP struct (less memory usage).
%   GP = GPLITE_CLEAN(GP) removes auxiliary computational structs from
%   the GP. These can be reconstructed via a call to GPLITE_POST.
%
%   See also GPLITE_POST.

if ~isempty(gp) && isfield(gp,'post')
    copyfields = {'hyp'};
    emptyfields = {'alpha','sW','L','sn2_mult','Lchol'};
    checkfields = {'intmean'};
    for ff = copyfields; post0.(ff{:}) = []; end
    for ff = emptyfields; post0.(ff{:}) = []; end
    for ff = checkfields
        if isfield(gp.post(1),ff{:}); post0.(ff{:}) = []; end
    end

    for iG = 1:numel(gp)
        Ns = numel(gp(iG).post);
        postnew = post0;
        for iS = 1:Ns
            post_tmp = post0;
            for ff = copyfields
                post_tmp.(ff{:}) = gp(iG).post(iS).(ff{:});
            end
            postnew(iS) = post_tmp;
        end
        gp(iG).post = postnew;
    end
end


function L = addContinuity(L, varargin)
%ADDCONSTRAINT Append to linop constraints.
%   L = ADDCONTINUITY(L,FUN,VAL) adds a new constraint on the linop L. The
%   functional FUN when applied to a function will be required to be VAL.
%
% See also LINOP.ADDCONSTRAINT, LINOPCONSTRAINT.

%  Copyright 2017 by The University of Oxford and The Chebfun Developers.
%  See http://www.chebfun.org/ for Chebfun information.

% We don't really care about the content. But because the continuity property is
% modified, other linop calls will not apply automatically derived continuity
% conditions.

% Append the input constraint to the LINOPCONSTRAINT currently stored in the
% input linop.
L.continuity = append(L.continuity, varargin{:});

% We may have to update the domain, if a new breakpoint was introduced.
n1 = length(L.domain);
F = L.continuity.functional;
n2 = length(F.domain);
if ( n1 ~= n2 )
    L.domain = domain.merge(L.domain,F.domain);
end

end


function [ad] = PL2AdSpanBySource(filename, source, channelInSource, startCount, endCount)
% PL2AdSpanBySource(filename, source, channelInSource, startCount, endCount): read a span of a/d data from a .pl2 file
%                returns a/d values in millivolts
%
% examples:
%
% ad = PL2AdSpanBySource(filename, source, channelInSource, startCount, endCount)
% ad = PL2AdSpanBySource(filename, 'FP', 1, 10000)
% ad = PL2AdSpanBySource(filename, 8, 1, 1000, 2000)
%
% INPUT:
%   filename - if empty string, will use File Open dialog
%   source - source name or source numeric id
%   channel -  1-based channel number within source
%           To print the list of analog channels, use these commands:
%
%           pl2 = PL2GetFileIndex(filename);
%           PL2Print(pl2.AnalogChannels);
%
%   startCount - index of first sample to fetch
%   endCount - index of last sample to fetch
%
% OUTPUT:
%   ad.Values - array of a/d values in millivolts
%   ad.ADFreq - digitization frequency for this channel (samples per second)

ad.Values = [];
ad.ADFreq = 0;

if nargin ~= 5
    error 'expected 5 input arguments';
end

filename = internalPL2ResolveFilename(filename);
pl2 = PL2GetFileIndex(filename);
channelNumber = internalPL2ResolveChannelBySource(pl2.AnalogChannels, source, channelInSource);
ad = Pl2AdSpan(filename, channelNumber, startCount, endCount);

end


% crop_fmf(infmf,outfmf,startframe,endframe)
function crop_fmf(infmf,outfmf,startframe,endframe)

[header_size,version,nr,nc,bytes_per_chunk,nframes,data_format] = fmf_read_header(infmf);

if startframe < 1 || startframe > nframes || endframe < 0 || endframe > nframes || startframe > endframe,
    error('startframe or endframe is out of range.');
end

fin = fopen(infmf,'r');
%fout = fopen(outfmf,'w');
%buf = fread(fin,header_size,'uint8');
%fwrite(fout,buf);

fout = fmf_write_header(outfmf,header_size,version,nr,nc,bytes_per_chunk,endframe-startframe+1,data_format);

fseek(fin,header_size+(startframe-1)*bytes_per_chunk,'bof');
for f = startframe:endframe,
   if mod(f-startframe+1,100) == 0,
     % fprintf('Copying frame %d in range %d to %d\n',f,startframe,endframe);
   end
   buf = fread(fin,bytes_per_chunk,'uint8');
   fwrite(fout,buf);
end

fclose(fin);
fclose(fout);


function elec_write_3dspace(p)

% elec_write_3dspace - Write electrode coordinate file (ascii).
%
% Useage: elec_write_3dspace(p)
%
% Write out the electrode labels, type, and Cartesian (x,y,z)
% coordinates in the format of Neuroscan 3Dspace ascii files.
%
% Each row of the file comprises an electrode label, an
% electrode type code (see below), and the x,y,z
% coordinates (cm). Each field is separated by spaces.
%

% $Revision: 1.1 $ $Date: 2009-04-28 22:13:55 $

% Licence:  GNU GPL, no express or implied warranties
% History:  10/2002, Darren.Weber_at_radiology.ucsf.edu
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

fprintf('\nELEC_WRITE_3DSPACE...\n'); tic;

if ~exist('p', 'var'),
    msg = sprintf('...no input p struct.\n\n');
    error(msg);
end

[path,name,ext] = fileparts(strcat(p.elec.path,filesep,p.elec.file));
ext = '.dat';
file = fullfile(path,[name ext]);

fprintf('...writing electrode data to:\n\t%s\n', file);

write_elec(file,p.elec.data);

t=toc; fprintf('...done (%5.2f sec).\n\n',t);

return

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function write_elec(file,elec);

fid = fopen(file,'w','ieee-le');

if(fid == -1),
    fprintf('...could not open file:\n\t%s',file);
    return;
else

    % Write out the electrode coordinates

    fprintf('...converting from meters to cm.\n');
    % Convert to Neuroscan 3Dspace coordinate
    % metric (cm) from meters.

    nasion = elec.nasion .* 100;
    rpa    = elec.rpa .* 100;
    lpa    = elec.lpa .* 100;

    x = elec.x .* 100;
    y = elec.y .* 100;
    z = elec.z .* 100;

    ref = elec.ref .* 100;

    centroid = elec.centroid .* 100;

    fprintf('...writing output coordinates.\n');

    fprintf(fid,'%+10s\t%3d\t%+12.6f\t%+12.6f\t%+12.6f\n','Nasion',  110, nasion);
    fprintf(fid,'%+10s\t%3d\t%+12.6f\t%+12.6f\t%+12.6f\n','Left',    108, lpa);
    fprintf(fid,'%+10s\t%3d\t%+12.6f\t%+12.6f\t%+12.6f\n','Right',   114, rpa);

    Nelec = size(x,1);
    type = 69;
    for e = 1:Nelec,
        fprintf(fid,'%+10s\t%3d\t%+12.6f\t%+12.6f\t%+12.6f\n',char(elec.label(e)), type, x(e), y(e), z(e));
    end

    fprintf(fid,'%+10s\t%3d\t%+12.6f\t%+12.6f\t%+12.6f\n','Centroid', 99, centroid(1),centroid(2),centroid(3));
    fprintf(fid,'%+10s\t%3d\t%+12.6f\t%+12.6f\t%+12.6f\n','Ref',     120, ref(1),ref(2),ref(3));

    fclose(fid);

end

return


function res = nconditions(this)
% Method for getting the number of unique conditions in the file
% FORMAT res = nconditions(obj)
% _______________________________________________________________________
% Copyright (C) 2008-2012 Wellcome Trust Centre for Neuroimaging

% Vladimir Litvak
% $Id: nconditions.m 5025 2012-10-31 14:44:13Z vladimir $

res = numel(condlist(this));


%CALCOPTICALFLOWSPARSETODENSE  Fast dense optical flow based on PyrLK sparse matches interpolation
%
%     flow = cv.calcOpticalFlowSparseToDense(from, to)
%     flow = cv.calcOpticalFlowSparseToDense(from, to, 'OptionName',optionValue, ...)
%
% ## Input
% * __from__ first 8-bit 3-channel or 1-channel image.
% * __to__ second 8-bit 3-channel or 1-channel image of the same size as
%   `from`.
%
% ## Output
% * __flow__ computed flow image that has the same size as `from` and type
%   `single` (2-channels). Flow for `(x,y)` is stored in the third dimension.
%
% ## Options
% * __GridStep__ stride used in sparse match computation. Lower values usually
%   result in higher quality but slow down the algorithm. default 8
% * __K__ number of nearest-neighbor matches considered, when fitting a
%   locally affine model. Lower values can make the algorithm noticeably
%   faster at the cost of some quality degradation. default 128
% * __Sigma__ parameter defining how fast the weights decrease in the
%   locally-weighted affine fitting. Higher values can help preserve fine
%   details, lower values can help to get rid of the noise in the output flow.
%   default 0.05
% * __UsePostProcessing__ defines whether the
%   cv.FastGlobalSmootherFilter.fastGlobalSmootherFilter is used for
%   post-processing after interpolation. default true
% * __FGSLambda__ see the respective parameter of
%   cv.FastGlobalSmootherFilter.fastGlobalSmootherFilter, default 500.0
% * __FGSSigma__ see the respective parameter of
%   cv.FastGlobalSmootherFilter.fastGlobalSmootherFilter, default 1.5
%
% See also: cv.calcOpticalFlowPyrLK, cv.SparsePyrLKOpticalFlow
%


function out = isDiag(~)
%ISDIAG   Returns 1 for a SPINOP3.
%
% See also SPINOP3.

% Copyright 2017 by The University of Oxford and The Chebfun Developers.
% See http://www.chebfun.org/ for Chebfun information.

out = 1;

end


function out = double(fa)
% Convert to double precision
% FORMAT double(fa)
% fa - a file_array
% _______________________________________________________________________
% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging

%
% $Id: double.m 1143 2008-02-07 19:33:33Z spm $

out = double(numeric(fa));


function [rifftype,msg,msgID] = readfourcc(fid)
%READFOURCC read four character code
%   [RIFFTYPE,MSG] = READFOURCC(FID) returns a four character code RIFFTYPE
%   from the file represented by FID. If the desired amount of data was not
%   read, then MSG is a string containing an error message, otherwise MSG is
%   empty.

%   Copyright 1984-2007 The MathWorks, Inc.
%   $Revision: 1.1.6.6 $  $Date: 2011/07/20 00:00:23 $

msg = '';
msgID='';
[rifftype, count] = fread(fid,4,'uchar');
rifftype = [char(rifftype)]';
if (count ~= 4)
  msgID='MATLAB:audiovideo:fourcc:invalidFourCC';
  msg = getString(message(msgID));
end
return;


function out = fatrix2_omask_array(st)
if isempty(st.omask)
	out = true([st.odim 1]);
else
	out = st.omask;
end


function autoShowClassMap(varargin)
% This function is used to provde all land classification maps for each year
% Results for LCMAP
% Version 1.2  Acquire map from annual classfication map (04/09/2015)
% Version 1.1  Add disturbance in the annual map (04/01/2015);
% Version 1.0  No disturbance class in the cover map (11/06/2015)
% Tools
%
%
% Specific parameters
% ------------------------
%   'CCDCDir'     Directory of input data.  Default is the path to
%                        the current folder.
%   'StartYear'          Start year.
%   'EndYear'            End year.
%   'Year'               Year. Only a certain date's map will be outputted
%                        when inputing this.
%   'Month'              7
%   'Day'                1
%
%
% Examples:
% ------------------------
% autoShowClassMap('StartYear',2010,'EndYear',2015)
% will give the classifiaction maps at July 1 between 2010 and 2015.
%
% autoShowClassMap('StartYear',2000,'EndYear',2015,'Month',12,'Day',1)
% will give the classifiaction maps at December 1 between 2010 and 2015.
%
% autoShowClassMap('Year',2000,'Month',7,'Day',1)
% will give the classifiaction map at July 1, 2000.
%
   %% get parameters from inputs
    % where the all Landsat zipped files are
    dir_cur = pwd;
    p = inputParser;
    p.FunctionName = 'paras';

    addParameter(p,'CCDCDir',dir_cur);

    % default date is 1th, July.
    addParameter(p,'StartYear',0);
    addParameter(p,'EndYear',0);
    addParameter(p,'Year',0);
    addParameter(p,'Month',7);
    addParameter(p,'Day',1);

    % request user's input
    parse(p,varargin{:});
    dir_cur=p.Results.CCDCDir;

    start_year=p.Results.StartYear;
    end_year=p.Results.EndYear;
    oneyear=p.Results.Year;

    mm=p.Results.Month;
    dd=p.Results.Day;

    % give certain year
    if oneyear>0
        start_year = oneyear;
        end_year = oneyear;
    end

    % optional
%     addpath('~/ccdc');

    cd(dir_cur);

    % INPUTS:
    all_yrs = start_year:end_year; % all of years for producing maps
    v_input = ccdc_Inputs;

    % dimension and projection of the image
    nrows = v_input.ijdim(1);
    ncols = v_input.ijdim(2);
    jiDim = [ncols,nrows];
    jiUL = v_input.jiul;
    res = v_input.resolu;
    zc = v_input.zc;
    % number of coefficients
    num_c = v_input.num_c;
    % number of bands
    nbands = v_input.nbands;
    % max number of maps
    max_n = length(all_yrs);
    % all julidan dates
%     mm = 7;
%     dd = 1;
    jul_d = datenummx(all_yrs,mm,dd);
    jul_start = datenummx(all_yrs,1,1);
    jul_end = datenummx(all_yrs,12,31);

    % produce land cover map
    CoverMap = 255*ones(nrows,ncols,max_n,'uint8'); % Trends categories (0~11)
    % produce land cover map QA (unsupervised emsemble margin)
    CoverQAMap = 255*ones(nrows,ncols,max_n,'uint8'); % Trends categories (0~100)

    % make Predict folder for storing predict images
    n_map=v_input.name_map;% 'CCDCMap';
    if isempty(dir(n_map))
        mkdir(n_map);
    end

    % cd to the folder for storing recored structure
    cd(v_input.name_rst);

    imf = dir(fullfile(pwd,'record_change*')); % folder names
    num_line = size(imf,1);
    line_pt = 0;
    %%
    for line = 1:num_line
        if 100*(line/num_line) - line_pt > 1
            fprintf('Processing %.0f percent\n',ceil(100*(line/num_line)));
            line_pt = 100*(line/num_line);
        end

        % load one line of time series models
        load(fullfile(pwd,imf(line).name));

        % postions
        pos = [rec_cg.pos];

        % continue if there is no model available
        l_pos = length(pos);
        if l_pos == 0
            continue
        end

        % matrix of each component
        % start time
        t_start = [rec_cg.t_start];
        % end time
        t_end = [rec_cg.t_end];
        % break time
        t_break = [rec_cg.t_break];
        % class
        class = [rec_cg.class];
        % classification QA
        class_qa = [rec_cg.classQA];

% %         % put disturbed class to grass/shrub
% %         class(class == 10) = 7;

        % For Condition & Cover Map
        for i = 1:l_pos
            % get row and col
            [I,J] = ind2sub(jiDim,pos(i));

            % initialize pixels have at least one model
            if sum(CoverMap(J,I,:) == 255) == max_n
                % write land cover to CoverMap
                CoverMap(J,I,:) = 0;
                % write land cover to CoverQAMap
                CoverQAMap(J,I,:) = 0;
            end

            % give disturbed class for each year
            % year (band) the curve belongs to
            n_band = jul_d >= t_start(i) & (jul_d <= t_end(i) | jul_d < t_break(i));
            % write land cover to CoverMap
            CoverMap(J,I,n_band) = class(i);
            % write land cover to CoverQAMap
            CoverQAMap(J,I,n_band) = class_qa(i);

            % give next land cover category for gaps
            if i > 1
                if pos(i) == pos(i-1) % same location
                    n_dist = jul_d < t_start(i) & jul_d >= t_break(i-1);
                    % find the first nonzero value from class(:,i);
                    idn = find(class(:,i)>0);
                    if ~isempty(idn)
                        % write land cover (distubed) to CoverMap
                        CoverMap(J,I,n_dist) = class(idn(1),i);
                    end
                end
            end
        end
    end
    cd ..

    enviwrite_bands(fullfile(v_input.l_dir,n_map,'CoverMap'),CoverMap,'uint8',res,jiUL,'bsq',zc,all_yrs);
    clear CoverMap;
    enviwrite_bands(fullfile(v_input.l_dir,n_map,'CoverQAMap'),CoverQAMap,'uint8',res,jiUL,'bsq',zc,all_yrs);
    clear CoverQAMap;

% %     % write ENVI files for ARD
% %     % Cover Map
% %     ARD_enviwrite_bands([v_input.l_dir,'/',n_map,'/CoverMap'],CoverMap,'uint8','bsq',all_yrs);
% %     clear CoverMap
% %     ARD_enviwrite_bands([v_input.l_dir,'/',n_map,'/CoverQAMap'],CoverQAMap,'uint8','bsq',all_yrs);
% %     clear CoverQAMap

end
% %% Add change event
% change_map = double(enviread('CCDCMap/ChangeMap'));
% % minimum mapping unit
% num_obj = 5;
%
% % spatial filtering
% for i = 1:size(change_map,3)
%     i
%     tmp = change_map(:,:,i);
%     tmp(tmp > 0) = 1;
%     segm_tmp=bwlabeln(tmp,8);
%     L = segm_tmp;
%     s = regionprops(L,'area');
%     area = [s.Area];
%
%     % filter out cloud object < than num_cldoj pixels
%     idx = find(area >= num_obj);
%     tmp(ismember(L,idx)==0) = 0;
%     copy_map = change_map(:,:,i);
%     copy_map(tmp==0) = 0;
%     change_map(:,:,i) = copy_map;
% end
% % write filtered change map
% ARD_enviwrite_bands([v_input.l_dir,'/',n_map,'/FChangeMap'],change_map,'uint16','bsq',all_yrs,'example_img');
%
% %% change analysis
% max_n = 30;
% map = double(enviread('CCDCMap/CoverMap1_4'));
% change_stat = [];
%
% for i = 1:max_n-1
%     i
%     change_map = map(:,:,i)*100 + map(:,:,i+1);
%     idstb = map(:,:,i+1)-map(:,:,i);
%     change_stat = [change_stat;change_map(idstb~=0)];
% end
%
% % update class number
% all_class = unique(change_stat);
% % update number of class
% n_class = length(all_class);
% % calculate proportion based # for each class
% number = hist(change_stat,all_class);
% prct = number/sum(number);

% %% add disturbance (3) to the classification map
% for i = 1:max_n
%     change_tmp = change_map(:,:,i);
%     class_tmp = cover_map(:,:,i);
%     class_tmp(change_tmp>0) = 3;
%     cover_map(:,:,i) = class_tmp;
% end


function test_suite=test_meeg_source()
% tests for MEEG datasets in source space
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #
    try % assignment of 'localfunctions' is necessary in Matlab >= 2016
        test_functions=localfunctions();
    catch % no problem; early Matlab versions can use initTestSuite fine
    end
    initTestSuite;

function test_meeg_dataset()
    tps={'freq_pow','time_mom','rpt_trial_mom','rpt_trial_mom_1d',...
                        'time_mom_1d',...
                        'rpt_trial_pow','strange_ft_lcmv_pow'};
    for j=1:numel(tps)
        is_ft_strange_lcmv_avg_pow=isequal(cosmo_strsplit(tps{j},'_',1),...
                                                'strange');

        [ft,fdim,data_label]=generate_ft_source(tps{j});
        ds=cosmo_meeg_dataset(ft);

        % check fdim
        assertEqual(ds.a.fdim,fdim);

        key=data_label{1};
        sub_key=data_label{2};

        ft_first_sample_all=ft.(key).(sub_key);

        if is_ft_strange_lcmv_avg_pow
            ft_first_sample=ft_first_sample_all(:,ft.inside);
        else
            ft_first_sample=ft_first_sample_all(ft.inside,:);
        end

        switch sub_key
            case 'mom'
                % choose a random sensor
                inside_idxs=find(ft.inside);
                pos=ceil(rand()*numel(inside_idxs));
                inside_idx=inside_idxs(pos);
                assertEqual(ds.samples(1,ds.fa.pos==inside_idx),...
                                        ft_first_sample{pos}(:)')

            otherwise
                assertEqual(ds.samples(1,:),ft_first_sample(:)');
        end

        [ft_arr, ft_labels, ft_values]=cosmo_unflatten(ds,2,...
                                            'matrix_labels','pos');

        if is_ft_strange_lcmv_avg_pow
            ft_arr=ft_arr';
        end
        assertEqual(ft_arr(ft_arr~=0),ds.samples(:));
        assertEqual(ft_labels, fdim.labels);
        assertEqual(ft_values, fdim.values);

        % select single element, and ensure it is the same in the
        % fieldtrip struct as in the dataset struct
        dim_sizes=cellfun(@(x)size(x,2),fdim.values);
        ndim=numel(dim_sizes);
        rp=ceil(rand(1,ndim).*dim_sizes(:)');
        [nsamples,nfeatures]=size(ds.samples);
        ds_msk=false(1,nfeatures);
        ft_idx=cell(1,1+ndim);
        ft_idx{1}=randperm(nsamples);
        for k=1:ndim
            dim_label=fdim.labels{k};
            ds_msk = ds_msk | rp(k)~=ds.fa.(dim_label);

            switch dim_label
                case 'mom'
                    ft_idx{k+1}=rp(k);

                case 'pos'
                    ft_values=ft.(dim_label);
                    ft_idx{k+1}=find(all(bsxfun(@eq,...
                                    ft_values(rp(k),:),ft_values),2));

                otherwise
                    ft_values=ft.(dim_label);
                    ft_idx{k+1}=find(ft_values(rp(k))==ft_values);
            end
        end

        ds_sel=cosmo_slice(cosmo_slice(ds,~ds_msk,2),ft_idx{1});

        if is_ft_strange_lcmv_avg_pow
            ft_idx=ft_idx(2:end);
        end

        ft_sel=ft_arr(ft_idx{:});

        if ft_sel==0
            assertTrue(isempty(ds_sel.samples));
        else
            assertEqual(ds_sel.samples,ft_sel);
        end

        %re-order features
        nfeatures=size(ds.samples,2);
        ds2=cosmo_slice(ds,randperm(nfeatures),2);

        ft2=cosmo_map2meeg(ds2);

        if is_ft_strange_lcmv_avg_pow
            ft2.avg.pow=ft2.avg.pow';
            ft2.time=ft.time;
        end

        assertEqual(ft,ft2);

        if j==1
            % test compatibility with old fieldtrip

            ft2.inside=find(ft2.inside);
            ds3=cosmo_meeg_dataset(ft2);
            assertEqual(ds,ds3);

            ft2.inside=struct();
            assertExceptionThrown(@()cosmo_meeg_dataset(ft2),'');
        end
    end

function test_meeg_fmri_dataset()
    ds=cosmo_synthetic_dataset('type','source');
    ds_fmri=cosmo_fmri_dataset(ds);
    ft=cosmo_map2meeg(ds);
    ds_ft_fmri=cosmo_fmri_dataset(ft);

    ds_vol=cosmo_vol_grid_convert(ds,'tovol');
    assertEqual(ds_vol,ds_fmri);

    assertTrue(isempty(fieldnames(ds_ft_fmri.sa)))
    ds_vol=rmfield(ds_vol,'sa');
    ds_ft_fmri=rmfield(ds_ft_fmri,'sa');
    assertEqual(ds_vol,ds_ft_fmri);

function test_irregular_source_grid()
    ft=generate_ft_source('freq_pow');
    ft.pos=ft.pos+randn(size(ft.pos));

    ds=cosmo_meeg_dataset(ft);
    ft2=cosmo_map2meeg(ds);

    assertEqual(ft,ft2);

function [ft,fdim,data_label]=generate_ft_source(tp)
    ft=struct();
    dim_pos_range={-3:3,-4:4,-5:5};
    nsamples=2;
    freq=[3 5 7 9];
    time=[-1 0 1 2];
    mom_labels_3d={'x','y','z'};
    mom_labels_1d={'xyz'};

    ft.dim=cellfun(@numel,dim_pos_range);
    ft.pos=cosmo_cartprod(dim_pos_range);
    ft.inside=sum(ft.pos.^2,2)<30;

    fdim=struct();

    switch tp
        case 'freq_pow'
            ft.freq=freq;
            ft.method='average';
            ft.avg=generate_data(ft.inside,numel(freq),1,'pow');
            fdim.labels={'pos';'freq'};
            fdim.values={ft.pos';freq(:)'};
            data_label={'avg','pow'};

        case 'time_mom'
            ft.time=time;
            ft.method='average';
            ft.avg=generate_data(ft.inside,numel(time),1,'mom3d');
            fdim.labels={'pos';'mom';'time'};
            fdim.values={ft.pos';mom_labels_3d;time(:)'};
            data_label={'avg','mom'};

        case 'time_mom_1d';
            ft.time=time;
            ft.method='average';
            ft.avg=generate_data(ft.inside,numel(time),1,'mom1d');
            fdim.labels={'pos';'mom';'time'};
            fdim.values={ft.pos';mom_labels_1d;time(:)'};
            data_label={'avg','mom'};

        case 'rpt_trial_pow';
            ft.time=time;
            ft.method='rawtrial';
            ft.trial=generate_data(ft.inside,numel(time),nsamples,'pow');
            fdim.labels={'pos';'time'};
            fdim.values={ft.pos';time(:)'};
            data_label={'trial','pow'};

        case 'rpt_trial_mom';
            ft.time=time;
            ft.method='rawtrial';
            ft.trial=generate_data(ft.inside,numel(time),nsamples,'mom3d');
            fdim.labels={'pos';'mom';'time'};
            fdim.values={ft.pos';mom_labels_3d;time(:)'};
            data_label={'trial','mom'};

        case 'rpt_trial_mom_1d';
            ft.time=time;
            ft.method='rawtrial';
            ft.trial=generate_data(ft.inside,numel(time),nsamples,'mom1d');
            fdim.labels={'pos';'mom';'time'};
            fdim.values={ft.pos';mom_labels_1d;time(:)'};
            data_label={'trial','mom'};

        case 'strange_ft_lcmv_pow'
            % FT with LCMV source puts .avg data in a 1xNSOURCE matrix,
            % even with NTIME>1 timepoints. In this case, the output
            % dataset structure has the time points averaged.
            ft.time=time;
            ft.method='average';
            ft.avg=generate_data(ft.inside,1,1,'pow');
            ft.avg.pow=ft.avg.pow';
            fdim.labels={'pos';'time'};
            fdim.values={ft.pos';mean(time)};
            data_label={'avg','pow'};

        otherwise
            error('unsupported type %s', tp);

    end

function d=generate_data(inside,nfeatures,nsamples,fld)
    is_single_trial=nargin<3;

    if is_single_trial
        nsamples=1;
    end

    switch fld
        case 'mom3d'
            d=generate_mom(inside,nfeatures,nsamples,3);
        case 'mom1d'
            d=generate_mom(inside,nfeatures,nsamples,1);
        case 'pow'
            d=generate_pow(inside,nfeatures,nsamples);
        otherwise
            error('not supported: %s', fld);
    end

function all_trials=generate_pow(inside,nfeatures,nsamples)
    nf=numel(inside);
    ni=sum(inside);

    all_data=NaN(nf,nfeatures);
    trial_data=cosmo_rand(ni,nfeatures);

    all_trials_cell=cell(nsamples,1);
    for j=1:nsamples
        data=all_data;
        data(inside,:)=trial_data+j;
        all_trials_cell{j}.pow=data;
    end

    all_trials=cat(1,all_trials_cell{:})';

function all_trials=generate_mom(inside,nfeatures,nsamples,ndim)
    nf=numel(inside);
    i=find(inside);
    ni=numel(i);

    data=cosmo_rand(ndim,nfeatures,ni);

    all_trials_cell=cell(1,nsamples);
    for j=1:nsamples
        one_trial=cell(nf,1);
        for k=1:ni
            one_trial{i(k)}=data(:,:,k)+j;
        end
        all_trials_cell{j}=one_trial;
    end

    all_trials=struct('mom',all_trials_cell);


function model = leReconstruct(leInfo, y)

% LERECONSTRUCT Reconstruct an LE form component parts.
% FORMAT
% DESC takes component parts of an LE model and reconstructs the
% LE model. The component parts are normally retrieved from a
% saved file.
% ARG leInfo : the active set and other information stored in a structure.
% ARG y : the output target training data for the LE.
% RETURN model : an LE model structure that combines the component
% parts.
%
% SEEALSO : leDeconstruct, leCreate
%
% COPYRIGHT : Neil D. Lawrence, 2009

% MLTOOLS

  model = leInfo;
  model.Y = y;

end


function [selectionDimInfo, selectionIndexArray, ...
    unusedVarargin] = select(this, varargin)
% Returns indexArray and corresponding dimInfo of sub-selection from array
% specified by dimInfo
%
%    [selectionDimInfo, selectionIndexArray, unusedVarargin] = ...
%       dimInfo.select('type', 'index'/'label', 'invert', true/false, ...
%             'dimLabel1', arrayIndicesDim1/samplingPointsDim1, ...,
%               ...
%              'dimLabelK', arrayIndicesDimK/samplingPointsDimK, ...)
%
% This is a method of class MrDimInfo.
%
% IN
%   'type'      'index' (default) or 'sample'
%               defines how selection elements should be
%               interpreted as array indices or sampling points
%   'invert'    true or false (default)
%               if true, selection is inverted and given arrayIndices are
%               excluded from returned selection, based on
%               dimInfo-information about whole array
%   'removeDims' true or false (default)
%               if true, singleton dimensions (1 or less dimensions) will be removed
%   arrayIndicesDimK/samplingPointsDimk
%               vector of either array indices or sampling point values
%               some strings for special values are possible, e.g.
%               'center'/'centre'/'middle' for ceil(nSamples(dimK)/2)
%               'last' for nSamples(dimK)
%
% OUT
%   selectionDimInfo        dimInfo of specified selection, derived as
%                           subset from dimInfo
%   selectionIndexArray     indexArray of selected samples in original
%                           dimInfo
%   unusedVarargin          if specified, returns all input arguments that
%                           did not match a dimLabel/Value pair of dimInfo
%                           Note: If this output is omitted, select returns
%                           an error for every unknown dimension requested
%
% EXAMPLE
%   select
%
%   See also MrDimInfo

% Author:   Lars Kasper
% Created:  2016-01-29
% Copyright (C) 2016 Institute for Biomedical Engineering
%                    University of Zurich and ETH Zurich
%
% This file is part of the TAPAS UniQC Toolbox, which is released
% under the terms of the GNU General Public License (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/>.

defaults.invert = false;
defaults.type = 'index'; % or sample(s)
defaults.removeDims = false;

[argsSelect, argsDimInfo] = tapas_uniqc_propval(varargin, defaults);

% selectionDimInfo what has to change? samplingPoints, everything else is
% derivative! ... So we have to get the selectionIndexArray and then reduce
% selectionDimInfo to it!
selectionDimInfo = this.copyobj();
unusedVarargin = {};
returnUnusedVarargin = nargout >=3;

parseDimLabels = argsDimInfo(1:2:end);
parseDimValues = argsDimInfo(2:2:end);

selectionIndexArray = cell(this.nDims, 1);

nParseDims = numel(parseDimLabels);
for iDimSelect = 1:nParseDims
    dimLabel = parseDimLabels{iDimSelect};
    currentIndices = parseDimValues{iDimSelect};

    iDim = this.get_dim_index(dimLabel);
    isUnknownDimLabel = isempty(iDim);
    if isUnknownDimLabel
        if returnUnusedVarargin
            unusedVarargin(end+1:end+2) = {dimLabel, currentIndices};
        else
            error('tapas:uniqc:MrDimInfoSelectNonExistingDimension', ...
                'Dimension with label "%s" does not exist in %s', dimLabel, ...
                inputname(1));
        end
    else

        % Inf or [] treated as take all
        if isinf(currentIndices) | isempty(currentIndices)
            currentIndices = 1:this.nSamples(iDim);
        end

        if ischar(currentIndices)
            switch currentIndices
                case {'center', 'centre', 'middle'}
                    currentIndices = ceil(this.nSamples(iDim)/2);
                case 'last'
                    this.nSamples(iDim);
            end
        end

        switch argsSelect.type
            case {'sample', 'samples'}
                currentIndices = this.sample2index(currentIndices, ...
                    iDim);
        end

        if argsSelect.invert
            currentIndices = setdiff(1:this.nSamples(iDim), ...
                currentIndices);
        end

        selectionDimInfo.samplingPoints{iDim} = ...
            this.samplingPoints{iDim}(currentIndices);

        selectionIndexArray{iDim} = currentIndices;
    end
end

% fill up dimensions without specific selection with full range
for iDim = 1:this.nDims
    if isempty(selectionIndexArray{iDim})
        selectionIndexArray{iDim} = 1:this.nSamples(iDim);
    end
end

if argsSelect.removeDims
    selectionDimInfo.remove_dims();
end


function w = uminus(w)
   %-  Unary minus for waveforms.
   %   -A negates the elements of A.
   %
   %   B = UMINUS(W) is called for the syntax '-W' when W is an waveform.

   % AUTHOR: Celso Reyes, Geophysical Institute, Univ. of Alaska Fairbanks
   % $Date$
   % $Revision$

   for n=1:numel(w)
      w(n).data = -w(n).data;
   end
end


% eeg_mergechan() - merge channel structure while preserving channel
%                   order
%
% >> mergelocs = eeg_mergechan(locs1, locs2);
%
% Inputs:
%     locs1     - EEGLAB channel location structure
%     locs2     - second EEGLAB channel location structure
%
% Output:
%     mergelocs - merged channel location structure
%
% Author: Arnaud Delorme, August 2006

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

% union of two channel location structure
% without loosing the order information
% ---------------------------------------
function alllocs = myunion(locs1, locs2)

   labs1 = { locs1.labels };
   labs2 = { locs2.labels };

   count1 = 1;
   count2 = 1;
   count3 = 1;
   alllocs = locs1; alllocs(:) = [];
   while count1 <= length(locs1) | count2 <= length(locs2)

       if count1 > length(locs1)
           alllocs(count3) = locs2(count2);
           count2 = count2 + 1;
           count3 = count3 + 1;
       elseif count2 > length(locs2)
           alllocs(count3) = locs1(count1);
           count1 = count1 + 1;
           count3 = count3 + 1;
       elseif strcmpi(labs1(count1), labs2(count2))
           alllocs(count3) = locs1(count1);
           count1 = count1 + 1;
           count2 = count2 + 1;
           count3 = count3 + 1;
       elseif isempty(strmatch(labs1(count1), labs2))
           alllocs(count3) = locs1(count1);
           count1 = count1 + 1;
           count3 = count3 + 1;
       else
           alllocs(count3) = locs2(count2);
           count2 = count2 + 1;
           count3 = count3 + 1;
       end;

   end;


function failed_bug2418

% WALLTIME 00:10:00
% MEM 2gb
% DEPENDENCY ft_senstype ft_senslabel

%% test the consistency between labels and type

type = {
  'ant128'
  'biosemi64'
  'biosemi128'
  'biosemi256'
  'bti148'
  'bti148_planar'
  'bti248'
  'bti248_planar'
%  'btiref'         % returns a subset
  'ctf151'
  'ctf151_planar'
  'ctf275'
  'ctf275_planar'
%  'ctfheadloc'     % returns a subset
%  'ctfref'         % returns a subset
  'eeg1005'
  'eeg1010'
  'eeg1020'
%  'ext1020'        % returns lower and upper case
  'egi32'
  'egi64'
  'egi128'
  'egi256'
  'neuromag122'
%  'neuromag122alt' % does not apply any more, see bugzilla
  'neuromag306'
%  'neuromag306alt' % does not apply any more, see bugzilla
  'itab28'
  'itab153'
  'itab153_planar'
  'yokogawa9'
  'yokogawa64'
  'yokogawa64_planar'
  'yokogawa160'
  'yokogawa160_planar'
  'yokogawa440'
  };

for i=1:length(type)
  lab = ft_senslabel(type{i});
  lab = lab(:);

  disp(type{i});
  assert(strcmp(type{i}, ft_senstype(lab)));
end

%% test the provided problematic data

cd(dccnpath('/home/common/matlab/fieldtrip/data/test'));
load bug2418.mat

assert(isequal(ft_senstype(testdata_short), 'neuromag306'));
assert(isequal(ft_senstype(testdata_short.label), 'neuromag306'));
assert(isequal(ft_senstype(testdata_short.grad), 'neuromag306'));
assert(isequal(ft_senstype(testdata_short.grad.label), 'neuromag306'));


function kern = translateKernExpandParam(kern, params)

% TRANSLATEKERNEXPANDPARAM Create kernel structure from TRANSLATE kernel's parameters.
% FORMAT
% DESC returns a input space translation kernel structure filled with the
% parameters in the given vector. This is used as a helper function to
% enable parameters to be optimised in, for example, the NETLAB
% optimisation functions.
% ARG kern : the kernel structure in which the parameters are to be
% placed.
% ARG param : vector of parameters which are to be placed in the
% kernel structure.
% RETURN kern : kernel structure with the given parameters in the
% relevant locations.
%
% SEEALSO : translateKernParamInit, translateKernExtractParam,
% cmpndKernExpandParam, kernExpandParam
%
% COPYRIGHT : Neil D. Lawrence, 2007

% KERN

endVal = length(params)-kern.inputDimension;
kern = cmpndKernExpandParam(kern, params(1, 1:endVal));
startVal = endVal + 1;
kern.centre = params(1, startVal:end);


function layer = vl_nndropout(x, varargin)
%VL_NNDROPOUT Additional options for vl_nndropout (CNN dropout)
%   Y = Layer.vl_nndropout(X) applies dropout to the data X. See help
%   vl_nndropout for more details.
%
%   This method overloads MatConvNet's vl_nndropout function for Layer
%   objects, so that instead of executing vl_nndropout, a new Layer object
%   is returned.
%
%   The dropout layer is composed of a vl_nndropout_wrapper and an
%   auxiliary mask generator layer, vl_nnmask.

% Copyright (C) 2016-2017 Joao F. Henriques.
% All rights reserved.
%
% This file is part of the VLFeat library and is made available under
% the terms of the BSD license (see the COPYING file).

  opts.rate = 0.5 ;
  opts = vl_argparse(opts, varargin) ;

  % create mask generator layer
  maskLayer = Layer(@vl_nnmask, x, opts.rate) ;

  % create dropout wrapper layer
  layer = Layer(@vl_nndropout_wrapper, x, maskLayer, Input('testMode')) ;

  % vl_nndropout_wrapper doesn't return a derivative for the mask
  layer.numInputDer = 1 ;

end


% op_takeextras.m
% Jamie Near, McGill University 2018.
%
% USAGE:
% out=op_takeextras(in,index);
%
% DESCRIPTION:
% Extract the extras with indices corresponding to the 'index' input
% array.
%
% INPUTS:
% in     = input data in matlab structure format.
% index  = vector indicating the indices of the extras you would like to extract.
%
% OUTPUTS:
% out    = Output dataset consisting of extras extracted from the input.

function out=op_takeextras(in,index);

if in.dims.extras==0
    %Can't take extras because there are none:
    error('ERROR:  There are no averages in this dataset!  Aborting!');
elseif in.dims.extras==1
    %SHOULD NEVER HAPPEN (Time dimension should always be dim=1)
    error('ERROR:  dims.subSpecs==1.  This should never happen!  Aborting!');
elseif in.dims.extras==2
    fids=in.fids(:,index,:,:,:);
elseif in.dims.extras==3;
    fids=in.fids(:,:,index,:,:);
elseif in.dims.extras==4;
    fids=in.fids(:,:,:,index,:);
elseif in.dims.extras==5
    fids=in.fids(:,:,:,:,index);
end

fids=squeeze(fids);

%re-calculate Specs using fft
specs=fftshift(ifft(fids,[],in.dims.t),in.dims.t);

%change the dims variables
dims.t=in.dims.t;
if length(index)==1
    dims.extras=0;
    if in.dims.coils > in.dims.extras
        dims.coils=in.dims.coils-1;
    else
        dims.coils=in.dims.coils;
    end
    if in.dims.subSpecs > in.dims.extras
        dims.subSpecs=in.dims.subSpecs-1;
    else
        dims.subSpecs=in.dims.subSpecs;
    end
    if in.dims.averages > in.dims.extras
        dims.averages=in.dims.averages-1;
    else
        dims.averages=in.dims.averages;
    end
elseif length(index)>1
    dims.coils=in.dims.coils;
    dims.averages=in.dims.averages;
    dims.subSpecs=in.dims.subSpecs;
    dims.extras=in.dims.extras;
end

%re-calculate the sz variable
sz=size(fids);

%change the number of extras
if dims.extras==0
    extras=1;
else
    extras=sz(dims.extras);
end

%FILLING IN DATA STRUCTURE
out=in;
out.fids=fids;
out.specs=specs;
out.sz=sz;
out.dims=dims;

%FILLING IN THE FLAGS
out.flags=in.flags;
out.flags.writtentostruct=1;


function [ shape2D, global_params, local_params, final_lhood, landmark_lhoods, view_used, early_term ] = Fitting_from_bb_multi_hyp( Image, DepthImage, bounding_box, PDM, patches, clmParams, views, early_term_params)

    num_points = numel(PDM.M)/3;

    shapes = zeros(num_points, 2, size(views,1));
    ls = zeros(size(views,1),1);
    lmark_lhoods = zeros(num_points,size(views,1));
    views_used = zeros(size(views,1),1);
    globals = cell(size(views,1),1);
    locals = cell(size(views,1),1);

    % If early termination parameters are defined use clever multi-hypothesis setup
    % otherwise evaluate all of them in a simplistic manner
    if(nargin > 7)

        patches_small = patches(1);
        patches_large = patches(2:end);

        clmParams_small = clmParams;
        clmParams_small.numPatchIters = 1;
        clmParams_large = clmParams;
        clmParams_large.regFactor = clmParams_large.regFactor(2:end);
        clmParams_large.sigmaMeanShift = clmParams_large.sigmaMeanShift(2:end);
        clmParams_large.tikhonov_factor = clmParams_large.tikhonov_factor(2:end);
        clmParams_large.numPatchIters = 3;
        clmParams_large.window_size = clmParams_large.window_size(2:end,:);

        early_term = 0;
        % Find the best orientation
        for v = 1:size(views,1)
            [shapes(:,:,v),globals{v},locals{v},ls(v),lmark_lhoods(:,v),views_used(v)]...
                = Fitting_from_bb(Image, DepthImage, bounding_box, PDM, patches_small, clmParams_small, 'orientation', views(v,:));

            ls(v) = ls(v) * early_term_params.weights_scale(views_used(v)) + early_term_params.weights_add(views_used(v));

            if(ls(v) > early_term_params.cutoffs(views_used(v)))
                early_term = v;
                [shape,global_params,local_params,final_lhood,landmark_lhoods,view_used]...
                    = Fitting_from_bb(Image, DepthImage, bounding_box, PDM, patches_large, clmParams_large, 'gparam', globals{v}, 'lparam', locals{v});
                break;
            end
        end

        if(early_term == 0)

            % Discard the really unreliable starts
            [~, ids] = sort(ls);
            to_refine = ids(end-3:end);

            % If there are any starts left try refining them in a
            % multiscale approach
            if(~isempty(to_refine))
                shapes = zeros(num_points, 2, numel(to_refine));
                ls = zeros(numel(to_refine),1);
                lmark_lhoods = zeros(num_points,numel(to_refine));
                views_used = zeros(numel(to_refine),1);
                globals_n = cell(size(to_refine,1),1);
                locals_n = cell(size(to_refine,1),1);
                ind = 1;
                for v = to_refine'
                    [shapes(:,:,ind), globals_n{ind}, locals_n{ind},...
                        ls(ind),lmark_lhoods(:,ind),views_used(ind)] ...
                        = Fitting_from_bb(Image, DepthImage, bounding_box, PDM, patches_large, clmParams_large, 'gparam', globals{v}, 'lparam', locals{v});
                    ind = ind+1;
                end
            end

            % TODO aggregation?
            [final_lhood, v_ind] = max(ls);
            landmark_lhoods = lmark_lhoods(:,v_ind);
            global_params = globals_n{v_ind};
            local_params = locals_n{v_ind};
            shape = shapes(:,:,v_ind);
            view_used = views_used(v_ind);
        end
        shape2D = shape;
    else

        % Find the best orientation
        for v = 1:size(views,1)
            [shapes(:,:,v),globals{v},locals{v},ls(v),lmark_lhoods(:,v),views_used(v)] = Fitting_from_bb(Image, [], bounding_box, PDM, patches, clmParams, 'orientation', views(v,:));
        end

        [final_lhood, v_ind] = max(ls);
        landmark_lhoods = lmark_lhoods(:,v_ind);

        global_params = globals{v_ind};
        local_params = locals{v_ind};

        shape2D = shapes(:,:,v_ind);
        view_used = views_used(v_ind);

    end

end


%if exist('images_read');
%   active_images = active_images & images_read;
%end;

var2fix = 'dX_default';

fixvariable;

var2fix = 'dY_default';

fixvariable;

var2fix = 'map';

fixvariable;

if ~exist('n_ima'),
    data_calib_no_read;
end;

check_active_images;

% Step used to clean the memory if a previous atttempt has been made to read the entire set of images into memory:
for kk = 1:n_ima,
    if (exist(['I_' num2str(kk)])==1),
        clear(['I_' num2str(kk)]);
    end;
end;

fprintf(1,'\nExtraction of the grid corners on the images\n');

if (exist('map')~=1), map = gray(256); end;

%disp('WARNING!!! Do not forget to change dX_default and dY_default in click_calib.m!!!')

if exist('dX'),
    dX_default = dX;
end;

if exist('dY'),
    dY_default = dY;
end;

if exist('n_sq_x'),
    n_sq_x_default = n_sq_x;
end;

if exist('n_sq_y'),
    n_sq_y_default = n_sq_y;
end;

if ~exist('dX_default')|~exist('dY_default');

    % Setup of JY - 3D calibration rig at Intel (new at Intel) - use units in mm to match Zhang
    dX_default = 30;
    dY_default = 30;

    % Setup of JY - 3D calibration of the Rosette (Google) - use units in mm to match Zhang
    dX_default = 0.1524; %100;
    dY_default = 0.1524; %100;

end;

if ~exist('n_sq_x_default')|~exist('n_sq_y_default'),
    n_sq_x_default = 10;
    n_sq_y_default = 10;
end;

if ~exist('wintx_default')|~exist('winty_default'),
    if ~exist('nx'),
        wintx_default = 20;
        winty_default = wintx_default;
        clear wintx winty
    else
        wintx_default = max(round(nx/128),round(ny/96));
        winty_default = wintx_default;
        clear wintx winty
    end;
end;

if ~exist('wintx') | ~exist('winty'),
    clear_windows; % Clear all the window sizes (to re-initiate)
end;

if ~exist('dont_ask'),
    dont_ask = 0;
end;

if ~dont_ask,
    ima_numbers = input('Number(s) of image(s) to process ([] = all images) = ');
else
    ima_numbers = [];
end;

if isempty(ima_numbers),
    ima_proc = 1:n_ima;
else
    ima_proc = ima_numbers;
end;

% Useful option to add images:
kk_first = ima_proc(1); %input('Start image number ([]=1=first): ');

%if isempty(kk_first), kk_first = 1; end;

if exist(['wintx_' num2str(kk_first)]),

    eval(['wintxkk = wintx_' num2str(kk_first) ';']);

    if isempty(wintxkk) | isnan(wintxkk),

        disp('Window size for corner finder (wintx and winty):');
        wintx = input(['wintx ([] = ' num2str(wintx_default) ') = ']);
        if isempty(wintx), wintx = wintx_default; end;
        wintx = round(wintx);
        winty = input(['winty ([] = ' num2str(winty_default) ') = ']);
        if isempty(winty), winty = winty_default; end;
        winty = round(winty);

        fprintf(1,'Window size = %dx%d\n',2*wintx+1,2*winty+1);

    end;

else

    disp('Window size for corner finder (wintx and winty):');
    wintx = input(['wintx ([] = ' num2str(wintx_default) ') = ']);
    if isempty(wintx), wintx = wintx_default; end;
    wintx = round(wintx);
    winty = input(['winty ([] = ' num2str(winty_default) ') = ']);
    if isempty(winty), winty = winty_default; end;
    winty = round(winty);

    fprintf(1,'Window size = %dx%d\n',2*wintx+1,2*winty+1);

end;

if ~dont_ask,
    fprintf(1,'Do you want to use the automatic square counting mechanism (0=[]=default)\n');
    manual_squares = input('  or do you always want to enter the number of squares manually (1,other)? ');
    if isempty(manual_squares),
        manual_squares = 0;
    else
        manual_squares = ~~manual_squares;
    end;
else
    manual_squares = 0;
end;

for kk = ima_proc,

    if ~type_numbering,
        number_ext =  num2str(image_numbers(kk));
    else
        number_ext = sprintf(['%.' num2str(N_slots) 'd'],image_numbers(kk));
    end;

    ima_name = [calib_name  number_ext '.' format_image];

    if exist(ima_name),

        fprintf(1,'\nProcessing image %d...\n',kk);

        fprintf(1,'Loading image %s...\n',ima_name);

        if format_image(1) == 'p',
            if format_image(2) == 'p',
                I = double(loadppm(ima_name));
            else
                I = double(loadpgm(ima_name));
            end;
        else
            if format_image(1) == 'r',
                I = readras(ima_name);
            else
                I = double(imread(ima_name));
            end;
        end;

        if size(I,3)>1,
            I = 0.299 * I(:,:,1) + 0.5870 * I(:,:,2) + 0.114 * I(:,:,3);
        end;

        [ny,nx,junk] = size(I);
        Wcal = nx; % to avoid errors later
        Hcal = ny; % to avoid errors later

        click_ima_calib_fisheye_no_read;

        active_images(kk) = 1;

    else
        eval(['dX_' num2str(kk) ' = NaN;']);
        eval(['dY_' num2str(kk) ' = NaN;']);

        eval(['wintx_' num2str(kk) ' = NaN;']);
        eval(['winty_' num2str(kk) ' = NaN;']);

        eval(['x_' num2str(kk) ' = NaN*ones(2,1);']);
        eval(['X_' num2str(kk) ' = NaN*ones(3,1);']);

        eval(['n_sq_x_' num2str(kk) ' = NaN;']);
        eval(['n_sq_y_' num2str(kk) ' = NaN;']);
    end;
end;

check_active_images;

% Fix potential non-existing variables:

for kk = 1:n_ima,
    if ~exist(['x_' num2str(kk)]),
        eval(['dX_' num2str(kk) ' = NaN;']);
        eval(['dY_' num2str(kk) ' = NaN;']);

        eval(['x_' num2str(kk) ' = NaN*ones(2,1);']);
        eval(['X_' num2str(kk) ' = NaN*ones(3,1);']);

        eval(['n_sq_x_' num2str(kk) ' = NaN;']);
        eval(['n_sq_y_' num2str(kk) ' = NaN;']);
    end;

    if ~exist(['wintx_' num2str(kk)]) | ~exist(['winty_' num2str(kk)]),

        eval(['wintx_' num2str(kk) ' = NaN;']);
        eval(['winty_' num2str(kk) ' = NaN;']);

    end;
end;

string_save = 'save calib_data active_images ind_active wintx winty n_ima type_numbering N_slots first_num image_numbers format_image calib_name Hcal Wcal nx ny map dX_default dY_default dX dY';

for kk = 1:n_ima,
    string_save = [string_save ' X_' num2str(kk) ' x_' num2str(kk) ' n_sq_x_' num2str(kk) ' n_sq_y_' num2str(kk) ' wintx_' num2str(kk) ' winty_' num2str(kk) ' dX_' num2str(kk) ' dY_' num2str(kk)];
end;

eval(string_save);

disp('done');


function dfaddbuttons(dffig)
%DFADDBUTTONS Add buttons to the curve fitting plot figure window

%   $Revision: 1.1.6.8 $  $Date: 2004/01/24 09:35:09 $
%   Copyright 2003-2004 The MathWorks, Inc.

% Clear out any old stuff
h0 = findall(dffig,'Type','uicontrol','Style','pushbutton');
if ~isempty(h0), delete(h0); end
p0 = ones(1,4);   % temporary position before adjustment

h0=uicontrol(dffig,'units','pixels','Tag','selectionframe',...
             'Style','frame','Position',p0);
h1=uicontrol(dffig,'units','pixels','Tag','displaytext',...
            'String','Display type:', 'style','text','Position',p0,...
            'HorizontalAlignment','left','FontWeight','bold');

choices = ['Density (PDF)|Cumulative probability (CDF)|Quantile (inverse CDF)|Probability plot|'...
           'Survivor function|Cumulative hazard'];
h2=uicontrol(dffig,'units','pixels','Tag','displaylist',...
            'String',choices, 'Style','pop','BackgroundColor',ones(1,3), ...
            'Callback', @cbkfunction,'Position',p0);
setappdata(h2,'codenames',...
             {'pdf' 'cdf' 'icdf' 'probplot' 'survivor' 'cumhazard'});

h3=uicontrol(dffig,'units','pixels','Tag','typetext',...
            'String','Distribution:', 'style','text','Position',p0,...
            'HorizontalAlignment','left','FontWeight','bold', 'Enable', 'off');

% Figure out which distributions could be used for probability plots,
% and remember their names and properties for later
alldist = dfgetdistributions;
dnames = {alldist.name};
dcodes = {alldist.code};
islocscale = [alldist.islocscale];
choices = dnames(islocscale);
default = strmatch('Normal',choices);
if length(default)~=1
   default = 1;
end
h4=uicontrol(dffig,'units','pixels','Tag','typelist',...
            'String',choices, 'Style','pop','BackgroundColor',ones(1,3),...
            'Value',default,'Enable','off', 'Callback',@cbkfunction,...
             'Position',p0);

% Store information about all distributions that could be used to make
% a probability plot, then separately list distributions that are ok
% given the current data.  Right now they're the same, but if we read
% in negative data we'll prune the "ok" list to omit positive
% distributions.
setappdata(h4,'allfullnames',choices);
setappdata(h4,'allcodenames',dcodes(islocscale));
setappdata(h4,'alldistspec',alldist(islocscale));
setappdata(h4,'okcodenames',dcodes(islocscale));

hvec = [h0 h1 h2 h3 h4];
setappdata(dffig,'selectioncontrols',hvec);

% Define information for the buttons
strings = {xlate('Data...') xlate('New Fit...') xlate('Manage Fits...') xlate('Evaluate...') xlate('Exclude...')};
tips = {xlate('Import, view, rename, plot and delete data') ...
        xlate('Add a fitted distribution') ...
        xlate('Edit, view, plot and rename fits') ...
        xlate('Evaluate fits to compute a table of results') ...
        xlate('Define rules for excluding data from a fit')};
cbacks = {@cbkdata @cbknewfit @cbkmanfit @cbkeval @cbkexclude};

% Add the buttons to the figure
tags = {'dfdata' 'dfnewfit' 'dfmanfit' 'dfevaluate' 'dfexclude'};
n = length(strings);
h = zeros(1,n);
for j=1:length(strings)
   h(j) = uicontrol(dffig,'Units','pixels', ...
                    'Position',[j,1,1,1],...
                    'String',strings{j}, 'TooltipString',tips{j}, ...
                    'Callback',cbacks{j}, 'Tag',tags{j});
end
setappdata(dffig,'buttoncontrols',h);

% ---------------------- callback for Import button
function cbkdata(varargin)
%CBKDATA Callback for Data button

delete(findall(gcbf,'Tag','dfstarthint'));
com.mathworks.toolbox.stats.Data.showData;

% ---------------------- callback for New Fit button
function cbknewfit(varargin)
%CBKNEWFIT Callback for New Fit button

com.mathworks.toolbox.stats.Fitting.getFitting.showNewFit;

% ---------------------- callback for Manage Fit button
function cbkmanfit(varargin)
%CBKMANFIT Callback for Manage Fit button

com.mathworks.toolbox.stats.FitManager.showFitManager;

% ---------------------- callback for Evaluate button
function cbkeval(varargin)
%CBKEVAL Callback for Evaluate button

% Get the current horizontal axis limits of the main figure.  We'll set the
% default points at which to evaluate to a "pretty" colon expression that
% spans those limits, with 10 steps.
xlims = dfgetset('xminmax');

if isempty(xlims)
   xstr = '';
else
   % Start out by choosing a rounding that will give the smaller (in
   % magnitude) of min(x) and max(x) a single sig digit, but at most two sig
   % digits in the larger.  The smaller may round to zero.  If the min and max
   % round to the same thing, use more digits until we get rounded numbers
   % that differ.
   xmag = max(min(abs(xlims)), max(abs(xlims))/10);
   rounder = 10^floor(log10(xmag));
   while true
       xmin = round(xlims(1)./rounder) * rounder;
       xmax = round(xlims(2)./rounder) * rounder;
       if xmin < xmax, break; end
       rounder = rounder/10;
   end

   % Create 10 steps, where the step size will have three more significant
   % digits than the endpoints.
   stepRounder = rounder ./ 1000;
   step = floor((xmax-xmin)./(10*stepRounder)) * stepRounder;

   % Figure out how many digits we need display in order to distinguish the
   % endpoints.  That's the number of digits to the left of the decimal, plus
   % however many we've rounded to on the right.  Allow for at least four so
   % that we'll get, e.g., "4000", and not "4e+03".
   xminDigits = max(round(log10(max(abs(xmin),1))-log10(rounder)),4);
   xmaxDigits = max(round(log10(max(abs(xmax),1))-log10(rounder)),4);
   xstr = sprintf('%0.*g:%g:%0.*g', xminDigits, xmin, step, xmaxDigits, xmax);
end

% Get the current plot type of the main figure, we'll set the default
% function type to evaluate based on that.
ftype = dfgetset('ftype');
if strcmp(ftype,'probplot')
    ftype = 'cdf'; % can't evaluate a prob plot, use cdf instead
% else {'pdf' 'cdf' 'survivor' 'icdf' 'cumhazard' 'hazrate'}
    % otherwise use the current setting
end

com.mathworks.toolbox.stats.Evaluate.showEvaluate(ftype,xstr);

% ---------------------- callback for Exclude button
function cbkexclude(varargin)
%CBKEXCLUDE Callback for Exclude button

com.mathworks.toolbox.stats.Exclude.showExclude;

% ---------------------- callback for display list
function cbkfunction(varargin)
%CBKFUNCTION Callback for setting function to display

% Get the requested function and distribution types
dffig = gcbf;
dfsetplottype(dffig);


function [GaitDBDir,probes,numprobe]=testData

GaitDBDir=['USFGait17_32x22x10/'];

%If you want to test on higher resolution, please download them from
%https://sites.google.com/site/tensormsl/#data
%or http://www.dsp.toronto.edu/~haiping/MSL#data
%and change the directory in this script.
%If you are using 32-bit Matlab, you may have out of memory problem. Please
%try:
%1. clear unused variables
%2. write matrix operations into for-loops
%3. use integer data type instead of single/double float point
%4. when passing variables to functions: save variables to mat files, clear
%the varialbes, load the variables in the calling function so Matlab won't
%keep two copies of the same data.

%GaitDBDir=['USFGait17_64x44x20/'];
%GaitDBDir=['USFGait17_128x88x20/'];

probes=['A','B','C','D','E','F','G'];
numprobe=length(probes);


function simplenn_caffe_deploy(net, caffeModelBaseName, varargin)
% SIMPLENN_CAFFE_DEPLOY export a simplenn network to Caffe model
%   SIMPLENN_CAFFE_DEPLOY(NET, CAFFE_BASE_MODELNAME)
%   Export a simplenn network NET to a Caffe model.
%   The caffe model is stored in the following files:
%
%     [CAFFE_BASE_MODELNAME '.prototxt']   - Network definition file
%     [CAFFE_BASE_MODELNAME '.caffemodel'] - Binary Caffe model
%     [CAFFE_BASE_MODELNAME '_mean_image.binaryproto'] (optional) -
%         The average image (if set in net.normalization.averageImage)
%
%   Compiled MatCaffe (usually located in `<caffe_dir>/matlab`, built
%   with the `matcaffe` target) must be in path.
%
%   Only a limited subset of layers is currently supported and those are:
%
%     Conv, ReLU, Pool, LRN, SoftMax, SoftMaxLogLoss, DropOut
%
%   Please note that thanks to different implementations, the outputs of
%   simplenn and Caffe models are not neccessarily identical.
%
%   SIMPLENN_CAFFE_DEPLOY(NET, CAFFE_BASE_MODELNAME, 'OPT', VAL, ...)
%   takes the following options:
%
%   `removeDropout`:: `true`
%      When true, do not deploy dropout layers.
%
%   `replaceSoftMaxLoss`:: `true`
%      Replace SoftMax log loss with SoftMax.
%
%   `doTest`:: `true`
%      Compare the caffe model to the simplenn model using
%      `simplenn_caffe_compare`.
%
%   `testData`:: Random
%      Perform the test on the given data.
%
%   `inputBlobName`:: 'data'
%      Name of the input data blob in the final protobuf.
%
%   `labelBlobName`:: 'label'
%      Name of the input label blob in the final protobuf.
%
%   `outputBlobName`:: 'prob'
%      Name of the output blob in the resulting protobuf.
%
%   `silent`:: false
%      When true, suppresses all output to stdout.
%
%  See Also: simplenn_caffe_compare

% Copyright (C) 2015-16 Zohar Bar-Yehuda, Karel Lenc
% All rights reserved.
%
% This file is part of the VLFeat library and is made available under
% the terms of the BSD license (see the COPYING file).

opts.inputBlobName = 'data';
opts.outputBlobName = 'prob';
opts.labelBlobName = 'label';
opts.removeDropout = true;
opts.replaceSoftMaxLoss = true;
opts.doTest = true;
opts.testData = [];
opts.silent = false;
opts = vl_argparse(opts, varargin);
if ~exist('caffe.Net', 'class'), error('MatCaffe not in path.'); end

info = @(varargin) fprintf(1, varargin{:});
if opts.silent, info = @(varargin) []; end;

info('Exporting simplenn model to caffe model %s\n', caffeModelBaseName);
[modelDir, name] = fileparts(caffeModelBaseName);
[~,~,~] = mkdir(modelDir);

% -------------------------------------------------------------------------
%                                                          Tidy the network
% -------------------------------------------------------------------------
net = vl_simplenn_tidy(net);
net = vl_simplenn_move(net, 'cpu');
% Remove dropout layers
if opts.removeDropout
  net.layers(cellfun(@(l) strcmp(l.type, 'dropout'), net.layers)) = [];
end

if opts.replaceSoftMaxLoss
  % If last layer is softmax loss, replace it with softmax
  ll = net.layers{end};
  if strcmp(ll.type, 'softmaxloss') || ...
      (strcmp(ll.type, 'loss') && strcmp(ll.loss, 'softmaxlog'))
    net.layers{end}.type = 'softmax';
  elseif isequal(net.layers{end}.type, 'loss')
    error('Unsupported loss function: %s', net.layers{end}.loss);
  end
end

for idx = 1:numel(net.layers)
  % Add missing layer names
  if ~isfield(net.layers{idx}, 'name')
    net.layers{idx}.name = sprintf('layer%d', idx);
  end
end

avImage = [];
if isfield(net.meta, 'normalization') && ...
    isfield(net.meta.normalization, 'imageSize')
  imSize = net.meta.normalization.imageSize;
  if isfield(net.meta.normalization, 'averageImage')
    avImage = net.meta.normalization.averageImage;
    if numel(avImage) == imSize(3)
      avImage = reshape(avImage, 1, 1, imSize(3));
    end
  end
else
  error('Missing image size. Please set `net.normalization.imageSize`.');
end

% -------------------------------------------------------------------------
%                                                           Export prototxt
% -------------------------------------------------------------------------
prototxtFilename = [caffeModelBaseName '.prototxt'];
fid = fopen(prototxtFilename, 'w');

fprintf(fid, 'name: "%s"\n\n', name); % Network name

% Export input dimensions
fprintf(fid, 'input: "data"\n');
fprintf(fid, 'input_dim: 1\n');
fprintf(fid, 'input_dim: %d\n', imSize(3));
fprintf(fid, 'input_dim: %d\n', imSize(1));
fprintf(fid, 'input_dim: %d\n\n', imSize(2));

dummyData = zeros(imSize, 'single'); % Keep track of data size at each layer;

isFullyConnected = false(size(net.layers));
for idx = 1:numel(net.layers)
  % write layers
  fprintf(fid,'layer {\n');
  fprintf(fid,'  name: "%s"\n', net.layers{idx}.name); % Layer name
  layerInputSize = size(dummyData);
  if numel(layerInputSize) == 2
    layerInputSize(3) = 1;
  end
  switch net.layers{idx}.type
    case 'conv'
      filtH = size(net.layers{idx}.weights{1},1);
      filtW = size(net.layers{idx}.weights{1},2);
      if filtH < layerInputSize(1) || filtW < layerInputSize(2)
        % Convolution layer
        fprintf(fid, '  type: "Convolution"\n');
        write_connection(fid, net.layers, idx);
        fprintf(fid, '  convolution_param {\n');
        write_kernel(fid, [filtH, filtW]);
        fprintf(fid, '    num_output: %d\n', size(net.layers{idx}.weights{1},4));
        write_stride(fid, net.layers{idx}.stride);
        if isfield(net.layers{idx}, 'pad') && numel(net.layers{idx}.pad) == 4
          % Make sure pad is symmetrical
          if any(net.layers{idx}.pad([1, 3]) ~= net.layers{idx}.pad([2, 3]))
            error('Caffe only supports symmetrical padding');
          end
        end
        write_pad(fid, net.layers{idx}.pad);
        numGroups = layerInputSize(3) / size(net.layers{idx}.weights{1}, 3);
        assert(mod(numGroups, 1) == 0);
        if numGroups > 1
          fprintf(fid, '    group: %d\n', numGroups);
        end

        fprintf(fid, '  }\n');
      elseif filtH == layerInputSize(1) && filtW == layerInputSize(2)
        % Fully connected layer
        isFullyConnected(idx) = true;
        fprintf(fid, '  type: "InnerProduct"\n');
        write_connection(fid, net.layers, idx);
        fprintf(fid, '  inner_product_param {\n');
        fprintf(fid, '    num_output: %d\n', ...
          size(net.layers{idx}.weights{1}, 4));
        fprintf(fid, '  }\n');
      else
        error('Filter size (%d,%d) is larger than input size (%d,%d)', ...
          filtH, filtW, layerInputSize(1), layerInputSize(2))
      end

    case 'relu'
      fprintf(fid, '  type: "ReLU"\n');
      write_connection(fid, net.layers, idx);

    case 'pool'
      fprintf(fid, '  type: "Pooling"\n');
      % Check padding compatability with caffe. See:
      % http://www.vlfeat.org/matconvnet/matconvnet-manual.pdf
      % for more details.
      if numel(net.layers{idx}.pad) == 1
        net.layers{idx}.pad = repmat(net.layers{idx}.pad, 1, 4);
      end
      if numel(net.layers{idx}.stride) == 1
        net.layers{idx}.stride = repmat(net.layers{idx}.stride, 1, 2);
      end
      if numel(net.layers{idx}.pool) == 1
        net.layers{idx}.pool = repmat(net.layers{idx}.pool, 1, 2);
      end
      pad = net.layers{idx}.pad;
      if pad([2, 4]) == net.layers{idx}.pool - 1
        pad([2, 4]) = 0;
      else
        pad([2, 4]) = pad([2, 4]) - net.layers{idx}.stride + 1;
      end
      % Some older versions did not use these upper bounds
      pad = max(pad, 0);

      write_connection(fid, net.layers, idx);
      fprintf(fid, '  pooling_param {\n');
      switch (net.layers{idx}.method)
        case 'max'
          caffe_pool = 'MAX';
        case 'avg'
          caffe_pool = 'AVE';
        otherwise
          error('Unknown pooling type');
      end
      fprintf(fid, '    pool: %s\n', caffe_pool);
      write_kernel(fid, net.layers{idx}.pool);
      write_stride(fid, net.layers{idx}.stride);
      write_pad(fid, pad);
      fprintf(fid, '  }\n');

    case {'normalize', 'lrn'}
      % MATLAB param = [local_size, kappa, alpha/local_size, beta]
      fprintf(fid, '  type: "LRN"\n');
      write_connection(fid, net.layers, idx);
      fprintf(fid, '  lrn_param {\n');
      fprintf(fid, '    local_size: %d\n', net.layers{idx}.param(1));
      fprintf(fid, '    k: %f\n', net.layers{idx}.param(2));
      fprintf(fid, '    alpha: %f\n', net.layers{idx}.param(3)*net.layers{idx}.param(1));
      fprintf(fid, '    beta: %f\n', net.layers{idx}.param(4));
      fprintf(fid, '  }\n');

    case 'softmax'
      fprintf(fid, '  type: "Softmax"\n');
      write_connection(fid, net.layers, idx);

    case {'loss', 'softmaxloss'}
      fprintf(fid, '  type: "SoftmaxWithLoss"\n');
      write_connection(fid, net.layers, idx, true);

    case 'dropout'
      fprintf(fid, '  type: "Dropout"\n');
      write_connection(fid, net.layers, idx);
      fprintf(fid, '  dropout_param {\n');
      fprintf(fid, '    dropout_ratio: %d\n', net.layers{idx}.rate);
      fprintf(fid, '  }\n');

    otherwise
      error('Unknown layer type: %s', net.layers{idx}.type);
  end
  fprintf(fid,'}\n\n');
  layer = struct('layers', {net.layers(idx)});
  res = vl_simplenn(layer, dummyData);
  dummyData = res(end).x;
end
fclose(fid);
info('Network definition exported to: %s.\n', prototxtFilename);

% -------------------------------------------------------------------------
%                                                         Export caffemodel
% -------------------------------------------------------------------------
caffe.set_mode_cpu();
caffeNet = caffe.Net(prototxtFilename, 'test');
firstConv = true;
for idx = 1:numel(net.layers)
  layer_type = net.layers{idx}.type;
  layer_name = net.layers{idx}.name;
  switch layer_type
    case 'conv'
      filters = net.layers{idx}.weights{1};
      % Convert from HxWxCxN to WxHxCxN per Caffe's convention
      filters = permute(filters, [2 1 3 4]);
      if firstConv
        if size(filters, 3) == 3
          % We assume this is RGB Conv., need to convert RGB to BGR
          filters = filters(:,:, [3 2 1], :);
        end
        firstConv = false; % Do this only for first convolution;
      end
      if isFullyConnected(idx)
        % Fully connected layer, squeeze to 2 dims
        filters = reshape(filters, [], size(filters, 4));
      end
      biases = net.layers{idx}.weights{2}(:);
      caffeNet.layers(layer_name).params(1).set_data(filters); % set weights
      caffeNet.layers(layer_name).params(2).set_data(biases); % set bias
    case {'relu', 'normalize', 'lrn', 'pool', 'softmax'}
      % No weights - nothing to do
    otherwise
      error('Unknown layer type %s', layer_type)
  end
end
modelFilename = [caffeModelBaseName '.caffemodel'];
caffeNet.save(modelFilename);
delete(caffeNet);
info('Model file exported to: %s.\n', modelFilename);

% -------------------------------------------------------------------------
%                                                        Export mean image
% -------------------------------------------------------------------------
if ~isempty(avImage)
  if size(avImage, 1) == 1 && size(avImage, 2) == 1
    % Single value, we'll duplicate it to im_size
    avImage = repmat(avImage, imSize(1), imSize(2));
  end
  avImage = matlab_img_to_caffe(avImage);
  meanFilename = [caffeModelBaseName, '_mean_image.binaryproto'];
  caffe.io.write_mean(avImage, meanFilename)
  info('Mean image exported to: %s.\n', meanFilename);
end

if opts.doTest
  simplenn_caffe_compare(net, caffeModelBaseName, opts.testData);
end

  function write_stride(fid, stride)
      if numel(stride) == 1
        fprintf(fid, '    stride: %d\n', stride);
      elseif numel(stride) == 2
        fprintf(fid, '    stride_h: %d\n', stride(1));
        fprintf(fid, '    stride_w: %d\n', stride(2));
      end
  end

  function write_kernel(fid, kernelSize)
    if numel(kernelSize) == 1
      fprintf(fid, '    kernel_size: %d\n', kernelSize);
    elseif numel(kernelSize) == 2
      fprintf(fid, '    kernel_h: %d\n', kernelSize(1));
      fprintf(fid, '    kernel_w: %d\n', kernelSize(2));
    end
  end

  function write_pad(fid, pad)
    if numel(pad) == 1
      fprintf(fid, '    pad: %d\n', pad);
    elseif numel(pad) == 4
      fprintf(fid, '    pad_h: %d\n', pad(1));
      fprintf(fid, '    pad_w: %d\n', pad(2));
    else
      error('pad vector size must be 1 or 4')
    end
  end

  function write_connection(fid, layers, idx, isLoss)
    if idx == 1
      bottom_name = opts.inputBlobName;
    else
      bottom_name = layers{idx-1}.name;
    end
    top_name = layers{idx}.name;
    if idx == numel(layers) && ~isempty(opts.outputBlobName)
      top_name = opts.outputBlobName;
    end
    fprintf(fid, '  bottom: "%s"\n', bottom_name);
    if nargin > 3 && isLoss
      fprintf(fid, '  bottom: "%s"\n', opts.labelBlobName);
    end
    fprintf(fid, '  top: "%s"\n', top_name);
  end

  function img = matlab_img_to_caffe(img)
    img = single(img);
    % Convert from HxWxCxN to WxHxCxN per Caffe's convention
    img = permute(img, [2 1 3 4]);
    if size(img,3) == 3
      % Convert from RGB to BGR channel order per Caffe's convention
      img = img(:,:, [3 2 1], :);
    end
  end
end


function slice_regressors = tapas_physio_split_regressor_slices(...
    slice_regressors_concat, nSampleSlices)
% Splits a concatenated phase regressor into slice-wise regressors, one per
% column
%
%   slice_regressors = tapas_physio_split_regressor_slices(...
%       slice_regressors_concat, nSampleSlices)
%
% IN
%   slice_regressors_concat [nSamples*nSlices, nRegressors] - matrix which
%                           per column has entries of the form:
%                           regressor value volume 1, slice 1
%                                           volume 1, slice 2
%                                           ...
%                                           volume 1, slice nSampleSlices
%                                           volume 2, slice 1
%                                           volume 2, slice 2
%
% OUT
%
% EXAMPLE
%   tapas_physio_split_regressor_slices
%
%   See also

% Author: Lars Kasper
% Created: 2014-08-14
% Copyright (C) 2014 TNU, Institute for Biomedical Engineering, University of Zurich and ETH Zurich.
%
% This file is part of the TAPAS 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/>.

% make multiple columns for multiple slices
nRegressors = size(slice_regressors_concat,2);
slice_regressors = [];
for iRegressor = 1:nRegressors
    slice_regressors = [slice_regressors, ...
        reshape(reshape(slice_regressors_concat(:,iRegressor),1,[]), ...
        [], nSampleSlices)];
end


function[struct]=structindex(struct,index)
%STRUCTINDEX  Applies an index to all array-valued fields in a structure.
%
%   STRUCTOUT=STRUCTINDEX(STRUCT,INDEX), where STRUCT is a structure
%   containing a set of fields that are non-string arrays all having the
%   same length N>1, applies INDEX to all of them and returns the result in
%   STRUCTOUT.  All other fields of STRUCT are left unchanged.
%
%   STRUCTINDEX(STRUCT,BOOL) where BOOL is a boolean array of length N also
%   works.
%
%   STRUCTINDEX is useful for dealing with the ridge structures output by
%   EDDYRIDGES.
%
%   See also EDDYRIDGES.
%
%   Usage: struct=structindex(struct,index);
%          struct=structindex(struct,bool);
%   __________________________________________________________________
%   This is part of JLAB --- type 'help jlab' for more information
%   (C) 2019 J.M. Lilly --- type 'help jlab_license' for details

names=fieldnames(struct);
for i=1:length(names)
    temp=getfield(struct,names{i});
    if length(temp)~=1&&~ischar(temp)
        %names{i}
        temp=temp(index);
        struct=setfield(struct,names{i},temp);
    end
end


% load Human-GEM
model = importYaml('../../model/Human-GEM.yml');

% specify pairs of duplicate reactions, where reactions in first column are
% those that will be kept and those in the second column removed
rxns = {
    'MAR08971','MAR04306'
    'MAR00121','MAR00607'
    'MAR04411','MAR04911'
    'MAR00079','MAR06324'
    'MAR00101','MAR02199'
    'MAR02339','MAR00445'
    'MAR01728','MAR07441'
    'MAR01827','MAR07742'
    'MAR11237','MAR04455'
    'MAR11251','MAR04463'
    'MAR11252','MAR04478'
    'MAR11253','MAR04502'
    'MAR11254','MAR04517'
    'MAR11247','MAR04576'
    'MAR11249','MAR04609'
    'MAR11248','MAR04622'
    'MAR11255','MAR04626'
    'MAR11256','MAR04653'
    'MAR11257','MAR04659'
    'MAR11258','MAR04669'
    'MAR11260','MAR04713'
    'MAR11261','MAR04724'
    'MAR11262','MAR04747'
    'MAR11263','MAR04751'
    'MAR11264','MAR04753'
    'MAR11265','MAR04761'
    'MAR11238','MAR04794'
    'MAR11266','MAR04795'
    'MAR11267','MAR04798'
    'MAR11239','MAR04800'
    'MAR11268','MAR04801'
    'MAR11240','MAR06494'
    'MAR11241','MAR06497'
    'MAR11242','MAR06504'
    'MAR11243','MAR06541'
    'MAR11244','MAR06582'
    'MAR11246','MAR06590'
    'MAR11271','MAR06594'
    'MAR11245','MAR06706'
    'MAR11280','MAR06853'
    'MAR11278','MAR06856'
    'MAR11279','MAR06857'
    'MAR11259','MAR06858'
    'MAR09057','MAR07191'};

% verify that the reaction pairs have identical stoichiometry (either forward or reverse)
for i = 1:size(rxns, 1)
    indx = getIndexes(model, rxns(i,:), 'rxns');
    coeffs = full(model.S(any(model.S(:, indx) ~= 0, 2), indx));
    coeffs_norm = coeffs ./ max(abs(coeffs), [], 1);
    if ~isequal(coeffs_norm(:,1), coeffs_norm(:,2)) && ...
            ~isequal(-coeffs_norm(:,1), coeffs_norm(:,2))
        error('Reactions in row %u are not duplicates!', i);
    end
end

% normalize the stoichiometric coefficients of MAR08971
%  currently: 3 glucose-6-phosphate[c] + 3 NADP+[c] => 3 glucono-1,5-lactone-6-phosphate[c] + 3 H+[c] + 3 NADPH[c]
% normalized:   glucose-6-phosphate[c] +   NADP+[c] =>   glucono-1,5-lactone-6-phosphate[c] +   H+[c] +   NADPH[c]
rxn_indx = getIndexes(model, 'MAR08971', 'rxns');
model.S(:, rxn_indx) = model.S(:, rxn_indx) / 3;

% merge rxn annotations from the deleted duplicate rxn into the kept rxn
rxnAssocFile = '../../model/reactions.tsv';
rxnAssoc = importTsvFile(rxnAssocFile);
[~, rxn_indx] = ismember(rxns, rxnAssoc.rxns);
mergeFields = setdiff(fieldnames(rxnAssoc), {'rxns', 'rxnRetired', 'spontaneous'});
for i = 1:size(rxns, 1)
    for f = 1:numel(mergeFields)
        entry1 = strsplit(rxnAssoc.(mergeFields{f}){rxn_indx(i,1)}, ';');
        entry2 = strsplit(rxnAssoc.(mergeFields{f}){rxn_indx(i,2)}, ';');
        merged_entry = setdiff(union(entry1, entry2), {''});
        if ~isempty(merged_entry)
            rxnAssoc.(mergeFields{f}){rxn_indx(i,1)} = strjoin(merged_entry, ';');
        end
    end
end

% save annotations for to-be-deleted reactions to the deprecated rxns file
depRxnFile = '../../data/deprecatedIdentifiers/deprecatedReactions.tsv';
deprecRxns = importTsvFile(depRxnFile);
deprecRxnsTable = struct2table(deprecRxns);
rxnAssocTable = struct2table(rxnAssoc);
exportTsvFile([deprecRxnsTable; rxnAssocTable(rxn_indx(:,2), :)], depRxnFile);

% merge additional model fields from duplicate rxns with the kept rxns
mergeFields = {'eccodes'; 'rxnReferences'};
for i = 1:size(rxns, 1)
    for f = 1:numel(mergeFields)
        entry1 = strsplit(model.(mergeFields{f}){rxn_indx(i,1)}, ';');
        entry2 = strsplit(model.(mergeFields{f}){rxn_indx(i,2)}, ';');
        merged_entry = setdiff(union(entry1, entry2), {''});
        if ~isempty(merged_entry)
            model.(mergeFields{f}){rxn_indx(i,1)} = strjoin(merged_entry, ';');
        end
    end
end

% delete reactions from model and annotation file
model = removeReactions(model, rxns(:,2));
exportYaml(model, '../../model/Human-GEM.yml');
rxnAssocTable(rxn_indx(:,2), :) = [];
exportTsvFile(rxnAssocTable, rxnAssocFile);


function result = iscolumnvector(obj)
    result = isvector(obj) && size(obj,2) == 1 && size(obj,1) > 1 && ndims(obj) == 2;
end