function [data] = ft_megplanar(cfg, data)

% FT_MEGPLANAR computes planar MEG gradients gradients for raw data or average
% event-related field data. It can also convert frequency-domain data that was computed
% using FT_FREQANALYSIS, as long as it contains the complex-valued fourierspcrm and not
% only the powspctrm.
%
% Use as
%    [interp] = ft_megplanar(cfg, data)
% where the input data corresponds to the output from FT_PREPROCESSING,
% FT_TIMELOCKANALYSIS or FT_FREQANALYSIS (with output='fourier').
%
% The configuration should contain
%   cfg.planarmethod   = string, can be 'sincos', 'orig', 'fitplane', 'sourceproject' (default = 'sincos')
%   cfg.channel        =  Nx1 cell-array with selection of channels (default = 'all'), see FT_CHANNELSELECTION for details
%   cfg.trials         = 'all' or a selection given as a 1xN vector (default = 'all')
%
% The methods orig, sincos and fitplane are all based on a neighbourhood interpolation.
% For these methods you need to specify
%   cfg.neighbours     = neighbourhood structure, see FT_PREPARE_NEIGHBOURS
%
% In the 'sourceproject' method a minumum current estimate is done using a large number
% of dipoles that are placed in the upper layer of the brain surface, followed by a
% forward computation towards a planar gradiometer array. This requires the
% specification of a volume conduction model of the head and of a source model. The
% 'sourceproject' method is not supported for frequency domain data.
%
% A dipole layer representing the brain surface must be specified with
%   cfg.inwardshift = depth of the source layer relative to the head model surface ,
%                     (default = 2.5 cm, which is appropriate for a skin-based head model)
%   cfg.spheremesh  = number of dipoles in the source layer (default = 642)
%   cfg.tolerance   = tolerance ratio for leadfield matrix inverse based on a truncated svd,
%                     reflects the relative magnitude of the largest singular value
%                     to retain (default = 1e-3)
%   cfg.headshape   = a filename containing headshape, a structure containing a
%                     single triangulated boundary, or a Nx3 matrix with surface
%                     points
% If no headshape is specified, the dipole layer will be based on the inner compartment
% of the volume conduction model.
%
% Optionally, you can modify the leadfields by reducing the rank, i.e. remove the weakest orientation
%   cfg.reducerank    = 'no', or number (default = 3 for EEG, 2 for MEG)
%   cfg.backproject   = 'yes' or 'no',  determines when reducerank is applied whether the
%                       lower rank leadfield is projected back onto the original linear
%                       subspace, or not (default = 'yes')
%
% The volume conduction model of the head should be specified as
%   cfg.headmodel     = structure with volume conduction model, see FT_PREPARE_HEADMODEL
%
% The following cfg fields are optional:
%   cfg.feedback
%
% To facilitate data-handling and distributed computing you can use
%   cfg.inputfile   =  ...
%   cfg.outputfile  =  ...
% If you specify one of these (or both) the input data will be read from a *.mat
% file on disk and/or the output data will be written to a *.mat file. These mat
% files should contain only a single variable, corresponding with the
% input/output structure.
%
% See also FT_COMBINEPLANAR, FT_PREPARE_NEIGHBOURS

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

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

% do the general setup of the function
ft_defaults
ft_preamble init
ft_preamble debug
ft_preamble loadvar data
ft_preamble provenance data

% the ft_abort variable is set to true or false in ft_preamble_init
if ft_abort
  return
end

% store the original input representation of the data, this is used later on to convert it back
isfreq = ft_datatype(data, 'freq');
istlck = ft_datatype(data, 'timelock');  % this will be temporary converted into raw

% check if the input data is valid for this function, this converts the data if needed
data = ft_checkdata(data, 'datatype', {'raw' 'freq'}, 'feedback', 'yes', 'hassampleinfo', 'yes', 'ismeg', 'yes', 'senstype', {'ctf151', 'ctf275', 'bti148', 'bti248', 'itab153', 'yokogawa160', 'yokogawa64'});

if isfreq
  if ~isfield(data, 'fourierspctrm'), ft_error('freq data should contain Fourier spectra'); end
end

% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'forbidden',  {'channels', 'trial'}); % prevent accidental typos, see issue 1729
cfg = ft_checkconfig(cfg, 'renamed',    {'hdmfile', 'headmodel'});
cfg = ft_checkconfig(cfg, 'renamed',    {'vol',     'headmodel'});
cfg = ft_checkconfig(cfg, 'renamed',    {'grid',    'sourcemodel'});
cfg = ft_checkconfig(cfg, 'renamed',    {'pruneratio', 'tolerance'});

% set the default configuration
cfg.channel      = ft_getopt(cfg, 'channel',      'all');
cfg.tolerance    = ft_getopt(cfg, 'tolerance',    1e-3);
cfg.trials       = ft_getopt(cfg, 'trials',       'all', 1);
cfg.planarmethod = ft_getopt(cfg, 'planarmethod', 'sincos');
cfg.feedback     = ft_getopt(cfg, 'feedback',     'text');

% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'renamedval',  {'headshape', 'headmodel', []});

if ~strcmp(cfg.planarmethod, 'sourceproject')
  % this is limited to reading neighbours from disk and/or selecting channels
  % the user should call FT_PREPARE_NEIGHBOURS directly for the actual construction
  tmpcfg = keepfields(cfg, {'neighbours', 'channel', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
  cfg.neighbours = ft_prepare_neighbours(tmpcfg);
end

% put the low-level options pertaining to the dipole grid in their own field
cfg = ft_checkconfig(cfg, 'renamed', {'tightgrid', 'tight'}); % this is moved to cfg.sourcemodel.tight by the subsequent createsubcfg
cfg = ft_checkconfig(cfg, 'renamed', {'sourceunits', 'unit'}); % this is moved to cfg.sourcemodel.unit by the subsequent createsubcfg

% put the low-level options pertaining to the sourcemodel in their own field
cfg = ft_checkconfig(cfg, 'createsubcfg', {'sourcemodel'});
% move some fields from cfg.sourcemodel back to the top-level configuration
cfg = ft_checkconfig(cfg, 'createtopcfg', {'sourcemodel'});

% select trials of interest
tmpcfg = keepfields(cfg, {'trials', 'channel', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'}); % don't keep tolerance, it is used differently here
data = ft_selectdata(tmpcfg, data);
% restore the provenance information
[cfg, data] = rollback_provenance(cfg, data);

if strcmp(cfg.planarmethod, 'sourceproject')
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % Do an inverse computation with a simplified distributed source model
  % and compute forward again with the axial gradiometer array replaced by
  % a planar one.
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

  % method specific configuration options
  cfg.headshape   = ft_getopt(cfg, 'headshape',   []);
  cfg.inwardshift = ft_getopt(cfg, 'inwardshift', 2.5); % this number assumes that all other inputs are in cm
  cfg.pruneratio  = ft_getopt(cfg, 'pruneratio',  1e-3);
  cfg.spheremesh  = ft_getopt(cfg, 'spheremesh',  642);

  if isfreq
    ft_error('the method ''sourceproject'' is not supported for frequency data as input');
  end

  % PREPARE_HEADMODEL will match the data labels, the gradiometer labels and the
  % volume model labels (in case of a localspheres model) and result in a gradiometer
  % definition that only contains the gradiometers that are present in the
  % data. This should exclude the non-MEG channels, so the user-defined
  % cfg.channel should be overruled
  tmpcfg = cfg;
  tmpcfg.channel = ft_channelselection('MEG', cfg.channel);
  [headmodel, axial.grad, tmpcfg] = prepare_headmodel(tmpcfg, data);

  % construct the low-level options for the leadfield computation as key-value pairs, these are passed to FT_COMPUTE_LEADFIELD
  leadfieldopt = {};
  leadfieldopt = ft_setopt(leadfieldopt, 'reducerank',     ft_getopt(cfg, 'reducerank'));
  leadfieldopt = ft_setopt(leadfieldopt, 'backproject',    ft_getopt(cfg, 'backproject'));
  leadfieldopt = ft_setopt(leadfieldopt, 'normalize',      ft_getopt(cfg, 'normalize'));
  leadfieldopt = ft_setopt(leadfieldopt, 'normalizeparam', ft_getopt(cfg, 'normalizeparam'));
  leadfieldopt = ft_setopt(leadfieldopt, 'weight',         ft_getopt(cfg, 'weight'));

  % copy all options that are potentially used in FT_PREPARE_SOURCEMODEL
  tmpcfg           = keepfields(cfg, {'sourcemodel', 'mri', 'headshape', 'symmetry', 'smooth', 'threshold', 'spheremesh', 'inwardshift', 'xgrid' 'ygrid', 'zgrid', 'resolution', 'tight', 'warpmni', 'template', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
  tmpcfg.headmodel = headmodel;
  tmpcfg.grad      = axial.grad;
  % determine the dipole layer that represents the surface of the brain
  sourcemodel = ft_prepare_sourcemodel(tmpcfg);

  % compute the forward model for the axial gradiometers
  ft_info('computing forward model for %d dipoles\n', size(sourcemodel.pos,1));
  lfold = ft_compute_leadfield(sourcemodel.pos, axial.grad, headmodel, leadfieldopt{:});

  % construct the planar gradient definition and compute its forward model
  % this will not work for a localspheres model, compute_leadfield will catch
  % the error
  planar.grad = constructplanargrad([], axial.grad);
  lfnew = ft_compute_leadfield(sourcemodel.pos, planar.grad, headmodel, leadfieldopt{:});

  % compute the interpolation matrix
  transform = lfnew * ft_inv(lfold, 'method', 'tsvd', 'tolerance', cfg.tolerance);

  planarmontage = [];
  planarmontage.tra      = transform;
  planarmontage.labelold = axial.grad.label;
  planarmontage.labelnew = planar.grad.label;

  % apply the linear transformation to the data
  interp  = ft_apply_montage(data, planarmontage, 'keepunused', 'yes');

  % also apply the linear transformation to the gradiometer definition
  interp.grad = ft_apply_montage(data.grad, planarmontage, 'balancename', 'planar', 'keepunused', 'yes');

  % ensure there is a type string describing the gradiometer definition
  if ~isfield(interp.grad, 'type')
    interp.grad.type = [ft_senstype(data.grad) '_planar'];
  else
    interp.grad.type = [interp.grad.type '_planar'];
  end

  %   % interpolate the data towards the planar gradiometers
  %   for i=1:Ntrials
  %     ft_info('interpolating trial %d to planar gradiometer\n', i);
  %     interp.trial{i} = transform * data.trial{i}(dataindx,:);
  %   end % for Ntrials
  %
  %   % all planar gradiometer channels are included in the output
  %   interp.grad  = planar.grad;
  %   interp.label = planar.grad.label;
  %
  %   % copy the non-gradiometer channels back into the output data
  %   other = setdiff(1:Nchan, dataindx);
  %   for i=other
  %     interp.label{end+1} = data.label{i};
  %     for j=1:Ntrials
  %       interp.trial{j}(end+1,:) = data.trial{j}(i,:);
  %     end
  %   end
  %
else

  sens = ft_determine_units(data.grad);
  chanposnans = any(isnan(sens.chanpos(:))) || any(isnan(sens.chanori(:)));
  if chanposnans
    if isfield(sens, 'chanposold')
      % temporarily replace chanpos and chanorig with the original values
      sens.chanpos = sens.chanposold;
      sens.chanori = sens.chanoriold;
      sens.label   = sens.labelold;
      sens = rmfield(sens, {'chanposold', 'chanoriold', 'labelold'});
    else
      ft_error('The channel positions (and/or orientations) contain NaNs; this prohibits correct behavior of the function. Please replace the input channel definition with one that contains valid channel positions');
    end
  end
  cfg.channel = ft_channelselection(cfg.channel, sens.label);
  cfg.channel = ft_channelselection(cfg.channel, data.label);

  % ensure channel order according to cfg.channel (there might be one check
  % too much in here somewhere or in the subfunctions, but I don't care.
  % Better one too much than one too little - JMH @ 09/19/12
  cfg = struct(cfg);
  [neighbsel] = match_str({cfg.neighbours.label}, cfg.channel);
  cfg.neighbours = cfg.neighbours(neighbsel);
  cfg.neighbsel = channelconnectivity(cfg);

  assert(any(cfg.neighbsel(:)), 'no neighbours found')
  ft_info('average number of neighbours is %.2f\n', mean(sum(cfg.neighbsel)));

  Ngrad = length(sens.label);
  distance = zeros(Ngrad,Ngrad);

  for i=1:size(cfg.neighbsel,1)
    j=find(cfg.neighbsel(i, :));
    d = sqrt(sum((sens.chanpos(j,:) - repmat(sens.chanpos(i, :), numel(j), 1)).^2, 2));
    distance(i,j) = d;
    distance(j,i) = d;
  end

  ft_info('minimum distance between neighbours is %6.2f %s\n', min(distance(distance~=0)), sens.unit);
  ft_info('maximum distance between gradiometers is %6.2f %s\n', max(distance(distance~=0)), sens.unit);

  % The following does not work when running in deployed mode because the
  % private functions that compute the planar montage are not recognized as
  % such and won't be compiled, unless explicitly specified.

  % % generically call megplanar_orig megplanar_sincos or megplanar_fitplane
  %fun = ['megplanar_'  cfg.planarmethod];
  %if ~exist(fun, 'file')
  %  ft_error('unknown method for computation of planar gradient');
  %end
  %planarmontage = eval([fun '(cfg, data.grad)']);

  switch cfg.planarmethod
    case 'sincos'
      planarmontage = megplanar_sincos(cfg, sens);
    case 'orig'
      % method specific info that is needed
      cfg.distance  = distance;

      planarmontage = megplanar_orig(cfg, sens);
    case 'fitplane'
      planarmontage = megplanar_fitplane(cfg, sens);
    otherwise
      fun = ['megplanar_' cfg.planarmethod];
      if ~exist(fun, 'file')
        ft_error('unknown method for computation of planar gradient');
      end
      planarmontage = eval([fun '(cfg, data.grad)']);
  end

  % apply the linear transformation to the data
  interp = ft_apply_montage(data, planarmontage, 'keepunused', 'yes', 'feedback', cfg.feedback);

  % also apply the linear transformation to the gradiometer definition
  interp.grad = ft_apply_montage(sens, planarmontage, 'balancename', 'planar', 'keepunused', 'yes');

  % ensure there is a type string describing the gradiometer definition
  if ~isfield(interp.grad, 'type')
    % put the original gradiometer type in (will get _planar appended)
    interp.grad.type = ft_senstype(sens);
  end
  interp.grad.type = [interp.grad.type '_planar'];

  % add the chanpos info back into the gradiometer description
  tmplabel = interp.grad.label;
  for k = 1:numel(tmplabel)
    if ~isempty(strfind(tmplabel{k}, '_dV')) || ~isempty(strfind(tmplabel{k}, '_dH'))
      tmplabel{k} = tmplabel{k}(1:end-3);
    end
  end
  [ix,iy] = match_str(tmplabel, sens.label);
  interp.grad.chanpos(ix,:) = sens.chanpos(iy,:);

  % if the original chanpos contained nans, make sure to put nans in the
  % updated one as well, and move the updated chanpos values to chanposold
  if chanposnans
    interp.grad.chanposold = sens.chanpos;
    interp.grad.chanoriold = sens.chanori;
    interp.grad.labelold   = sens.label;
    interp.grad.chanpos    = nan(size(interp.grad.chanpos));
    interp.grad.chanori    = nan(size(interp.grad.chanori));
  end
end

if istlck
  % convert the raw structure back into a timelock structure
  interp = ft_checkdata(interp, 'datatype', 'timelock');
end

% copy the trial specific information into the output
if isfield(data, 'trialinfo')
  interp.trialinfo = data.trialinfo;
end

% copy the sampleinfo field as well
if isfield(data, 'sampleinfo')
  interp.sampleinfo = data.sampleinfo;
end

% do the general cleanup and bookkeeping at the end of the function
ft_postamble debug
ft_postamble previous data

% rename the output variable to accomodate the savevar postamble
data = interp;

ft_postamble provenance data
ft_postamble history    data
ft_postamble savevar    data


function varargout = meshReduce(nodes, varargin)
%MESHREDUCE Merge coplanar faces of a polyhedral mesh
%
%   Note: deprecated, should use "mergeCoplanarFaces" instead
%
%   [NODES FACES] = meshReduce(NODES, FACES)
%   [NODES EDGES FACES] = meshReduce(NODES, EDGES, FACES)
%   NODES is a set of 3D points (as a Nn-by-3 array),
%   and FACES is one of:
%   - a Nf-by-X array containing vertex indices of each face, with each
%   face having the same number of vertices,
%   - a Nf-by 1 cell array, each cell containing indices of a face.
%   The function groups faces which are coplanar and contiguous, resulting
%   in a "lighter" mesh. This can be useful for visualizing binary 3D
%   images for example.
%
%   FACES = meshReduce(..., PRECISION)
%   Adjust the threshold for deciding if two faces are coplanar or
%   parallel. Default value is 1e-14.
%
%   Example
%   [n e f] = createCube;
%   figure; drawMesh(n, f); view(3); axis equal;
%   f2 = meshReduce(n, f);
%   figure; drawMesh(n, f2); view(3); axis equal;
%
%   See also
%   meshes3d, mergeCoplanarFaces
%

% ------
% Author: David Legland
% e-mail: david.legland@grignon.inra.fr
% Created: 2006-07-05
% Copyright 2006 INRA - CEPIA Nantes - MIAJ (Jouy-en-Josas).

% 20/07/2006 add tolerance for coplanarity test
% 21/08/2006 fix small bug due to difference of methods to test
%   coplanaritity, sometimes resulting in 3 points of a face not coplanar !
%   Also add control on precision
% 14/08/2007 rename minConvexHull->meshReduce, and extend to non convex
%   shapes
% 2011-01-14 code clean up
% 2013-02-22 deprecate and rename to mergeCoplanarFaces

warning('MatGeom:meshes3d:deprecated', ...
    'Function ''meshReduce'' is deprecated, should use ''mergeCoplanarFaces'' instead');

%% Process input arguments

% set up precision
acc = 1e-14;
if ~isempty(varargin)
    var = varargin{end};
    if length(var)==1
        acc = var;
        varargin(end) = [];
    end
end

% extract faces and edges
if length(varargin)==1
    faces = varargin{1};
else
    faces = varargin{2};
end

%% Initialisations

% number of faces
Nn = size(nodes, 1);
Nf = size(faces, 1);

% compute number of vertices of each face
Fn = ones(Nf, 1)*size(faces, 2);

% compute normal of each faces
normals = faceNormal(nodes, faces);

% initialize empty faces and edges
faces2  = cell(0, 1);
edges2  = zeros(0, 2);

% Processing flag for each face
% 1: face to process, 0: already processed
% in the beginning, every triangle face need to be processed
flag = ones(Nf, 1);

%% Main iteration

% iterate on each  face
for f = 1:Nf

    % check if face was already performed
    if ~flag(f)
        continue;
    end

    % indices of faces with same normal
    ind = find(abs(vectorNorm3d(cross(repmat(normals(f, :), [Nf 1]), normals)))<acc);
    %ind = ind(ind~=i);

    % keep only coplanar faces (test coplanarity of points in both face)
    ind2 = false(size(ind));
    for j = 1:length(ind)
        ind2(j) = isCoplanar(nodes([faces(f,:) faces(ind(j),:)], :), acc);
    end
    ind2 = ind(ind2);

    % compute edges of all faces in the plane
    planeEdges  = zeros(sum(Fn(ind2)), 2);
    pos  = 1;
    for i=1:length(ind2)
        face = faces(ind2(i), :);
        faceEdges = sort([face' face([2:end 1])'], 2);
        planeEdges(pos:sum(Fn(ind2(1:i))), :) = faceEdges;
        pos = sum(Fn(ind2(1:i)))+1;
    end
    planeEdges = unique(planeEdges, 'rows');

    % relabel plane edges, and find connected components
    [planeNodes, I, J] = unique(planeEdges(:)); %#ok<ASGLU>
    planeEdges2 = reshape(J, size(planeEdges));
    component   = grLabel(nodes(planeNodes, :), planeEdges2);

    % compute degree (number of adjacent faces) of each edge.
    Npe = size(planeEdges, 1);
    edgesDegree = zeros(Npe, 1);
    for i = 1:length(ind2)
        face = faces(ind2(i), :);
        faceEdges = sort([face' face([2:end 1])'], 2);
        for j = 1:size(faceEdges, 1)
            indEdge = find(sum(ismember(planeEdges, faceEdges(j,:)),2)==2);
            edgesDegree(indEdge) = edgesDegree(indEdge)+1;
        end
    end

    % extract unique edges and nodes of the plane
    planeEdges  = planeEdges(edgesDegree==1, :);
    planeEdges2 = planeEdges2(edgesDegree==1, :);

    % find connected component of each edge
    planeEdgesComp = zeros(size(planeEdges, 1), 1);
    for e = 1:size(planeEdges, 1)
        planeEdgesComp(e) = component(planeEdges2(e, 1));
    end

    % iterate on connected faces
    for c=1:max(component)

        % convert to chains of nodes
        loops = graph2Contours(nodes, planeEdges(planeEdgesComp==c, :));

        % add a simple Polygon for each loop
        facePolygon = loops{1};
        for l = 2:length(loops)
            facePolygon = [facePolygon, NaN, loops{l}]; %#ok<AGROW>
        end
        faces2{length(faces2)+1, 1}  = facePolygon;

        % also add news edges
        edges2 = unique([edges2; planeEdges], 'rows');
    end

    % mark processed faces
    flag(ind2) = 0;
end

%% Additional processing on nodes

% select only nodes which appear in at least one edge
indNodes = unique(edges2(:));

% for each node, compute index of corresponding new node (or 0 if dropped)
refNodes = zeros(Nn, 1);
for i=1:length(indNodes)
    refNodes(indNodes(i)) = i;
end

% changes indices of nodes in edges2 array
for i=1:length(edges2(:))
    edges2(i) = refNodes(edges2(i));
end

% changes indices of nodes in faces2 array
for f=1:length(faces2)
    face = faces2{f};
    for i=1:length(face)
        if ~isnan(face(i))
            face(i) = refNodes(face(i));
        end
    end
    faces2{f} = face;
end

% keep only boundary nodes
nodes2 = nodes(indNodes, :);

%% Process output arguments

if nargout == 1
    varargout{1} = faces2;
elseif nargout == 2
    varargout{1} = nodes2;
    varargout{2} = faces2;
elseif nargout==3
    varargout{1} = nodes2;
    varargout{2} = edges2;
    varargout{3} = faces2;
end

function labels = grLabel(nodes, edges)
%GRLABEL associate a label to each connected component of the graph
%   LABELS = grLabel(NODES, EDGES)
%   Returns an array with as many rows as the array NODES, containing index
%   number of each connected component of the graph. If the graph is
%   totally connected, returns an array of 1.
%
%   Example
%       nodes = rand(6, 2);
%       edges = [1 2;1 3;4 6];
%       labels = grLabel(nodes, edges);
%   labels =
%       1
%       1
%       1
%       2
%       3
%       2
%
%   See also
%   getNeighbourNodes
%
%
% ------
% Author: David Legland
% e-mail: david.legland@grignon.inra.fr
% Created: 2007-08-14,    using Matlab 7.4.0.287 (R2007a)
% Copyright 2007 INRA - BIA PV Nantes - MIAJ Jouy-en-Josas.

% init
Nn = size(nodes, 1);
labels = (1:Nn)';

% iteration
modif = true;
while modif
    modif = false;

    for i=1:Nn
        neigh = getNeighbourNodes(i, edges);
        neighLabels = labels([i;neigh]);

        % check for a modification
        if length(unique(neighLabels))>1
            modif = true;
        end

        % put new labels
        labels(ismember(labels, neighLabels)) = min(neighLabels);
    end
end

% change to have fewer labels
labels2 = unique(labels);
for i=1:length(labels2)
    labels(labels==labels2(i)) = i;
end

function nodes2 = getNeighbourNodes(node, edges)
%GETNEIGHBOURNODES find nodes adjacent to a given node
%
%   NEIGHS = getNeighbourNodes(NODE, EDGES)
%   NODE: index of the node
%   EDGES: the complete edges list
%   NEIGHS: the nodes adjacent to the given node.
%
%   NODE can also be a vector of node indices, in this case the result is
%   the set of neighbors of any input node.
%
%
%   -----
%
%   author : David Legland
%   INRA - TPV URPOI - BIA IMASTE
%   created the 16/08/2004.
%

%   HISTORY
%   10/02/2004 documentation
%   13/07/2004 faster algorithm
%   03/10/2007 can specify several input nodes

[i, j] = find(ismember(edges, node));
nodes2 = edges(i,1:2);
nodes2 = unique(nodes2(:));
nodes2 = sort(nodes2(~ismember(nodes2, node)));

function curves = graph2Contours(nodes, edges)
%GRAPH2CONTOURS convert a graph to a set of contour curves
%
%   CONTOURS = GRAPH2CONTOURS(NODES, EDGES)
%   NODES, EDGES is a graph representation (type "help graph" for details)
%   The algorithm assume every node has degree 2, and the set of edges
%   forms only closed loops. The result is a list of indices arrays, each
%   array containing consecutive point indices of a contour.
%
%   To transform contours into drawable curves, please use :
%   CURVES{i} = NODES(CONTOURS{i}, :);
%
%
%   NOTE : contours are not oriented. To manage contour orientation, edges
%   also need to be oriented. So we must precise generation of edges.
%
%   -----
%
%   author : David Legland
%   INRA - TPV URPOI - BIA IMASTE
%   created the 05/08/2004.
%

curves = {};
c = 0;

while size(edges,1)>0
	% find first point of the curve
	n0 = edges(1,1);
    curve = n0;

    % second point of the curve
	n = edges(1,2);
	e = 1;

	while true
        % add current point to the curve
		curve = [curve n];

        % remove current edge from the list
        edges = edges((1:size(edges,1))~=e,:);

		% find index of edge containing reference to current node
		e = find(edges(:,1)==n | edges(:,2)==n);
        e = e(1);

		% get index of next current node
        % (this is the other node of the current edge)
		if edges(e,1)==n
            n = edges(e,2);
		else
            n = edges(e,1);
		end

        % if node is same as start node, loop is closed, and we stop
        % node iteration.
        if n==n0
            break;
        end
	end

    % remove the last edge of the curve from edge list.
    edges = edges((1:size(edges,1))~=e,:);

    % add the current curve to the list, and start a new curve
    c = c+1;
    curves{c} = curve;
end


function func_YUV_split
% Objective: extract YUV components from the CIF 4:2:0 video file, that is,
% split CIF file into three files, i.e., .Y, .U, .V.
%
% Jing Tian
% Contact: scuteejtian@hotmail.com
% This program is written in 2005 during my postgraduate studying in
% NTU, Singapore.

in_file_name = 'test.cif'
nFrame = 10;

[fid1 message]= fopen(in_file_name,'rb');

if length(strfind(in_file_name, '.qcif')) == 0
    nRow = 288;
    nColumn = 352;
    in_file_name2 = strrep(in_file_name, '.cif', '.Y');
    [fid2 message]= fopen(in_file_name2,'w');
    in_file_name3 = strrep(in_file_name, '.cif', '.U');
    [fid3 message]= fopen(in_file_name3,'w');
    in_file_name4 = strrep(in_file_name, '.cif', '.V');
    [fid4 message]= fopen(in_file_name4,'w');
else
    nRow = 288 / 2;
    nColumn = 352 / 2;
    in_file_name2 = strrep(in_file_name, '.qcif', '.Y');
    [fid2 message]= fopen(in_file_name2,'w');
    in_file_name3 = strrep(in_file_name, '.qcif', '.U');
    [fid3 message]= fopen(in_file_name3,'w');
    in_file_name4 = strrep(in_file_name, '.qcif', '.V');
    [fid4 message]= fopen(in_file_name4,'w');
end

for i = 1: nFrame
    %reading Y component
	img_y = fread(fid1, nRow * nColumn, 'uchar');
    img_y = reshape(img_y, nColumn, nRow);
    img_y = img_y';
    %reading U component
    img_u = fread(fid1, nRow * nColumn / 4, 'uchar');
    img_u = reshape(img_u, nColumn/2, nRow/2);
    img_u = img_u';
    %reading V component
    img_v = fread(fid1, nRow * nColumn / 4, 'uchar');
    img_v = reshape(img_v, nColumn/2, nRow/2);
    img_v = img_v';
    %writing file
	count = fwrite(fid2, img_y', 'uchar');
	count = fwrite(fid3, img_u', 'uchar');
	count = fwrite(fid4, img_v', 'uchar');
end

fclose(fid1);
fclose(fid2);
fclose(fid3);
fclose(fid4);

disp('ok');


classdef tree2

    properties
        % parent pointers
        pp = [];
        nodeNames;
        nodeFeatures_unnormalized =[];
        nodeFeatures = [];
        leafFeatures=[];
        % the parent pointers do not save which is the left and right child of each node, hence:
        % numNodes x 2 matrix of kids, [0 0] for leaf nodes
        kids = [];
        numkids = [];
        freq = [];
        nodeLabels=[];
        score=0;
        nodeScores=[];
        nodeZ = [];
        nodeDelta_out1 = [];
        nodeDelta_out2 = [];
        parentDelta = [];
        catDelta = [];
        catDelta_out = [];
        node_dW1 = [];
        node_dW2 = [];
        node_dW3 = [];
        node_dW4 = [];
        node_dL = [];
        node_y1c1 = [];
        node_y2c2 = [];

    end

    methods
        function id = getTopNode(obj)
            id = find(obj.pp==0);
        end

        function kids = getKids(obj,node)
            %kids = find(obj.pp==node);
            kids = obj.kids(node,:);
        end

        %TODO: maybe compute leaf-node-ness once and then just check for it
        function l = isLeaf(obj,node)
            l = ~any(obj.pp==node);
        end

function plotTree(obj)
            %TREEPLOT Plot picture of tree.
            %   TREEPLOT(p) plots a picture of a tree given a row vector of
            %   parent pointers, with p(i) == 0 for a root and labels on each node.
            %
            %   Example:
            %      myTreeplot([2 4 2 0 6 4 6],{'i' 'like' 'labels' 'on' 'pretty' 'trees' '.'})
            %   returns a binary tree with labels.
            %
            %   Copyright 1984-2004 The MathWorks, Inc.
            %   $Revision: 5.12.4.2 $  $Date: 2004/06/25 18:52:28 $
            %   Modified by Richard @ Socher . org to display text labels

            p = obj.pp';
            [x,y,h]=treelayout(p);
            f = find(p~=0);
            pp = p(f);
            X = [x(f); x(pp); NaN(size(f))];
            Y = [y(f); y(pp); NaN(size(f))];
            X = X(:);
            Y = Y(:);

            n = length(p);
            if n < 500,
                plot (x, y, 'wo', X, Y, 'b-');
            else
                plot (X, Y, 'r-');
            end;
            xlabel(['height = ' int2str(h)]);
            axis([0 1 0 1]);

            if ~isempty(obj.nodeNames)
                for l=1:length(obj.nodeNames)
                        if isnumeric(obj.nodeNames(l))
                            text(x(l),y(l),num2str(obj.nodeNames(l)),'Interpreter','none',...
                                'HorizontalAlignment','center','FontSize',8,'BackgroundColor',[1 1 .6])
                        else
                            text(x(l),y(l),obj.nodeNames{l},'Interpreter','none',...
                                'HorizontalAlignment','center','FontSize',8,'BackgroundColor',[1 1 .6])
                        end
%                     if ~isempty(obj.nodeLabels)
%                         if iscell(obj.nodeNames)
%                             text(x(l),y(l),[labels{l} '(' obj.nodeLabels{l} ')'],'Interpreter','none',...
%                                 'HorizontalAlignment','center','FontSize',8,'BackgroundColor',[1 1 .6])
%                         else
%                             % for numbers
%                             if isnumeric(obj.nodeLabels(l))
% %                                 if isinteger(obj.nodeLabels(l))
%                                     allL = obj.nodeLabels(:,l);
%                                     allL = find(allL);
%                                     if isempty(allL)
%                                         text(x(l),y(l),[num2str(obj.nodeNames(l))],'Interpreter','none',...
%                                         'HorizontalAlignment','center','FontSize',8,'BackgroundColor',[1 1 .6])
%                                     else
%                                         text(x(l),y(l),[num2str(obj.nodeNames(l)) ' (' mat2str(allL) ')'],'Interpreter','none',...
%                                             'HorizontalAlignment','center','FontSize',8,'BackgroundColor',[1 1 .6])
%                                     end
%
% %                                 else
% %                                     text(x(l),y(l),[obj.nodeLabels(l) ' ' num2str(obj.nodeLabels(l),'%.1f') ],'Interpreter','none',...
% %                                         'HorizontalAlignment','center','FontSize',8,'BackgroundColor',[1 1 .6])
% %                                 end
%                                 % change to font size 6 for nicer tree prints
%                             else
%                                 text(x(l),y(l),[obj.nodeNames{l}],'Interpreter','none',...
%                                     'HorizontalAlignment','center','FontSize',8,'BackgroundColor',[1 1 .6])
%                             end
%                         end
%                     end
                end
            end

        end

    end
end


function binfeatures = derivebinaryfeat(randf, Tr_Data, params, stage)
%DERIVEBINARYFEAT Summary of this function goes here
%   Function: Derive binary features for each sample given learned random forest
%   Detailed explanation goes here
%   Input:
%       lmarkID: the ID of landmark
%       randf:   learned random forest in current stage
%       Tr_Data: training data
%       params:  parameters for curent stage

% calculate the overall dimension of binary feature, concatenate the
% features for all landmarks, and the feature of one landmark is sum
% leafnodes of all random trees;

dims_binfeat = 0;

ind_bincode = zeros(size(randf));

for l = 1:size(randf, 1)
    for t = 1:size(randf, 2)
        ind_bincode(l, t) = randf{l, t}.num_leafnodes;
        dims_binfeat = dims_binfeat + randf{l, t}.num_leafnodes;
    end
end

% initilaize the memory for binfeatures
dbsize = length(Tr_Data);

% faster implementation
%{
tic;
binfeatures   = zeros(dbsize*(params.augnumber), dims_binfeat);
img_sizes     = zeros(2, dbsize*(params.augnumber));
shapes        = zeros([size(params.meanshape), dbsize*(params.augnumber)]);
tfs2meanshape = cell(1, dbsize*params.augnumber);

img_areas = zeros(1, dbsize);
parfor i = 1:dbsize
    img_areas(i) = Tr_Data{i}.width*Tr_Data{i}.height;
end

imgs_gray = zeros(dbsize, max(img_areas));

for i = 1:dbsize
    area = img_areas(i);
    imgs_gray(i, 1:area) = Tr_Data{i}.img_gray(:)';
    shapes(:, :, (i-1)*params.augnumber+1:i*params.augnumber) = Tr_Data{i}.intermediate_shapes{stage};

    for k = 1:params.augnumber
        img_sizes(:, (i-1)*params.augnumber+k) = [Tr_Data{i}.width; Tr_Data{i}.height];
        tfs2meanshape{(i-1)*params.augnumber+k} = Tr_Data{i}.tf2meanshape{k};
    end
end

binfeature_lmarks = cell(1, size(params.meanshape, 1));
num_leafnodes = zeros(1, size(params.meanshape, 1));
for l = 1:size(params.meanshape, 1)
    [binfeature_lmarks{l}, num_leafnodes(l)] = lbf_faster(randf{l}, imgs_gray, img_sizes, shapes(l, :, :), tfs2meanshape, params, stage);
end

cumnum_leafnodes = [0 cumsum(num_leafnodes)];
binfeatures_faster = binfeatures;
for l = 1:size(params.meanshape, 1)
    binfeatures_faster(:, cumnum_leafnodes(l)+1:cumnum_leafnodes(l+1)) = binfeature_lmarks{l};
end

toc;
%}
feats = cell(size(params.meanshape, 1), 1);
isleaf = cell(size(params.meanshape, 1), 1);
threshs = cell(size(params.meanshape, 1), 1);
cnodes = cell(size(params.meanshape, 1), 1);

% prepare for the derivation of local binary codes
for l = 1:size(params.meanshape, 1)
    % concatenate all nodes of all random trees
    rf = randf(l, :);
    num_rfnodes = 0;
    for t = 1:params.max_numtrees
        num_rfnodes = num_rfnodes + rf{t}.num_nodes;
    end

    % fast implementation
    feats{l} = zeros(num_rfnodes, 4);
    isleaf{l} = zeros(num_rfnodes, 1);
    threshs{l} = zeros(num_rfnodes, 1);
    cnodes{l} = zeros(num_rfnodes, 2);

    id_rfnode = 1;

    for t = 1:params.max_numtrees
        feats{l}(id_rfnode:(id_rfnode + rf{t}.num_nodes - 1), :)   = rf{t}.feat(1:rf{t}.num_nodes, :);
        isleaf{l}(id_rfnode:(id_rfnode + rf{t}.num_nodes - 1), :)  = rf{t}.isleafnode(1:rf{t}.num_nodes, :);
        threshs{l}(id_rfnode:(id_rfnode + rf{t}.num_nodes - 1), :) = rf{t}.thresh(1:rf{t}.num_nodes, :);
        cnodes{l}(id_rfnode:(id_rfnode + rf{t}.num_nodes - 1), :)  = rf{t}.cnodes(1:rf{t}.num_nodes, :);

        id_rfnode = id_rfnode + rf{t}.num_nodes;
    end
end

% extract feature for each samples
parfor i = 1:dbsize*(params.augnumber)
    k = floor((i-1)/(params.augnumber)) + 1;
    s = mod(i-1, (params.augnumber)) + 1;
    img_gray = Tr_Data{k}.img_gray;
    bbox     = Tr_Data{k}.intermediate_bboxes{stage}(s, :);
    shapes   = Tr_Data{k}.intermediate_shapes{stage};
    shape    = shapes(:, :, s);

    % tf2meanshape = Tr_Data{k}.tf2meanshape{s};
    meanshape2tf = Tr_Data{k}.meanshape2tf{s};
    %{
    feats_cell   = cell(size(params.meanshape, 1), params.max_numtrees);

    for l = 1:size(params.meanshape, 1)
        % extract feature for each landmark from the random forest
        for t = 1:params.max_numtrees
            % extract feature for each ladnmark from a random tree
            bincode = traversetree(randf{l}{t}, img_gray, bbox, shape(l, :), tf2meanshape, params, stage);
            feats_cell{l, t} = bincode;
        end
    end

    binfeature = zeros(1, dims_binfeat);
    for l = 1:size(params.meanshape, 1)
        % extract feature for each landmark from the random forest
        offset = sum(ind_bincodes(1:l-1, end));
        for t = 1:params.max_numtrees
            ind_s = ind_bincodes(l, t) + 1 + offset;
            ind_e = ind_bincodes(l, t+1) + offset;
            % extract feature for each ladnmark from a random tree
            binfeature(ind_s:ind_e) = feats_cell{l, t};
        end
    end
    %}
    % fast implementation

    binfeature_lmarks = cell(1, size(params.meanshape, 1));
    num_leafnodes = zeros(1, size(params.meanshape, 1));
    for l = 1:size(params.meanshape, 1)
        % concatenate all nodes of all random trees
        [binfeature_lmarks{l}, num_leafnodes(l)]= lbf_fast(randf(l, :), feats{l}, isleaf{l}, threshs{l}, cnodes{l}, img_gray, bbox, shape(l, :), meanshape2tf, params, stage);
    end

    cumnum_leafnodes = [0 cumsum(num_leafnodes)];

    binfeature_alllmarks = zeros(1, cumnum_leafnodes(end));
    for l = 1:size(params.meanshape, 1)
        binfeature_alllmarks(cumnum_leafnodes(l)+1:cumnum_leafnodes(l+1)) = binfeature_lmarks{l};
    end

    %{
    % faster implementation (thinking...)
    binfeature_lmarks = cell(1, size(params.meanshape, 1));
    num_leafnodes = zeros(1, size(params.meanshape, 1));
    for l = 1:size(params.meanshape, 1)
        % concatenate all nodes of all random trees
        [binfeature_lmarks{l}, num_leafnodes(l)]= lbf_faster(randf{l}, img_gray, bbox, shape(l, :), tf2meanshape, params, stage);
    end

    cumnum_leafnodes = [0 cumsum(num_leafnodes)];

    binfeature_alllmarks = zeros(1, cumnum_leafnodes(end));
    for l = 1:size(params.meanshape, 1)
        binfeature_alllmarks(cumnum_leafnodes(l)+1:cumnum_leafnodes(l+1)) = binfeature_lmarks{l};
    end
    %}

    binfeatures(i, :) = binfeature_alllmarks;
end

end

function bincode = traversetree(tree, img_gray, bbox, shape, tf2meanshape, params, stage)

currnode = tree.rtnodes{1};

bincode = zeros(1, tree.num_leafnodes);

width = size(img_gray, 2);
height = size(img_gray, 1);

while(1)

    anglepairs = currnode.feat(1:2);
    radiuspairs = currnode.feat(3:4);

    % calculate the relative location under the coordinate of meanshape
    pixel_a_x_imgcoord = cos(anglepairs(:, 1)).*radiuspairs(:, 1)*params.max_raio_radius(stage)*bbox(3);
    pixel_a_y_imgcoord = sin(anglepairs(:, 1)).*radiuspairs(:, 1)*params.max_raio_radius(stage)*bbox(4);

    pixel_b_x_imgcoord = cos(anglepairs(:, 2)).*radiuspairs(:, 2)*params.max_raio_radius(stage)*bbox(3);
    pixel_b_y_imgcoord = sin(anglepairs(:, 2)).*radiuspairs(:, 2)*params.max_raio_radius(stage)*bbox(4);

    % transform the pixels from image coordinate (meanshape) to coordinate of current shape
    [pixel_a_x_lmcoord, pixel_a_y_lmcoord] = tforminv(tf2meanshape, pixel_a_x_imgcoord, pixel_a_y_imgcoord);

    [pixel_b_x_lmcoord, pixel_b_y_lmcoord] = tforminv(tf2meanshape, pixel_b_x_imgcoord, pixel_b_y_imgcoord);

    pixel_a_x = ceil(pixel_a_x_lmcoord + shape(1));
    pixel_a_y = ceil(pixel_a_y_lmcoord + shape(2));

    pixel_b_x = ceil(pixel_b_x_lmcoord + shape(1));
    pixel_b_y = ceil(pixel_b_y_lmcoord + shape(2));

    pixel_a_x = max(1, min(pixel_a_x, width));
    pixel_a_y = max(1, min(pixel_a_y, height));

    pixel_b_x = max(1, min(pixel_b_x, width));
    pixel_b_y = max(1, min(pixel_b_y, height));

    f = int16(img_gray(pixel_a_y + (pixel_a_x-1)*height)) - int16(img_gray(pixel_b_y + (pixel_b_x-1)*height));

    if f < (currnode.thresh)
        id_chilenode = currnode.cnodes(1);
        currnode = tree.rtnodes{id_chilenode};
    else
        id_chilenode = currnode.cnodes(2);
        currnode = tree.rtnodes{id_chilenode};
    end

    if isempty(currnode.cnodes)
        % if the current node is a leaf node, then stop traversin
        bincode(tree.id_leafnodes == id_chilenode) = 1;
        break;
    end
end

end

function [binfeature, num_leafnodes] = lbf_fast(rf, feats, isleaf, threshs, cnodes, img_gray, bbox, shape, meanshape2tf, params, stage)

%{
num_rfnodes = 0;
for t = 1:params.max_numtrees
    num_rfnodes = num_rfnodes + rf{t}.num_nodes;
end

% fast implementation
feats = zeros(num_rfnodes, 4);
isleaf = zeros(num_rfnodes, 1);
threshs = zeros(num_rfnodes, 1);
cnodes = zeros(num_rfnodes, 2);

id_rfnode = 1;

for t = 1:params.max_numtrees
    feats(id_rfnode:(id_rfnode + rf{t}.num_nodes - 1), :)   = rf{t}.feat(1:rf{t}.num_nodes, :);
    isleaf(id_rfnode:(id_rfnode + rf{t}.num_nodes - 1), :)  = rf{t}.isleafnode(1:rf{t}.num_nodes, :);
    threshs(id_rfnode:(id_rfnode + rf{t}.num_nodes - 1), :) = rf{t}.thresh(1:rf{t}.num_nodes, :);
    cnodes(id_rfnode:(id_rfnode + rf{t}.num_nodes - 1), :)  = rf{t}.cnodes(1:rf{t}.num_nodes, :);

    id_rfnode = id_rfnode + rf{t}.num_nodes;
end
%}

width = size(img_gray, 2);
height = size(img_gray, 1);

anglepairs = feats(:, 1:2);
radiuspairs = feats(:, 3:4);

% calculate the relative location under the coordinate of meanshape
pixel_a_x_imgcoord = cos(anglepairs(:, 1)).*radiuspairs(:, 1)*params.max_raio_radius(stage)*bbox(3);
pixel_a_y_imgcoord = sin(anglepairs(:, 1)).*radiuspairs(:, 1)*params.max_raio_radius(stage)*bbox(4);

pixel_b_x_imgcoord = cos(anglepairs(:, 2)).*radiuspairs(:, 2)*params.max_raio_radius(stage)*bbox(3);
pixel_b_y_imgcoord = sin(anglepairs(:, 2)).*radiuspairs(:, 2)*params.max_raio_radius(stage)*bbox(4);

% no transformaiton
%{
pixel_a_x_lmcoord = pixel_a_x_imgcoord;
pixel_a_y_lmcoord = pixel_a_y_imgcoord;

pixel_b_x_lmcoord = pixel_b_x_imgcoord;
pixel_b_y_lmcoord = pixel_b_y_imgcoord;
%}

% transform the pixels from image coordinate (meanshape) to coordinate of current shape

[pixel_a_x_lmcoord, pixel_a_y_lmcoord] = transformPointsForward(meanshape2tf, pixel_a_x_imgcoord', pixel_a_y_imgcoord');
pixel_a_x_lmcoord = pixel_a_x_lmcoord';
pixel_a_y_lmcoord = pixel_a_y_lmcoord';

[pixel_b_x_lmcoord, pixel_b_y_lmcoord] = transformPointsForward(meanshape2tf, pixel_b_x_imgcoord', pixel_b_y_imgcoord');
pixel_b_x_lmcoord = pixel_b_x_lmcoord';
pixel_b_y_lmcoord = pixel_b_y_lmcoord';

pixel_a_x = ceil(pixel_a_x_lmcoord + shape(1));
pixel_a_y = ceil(pixel_a_y_lmcoord + shape(2));

pixel_b_x = ceil(pixel_b_x_lmcoord + shape(1));
pixel_b_y = ceil(pixel_b_y_lmcoord + shape(2));

pixel_a_x = max(1, min(pixel_a_x, width));
pixel_a_y = max(1, min(pixel_a_y, height));

pixel_b_x = max(1, min(pixel_b_x, width));
pixel_b_y = max(1, min(pixel_b_y, height));

pdfeats = double(img_gray(pixel_a_y + (pixel_a_x-1)*height)) - double(img_gray(pixel_b_y + (pixel_b_x-1)*height));
    % ./ double(img_gray(pixel_a_y + (pixel_a_x-1)*height)) + double(img_gray(pixel_b_y + (pixel_b_x-1)*height));

cind = (pdfeats >= threshs) + 1;

% obtain the indice of child nodes for all nodes, if the current node is
% leaf node, then the indice of its child node is 0
ind_cnodes = cnodes; % diag(cnodes(:, cind));

binfeature = zeros(1, sum(isleaf));

cumnum_nodes = 0;
cumnum_leafnodes = 0;
for t = 1:params.max_numtrees
    num_nodes = (rf{t}.num_nodes);
    id_cnode = 1;
    while(1)
        if isleaf(id_cnode + cumnum_nodes)
            binfeature(cumnum_leafnodes + find(rf{t}.id_leafnodes == id_cnode)) = 1;
            cumnum_nodes = cumnum_nodes + num_nodes;
            cumnum_leafnodes = cumnum_leafnodes + rf{t}.num_leafnodes;
            break;
        end
        id_cnode = ind_cnodes(cumnum_nodes + id_cnode, cind(cumnum_nodes + id_cnode));
    end
end

num_leafnodes = sum(isleaf);

end


function  mShed = BSLshedMask(Wsheds,Cut)
%"BSLshedMask"
%   BSL subfunction -- Returns WS masks used for a BSL estimate made with a
%   user specified number of low activity regions removed
%
% CRS 05/20/13
%
%Usage:
%   mShed = BSLshedMask(Wsheds,Cut)
%       Wsheds = watershed masks for the region
%       Cut    = Specifies the number of sheds removed
%
% Copyright 2010, Joseph O. Deasy, on behalf of the CERR development team.
%
% This file is part of The Computational Environment for Radiotherapy Research (CERR).
%
% CERR development has been led by:  Aditya Apte, Divya Khullar, James Alaly, and Joseph O. Deasy.
%
% CERR has been financially supported by the US National Institutes of Health under multiple grants.
%
% CERR is distributed under the terms of the Lesser GNU Public License.
%
%     This version of CERR 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.
%
% CERR 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 CERR.  If not, see <http://www.gnu.org/licenses/>.%
%
%% BSL watershed mask calc

PT = Wsheds.PET;
Shed = Wsheds.Shed;
voxVol = Wsheds.voxVol;
[pX pY pZ] = size(PT);

% build shed masks
ShedTmp = zeros(size(PT));
indNZ = find(Shed);
Shed(isnan(Shed)) = 0;
Shed(isinf(Shed)) = 0;
ShedTmp(indNZ) = Shed(indNZ);

% removes very low background (i.e. bkg outside of patient)
SUVcut = 0.2;
ShedTmp(ShedTmp < SUVcut) = 0;
PT(ShedTmp <= 0) = 0;

% Shed trimming sucessively removing the lowest remaining shed
for k = 0:Cut
    minShed = min(nonzeros(ShedTmp(:)));
    indS = find(ShedTmp == minShed);
    ShedTmp(indS) = 0;
    PT(indS) = 0;
    if (isempty(indS) == 1)
        k = k - 1;
        continue
    end
end

if (sum(ShedTmp(:)) <=0)
    mPT = -1;
    return
end

mShed = ShedTmp;
mShed(mShed > 0) = 1;
voxVec = nonzeros(mShed.*PT);

% preliminary histogram estimate given the shed cuts
nVox = numel(voxVec);
SUViqr = iqr(voxVec);
IVHBinWidth = 2 * SUViqr * nVox^(-1/3);

[binHistV, volHistV]  = doseHist(voxVec, voxVol*ones(size(voxVec)), IVHBinWidth);
nBins = numel(binHistV);

% preliminary BKG estimate using histogram peak
[Hmax, iBkg] = max(volHistV);
Hbkg = binHistV(iBkg);
if (iBkg > 1 && iBkg < numel(binHistV))
    Hbkg = ( volHistV(iBkg-1)*binHistV(iBkg-1) ...
        + volHistV(iBkg)*binHistV(iBkg) ...
        + volHistV(iBkg+1)*binHistV(iBkg+1) ) ...
        / ( volHistV(iBkg-1) + volHistV(iBkg) + volHistV(iBkg+1) );
else
    if (iBkg < numel(binHistV))
        Hbkg = ( volHistV(iBkg)*binHistV(iBkg) ...
            + volHistV(iBkg+1)*binHistV(iBkg+1) ) ...
            / ( volHistV(iBkg) + volHistV(iBkg+1) );
    else
        Hbkg = ( volHistV(iBkg-1)*binHistV(iBkg-1) ...
            + volHistV(iBkg)*binHistV(iBkg) ) ...
            / ( volHistV(iBkg-1) + volHistV(iBkg) );
    end
end
% removes some activity from the left side of the histogram to remove
% fitting bias from activity below the background
SUVcut = Hbkg/3;
mShed(PT < SUVcut) = 0;


function dist = getSlipDist(D)

%This function computes the amount that the stick slid in each direction

N = length(D.phase);
dist = zeros(N,1);

for i=1:N
   if strcmp(D.phase{i},'SLIDE_POS')
       dist(i) = range(D.raw(i).state.x);
   elseif strcmp(D.phase{i},'SLIDE_NEG')
       dist(i) = -range(D.raw(i).state.x);
   end
end

end


function cellInfo = Mission_Data(varargin)
% MISSION_DATA returns a cell array containing bus object information
%
% Optional Input: 'false' will suppress a call to Simulink.Bus.cellToObject
%                 when the MATLAB file is executed.
% The order of bus element attributes is as follows:
%   ElementName, Dimensions, DataType, SampleTime, Complexity, SamplingMode, DimensionsMode, Min, Max, DocUnits, Description

suppressObject = false;
if nargin == 1 && islogical(varargin{1}) && varargin{1} == false
    suppressObject = true;
elseif nargin > 1
    error('Invalid input argument(s) encountered');
end

cellInfo = { ...
  { ...
    'Mission_Data_Bus', ...
    '', ...
    '', ...
    'Auto', ...
    '-1', {...
{'timestamp', 1, 'uint32', -1, 'real', 'Sample', 'Fixed', [], [], sprintf('ms'), ''}; ...
{'valid_items', 1, 'uint16', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'reserved', 1, 'uint16', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'seq', 8, 'uint16', -1, 'real', 'Sample', 'Fixed', [], [], '', sprintf('Start from 0')}; ...
{'command', 8, 'uint16', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'frame', 8, 'uint8', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'current', 8, 'uint8', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'autocontinue', 8, 'uint8', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'mission_type', 8, 'uint8', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'param1', 8, 'single', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'param2', 8, 'single', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'param3', 8, 'single', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'param4', 8, 'single', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'x', 8, 'int32', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'y', 8, 'int32', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
{'z', 8, 'single', -1, 'real', 'Sample', 'Fixed', [], [], '', ''}; ...
    } ...
  } ...
}';

if ~suppressObject
    % Create bus objects in the MATLAB base workspace
    Simulink.Bus.cellToObject(cellInfo)
end


function moleFractionStats(modelT)
% Plots of mole fraction statistics.
%
% Plot a histogram of the number of metabolite species relevant between pH 5
% and 9, and a stacked bar chart of the mole fractions of reactants with
% significant (<0.99) distributions over more than one metabolite species.
%
% USAGE:
%
%    moleFractionStats(modelT)
%
% INPUT:
%    modelT:    structure with fields:
%
%                 * .S
%                 * .officialName
%                 * .mf
%
% .. Authors:
%       - Ronan M.T. Fleming
%       - Hulda SH Jul 7 2011 Enabled inclusion of up to 10 microspecies in bar graph

[nMets,nRxn]=size(modelT.S);

for m=1:nMets
    %nSpecies(m)=length(find(modelT.mf(m))>=0.001); % Imposed a 0.1% threshold on the abundance of species - Hulda
    nSpecies(m)=length(find(cell2mat(modelT.mf(m))>=0.001)); % Imposed a 0.1% threshold on the abundance of species - Hulda
    if (sum(cell2mat(modelT.mf(m)))-1)>1e-8
        %modelT.met(m)
        %modelT.met(m).mf
        error('Sum of mole fractions should be one')
    end
    if nnz(find(cell2mat(modelT.mf(m))<0))>1
        %modelT.met(m)
        %modelT.met(m).mf
        error('Mole fractions should be positive')
    end
end
[n, xout]=hist(nSpecies, [1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5 10.5]); % Uses as many bins as are needed, not just 3 - Hulda
n = n(1:find(n,1,'last'));
figure;
bar(0.5:1:(length(n) - 0.5),n);
title('Number of metabolite species for each reactant.')
ylabel('# Reactants','FontSize',14);
set(gca,'XTick',0.5:1:(length(n) - 0.5));
xTickLabels = {'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten'};
set(gca,'XTickLabel',xTickLabels(1:length(n)),'FontSize',14);

maxNSpecies=max(nSpecies);
%Y=zeros(nSpecies,maxNSpecies);
%Y=zeros(1,maxNSpecies);
Y=zeros(nMets,maxNSpecies);

ns=1;
p=1;
%sorted 1,2,3
while ns<=maxNSpecies
    for m=1:nMets
        if ns==nSpecies(m)
            %Y(p,1:nSpecies(m))=sort(modelT.mf{m}','descend');
            %Y(p,1:nSpecies(m))=cell2mat(modelT.mf(m));
            temp = cell2mat(modelT.mf(m));
            for i=1:nSpecies(m)
                Y(p,i)=temp(i);
            end
            p=p+1;
        end
    %     Y(m,1:nSpecies(m))=modelT.met(m).mf';
    end
    ns=ns+1;
end
 figure
 barh(Y,'stack')

Y=[];
%sorted by connectivity
significantMF=false(nMets,1);
Shat=sign(abs(modelT.S));
A=Shat*Shat';
metConnect=diag(A,0)';
size(metConnect);
[metConnectSorted,ix]=sort(metConnect);
p=1;
for m=1:nMets
    %only take cytoplasmic metabolites
    metAbbr=modelT.mets{ix(m)};
    if max(cell2mat(modelT.mf(ix(m))))<0.95 && strcmp('[c]',metAbbr(end-2:end))
%         Y(p,1:nSpecies(ix(m)))=sort(modelT.met(ix(m)).mf','descend');
        temp = cell2mat(modelT.mf(ix(m)));
        for i=1:length(temp)
            Y(p,i) = temp(i);
        end
        %Y(p,1:nSpecies(ix(m)))=modelT.mf(ix(m))';
        metInd(p)=ix(m);
%         metNames{p,1}=[modelT.met(ix(m)).officialName metAbbr(end-2:end)];
        metNames(p)=modelT.metNames(ix(m));
        p=p+1;
    end

end

figure;
left=0.1;
bottom=0.1;
width=2;
height=0.8;
%subplot(1,2,1,'Position',[left bottom width height])
%subplot(1,2,1,'Position',[left bottom width height])
%figure;
barh(Y,'stack');
set(gca,'XTick',[0:0.2:1]);
set(gca,'YTick',1:length(metInd));
set(gca,'YTickLabel',metNames);
ylim([0.5 length(metInd)-0.5]) ;
xlabel('Mole Fraction','FontSize',14);
set(gca,'FontSize',9)
title('Mole fraction statistics')
saveas(gcf,'moleFraction95','fig');

left=0.8;
%subplot(1,2,2,'Position',[left bottom width height])
figure;
barh(metConnect(metInd));
set(gca,'YTick',1:length(metInd));
set(gca,'YTickLabel',metNames);
ylim([0.5 length(metInd)-0.5]) ;
xlabel('Metabolite (ordered as mole fraction stat figure)','FontSize',14);
xlabel('Connectivity','FontSize',14);
set(gca,'FontSize',9);
title('Mole fraction statistics: connectivity')
saveas(gcf,'moleFraction95connectivity','fig');


% Wrapper for vl_nnselection block
% inputs{1} :   X     : 1 x 2 x N x b
% obj       :   idx   : 1 x n
% outputs{1}:   y     : 1 x 2 x n x b

classdef selection < dagnn.Layer

    properties
        idx = [];
    end

    methods
        function outputs = forward(obj, inputs, ~)

            useGPU = isa(inputs{1}, 'gpuArray');
            if useGPU
                outputs{1} = gpuArray( vl_nnselection(gather(inputs{1}), obj.idx) );
            else
                outputs{1} = vl_nnselection(inputs{1}, obj.idx);
            end

        end

        function [derInputs, derParams] = backward(obj, inputs, ~, derOutputs)

            useGPU = isa(inputs{1}, 'gpuArray');
            if useGPU
                derInputs{1} = gpuArray( vl_nnselection(gather(inputs{1}), obj.idx, gather(derOutputs{1})) );
            else
                derInputs{1} = vl_nnselection(inputs{1}, obj.idx, derOutputs{1});
            end

            derParams = {};
        end

        function outputSizes = getOutputSizes(obj, inputSizes)
            outputSizes = {1 inputSizes{1}(2) length(obj.idx) inputSizes{1}(4)} ;
        end

        function obj = selection(varargin)
            obj.load(varargin);
        end
    end
end


classdef TankMassFlowRateConstraint < AbstractConstraint
    %TankMassFlowRateConstraint Summary of this class goes here
    %   Detailed explanation goes here

    properties
        normFact = 1;
        tank LaunchVehicleTank
        event LaunchVehicleEvent
        eventNode(1,1) ConstraintStateComparisonNodeEnum = ConstraintStateComparisonNodeEnum.FinalState;

        lb(1,1) double = 0;
        ub(1,1) double = 0;

        evalType(1,1) ConstraintEvalTypeEnum = ConstraintEvalTypeEnum.FixedBounds;
        stateCompType(1,1) ConstraintStateComparisonTypeEnum = ConstraintStateComparisonTypeEnum.Equals;
        stateCompEvent LaunchVehicleEvent
        stateCompNode(1,1) ConstraintStateComparisonNodeEnum = ConstraintStateComparisonNodeEnum.FinalState;
    end

    methods
        function obj = TankMassFlowRateConstraint(tank, event, lb, ub)
            obj.tank = tank;
            obj.event = event;
            obj.lb = lb;
            obj.ub = ub;

             obj.id = rand();
        end

        function [lb, ub] = getBounds(obj)
            lb = obj.lb;
            ub = obj.ub;
        end

        function [c, ceq, value, lwrBnd, uprBnd, type, eventNum, valueStateComp] = evalConstraint(obj, stateLog, celBodyData)
            type = obj.getConstraintType();

            switch obj.eventNode
                case ConstraintStateComparisonNodeEnum.FinalState
                    stateLogEntry = stateLog.getLastStateLogForEvent(obj.event);

                case ConstraintStateComparisonNodeEnum.InitialState
                    stateLogEntry = stateLog.getFirstStateLogForEvent(obj.event);

                otherwise
                    error('Unknown event node.');
            end

            value = lvd_TankMassTasks(stateLogEntry, 'tankMDot', obj.tank);

            if(obj.evalType == ConstraintEvalTypeEnum.StateComparison)
                switch obj.stateCompNode
                    case ConstraintStateComparisonNodeEnum.FinalState
                        stateLogEntryStateComp = stateLog.getLastStateLogForEvent(obj.stateCompEvent);

                    case ConstraintStateComparisonNodeEnum.InitialState
                        stateLogEntryStateComp = stateLog.getFirstStateLogForEvent(obj.stateCompEvent);

                    otherwise
                        error('Unknown event node.');
                end

                valueStateComp = lvd_TankMassTasks(stateLogEntryStateComp, 'tankMDot', obj.tank);
            else
                valueStateComp = NaN;
            end

            [c, ceq] = obj.computeCAndCeqValues(value, valueStateComp);

            lwrBnd = obj.lb;
            uprBnd = obj.ub;

            eventNum = obj.event.getEventNum();
        end

        function sF = getScaleFactor(obj)
            sF = obj.normFact;
        end

        function setScaleFactor(obj, sF)
            obj.normFact = sF;
        end

        function tf = usesStage(obj, stage)
            tf = false;
        end

        function tf = usesEngine(obj, engine)
            tf = false;
        end

        function tf = usesTank(obj, tank)
            tf = obj.tank == tank;
        end

        function tf = usesEngineToTankConn(obj, engineToTank)
            tf = false;
        end

        function tf = usesEvent(obj, event)
            tf = obj.event == event;
            if(obj.evalType == ConstraintEvalTypeEnum.StateComparison)
                tf = tf || obj.stateCompEvent == event;
            end
        end

        function tf = usesStopwatch(obj, stopwatch)
            tf = false;
        end

        function tf = usesExtremum(obj, extremum)
            tf = false;
        end

        function tf = usesGroundObj(obj, grdObj)
            tf = false;
        end

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

        function event = getConstraintEvent(obj)
            event = obj.event;
        end

        function type = getConstraintType(obj)
            type = 'Tank Mass Flow Rate';
        end

        function [unit, lbLim, ubLim, usesLbUb, usesCelBody, usesRefSc] = getConstraintStaticDetails(obj)
            unit = 'mT/s';
            lbLim = -Inf;
            ubLim = Inf;
            usesLbUb = true;
            usesCelBody = false;
            usesRefSc = false;
        end

        function addConstraintTf = openEditConstraintUI(obj, lvdData)
            [~, tanks] = lvdData.launchVehicle.getTanksListBoxStr();
            if(numel(tanks) >= 1)
%                 addConstraintTf = lvd_EditTankConstraintGUI(obj, lvdData);

                output = AppDesignerGUIOutput({false});
                lvd_EditTankConstraintGUI_App(obj, lvdData, output);
                addConstraintTf = output.output{1};
            else
                errordlg('There are currently no tanks assigned to the launch vehicle in this scenario.  Add at least one tank first.');

                addConstraintTf = false;
            end
        end
    end

    methods(Static)
        function constraint = getDefaultConstraint(~, ~)
            constraint = TankMassFlowRateConstraint(LaunchVehicleTank.empty(1,0), LaunchVehicleEvent.empty(1,0), 0, 0);
        end
    end
end


%  Author: Relja Arandjelovic (relja@relja.info)

% This file contains a few examples of how to test our NetVLAD on standard object/image retrieval benchmarks, refer to README.md for setup instructions, and to our project page for all relevant information (e.g. our paper): http://www.di.ens.fr/willow/research/netvlad/

%  The code samples use the GPU by default, if you want to use the CPU instead (quite slow!), add `'useGPU', false` to the affected function call (serialAllFeats)

% Set the MATLAB paths
setup;

% ---------- Use/test our networks

% Load our network
netID= 'vd16_pitts30k_conv5_3_vlad_preL2_intra_white';
paths= localPaths();
load( sprintf('%s%s.mat', paths.ourCNNs, netID), 'net' );
net= relja_simplenn_tidy(net);

% --- Oxford Buildings / Paris Buildings

dsetName= 'ox5k'; % change to `'paris'` for Paris Buildings

% Define if you want to use the query ROI information.
% The original evaluation procedure for Oxford / Paris DOES use ROIs,
% so we really recommend to keep this as is, but we also include the setting
% where they are not used in order to be able to compare to some recent works.
% Please continue using ROIs as this makes it easier to compare numbers
% accross different papers, and it is the proper evaluation procedure for
% these datasets.
% Note that, for ease of implementation, if you are using ROIs the code will
% crop the query images and save them into a new folder
% `images_crop_<amount_of_crop>` inside the dataset root
% (i.e. makes sure this is writable). The crop is made by extending the ROI
% by half of the receptive field size, as this ensures that the centres of
% all 'features' % (i.e. conv responses) are inside the ROI; this is a setting
% similar to the original test procedure where a SIFT feature is kept iff its
% centre is inside the ROI. Note that this is ROI extension is only valid
% for single-scale extraction of features, if you want multiple scales then
% the implementation needs to be different
%
% To use the ROI: specify the receptive field size (can be 0 for a tight crop)
% Not to use the ROI (not recommended): specify a negative number

useROI= true;
if useROI
    lastConvLayer= find(ismember(relja_layerTypes(net), 'custom'),1)-1; % Relies on the fact that NetVLAD, Max and Avg pooling are implemented as a custom layer and are the first custom layer in the network. Change if you use another network which has other custom layers before
    netBottom= net;
    netBottom.layers= netBottom.layers(1:lastConvLayer);
    info= vl_simplenn_display(netBottom);
    clear netBottom;
    recFieldSize= info.receptiveFieldSize(:, end);
    assert(recFieldSize(1) == recFieldSize(2));% we are assuming square receptive fields, otherwise dbVGG needs to change to account for non-square
    recFieldSize= recFieldSize(1);
    strMode= 'crop';
else
    recFieldSize= -1;
    strMode= 'full';
end

dbTest= dbVGG(dsetName, recFieldSize);

% Compute db image representations (images have different resolutions so batchSize is constrained to 1)
dbFeatFn= sprintf('%s%s_%s_db.bin', paths.outPrefix, netID, dbTest.name);
serialAllFeats(net, dbTest.dbPath, dbTest.dbImageFns, dbFeatFn, 'batchSize', 1);

if useROI
    qFeatFn = sprintf('%s%s_%s_q_%d.bin', paths.outPrefix, netID, dbTest.name, recFieldSize);
    serialAllFeats(net, dbTest.qPath, dbTest.qImageFns, qFeatFn, 'batchSize', 1);
end

% Load the image representations
dbFeat= fread( fopen(dbFeatFn, 'rb'), inf, 'float32=>single');
dbFeat= reshape(dbFeat, [], dbTest.numImages);
nDims= size(dbFeat, 1);
if useROI
    qFeat= fread( fopen(qFeatFn, 'rb'), [nDims, dbTest.numImages], 'float32=>single');
    assert(size(qFeat,2)==dbTest.numQueries);
else
    qFeat= dbFeat(:, dbTest.queryIDs);
end

% Measure recall@N
mAP= relja_retrievalMAP(dbTest, struct('db', dbFeat, 'qs', qFeat), true);
relja_display('Performance on %s (%s), %d-D: %.2f', dbTest.name, strMode, size(dbFeat,1), mAP*100);

% Try 256-D
D= 256;
mAPsmall= relja_retrievalMAP(dbTest, struct( ...
    'db', relja_l2normalize_col(dbFeat(1:D,:)), ...
    'qs', relja_l2normalize_col(qFeat(1:D,:)) ...
    ), true);
relja_display('Performance on %s (%s), %d-D: %.2f', dbTest.name, strMode, D, mAPsmall*100);

% --- Holidays

useRotated= false; % for rotated Holidays
dbTest= dbHolidays(useRotated); % it will automatically downscale images to (1024x768) pixels, as per the original testing procedure (see the Holidays website)

% Set the output filename for the database (query images are a subset)
dbFeatFn= sprintf('%s%s_%s_db.bin', paths.outPrefix, netID, dbTest.name);

% Compute db image representations (images have different resolutions so batchSize is constrained to 1)
serialAllFeats(net, dbTest.dbPath, dbTest.dbImageFns, dbFeatFn, 'batchSize', 1);

% Load the image representations
dbFeat= fread( fopen(dbFeatFn, 'rb'), inf, 'float32=>single');
dbFeat= reshape(dbFeat, [], dbTest.numImages);
nDims= size(dbFeat, 1);
qFeat= dbFeat(:, dbTest.queryIDs);

mAP= relja_retrievalMAP(dbTest, struct('db', dbFeat, 'qs', qFeat), true);
relja_display('Performance on %s, %d-D: %.2f', dbTest.name, size(dbFeat,1), mAP*100);

% Try 256-D
D= 256;
mAPsmall= relja_retrievalMAP(dbTest, struct( ...
    'db', relja_l2normalize_col(dbFeat(1:D,:)), ...
    'qs', relja_l2normalize_col(qFeat(1:D,:)) ...
    ), true);
relja_display('Performance on %s, %d-D: %.2f', dbTest.name, D, mAPsmall*100);


function cortMag = buildCortMagBins(cortMag, flatView, grayView)
%
%  cortmag = buildCortMagBins(cortMag, flatView, grayView)
%
%  Loads the bins for all the ROIs. This is a necessary and computationally
%  expensive
%   step torward computing the cortical magnification function. We should
%   avoid doing it
%   more often than necessary.
%
% HISTORY:
%   2002.03.20 RFD (bob@white.stanford.edu) wrote it, based
%   on code by Wandell, Baseler and Brewer.
%   2002.12.11 RFD: added flatDataFlag check so that we can
%   either use the data pulled from the flat map, or get the
%   data from the volume.

% The basic steps that we need to do:
% * find a start point
%    - ROI coords are already sorted, so we just take the first coord
% * find a gray node for each point
% * find distance from start node to every other node
% * grab the co and ph for each node.

bins = {};

% if(strcmp(cortMag.hemisphere,'left'))
%     nodes = grayView.allLeftNodes;
%     edges = grayView.allLeftEdges;
% else
%     nodes = grayView.allRightNodes;
%     edges = grayView.allRightEdges;
% end
nodes = grayView.nodes;
edges = grayView.edges;

for(roiNum=1:length(cortMag.ROIs))
    disp(['Binning nodes for ROI ',num2str(roiNum),' of ',num2str(length(cortMag.ROIs)),'...']);

    % We loop until we've dealt with all the nodes. The basic idea is to
    % pick a binCenterNode, bin all nodes within binDist of that
    % binCenterNode,
    % compute the phase of all those points, then find the next
    % binCenterNode and
    % do it again.
    %
    binNum = 1;
    thisNodeIndex = cortMag.nodeIndices{roiNum};
    % Here we use a sparse matix to create a look-up table of phase and co values
    % for each nodeIndex. These data have been derived from the flat map
    % coranal. The corMag struct has a flag (flatDataFlag) that tells us if we should use
    % the flat data or actually go and get the data from the volume.
    nodePhTable = sparse(cortMag.nodeIndices{roiNum}, 1, cortMag.data{roiNum}.ph);
    nodeCoTable = sparse(cortMag.nodeIndices{roiNum}, 1, cortMag.data{roiNum}.co);
    while(~isempty(thisNodeIndex))
        % Select binCenterNode
        bins{roiNum}(binNum).binCenterNode = thisNodeIndex(1);
        if(length(thisNodeIndex)>1)
            thisNodeIndex = thisNodeIndex(2:end);

            % Find all points within binDist of binCenterNode.
            % We actually don't care about the distances right now, so we
            % just grab the
            % indices of the non-NaN entries.
            nodesInBin = find(~isnan(mrManDist(nodes, edges, bins{roiNum}(binNum).binCenterNode, ...
                                            cortMag.mmPerPix, NaN, cortMag.binDist)));
            [c, ia] = intersect(thisNodeIndex, nodesInBin);
            bins{roiNum}(binNum).allNodes = [bins{roiNum}(binNum).binCenterNode, c];
            % now we can remove all the nodes that intersected (ia) from
            % thisNodeIndex.
            % BUT- if ia is not consecutive- that's a problem. These nodes
            % are supposed to be sorted for
            % us! We do a hack for now.
            if(~isempty(ia))
                thisNodeIndex = thisNodeIndex(max(ia):end);
            end

            if(isfield(cortMag,'flatDataFlag') & cortMag.flatDataFlag==1)
                bins{roiNum}(binNum).allPh = full(nodePhTable(bins{roiNum}(binNum).allNodes));
                bins{roiNum}(binNum).allCo = full(nodeCoTable(bins{roiNum}(binNum).allNodes));
            else

                bins{roiNum}(binNum).allPh = grayView.ph{cortMag.expNumber}(bins{roiNum}(binNum).allNodes);
                bins{roiNum}(binNum).allCo = grayView.co{cortMag.expNumber}(bins{roiNum}(binNum).allNodes);
            end

            % But, the clipped nodes may contain some unconnected nodes.
            % So, we may need to go back to
            % the complete set of nodes. And, since those have different
            % indices, we need to extract the
            % volume coords and then look up the corresponding data.
            % Unfortunately, this makes this loop an order of magnitude
            % slower. So, we cheat with the above shortcut.
            % The following code is a stream-lined version of
            % 'getCurDataROI' to try to speed things up a bit.
%             coords = nodes([2,1,3], bins{roiNum}(binNum).allNodes);
%             [inter,ROIIndices,viewIndices] = intersectCols(coords,
%             grayView.coords);
%             bins{roiNum}(binNum).allPh = NaN*ones([1,size(coords,2)]);
%             bins{roiNum}(binNum).allPh(ROIIndices) =
%             grayView.co{cortMag.expNumber}(viewIndices);
%             bins{roiNum}(binNum).allCo = NaN*ones([1,size(coords,2)]);
%             bins{roiNum}(binNum).allCo(ROIIndices) =
%             grayView.ph{cortMag.expNumber}(viewIndices);

            binNum = binNum+1;
        else
            thisNodeIndex = [];
        end
    end
    bins{roiNum}(1).distToPrev = 0;
    for(binNum=2:length(bins{roiNum}))
        allDist = mrManDist(nodes, edges, bins{roiNum}(binNum).binCenterNode, ...
                            cortMag.mmPerPix, NaN, 0);
        bins{roiNum}(binNum).distToPrev = allDist(bins{roiNum}(binNum-1).binCenterNode);
    end
    if(any(isnan(cat(1,bins{roiNum}.distToPrev))))
        % Oops! this shouldn't happen with a properly connected mesh!
        bins{roiNum} = [];
        disp(['   ROI ',num2str(roiNum),' DROPPED BECAUSE OF UNCONNECTED NODES.']);
    else
        disp(['   ',num2str(binNum-1),' bins created from ',num2str(length(cortMag.nodeIndices{roiNum})),' nodes.']);
    end
end
cortMag.bins = bins;

return;

% Debugging
dist={}; meanPh={};
for(roiNum=1:length(cortMag.ROIs))
    for(binNum=1:length(bins{roiNum}))
        dist{roiNum}(binNum) = bins{roiNum}(binNum).distToPrev;
        meanPh{roiNum}(binNum) = mean(unwrap(bins{roiNum}(binNum).allPh));
    end
    figure;plot(cumsum(dist{roiNum}), meanPh{roiNum});
end


 function [net,stats] = cnn_train(net, imdb, varargin)
%CNN_TRAIN_DAG Demonstrates training a CNN using the DagNN wrapper
%    CNN_TRAIN_DAG() is similar to CNN_TRAIN(), but works with
%    the DagNN wrapper instead of the SimpleNN wrapper.

% Copyright (C) 2014-16 Andrea Vedaldi.
% 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.modelName   = 'model';
opts.expDir = fullfile('data',opts.modelName) ;
opts.continue = true ;
opts.batchSize = 256 ;
opts.numSubBatches = 1 ;
opts.train = [] ;
opts.val = [] ;
opts.gpus = [] ;
opts.prefetch = false ;
opts.numEpochs = 20 ;

%%% solver: Adam
%%% opts.solver = 'Adam';
opts.beta1   = 0.9;
opts.beta2   = 0.999;
opts.alpha   = 0.01;
opts.epsilon = 1e-8;

%%% solver: SGD
opts.solver = 'SGD';
opts.learningRate = 0.001;
opts.weightDecay  = 0.0005;
opts.momentum     = 0.9 ;

%%% GradientClipping
opts.gradientClipping = false;
opts.theta            = 0.005;

%%% specific parameter for Bnorm
opts.bnormLearningRate = 0;

opts.randomSeed = 0 ;
opts.memoryMapFile = fullfile(tempdir, 'matconvnet.bin') ;
opts.profile = false ;

opts.derOutputs = {'objective', 1} ;
opts.extractStatsFn = @extractStats ;
opts.plotStatistics = true;
opts.conserveMemory = true;
opts.mode = 'normal';
opts.cudnn = true ;
opts.backPropDepth = +inf ;
opts.skipForward = false;
opts.numSubBatches = 1;
opts.numberImdb   = 1;
opts.sigma = 50;

opts = vl_argparse(opts, varargin) ;
opts.numEpochs = numel(opts.learningRate);

if ~exist(opts.expDir, 'dir'), mkdir(opts.expDir) ; end
if isempty(opts.train), opts.train = find(imdb.images.set==1) ; end
if isempty(opts.val), opts.val = find(imdb.images.set==2) ; end
if isnan(opts.train), opts.train = [] ; end

% -------------------------------------------------------------------------
%                                                            Initialization
% -------------------------------------------------------------------------

evaluateMode = isempty(opts.train) ;
if ~evaluateMode
  if isempty(opts.derOutputs)
    error('DEROUTPUTS must be specified when training.\n') ;
  end
end

state.getBatch = getBatch ;
stats = [] ;

% -------------------------------------------------------------------------
%                                                        Train and validate
% -------------------------------------------------------------------------

modelPath = @(ep) fullfile(opts.expDir, sprintf([opts.modelName '-epoch-%d.mat'], ep));
modelFigPath = fullfile(opts.expDir, 'net-train.pdf') ;

start = opts.continue * findLastCheckpoint(opts.expDir,opts.modelName);
if start >= 1
  fprintf('%s: resuming by loading epoch %d\n', mfilename, start) ;
  [net, stats] = loadState(modelPath(start)) ;

%   net.params(8).learningrate = update_flag;
%   net.params(19).learningrate = update_flag;
%   net.params(60).learningrate = update_flag;
%   net.params(71).learningrate = update_flag;
end

for epoch=start+1:opts.numEpochs
%   parpool;
  % Set the random seed based on the epoch and opts.randomSeed.
  % This is important for reproducibility, including when training
  % is restarted from a checkpoint.

  rng(epoch + opts.randomSeed) ;
  prepareGPUs(opts, epoch == start+1) ;

  % Train for one epoch.

  state.epoch = epoch ;
  state.learningRate = opts.learningRate(min(epoch, numel(opts.learningRate))) ;
  state.thetaCurrent = opts.theta(min(epoch, numel(opts.theta)));
  state.train = opts.train(randperm(numel(opts.train))) ; % shuffle
  state.val = opts.val(randperm(numel(opts.val))) ;

  state.beta1 = opts.beta1;
  state.beta2 = opts.beta2;
  state.alpha = opts.alpha;
  state.epsilon = opts.epsilon;
  state.gradientClipping = opts.gradientClipping;

  state.imdb = imdb ;

%   state = distributed(state);

  if numel(opts.gpus) <= 1
    [stats.train(epoch),prof] = process_epoch(net, state, opts, 'train') ;
    stats.val(epoch) = process_epoch(net, state, opts, 'val') ;
    if opts.profile
      profview(0,prof) ;
      keyboard ;
    end
  else
    savedNet = net.saveobj() ;
    spmd
      net_ = dagnn.DagNN.loadobj(savedNet) ;
      [stats_.train, prof_] = process_epoch(net_, state, opts, 'train') ;
      stats_.val = process_epoch(net_, state, opts, 'val') ;
      if labindex == 1, savedNet_ = net_.saveobj() ; end
    end
    net = dagnn.DagNN.loadobj(savedNet_{1}) ;
    stats__ = accumulateStats(stats_) ;
    stats.train(epoch) = stats__.train ;
    stats.val(epoch) = stats__.val ;
    if opts.profile
      mpiprofile('viewer', [prof_{:,1}]) ;
      keyboard ;
    end
    clear net_ stats_ stats__ savedNet savedNet_ ;
  end

  % save
  if ~evaluateMode
    saveState(modelPath(epoch), net, stats) ;
  end

  if opts.plotStatistics
    switchFigure(1) ; clf ;
    plots = setdiff(...
      cat(2,...
      fieldnames(stats.train)', ...
      fieldnames(stats.val)'), {'num', 'time'}) ;
    for p = plots
      p = char(p) ;
      values = zeros(0, epoch) ;
      leg = {} ;
      for f = {'train', 'val'}
        f = char(f) ;
        if isfield(stats.(f), p)
          tmp = [stats.(f).(p)] ;
          values(end+1,:) = tmp(1,:)' ;
          leg{end+1} = f ;
        end
      end
      subplot(1,numel(plots),find(strcmp(p,plots))) ;
      plot(1:epoch, values','o-') ;
      xlabel('epoch') ;
      title(p) ;
      legend(leg{:}) ;
      grid on ;
    end
    drawnow ;
    print(1, modelFigPath, '-dpdf') ;
  end
%   delete(gcp('nocreate'));
end

% -------------------------------------------------------------------------
function [stats, prof] = process_epoch(net, state, opts, mode)
% -------------------------------------------------------------------------

% initialize empty momentum
if strcmp(mode,'train')
  state.momentum = num2cell(zeros(1, numel(net.params))) ;
  state.m = num2cell(zeros(1, numel(net.params))) ;
  state.v = num2cell(zeros(1, numel(net.params))) ;
  state.t = num2cell(zeros(1, numel(net.params))) ;
end

% move CNN  to GPU as needed
numGpus = numel(opts.gpus) ;
if numGpus >= 1
  net.move('gpu') ;
  if strcmp(mode,'train')
    state.momentum = cellfun(@gpuArray,state.momentum,'UniformOutput',false) ;
    state.m = cellfun(@gpuArray,state.m,'UniformOutput',false) ;
    state.v = cellfun(@gpuArray,state.v,'UniformOutput',false) ;
    state.t = cellfun(@gpuArray,state.t,'UniformOutput',false) ;
  end
end
if numGpus > 1
  mmap = map_gradients(opts.memoryMapFile, net, numGpus) ;
else
  mmap = [] ;
end

% profile
if opts.profile
  if numGpus <= 1
    profile clear ;
    profile on ;
  else
    mpiprofile reset ;
    mpiprofile on ;
  end
end

subset = state.(mode) ;
num = 0 ;
stats.num = 0 ; % return something even if subset = []
stats.time = 0 ;
adjustTime = 0 ;
diary(fullfile(opts.expDir, [opts.modelName '_test.txt']));
diary on;
start = tic ;
for t=1:opts.batchSize:numel(subset)
  fprintf('%s %s: epoch %02d: %3d/%3d:', opts.modelName, mode, state.epoch, ...
          fix((t-1)/opts.batchSize)+1, ceil(numel(subset)/opts.batchSize)) ;
  batchSize = min(opts.batchSize, numel(subset) - t + 1) ;

  for s=1:opts.numSubBatches
    % get this image batch and prefetch the next
    batchStart = t + (labindex-1) + (s-1) * numlabs ;
    batchEnd = min(t+opts.batchSize-1, numel(subset)) ;
    batch = subset(batchStart : opts.numSubBatches * numlabs : batchEnd) ;
    num = num + numel(batch) ;
    if numel(batch) == 0, continue ; end

    inputs = state.getBatch(state.imdb, batch, opts.sigma) ;

    if opts.prefetch
      if s == opts.numSubBatches
        batchStart = t + (labindex-1) + opts.batchSize ;
        batchEnd = min(t+2*opts.batchSize-1, numel(subset)) ;
      else
        batchStart = batchStart + numlabs ;
      end
      nextBatch = subset(batchStart : opts.numSubBatches * numlabs : batchEnd) ;
      state.getBatch(state.imdb, nextBatch) ;
    end

    if strcmp(mode, 'train')
      net.mode = 'normal' ;
      net.accumulateParamDers = (s ~= 1) ;
      net.eval(inputs, opts.derOutputs) ;
    else
      net.mode = 'test' ;
      net.eval(inputs) ;
    end
  end

  % accumulate gradient
  if strcmp(mode, 'train')
    if ~isempty(mmap)
      write_gradients(mmap, net) ;
      labBarrier() ;
    end
    state = accumulate_gradients(state, net, opts, batchSize, mmap) ;
  end

  % get statistics
  time = toc(start) + adjustTime ;
  batchTime = time - stats.time ;
  stats = opts.extractStatsFn(net) ;
  stats.num = num ;
  stats.time = time ;
  currentSpeed = batchSize / batchTime ;
  averageSpeed = (t + batchSize - 1) / time ;
  if t == opts.batchSize + 1
    % compensate for the first iteration, which is an outlier
    adjustTime = 2*batchTime - time ;
    stats.time = time + adjustTime ;
  end

  fprintf(' %.1f (%.1f) Hz', averageSpeed, currentSpeed) ;
  for f = setdiff(fieldnames(stats)', {'num', 'time'})
    f = char(f) ;
    fprintf(' %s:', f) ;
    fprintf(' %.3f', stats.(f)) ;
  end
  fprintf('\n') ;
end
diary off;
if ~isempty(mmap)
  unmap_gradients(mmap) ;
end

if opts.profile
  if numGpus <= 1
    prof = profile('info') ;
    profile off ;
  else
    prof = mpiprofile('info');
    mpiprofile off ;
  end
else
  prof = [] ;
end

net.reset() ;
net.move('cpu') ;

% -------------------------------------------------------------------------
function state = accumulate_gradients(state, net, opts, batchSize, mmap)
% -------------------------------------------------------------------------
numGpus = numel(opts.gpus) ;
otherGpus = setdiff(1:numGpus, labindex) ;

for p=1:numel(net.params)

  % accumualte gradients from multiple labs (GPUs) if needed
  if numGpus > 1
    tag = net.params(p).name ;
    for g = otherGpus
      tmp = gpuArray(mmap.Data(g).(tag)) ;
      net.params(p).der = net.params(p).der + tmp ;
    end
  end

  switch net.params(p).trainMethod

    case 'average' % mainly for batch normalization
%       thisLR = net.params(p).learningRate ;
%       net.params(p).value = ...
%           (1 - thisLR) * net.params(p).value + ...
%           (thisLR/batchSize/net.params(p).fanout) * net.params(p).der ;
      thisLR = net.params(p).learningRate ;
      net.params(p).value = vl_taccum(...
          1 - thisLR, net.params(p).value, ...
          (thisLR/batchSize/net.params(p).fanout),  net.params(p).der) ;

    case 'gradient'
        thisLR = state.learningRate * net.params(p).learningRate ;
        state.t{p} = state.t{p} + 1;
        t = state.t{p};
        alpha = thisLR; % opts.alpha; %opts.alpha / sqrt(t);
        lr = alpha * sqrt(1 - state.beta2^t) / (1 - state.beta1^t);
        if lr > 0 && thisLR > 0
            state.m{p} = vl_taccum( state.beta1, state.m{p}, 1 - state.beta1, net.params(p).der);
            state.v{p} = vl_taccum( state.beta2, state.v{p}, 1 - state.beta2, (net.params(p).der).^2);
            net.params(p).value = vl_taccum(1, net.params(p).value, -gather(lr), state.m{p} ./ (state.v{p}.^0.5 + state.epsilon));
        end

    case 'adam'
        thisLR = state.learningRate * net.params(p).learningRate ;
        state.t{p} = state.t{p} + 1;
        t = state.t{p};
        alpha = thisLR; % opts.alpha; %opts.alpha / sqrt(t);
        lr = alpha * sqrt(1 - state.beta2^t) / (1 - state.beta1^t);
        if lr > 0 && thisLR > 0
            state.m{p} = vl_taccum( state.beta1, state.m{p}, 1 - state.beta1, net.params(p).der);
            state.v{p} = vl_taccum( state.beta2, state.v{p}, 1 - state.beta2, (net.params(p).der).^2);
            net.params(p).value = vl_taccum(1, net.params(p).value, -gather(lr), state.m{p} ./ (state.v{p}.^0.5 + state.epsilon));
        end

    case 'otherwise'
      error('Unknown training method ''%s'' for parameter ''%s''.', ...
        net.params(p).trainMethod, ...
        net.params(p).name) ;
  end
end

% -------------------------------------------------------------------------
function mmap = map_gradients(fname, net, numGpus)
% -------------------------------------------------------------------------
format = {} ;
for i=1:numel(net.params)
  format(end+1,1:3) = {'single', size(net.params(i).value), net.params(i).name} ;
end
format(end+1,1:3) = {'double', [3 1], 'errors'} ;
if ~exist(fname) && (labindex == 1)
  f = fopen(fname,'wb') ;
  for g=1:numGpus
    for i=1:size(format,1)
      fwrite(f,zeros(format{i,2},format{i,1}),format{i,1}) ;
    end
  end
  fclose(f) ;
end
labBarrier() ;
mmap = memmapfile(fname, ...
                  'Format', format, ...
                  'Repeat', numGpus, ...
                  'Writable', true) ;

% -------------------------------------------------------------------------
function write_gradients(mmap, net)
% -------------------------------------------------------------------------
for i=1:numel(net.params)
  mmap.Data(labindex).(net.params(i).name) = gather(net.params(i).der) ;
end

% -------------------------------------------------------------------------
function unmap_gradients(mmap)
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
function stats = accumulateStats(stats_)
% -------------------------------------------------------------------------

for s = {'train', 'val'}
  s = char(s) ;
  total = 0 ;

  % initialize stats stucture with same fields and same order as
  % stats_{1}
  stats__ = stats_{1} ;
  names = fieldnames(stats__.(s))' ;
  values = zeros(1, numel(names)) ;
  fields = cat(1, names, num2cell(values)) ;
  stats.(s) = struct(fields{:}) ;

  for g = 1:numel(stats_)
    stats__ = stats_{g} ;
    num__ = stats__.(s).num ;
    total = total + num__ ;

    for f = setdiff(fieldnames(stats__.(s))', 'num')
      f = char(f) ;
      stats.(s).(f) = stats.(s).(f) + stats__.(s).(f) * num__ ;

      if g == numel(stats_)
        stats.(s).(f) = stats.(s).(f) / total ;
      end
    end
  end
  stats.(s).num = total ;
end

% -------------------------------------------------------------------------
function stats = extractStats(net)
% -------------------------------------------------------------------------
sel = find(cellfun(@(x) isa(x,'dagnn.Loss'), {net.layers.block})) ;
stats = struct() ;
for i = 1:numel(sel)
  stats.(net.layers(sel(i)).outputs{1}) = net.layers(sel(i)).block.average ;
end

% -------------------------------------------------------------------------
function saveState(fileName, net, stats)
% -------------------------------------------------------------------------
net_ = net ;
net = net_.saveobj() ;
save(fileName, 'net', 'stats') ;

% -------------------------------------------------------------------------
function [net, stats] = loadState(fileName)
% -------------------------------------------------------------------------
load(fileName, 'net', 'stats') ;
net = dagnn.DagNN.loadobj(net) ;

% -------------------------------------------------------------------------
function epoch = findLastCheckpoint(modelDir, modelName)
% -------------------------------------------------------------------------
% list = dir(fullfile(modelDir, 'net-epoch-*.mat')) ;
% tokens = regexp({list.name}, 'net-epoch-([\d]+).mat', 'tokens') ;
% epoch = cellfun(@(x) sscanf(x{1}{1}, '%d'), tokens) ;
% epoch = max([epoch 0]) ;
list = dir(fullfile(modelDir, [modelName,'-epoch-*.mat'])) ;
tokens = regexp({list.name}, [modelName,'-epoch-([\d]+).mat'], 'tokens') ;
epoch = cellfun(@(x) sscanf(x{1}{1}, '%d'), tokens) ;
epoch = max([epoch 0]) ;

% -------------------------------------------------------------------------
function switchFigure(n)
% -------------------------------------------------------------------------
if get(0,'CurrentFigure') ~= n
  try
    set(0,'CurrentFigure',n) ;
  catch
    figure(n) ;
  end
end

% -------------------------------------------------------------------------
function prepareGPUs(opts, cold)
% -------------------------------------------------------------------------
numGpus = numel(opts.gpus) ;
if numGpus > 1
  % check parallel pool integrity as it could have timed out
  pool = gcp('nocreate') ;
  if ~isempty(pool) && pool.NumWorkers ~= numGpus
    delete(pool) ;
  end
  pool = gcp('nocreate') ;
  if isempty(pool)
    parpool('local', numGpus) ;
    cold = true ;
  end
  if exist(opts.memoryMapFile)
    delete(opts.memoryMapFile) ;
  end

end
if numGpus >= 1 && cold
  fprintf('%s: resetting GPU\n', mfilename)
  if numGpus == 1
    gpuDevice(opts.gpus)
  else
    spmd, gpuDevice(opts.gpus(labindex)), end
  end
end

%end
%%%-------------------------------------------------------------------------
function A = gradientClipping(A, theta,gradientClip)
%%%-------------------------------------------------------------------------
if gradientClip
    A(A>theta)  = theta;
    A(A<-theta) = -theta;
else
    return;
end

% -------------------------------------------------------------------------
function fn = getBatch()
% -------------------------------------------------------------------------
fn = @(x,y,z) getDagNNBatch(x,y,z) ;

% -------------------------------------------------------------------------
function [inputs2] = getDagNNBatch(imdb, batch, sigma)
% -------------------------------------------------------------------------
patch_size = imdb.patch_size ;
if ~exist('tmp', 'file') mkdir('tmp'); end
global CurTask;
labels  = zeros(patch_size, patch_size, 1, numel(batch));
for ii = 1:numel(batch)
    labels(:,:,:,ii) = im2single(imread(fullfile(imdb.imdbPath, imdb.filepaths(batch(ii)).name )));
    switch CurTask
        case 'Denoising'
            inputs(:,:,:,ii)  = labels(:,:,:,ii) + single(sigma/255*randn(size(labels(:,:,:,ii))));
        case 'Deblocking'
            str = [ './tmp/' imdb.modelName '_' num2str(sigma) '_' imdb.filepaths(batch(ii)).name(1:end-4) '.jpg'];
            imwrite(labels(:,:,:,ii), str, 'jpg', 'quality', sigma);
            inputs(:,:,:,ii) = im2single(imread(str));
            delete(str);
        case 'SISR'
            inputs(:,:,:,ii) = imresize(imresize(labels(:,:,:,ii), 1/sigma, 'bicubic'), sigma, 'bicubic');
    end
end

rng('shuffle');
mode = randperm(8);
inputs = data_augmentation(inputs, mode(1));
labels = data_augmentation(labels, mode(1));

inputs  = gpuArray(single(inputs))*4-2;
labels= gpuArray(single(labels))*4-2;

inputs2 = {'input', inputs, 'label', labels} ;