function [headmodel, sens] = ft_prepare_vol_sens(headmodel, sens, varargin)

% FT_PREPARE_VOL_SENS does some bookkeeping to ensure that the volume conductor model
% and the sensor array are ready for subsequent forward leadfield computations and
% takes care of some pre-computations to make the calculations more efficient.
%
% Use as
%   [headmodel, sens] = ft_prepare_vol_sens(headmodel, sens, ...)
% with input arguments
%   headmodel = structure with volume conductor definition
%   sens      = structure with gradiometer or electrode definition
%
% The headmodel structure represents a volume conductor model of the head,
% its contents depend on the type of model. It is described in more detail
% in FT_DATATYPE_HEADMODEL. The sens structure represents a electrode or
% gradiometer array. It is described in more detail in FT_DATATYPE_SENS.
%
% Additional options should be specified in key-value pairs and can be
%   'channel'  = cell-array with strings (default = 'all')
%
% The detailed behavior of this function depends on whether the input
% consists of EEG or MEG and furthermoree depends on the type of volume
% conductor model:
% - in case of EEG single and concentric sphere models, the electrodes are
%   projected onto the skin surface.
% - in case of EEG boundary element models, the electrodes are projected on
%   the surface and a blilinear interpoaltion matrix from vertices to
%   electrodes is computed.
% - in case of MEG and a localspheres model, a local sphere is determined
%   for each coil in the gradiometer definition.
%  - in case of MEG with a singleshell Nolte model, the volume conduction
%    model is initialized
% In any case channel selection and reordering will be done. The channel
% order returned by this function corresponds to the order in the 'channel'
% option, or if not specified, to the order in the input sensor array.
%
% See also FT_COMPUTE_LEADFIELD, FT_READ_HEADMODEL, FT_READ_SENS

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

if iscell(headmodel) && iscell(sens)
  % this represents combined EEG, ECoG and/or MEG
  for i=1:numel(headmodel)
    [headmodel{i}, sens{i}] = ft_prepare_vol_sens(headmodel{i}, sens{i}, varargin{:});
  end
  return
end

% get the optional input arguments
% fileformat = ft_getopt(varargin, 'fileformat');
channel = ft_getopt(varargin, 'channel', sens.label);   % cell-array with channel labels, default is all

% ensure that the sensor description is up-to-date (Aug 2011)
sens = ft_datatype_sens(sens);

% this is to support volumes saved in mat-files, particularly interpolated
if ischar(headmodel)
  vpath     = fileparts(headmodel);   % remember the path to the file
  headmodel = ft_read_headmodel(headmodel); % replace the filename with the content of the file
end

% ensure that the volume conduction description is up-to-date (Jul 2012)
headmodel = ft_datatype_headmodel(headmodel);

% determine whether the input contains EEG or MEG sensors
iseeg = ft_senstype(sens, 'eeg');
ismeg = ft_senstype(sens, 'meg');

% determine the skin compartment
if ~isfield(headmodel, 'skin_surface')
  if isfield(headmodel, 'bnd')
    headmodel.skin_surface   = find_outermost_boundary(headmodel.bnd);
  elseif isfield(headmodel, 'r') && length(headmodel.r)<=4
    [dum, headmodel.skin_surface] = max(headmodel.r);
  end
end

% determine the inner_skull_surface compartment
if ~isfield(headmodel, 'inner_skull_surface')
  if isfield(headmodel, 'bnd')
    headmodel.inner_skull_surface  = find_innermost_boundary(headmodel.bnd);
  elseif isfield(headmodel, 'r') && length(headmodel.r)<=4
    [dum, headmodel.inner_skull_surface] = min(headmodel.r);
  end
end

% FT_HEADMODELTYPE to an empty struct won't work further down
if isempty(headmodel)
  headmodel = [];
end

% this makes them easier to recognise
sens.type       = ft_senstype(sens);
headmodel.type  = ft_headmodeltype(headmodel);

if isfield(headmodel, 'unit') && isfield(sens, 'unit') && ~strcmp(headmodel.unit, sens.unit)
  ft_error('inconsistency in the units of the volume conductor and the sensor array');
end

if ismeg && iseeg
  % this is something that could be implemented relatively easily
  ft_error('simultaneous EEG and MEG not yet supported');

elseif ~ismeg && ~iseeg
  ft_error('the input does not look like EEG, nor like MEG');

elseif ismeg

  % always ensure that there is a linear transfer matrix for combining the coils into gradiometers
  if ~isfield(sens, 'tra');
    Nchans = length(sens.label);
    Ncoils = size(sens.coilpos,1);
    if Nchans~=Ncoils
      ft_error('inconsistent number of channels and coils');
    end
    sens.tra = eye(Nchans, Ncoils);
  end

  if ~ft_headmodeltype(headmodel, 'localspheres')
    % select the desired channels from the gradiometer array
    [selchan, selsens] = match_str(channel, sens.label);
    % only keep the desired channels, order them according to the users specification
    try, sens.chantype = sens.chantype(selsens,:); end
    try, sens.chanunit = sens.chanunit(selsens,:); end
    try, sens.chanpos  = sens.chanpos (selsens,:); end
    try, sens.chanori  = sens.chanori (selsens,:); end
    sens.label    = sens.label(selsens);
    sens.tra      = sens.tra(selsens,:);
  else
    % for the localspheres model it is done further down
  end

  % remove the coils that do not contribute to any channel output
  selcoil      = any(sens.tra~=0,1);
  sens.coilpos = sens.coilpos(selcoil,:);
  sens.coilori = sens.coilori(selcoil,:);
  sens.tra     = sens.tra(:,selcoil);

  switch ft_headmodeltype(headmodel)
    case {'infinite' 'infinite_monopole' 'infinite_currentdipole' 'infinite_magneticdipole'}
      % nothing to do

    case 'singlesphere'
      % nothing to do

    case 'concentricspheres'
      % nothing to do

    case 'neuromag'
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % if the forward model is computed using the external Neuromag toolbox,
      % we have to add a selection of the channels so that the channels
      % in the forward model correspond with those in the data.
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      [selchan, selsens] = match_str(channel, sens.label);
      headmodel.chansel = selsens;

    case 'localspheres'
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % If the volume conduction model consists of multiple spheres then we
      % have to match the channels in the gradiometer array and the volume
      % conduction model.
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

      % the initial localspheres volume conductor has a local sphere per
      % channel, whereas it should have a local sphere for each coil
      if size(headmodel.r,1)==size(sens.coilpos,1) && ~isfield(headmodel, 'label')
        % it appears that each coil already has a sphere, which suggests
        % that the volume conductor already has been prepared to match the
        % sensor array
        return
      elseif size(headmodel.r,1)==size(sens.coilpos,1) && isfield(headmodel, 'label')
        if ~isequal(headmodel.label(:), sens.label(:))
          % if only the order is different, it would be possible to reorder them
          ft_error('the coils in the volume conduction model do not correspond to the sensor array');
        else
          % the coil-specific spheres in the volume conductor should not have a label
          % because the label is already specified for the coils in the
          % sensor array
          headmodel = rmfield(headmodel, 'label');
        end
        return
      end

      % the CTF way of representing the headmodel is one-sphere-per-channel
      % whereas the FieldTrip way of doing the forward computation is one-sphere-per-coil
      Nchans   = size(sens.tra,1);
      Ncoils   = size(sens.tra,2);
      Nspheres = size(headmodel.label);

      if isfield(headmodel, 'orig')
        % these are present in a CTF *.hdm file
        singlesphere.o(1,1) = headmodel.orig.MEG_Sphere.ORIGIN_X;
        singlesphere.o(1,2) = headmodel.orig.MEG_Sphere.ORIGIN_Y;
        singlesphere.o(1,3) = headmodel.orig.MEG_Sphere.ORIGIN_Z;
        singlesphere.r      = headmodel.orig.MEG_Sphere.RADIUS;
        % ensure consistent units
        singlesphere = ft_convert_units(singlesphere, headmodel.unit);
        % determine the channels that do not have a corresponding sphere
        % and use the globally fitted single sphere for those
        missing = setdiff(sens.label, headmodel.label);
        if ~isempty(missing)
          ft_warning('using the global fitted single sphere for %d channels that do not have a local sphere', length(missing));
        end
        for i=1:length(missing)
          headmodel.label(end+1) = missing(i);
          headmodel.r(end+1,:)   = singlesphere.r;
          headmodel.o(end+1,:)   = singlesphere.o;
        end
      end

      % make a new structure that only holds the local spheres, one per coil
      localspheres = [];
      localspheres.type = headmodel.type;
      localspheres.unit = headmodel.unit;

      % for each coil in the MEG helmet, determine the corresponding channel and from that the corresponding local sphere
      for i=1:Ncoils
        coilindex = find(sens.tra(:,i)~=0); % to which channel does this coil belong
        if length(coilindex)>1
          % this indicates that there are multiple channels to which this coil contributes,
          % which happens if the sensor array represents a synthetic higher-order gradient.
          [dum, coilindex] = max(abs(sens.tra(:,i)));
        end

        coillabel = sens.label{coilindex};               % what is the label of this channel
        chanindex = find(strcmp(coillabel, headmodel.label));  % what is the index of this channel in the list of local spheres
        localspheres.r(i,:) = headmodel.r(chanindex);
        localspheres.o(i,:) = headmodel.o(chanindex,:);
      end
      headmodel = localspheres;

      % finally do the selection of channels and coils
      % order them according to the users specification
      [selchan, selsens] = match_str(channel, sens.label);

      % first only modify the linear combination of coils into channels
      try, sens.chantype = sens.chantype(selsens,:); end
      try, sens.chanunit = sens.chanunit(selsens,:); end
      try, sens.chanpos  = sens.chanpos (selsens,:); end
      try, sens.chanori  = sens.chanori (selsens,:); end
      sens.label   = sens.label(selsens);
      sens.tra     = sens.tra(selsens,:);
      % subsequently remove the coils that do not contribute to any sensor output
      selcoil      = find(sum(sens.tra,1)~=0);
      sens.coilpos = sens.coilpos(selcoil,:);
      sens.coilori = sens.coilori(selcoil,:);
      sens.tra     = sens.tra(:,selcoil);
      % make the same selection of coils in the localspheres model
      headmodel.r = headmodel.r(selcoil);
      headmodel.o = headmodel.o(selcoil,:);

    case 'singleshell'
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % if the forward model is computed using the code from Guido Nolte, we
      % have to initialize the volume model using the gradiometer coil
      % locations
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

      % compute the surface normals for each vertex point
      if ~isfield(headmodel.bnd, 'nrm')
        fprintf('computing surface normals\n');
        headmodel.bnd.nrm = surface_normals(headmodel.bnd.pos, headmodel.bnd.tri);
      end

      % estimate center and radius
      [center, radius] = fitsphere(headmodel.bnd.pos);

      % order of spherical spherical harmonics, for 'real' realistic volume conductors order=10 is o.k
      if isfield(headmodel, 'order')
        order = headmodel.order;
      else
        order = 10;
      end

      % initialize the forward calculation (only if  coils are available)
      if size(sens.coilpos,1)>0 && ~isfield(headmodel, 'forwpar')
        s = ft_scalingfactor(headmodel.unit, 'cm');
        headmodel.forwpar = meg_ini([s*headmodel.bnd.pos headmodel.bnd.nrm], s*center', order, [s*sens.coilpos sens.coilori]);
        headmodel.forwpar.scale = s;
      end

    case  'openmeeg'
      % don't do anything, h2em or h2mm generated later in ft_prepare_leadfield

    case 'duneuro'

      %compute transfer matrix
      if(~isfield(headmodel,'meg_transfer'))

        % set coils and projections
        coils = sens.coilpos;
        projections = sens.coilori;
        headmodel.driver.set_coils_and_projections(coils', projections');

        % compute transfer matrix
        cfg = [];
        cfg.solver.reduction   = headmodel.reduction;
        cfg.solver.intorderadd = headmodel.intorderadd;
        headmodel.meg_transfer = headmodel.driver.compute_meg_transfer_matrix(cfg);
      end

    case 'interpolate'
      % this is to allow moving leadfield files
      if ~exist(headmodel.filename{1}, 'file')
        for i = 1:length(headmodel.filename)
          [p, f, x] = fileparts(headmodel.filename{i});
          headmodel.filename{i} = fullfile(vpath, [f x]);
        end
      end

      matchlab = isequal(sens.label, headmodel.sens.label);
      matchpos = isequal(sens.coilpos, headmodel.sens.coilpos);
      matchtra = (~isfield(sens, 'tra') && ~isfield(headmodel.sens, 'tra')) || (isfield(sens, 'tra') && isfield(headmodel.sens, 'tra') && isequal(sens.tra, headmodel.sens.tra));

      if matchlab && matchpos && matchtra
        % the input sensor array matches precisely with the forward model
        % no further interpolation is needed
      else
        % interpolate the channels in the forward model to the desired channels
        filename = tempname;
        headmodel  = ft_headmodel_interpolate(filename, sens, headmodel);
        % update the sensor array with the one from the volume conductor
        sens = headmodel.sens;
      end % if recomputing interpolation

      % for the leadfield computations the @nifti object is used to map the image data into memory
      ft_hastoolbox('spm8up', 1);
      for i=1:length(headmodel.sens.label)
        % map each of the leadfield files into memory
        headmodel.chan{i} = nifti(headmodel.filename{i});
      end

    case 'simbio'
      ft_error('MEG not yet supported with simbio');

    otherwise
      ft_error('unsupported volume conductor model for MEG');
  end

elseif iseeg

  % the electrodes are used, the channel positions are not relevant any more
  % channel positinos need to be recomputed after projecting the electrodes on the skin
  if isfield(sens, 'chanpos'); sens = rmfield(sens, 'chanpos'); end

  % select the desired channels from the electrode array
  % order them according to the users specification
  [selchan, selsens] = match_str(channel, sens.label);
  Nchans = length(sens.label);

  sens.label     = sens.label(selsens);
  try, sens.chantype  = sens.chantype(selsens); end
  try, sens.chanunit  = sens.chanunit(selsens); end

  if isfield(sens, 'tra')
    % first only modify the linear combination of electrodes into channels
    sens.tra     = sens.tra(selsens,:);
    % subsequently remove the electrodes that do not contribute to any channel output
    selelec      = any(sens.tra~=0,1);
    sens.elecpos = sens.elecpos(selelec,:);
    sens.tra     = sens.tra(:,selelec);
  else
    % the electrodes and channels are identical
    sens.elecpos = sens.elecpos(selsens,:);
  end

  switch ft_headmodeltype(headmodel)
    case {'infinite' 'infinite_monopole' 'infinite_currentdipole'}
      % nothing to do

    case {'halfspace', 'halfspace_monopole'}
      % electrodes' all-to-all distances
      numelec = size(sens.elecpos,1);
      ref_el = sens.elecpos(1,:);
      md = dist( (sens.elecpos-repmat(ref_el,[numelec 1]))' );
      % take the min distance as reference
      md = min(md(1,2:end));
      pos = sens.elecpos;
      % scan the electrodes and reposition the ones which are in the
      % wrong halfspace (projected on the plane)... if not too far away!
      for i=1:size(pos,1)
        P = pos(i,:);
        is_in_empty = acos(dot(headmodel.ori,(P-headmodel.pos)./norm(P-headmodel.pos))) < pi/2;
        if is_in_empty
          dPplane = abs(dot(headmodel.ori, headmodel.pos-P, 2));
          if dPplane>md
            ft_error('Some electrodes are too distant from the plane: consider repositioning them')
          else
            % project point on plane
            Ppr = pointproj(P,[headmodel.pos headmodel.ori]);
            pos(i,:) = Ppr;
          end
        end
      end
      sens.elecpos = pos;

    case {'slab_monopole'}
      % electrodes' all-to-all distances
      numelc  = size(sens.elecpos,1);
      ref_elc = sens.elecpos(1,:);
      md  = dist( (sens.elecpos-repmat(ref_elc,[numelc 1]))' );
      % choose min distance between electrodes
      md  = min(md(1,2:end));
      pos = sens.elecpos;
      % looks for contacts outside the strip which are not too far away
      % and projects them on the nearest plane
      for i=1:size(pos,1)
        P = pos(i,:);
        instrip1 = acos(dot(headmodel.ori1,(P-headmodel.pos1)./norm(P-headmodel.pos1))) > pi/2;
        instrip2 = acos(dot(headmodel.ori2,(P-headmodel.pos2)./norm(P-headmodel.pos2))) > pi/2;
        is_in_empty = ~(instrip1&instrip2);
        if is_in_empty
          dPplane1 = abs(dot(headmodel.ori1, headmodel.pos1-P, 2));
          dPplane2 = abs(dot(headmodel.ori2, headmodel.pos2-P, 2));
          if dPplane1>md && dPplane2>md
            ft_error('Some electrodes are too distant from the planes: consider repositioning them')
          elseif dPplane2>dPplane1
            % project point on nearest plane
            Ppr = pointproj(P,[headmodel.pos1 headmodel.ori1]);
            pos(i,:) = Ppr;
          else
            % project point on nearest plane
            Ppr = pointproj(P,[headmodel.pos2 headmodel.ori2]);
            pos(i,:) = Ppr;
          end
        end
      end
      sens.elecpos = pos;

    case {'singlesphere', 'concentricspheres'}
      % ensure that the electrodes ly on the skin surface
      radius = max(headmodel.r);
      pos    = sens.elecpos;
      if isfield(headmodel, 'o')
        % shift the the centre of the sphere to the origin
        pos(:,1) = pos(:,1) - headmodel.o(1);
        pos(:,2) = pos(:,2) - headmodel.o(2);
        pos(:,3) = pos(:,3) - headmodel.o(3);
      end
      distance = sqrt(sum(pos.^2,2)); % to the center of the sphere
      if any((abs(distance-radius)/radius)>0.005)
        ft_warning('electrodes do not lie on skin surface -> using radial projection')
      end
      pos = pos * radius ./ [distance distance distance];
      if isfield(headmodel, 'o')
        % shift the center back to the original location
        pos(:,1) = pos(:,1) + headmodel.o(1);
        pos(:,2) = pos(:,2) + headmodel.o(2);
        pos(:,3) = pos(:,3) + headmodel.o(3);
      end
      sens.elecpos = pos;

    case {'bem', 'dipoli', 'asa', 'bemcp'}
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % do postprocessing of volume and electrodes in case of BEM model
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

      % project the electrodes on the skin and determine the bilinear interpolation matrix
      if ~isfield(headmodel, 'tra') && (isfield(headmodel, 'mat') && ~isempty(headmodel.mat))
        % determine boundary corresponding with skin and inner_skull_surface
        if ~isfield(headmodel, 'skin_surface')
          headmodel.skin_surface = find_outermost_boundary(headmodel.bnd);
          fprintf('determining skin compartment (%d)\n', headmodel.skin_surface);
        end
        if ~isfield(headmodel, 'source')
          headmodel.source = find_innermost_boundary(headmodel.bnd);
          fprintf('determining source compartment (%d)\n', headmodel.source);
        end
        if size(headmodel.mat,1)~=size(headmodel.mat,2) && size(headmodel.mat,1)==length(sens.elecpos)
          fprintf('electrode transfer and system matrix were already combined\n');
        else
          fprintf('projecting electrodes on triangulated skin surface\n');
          % compute linear interpolation from triangle vertices towards electrodes
          [el, prj] = project_elec(sens.elecpos, headmodel.bnd(headmodel.skin_surface).pos, headmodel.bnd(headmodel.skin_surface).tri);
          tra       = transfer_elec(headmodel.bnd(headmodel.skin_surface).pos, headmodel.bnd(headmodel.skin_surface).tri, el);

          % replace the original electrode positions by the projected positions
          sens.elecpos = prj;

          if size(headmodel.mat,1)==size(headmodel.bnd(headmodel.skin_surface).pos,1)
            % construct the transfer from only the skin vertices towards electrodes
            interp = tra;
          else
            % construct the transfer from all vertices (also inner_skull_surface/outer_skull_surface) towards electrodes
            interp = [];
            for i=1:length(headmodel.bnd)
              if i==headmodel.skin_surface
                interp = [interp, tra];
              else
                interp = [interp, zeros(size(el,1), size(headmodel.bnd(i).pos,1))];
              end
            end
          end

          % incorporate the linear interpolation matrix and the system matrix into one matrix
          % this speeds up the subsequent repeated leadfield computations
          fprintf('combining electrode transfer and system matrix\n');

          % convert to sparse matrix to speed up the subsequent multiplication
          interp  = sparse(interp);
          headmodel.mat = interp * headmodel.mat;
          % ensure that the model potential will be average referenced
          avg = mean(headmodel.mat, 1);
          headmodel.mat = headmodel.mat - repmat(avg, size(headmodel.mat,1), 1);
        end
      end
    case  'openmeeg'
      % don't do anything, h2em or h2mm generated later in ft_prepare_leadfield

    case 'fns'
      if isfield(headmodel,'bnd')
        [el, prj] = project_elec(sens.elecpos, headmodel.bnd.pos, headmodel.bnd.tri);
        sens.tra = transfer_elec(headmodel.bnd.pos, headmodel.bnd.tri, el);
        % replace the original electrode positions by the projected positions
        sens.elecpos = prj;
      end

    case 'simbio'
      % check that the external toolbox is present
      ft_hastoolbox('simbio', 1);

      % extract the outer surface
      bnd = mesh2edge(headmodel);
      for j=1:length(sens.label)
        d = bsxfun(@minus, bnd.pos, sens.elecpos(j,:));
        [d, i] = min(sum(d.^2, 2));
        % replace the position of each electrode by the closest vertex
        sens.elecpos(j,:) = bnd.pos(i,:);
      end

      if (isfield(headmodel,'transfer') && isfield(headmodel,'elec'))
        if all(ismember(sens.label,headmodel.elec.label))
          [sensmember, senslocation] = ismember(sens.label,headmodel.elec.label);
          if (norm(sens.elecpos - headmodel.elec.elecpos(senslocation,:))<1e-8)
            headmodel.transfer = headmodel.transfer(senslocation,:);
            headmodel.elec = sens;
          else
            ft_error('Electrode positions do not fit to the given transfer matrix!');
          end
        else
          ft_error('Transfer matrix does not fit the given set of electrodes!');
        end
      else
        headmodel.transfer = sb_transfer(headmodel,sens);
        headmodel.elec = sens;
      end

    case 'duneuro'
      ft_hastoolbox('duneuro', 1);

      if(~isfield(headmodel,'eeg_transfer'))
        % set electrodes
        cfg = [];
        cfg.type = headmodel.electrodes;
        cfg.codims = headmodel.subentities;
        headmodel.driver.set_electrodes(sens.elecpos', cfg);

        % compute transfer matrix
        cfg = [];
        cfg.solver.reduction = headmodel.reduction;
        headmodel.eeg_transfer = headmodel.driver.compute_eeg_transfer_matrix(cfg);
      end

    case 'interpolate'
      % this is to allow moving leadfield files
      if ~exist(headmodel.filename{1}, 'file')
        for i = 1:length(headmodel.filename)
          [p, f, x] = fileparts(headmodel.filename{i});
          headmodel.filename{i} = fullfile(vpath, [f x]);
        end
      end

      matchlab = isequal(sens.label, headmodel.sens.label);
      matchpos = isequal(sens.elecpos, headmodel.sens.elecpos);
      matchtra = (~isfield(sens, 'tra') && ~isfield(headmodel.sens, 'tra')) || (isfield(sens, 'tra') && isfield(headmodel.sens, 'tra') && isequal(sens.tra, headmodel.sens.tra));

      if matchlab && matchpos && matchtra
        % the input sensor array matches precisely with the forward model
        % no further interpolation is needed
      else
        % interpolate the channels in the forward model to the desired channels
        filename = tempname;
        headmodel  = ft_headmodel_interpolate(filename, sens, headmodel);
        % update the sensor array with the one from the volume conductor
        sens = headmodel.sens;
      end % if recomputing interpolation

      % for the leadfield computations the @nifti object is used to map the image data into memory
      ft_hastoolbox('spm8up', 1);
      for i=1:length(headmodel.sens.label)
        % map each of the leadfield files into memory
        headmodel.chan{i} = nifti(headmodel.filename{i});
      end

    otherwise
      ft_error('unsupported volume conductor model for EEG');
  end

  % FIXME this needs careful thought to ensure that the average referencing which is
  % now done here and there, and that the linear interpolation in case of BEM are all
  % dealt with consistently always ensure that there is a linear transfer matrix for
  % rereferencing the EEG potential
  %
  % if ~isfield(sens, 'tra');
  %   sens.tra = eye(length(sens.label)) - 1/length(sens.label);
  % end

  % update the channel positions as the electrodes were projected to the skin surface
  [pos, ori, lab] = channelposition(sens);
  [selsens, selpos] = match_str(sens.label, lab);
  sens.chanpos = nan(length(sens.label),3);
  sens.chanpos(selsens,:) = pos(selpos,:);

end % if iseeg or ismeg

if isfield(sens, 'tra')
  if issparse(sens.tra) && size(sens.tra, 1)==1
    % this multiplication would result in a sparse leadfield, which is not what we want
    % the effect can be demonstrated as sparse(1)*rand(1,10), see also http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=1169#c7
    sens.tra = full(sens.tra);
  elseif ~issparse(sens.tra) && size(sens.tra, 1)>1
    % the multiplication of the "sensor" leadfield (electrode or coil) with the tra matrix to get the "channel" leadfield
    % is faster for most cases if the pre-multiplying weighting matrix is made sparse
    sens.tra = sparse(sens.tra);
  end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function Ppr = pointproj(P,plane)
% projects a point on a plane
% plane(1:3) is a point on the plane
% plane(4:6) is the ori of the plane
Ppr  = [];
ori  = plane(4:6);
line = [P ori];
% get indices of line and plane which are parallel
par = abs(dot(plane(4:6), line(:,4:6), 2))<1e-14;
% difference between origins of plane and line
dp = plane(1:3) - line(:, 1:3);
% Divide only for non parallel vectors (DL)
t = dot(ori(~par,:), dp(~par,:), 2)./dot(ori(~par,:), line(~par,4:6), 2);
% compute coord of intersection point
Ppr(~par, :) = line(~par,1:3) + repmat(t,1,3).*line(~par,4:6);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% This function serves as a replacement for the dist function in the Neural
% Networks toolbox.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [d] = dist(x)
n = size(x,2);
d = zeros(n,n);
for i=1:n
  for j=(i+1):n
    d(i,j) = sqrt(sum((x(:,i)-x(:,j)).^2));
    d(j,i) = d(i,j);
  end
end


function [all_bboxes_out] = localize_bboxes_of_all_imgs(...
    model, image_paths, all_bboxes_in, dst_directory, image_set_name, varargin)
% localize_bboxes_of_all_imgs: given a bounding box localization model and
% a set of images with their input candidate bounding boxes, for each
% image it predicts new bounding box coordinates such that the new boxes
% will be close (i.e. better localize) the objects of interest.
%
% INPUTS:
% 1) model: (type struct) the bounding box localization model
% 2) image_paths: NI x 1 cell array with the image paths of the set in the
% form of strings. NI is the number of images.
% 3) all_bboxes_in: a cell array with the candidate bounding
% boxes that are going to be processed. It can be of two forms:
%   a) (the flag is_per_class must being set to false) a NI x 1 cell array
%   where NI is the number of images. The all_bboxes_in{i} element
%   contains the candidate bounding boxes of the i-th image in the form of a
%   NB_i x 4 array where NB_i is the number of candidate bounding boxes of
%   the i-th image. The 4 columns of this array contain the bounding box
%   coordinates in the form of [x0,y0,x1,y1] (where the (x0,y0) and (x1,y1)
%   are the top-left and bottom-right corners).
%   b) (the flag is_per_class must being set to true) a C x 1 cell array
%   where C is the number of categories. The all_bboxes_in{j} element
%   in this case is NI x 1 cell array, where NI is the number of images,
%   with the candidate bounding boxes of the j-th category for each image.
%   The element all_bboxes_in{j}{i} is a NB_{i,j} x 4 array, where
%   NB_{i,j} is the number candidate bounding box of the i-th
%   image for the j-th category. The 4 columns of this array are the
%   bounding boxes in the form of [x0,y0,x1,y1] (where the (x0,y0) and (x1,y1)
%   are the top-left and bottom-right corners).
% 4) dst_directory: string with the path of the destination directory where
% the results are going to be cached.
% 5) image_set_name: string with the name of the image set that is being
% processed. It will be used on the name of the file where the results will
% be saved.
% 6) The rest input arguments are given in the form of Name,Value pair
% arguments and are:
% 'is_per_class': boolean value that if set to false, then the 3.a) form
% of the all_bboxes_in input parameter will be expected; otherwise, if set to
% true then the 3.b) form of the all_bboxes_in input parameter will be
% expected. Default value: false
% 'suffix': string with a suffix that it will be used on the name of the
% file where the results will be saved. Default value: ''
% 'all_bbox_gt': (optional) a NI cell array with the ground truth bounding
% boxes of each image; it will be used (if given) for printing the mAP on
% regular (see param 'checkpoint_step') step during processing the image set
% 'checkpoint_step': scalar value; during processing the NI images, after each
% checkpoint_step number of images the results are cached and the mAP
% results are printed. Default value: 500
%
% This file is part of the code that implements the following paper:
% Title      : "LocNet: Improving Localization Accuracy for Object Detection"
% Authors    : Spyros Gidaris, Nikos Komodakis
% Institution: Universite Paris Est, Ecole des Ponts ParisTech
% ArXiv link : http://arxiv.org/abs/1511.07763
% code       : https://github.com/gidariss/LocNet
%
% AUTORIGHTS
% --------------------------------------------------------
% Copyright (c) 2016 Spyros Gidaris
%
% Title     : "LocNet: Improving Localization Accuracy for Object Detection"
% ArXiv link: http://arxiv.org/abs/1511.07763
% Licensed under The MIT License [see LICENSE for details]
% ---------------------------------------------------------

ip = inputParser;
ip.addParamValue('is_per_class',    true,  @islogical);
ip.addParamValue('suffix',          '',    @ischar);
ip.addParamValue('all_bbox_gt',     {},    @iscell);
ip.addParamValue('checkpoint_step', 500,   @isnumeric);

ip.parse(varargin{:});
opts = ip.Results;

mkdir_if_missing(dst_directory);

filepath_results     = [dst_directory, filesep, 'loc_boxes_', opts.suffix, image_set_name, '.mat'];
in_progress_filepath = [dst_directory, filesep, 'loc_boxes_', opts.suffix, image_set_name, '_in_progress.mat'];

timestamp       = datestr(datevec(now()), 'yyyymmdd_HHMMSS');
log_file        = fullfile(dst_directory, 'output', ...
    ['log_file_localization',opts.suffix, image_set_name, '_', timestamp, '.txt']);
mkdir_if_missing(fileparts(log_file));

t_start = tic();
try
    all_bboxes_out = load_bboxes_scores(filepath_results);
catch
    diary(log_file);
    all_bboxes_out = process_images(...
        model, image_paths, all_bboxes_in, opts.all_bbox_gt, ...
        opts.is_per_class, in_progress_filepath, opts.checkpoint_step);

    save_bboxes_scores(filepath_results, all_bboxes_out);

    delete(in_progress_filepath);
    diary off;
end
fprintf('Localize bounding box proposals in %.4f minutes.\n', toc(t_start)/60);
end

function save_bboxes_scores(filename, bboxes_scores)
save(filename, 'bboxes_scores', '-v7.3');
end

function bboxes_scores = load_bboxes_scores(filename)
load(filename, 'bboxes_scores');
end

function [all_bboxes_out] = process_images(...
    model, image_paths, all_bboxes_in, all_bbox_gt, is_per_class, ...
    in_progress_filepath, checkpoint_step)

num_imgs      = length(image_paths);
nms_iou_thrs  = 0.3;
max_per_set   = 5 * num_imgs;
max_per_image = 100;
num_classes   = length(model.classes);
in_progress_filepath_prev = [in_progress_filepath, '.prev'];

try
    % load (if any) cached subset of results from images that have being
    % already processed from a previous run of the script that was stopped
    % before finish processing all the images.
    try
        [fist_img_idx, aboxes, all_bboxes_out, thresh] = load_progress(in_progress_filepath);
    catch
        [fist_img_idx, aboxes, all_bboxes_out, thresh] = load_progress(in_progress_filepath_prev);
    end
    mAP   = check_progress_on_mAP(aboxes, all_bbox_gt, fist_img_idx-1, model.classes);
    mAP_i = fist_img_idx-1;
catch exception
    fprintf('Exception message %s\n', getReport(exception));
    aboxes = cell(num_classes, 1);

    for i = 1:num_classes, aboxes{i} = cell(num_imgs, 1); end
    if is_per_class
        all_bboxes_out = cell(num_classes,1);
        for i = 1:num_classes, all_bboxes_out{i} = cell(num_imgs, 1); end
    else
        all_bboxes_out = cell(num_imgs,1);
    end
    thresh = -2.5 * ones(num_classes, 1);
    fist_img_idx = 1; mAP = 0; mAP_i = 0;
end

model.max_rois_num_in_gpu = 500; %find_max_rois_num_in_gpu(model);
num_chars = 0;
total_el_time = 0;
for i = fist_img_idx:num_imgs
    th = tic;
    % get the bounding box proposals of this image
    [bbox_proposals, bbox_scores] = get_this_img_bbox_proposals(all_bboxes_in, i, is_per_class);
    image = get_image(image_paths{i}); % read the image
    % refine the location of each bounding box with the localization model
    bbox_refined = localize_bboxes_of_image(model, image, bbox_proposals);
    localization_time = toc(th);

    th = tic;
    % prepare the candidate boundng box detections of this image
    bbox_cand_dets = prepare_bbox_cand_dets(bbox_refined, bbox_scores, num_classes, is_per_class);
    % prepare the output for this image
    all_bboxes_out = prepare_this_img_output(all_bboxes_out, i, is_per_class, bbox_cand_dets);
    % perform the post-processing step of non-max-suppresion on the
    % candidate boundng box detections of this image
    bbox_detections = post_process_candidate_detections(bbox_cand_dets, ...
        'thresholds',thresh, 'nms_iou_thrs',nms_iou_thrs,'use_gpu',true,...
        'max_per_image',max_per_image);

    for j = 1:num_classes, aboxes{j}{i} = bbox_detections{j}; end
    postprocessing_time = toc(th);

    if mod(i, checkpoint_step) == 0
        th = tic;
        for j = 1:num_classes, [aboxes{j}, thresh(j)] = keep_top_k(aboxes{j}, i, max_per_set, thresh(j)); end

        % save the till now progress
        save_progress(aboxes, all_bboxes_out, thresh, i, in_progress_filepath, in_progress_filepath_prev);

        % evaluate the mAP of the images 1 till i
        mAP = check_progress_on_mAP(aboxes, all_bbox_gt, i, model.classes);
        mAP_i = i;
        diary; diary; % flush diary

        %disp(thresh(:)');
        checkpoint_time = toc(th);
        fprintf('check_point time %.2fs\n',checkpoint_time)
        num_chars = 0;
    end

    elapsed_time = localization_time + postprocessing_time;
    [total_el_time, avg_time, est_rem_time] = timing_process(elapsed_time, total_el_time, fist_img_idx, i, num_imgs);
    fprintf(repmat('\b',[1, num_chars]));
    num_chars = fprintf('%s: bbox loc %d/%d:| ET %.3fs + %.3fs | AT: %.3fs | TT %.4fmin | ERT %.4fmin | mAP[%d/%d] = %.4f\n', ...
        procid(), i, num_imgs, localization_time, postprocessing_time, ...
        avg_time, total_el_time/60, est_rem_time/60, mAP_i, num_imgs, mAP);
end

delete(in_progress_filepath_prev);
end

function [total_el_time, ave_time, est_rem_time] = timing_process(...
    elapsed_time, total_el_time, fist_img_idx, i, num_imgs)
total_el_time   = total_el_time + elapsed_time;
ave_time        = total_el_time / (i-fist_img_idx+1);
est_rem_time    = ave_time * (num_imgs - i);
end

function [bbox_proposals, bbox_scores] = get_this_img_bbox_proposals(all_bboxes_in, img_id, is_per_class)
if is_per_class
    bbox_proposals_per_class = cellfun(@(x) x{img_id}(:,1:4), all_bboxes_in, 'UniformOutput', false);
    class_indices = [];
    for c = 1:length(all_bboxes_in)
        class_indices = [class_indices; ones(size(all_bboxes_in{c}{img_id},1),1,'single')*c];
    end
    bbox_scores_per_class = cellfun(@(x) x{img_id}(:,5:end), all_bboxes_in, 'UniformOutput', false);

    num_bbox_per_class  = cellfun(@(x) size(x,1), bbox_proposals_per_class,  'UniformOutput', true);
    bbox_proposals      = cell2mat(bbox_proposals_per_class(num_bbox_per_class>0));
    bbox_scores         = cell2mat(bbox_scores_per_class(num_bbox_per_class>0));
    bbox_proposals      = [bbox_proposals, class_indices];
else
    class_indices  = ones(size(all_bboxes_in{img_id},1),1,'single');
    bbox_proposals = [all_bboxes_in{img_id}(:,1:4),class_indices];
    bbox_scores    = all_bboxes_in{img_id}(:,5:end);
end
end

function bbox_cand_dets = prepare_bbox_cand_dets(bbox_refined, bbox_scores, num_classes, is_per_class)
if is_per_class
    class_indices      = bbox_refined(:,5);
    bbox_cand_dets_tmp = single([bbox_refined(:,1:4),bbox_scores]);
    bbox_cand_dets = cell(num_classes,1);
    for c = 1:num_classes
        bbox_cand_dets{c} = bbox_cand_dets_tmp(class_indices==c,:);
        if isempty(bbox_cand_dets{c}), bbox_cand_dets{c} = zeros(0,5,'single'); end
    end
else
    assert(size(bbox_refined,2)==5);
    assert(all(bbox_refined(:,5) == 1));
    bbox_cand_dets = single([bbox_refined(:,1:4), bbox_scores]);
end
end

function all_bboxes_out = prepare_this_img_output(all_bboxes_out, img_idx, is_per_class, bbox_this_img)
if is_per_class
    for j = 1:length(bbox_this_img), all_bboxes_out{j}{img_idx} = bbox_this_img{j}; end
else
    all_bboxes_out{img_idx} = bbox_this_img;
end
end

function [fist_img_idx, aboxes, abbox_scores, thresh] = load_progress(in_progress_filepath)
ld = load(in_progress_filepath);
fist_img_idx = ld.progress_state.img_idx + 1;
aboxes       = ld.progress_state.aboxes;
abbox_scores = ld.progress_state.abbox_scores;
thresh       = ld.progress_state.thresh;
end

function save_progress(aboxes, abbox_scores, thresh, img_idx, in_progress_filepath, in_progress_filepath_prev)
progress_state              = struct;
progress_state.img_idx      = img_idx;
progress_state.aboxes       = aboxes;
progress_state.abbox_scores = abbox_scores;
progress_state.thresh       = thresh;
if exist(in_progress_filepath, 'file')
    % in case it crash during updating in_progress_filepath
    copyfile(in_progress_filepath, in_progress_filepath_prev);
end
save(in_progress_filepath, 'progress_state', '-v7.3');
end

function mAP = check_progress_on_mAP(aboxes, all_bbox_gt, img_idx, classes)
mAP = 0;
if ~isempty(all_bbox_gt)
    aboxes      = cellfun(@(x) x(1:img_idx), aboxes, 'UniformOutput', false);
    mAP_result  = evaluate_average_precision_pascal( all_bbox_gt(1:img_idx), aboxes, classes );
    printAPResults(classes, mAP_result);
    mAP = mean([mAP_result(:).ap]');
end
end

function [boxes, thresh] = keep_top_k(boxes, end_at, top_k, thresh)
% ------------------------------------------------------------------------
% Keep top K
X = cat(1, boxes{1:end_at});
if isempty(X), return; end

scores = sort(X(:,end), 'descend');
thresh = scores(min(length(scores), top_k));
for image_index = 1:end_at
    bbox = boxes{image_index};
    keep = find(bbox(:,end) >= thresh);
    boxes{image_index} = bbox(keep,:);
end

end


function opts = getGlobalDefaults()
% Returns a structure with default option values for Manopt.
%
% function opts = getGlobalDefaults()
%
% Returns a structure opts containing the global default options such as
% verbosity level etc. Typically, global defaults are overwritten by solver
% defaults, which are in turn overwritten by user-specified options.
% See the online Manopt documentation for details on options.
%
% See also: mergeOptions

% This file is part of Manopt: www.manopt.org.
% Original author: Nicolas Boumal, Dec. 30, 2012.
% Contributors:
% Change log:
%
%   Aug. 2, 2018 (NB):
%       Changed default storedepth to 2 from 20, since solvers should now
%       use storedb.erase() to keep the cache lean.

    % There should be no reason to modify this file.
    % For better compatibility with future Manopt versions,
    % use the options structure of solvers.
    %
    % Really: don't modify it.

    % Verbosity level: 0 is no output at all. The higher the verbosity, the
    % more info is printed / displayed during solver execution.
    opts.verbosity = 3;

    % If debug is set to true, additional computations may be performed and
    % debugging information is outputed during solver execution.
    opts.debug = false;

    % Maximum number of store structures to store. If set to 0, caching
    % capabilities are not disabled, but the cache will be emptied at each
    % iteration of iterative solvers (more specifically: every time the
    % solver calls to purge the storedb).
    opts.storedepth = 2;

    % Maximum amount of time a solver may execute, in seconds.
    opts.maxtime = inf;

end


classdef TestXYFunctions < matlab.uitest.TestCase
    properties(TestParameter)
        classToTest = getclasses('XYfun');
    end

    properties
        zap         =  getZmapAnalysisPkg();
    end

    methods(Test)
        function testInitializable(testCase, classToTest)
            disp(classToTest);
            myclass = str2func(classToTest);
            obj = myclass(testCase.zap,'DelayProcessing',true,'InteractiveMode',false,'AutoShowPlots',false);
            testCase.assumeClass(obj,myclass);
            testCase.assertEqual(obj.RawCatalog,    zap.Catalog);
            testCase.assertEqual(obj.EventSelector, zap.EventSel);
            testCase.assertEqual(obj.Grid,          zap.Grid);
            testCase.assertEqual(obj.Shape,         zap.Shape);
            testCase.assertTrue(any( ReturnDetails.Properties.VariableNames == obj.active_col ));

            % XYfun.objectHandle(abc)
        end

        function testCalculatable(testCase, classToTest)
            disp(classToTest);
            myclass = str2func(classToTest);
            obj = myclass(testCase.zap,'DelayProcessing',true,'InteractiveMode',false,'AutoShowPlots',false);
            results = obj.Calculate();
            testCase.assumeNotEmpty(results);

        end
    end

end

function zap = getZmapAnalysisPkg()

    % get catalog
    catalogFile = 'resrc/sample/SED_fdsn_2000_on.mat';
    c=load(catalogFile,'catalog');

    % set up sampling parameters
    num_events  = 200;
    max_radius = 50;
    dist_units = 'km';
    evsel = EventSelectionParameters('NumClosestEventsUpToRadius', num_events, max_radius,'DistanceUnits',dist_units);

    % set up grid
    g_opts.dx = 10;
    g_opts.dy = 10;
    g_opts.dz = 10;
    g_opts.xyunits = 'kilometer';
    g_opts.FollowMeridians = false;
    g_opts.GridEntireArea = false;

    %center on ETHZ
    fixedOpts.UseFixedAnchorPoint = true;
    fixedOpts.XAnchor = 47.3763;
    fixedOpts.YAnchor = 8.5477;
    fixedOpts.ZAnchor = 0;

    gopt= GridOptions(g_opts, fixedOpts);
    g = ZmapGrid('testgrid', gopt);

    % set up shape
    sh = load_shape('eq_data/switzerland_shape.csv');

    zap = ZmapAnalysisPkg([],c,evsel,g,sh);

    %zap = ZmapAnalysisPkg.fromGlobal('primeCatalog');

end

function c = getfuns(packageName)
    a=what(packageName);
    a.m(a.m == "Contents.m") = [];
    funNames=replace(a.m,'.m','');
    funNames = strcat(packageName, '.', funNames);
    c = cellfun(@str2func,funNames,'UniformOutput',false);
end

function c = getclasses(packageName)
    a=what(packageName);
    a.m(a.m == "Contents.m") = [];
    funNames=replace(a.m,'.m','');
    c = strcat(packageName, '.', funNames);
end


function sVF = minus(sVF1, sVF2)
%
% Syntax
%   sVF = sVF1-sVF2
%   sVF = a-sVF1
%   sVF = sVF1-a
%

sVF = plus(sVF1, -sVF2);

end


function copyBinFile(ops1)

fid    = cell(size(ops1));
for i = 1:size(ops1,1)
    for j = 1:size(ops1,2)
        fid{i,j}           = fopen(ops1{i,j}.RegFile, 'r');
        folder = fullfile(ops1{i,j}.RegFileBinLocation, ops1{i,j}.mouse_name, ...
            ops1{i,j}.date, ops1{i,j}.CharSubDirs);
        if ~exist(folder, 'dir')
            mkdir(folder)
        end
        fidCopy = fopen(fullfile(folder, ...
            sprintf('%s_%s_%s_plane%d.bin', ops1{i,j}.mouse_name, ops1{i,j}.date, ...
            ops1{i,j}.CharSubDirs, i)), 'w');
        sz = ops1{i,j}.Lx * ops1{i,j}.Ly;
        parts = ceil(sum(ops1{i,j}.Nframes) / 2000);
        for p = 1:parts
            toRead = 2000;
            if p == parts
                toRead = sum(ops1{i,j}.Nframes) - 2000 * (parts-1);
            end
            data = fread(fid{i,j},  sz*toRead, '*int16');
            fwrite(fidCopy, data, class(data));
        end
        fclose(fidCopy);
        fclose(fid{i,j});
    end
end


function batch = StartBatch(mfile,bfile,varargin)

%StartBatch - Start a new batch job.
%
% Batch processing is useful if you need to repeatedly perform a given analysis
% on different data sets. Using batch processing is easier and more reliable
% than writing a simple 'for' loop:
%
%  * You must only supply the code to run the analysis on one data set,
%    and a text file listing the data sets, including specific parameters
%
%  * Running the batch on a subset of the data is as easy as commenting
%    out the appropriate lines in this file
%
%  * Batch processing will not be interrupted when the analysis of one
%    particular data set generates an error
%
%  * Errors can be logged to disk
%
%  * Processing can be delayed to a later time (e.g. during the night)
%
% Results can either be kept in memory using the batch output variable, or
% stored in a central database. The latter is the recommended solution, as it
% is far more powerful and reliable. See EXAMPLES below.
%
% During batch processing, figures are typically hidden so they do not keep
% popping up as the user is performing other tasks as the batch is running in
% the background (although this is configurable). In order to keep figures
% hidden, batch functions should not use figure(h) or axes(a) which make the
% figure visible. Two helper functions, scf(h) and sca(h), can be used instead
% to set the current figure or axes without making the figure visible.
%
%  USAGE
%
%    batch = StartBatch(mfile,bfile,<options>)
%
%    mfile          batch function (M-file name or function handle)
%    bfile          batch file listing the parameters for each iteration
%    <options>      optional list of property-value pairs (see table below)
%
%    =========================================================================
%     Properties    Values
%    -------------------------------------------------------------------------
%     'delay'       delay (in minutes) before execution starts
%     'hide'        by default, new figures are hidden to speed up batch
%                   processing (use 'off' to cancel this behavior)
%     'log'         error log file (default = none) (see NOTE below)
%    =========================================================================
%
%  NOTE
%
%    Log file names can include wildcards to indicate current date and time:
%
%      %y    year
%      %m    month
%      %d    day
%      %t    time
%
%  EXAMPLE 1 - RESULTS KEPT IN BATCH OUTPUT VARIABLE
%
%    This is the simpler solution, but it is not as reliable (e.g. the results
%    can be lost if Matlab crashes) and powerful (e.g. the results cannot be
%    shared between users) as using a database. See EXAMPLE 2 below.
%
%    Suppose you wish to count the number of 'ripples' in a set of sleep
%    sessions recorded from different rats. You could create the file
%    'batch.txt', where you would list the session files to process, as
%    well as the LFP channel to use in each case to detect ripples:
%
%        Rat-01-20100915-01-sleep     5
%        Rat-01-20100920-01-sleep     5
%        Rat-02-20101001-01-sleep     3
%        Rat-01-20101005-04-rest      1
%        ...
%
%    The function used to process the data would look like:
%
%        function n = CountRipples(session,channel)
%
%        SetCurrentSession(session);
%        lfp = GetLFP(channel);
%        fil = FilterLFP(lfp,'bandpass','ripples');
%        ripples = FindRipples(fil);
%        n = size(ripples,1);
%
%    To start processing the data in one hour, you would then call:
%
%        b = StartBatch(@CountRipples,'batch.txt','delay',60);
%
%    To get the results:
%
%        n = GetBatch(b);
%
%    To log errors:
%
%        b = StartBatch(@CountRipples,'batch.txt','log','Errors-%y%m%d.log');
%
%  NOTE
%
%    For technical reasons, the batch function must have a fixed number of output
%    parameters, e.g. it cannot have 'varargout' as one of its output parameters.
%    It is possible to circumvent this limitation if you know in advance the number
%    of output parameters that the batch function would actually return in your
%    particular case. Assuming this function is called 'VariableProcess', takes two
%    inputs and will always return three outputs for your data, you could define:
%
%        function [x,y,z] = FixedProcess(u,v)
%
%        [x,y,z] = VariableProcess(u,v);
%
%    and then use this as the batch function.
%
%  EXAMPLE 2 - RESULTS STORED IN CENTRAL DATABASE
%
%    Let us rewrite the previous example using a central database. First, we need
%    to create the database:
%
%        % This assumes your login/password are stored
%        % in a configuration file in your home directory
%        DBConnect;
%        DBCreate('Ripples_%y%m%d');
%
%    The function used to process the data would now look like:
%
%        function CountRipples(session,channel)
%
%        SetCurrentSession(session);
%        lfp = GetLFP(channel);
%        fil = FilterLFP(lfp,'bandpass','ripples');
%        ripples = FindRipples(fil);
%        n = size(ripples,1);
%        eid = [session '-' channel];
%        DBAddVariable(n,eid,'Ripple Count','','',{'CountRipples'});
%
%    This would store the number of ripples for each session, as well as useful
%    information such as who stored these data, when, using which function (the
%    actual code, not just the function name, is stored in the database).
%
%    To get the results:
%
%        n = DBGetValues;
%
%  SEE
%
%    See also Database, GetBatch, BatchInfo, CancelBatch, CleanBatches, Store,
%    Recall, Hide, sca, scf.
%

% Copyright (C) 2007-2013 by Michaël Zugaro
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 3 of the License, or
% (at your option) any later version.

% Default values
delay = 0;
hide = 'off';
log = '';

% Check number of parameters
if nargin < 2,
	error('Incorrect number of parameters (type ''help StartBatch'' for details).');
end

% Parse parameter list
for i = 1:2:length(varargin),
  if ~ischar(varargin{i}),
    error(['Parameter ' num2str(i+1) ' is not a property (type ''help StartBatch'' for details).']);
  end
  switch(lower(varargin{i})),
    case 'delay',
      delay = varargin{i+1};
      if ~isiscalar(delay,'>0'),
        error('Incorrect value for property ''delay'' (type ''help StartBatch'' for details).');
      end
    case 'hide',
      hide = lower(varargin{i+1});
      if ~isstring_FMAT(hide,'on','off'),
        error('Incorrect value for property ''hide'' (type ''help StartBatch'' for details).');
      end
    case 'log',
      log = varargin{i+1};
      if ~isstring_FMAT(log),
        error('Incorrect value for property ''log'' (type ''help StartBatch'' for details).');
      end
    otherwise,
      error(['Unknown property ''' num2str(varargin{i}) ''' (type ''help StartBatch'' for details).']);
  end
end

% Compatibility with previous versions: reverse parameter order if necessary
if isa(bfile,'function_handle') || (isstring_FMAT(mfile) && isempty(which(mfile))),
	tmp = bfile;
	bfile = mfile;
	mfile = tmp;
end

% Batch function name
if isa(mfile,'function_handle'),
	mfileName = func2str(mfile);
else
	mfileName = mfile;
end

% Check batch file and function are valid
if ~isstring_FMAT(bfile) || ~exist(bfile,'file'),
	error('Batch file not found (type ''help StartBatch'' for details).');
end
if isempty(which(mfileName)),
	error('Batch function not found (type ''help StartBatch'' for details).');
end

% Make sure M-file has a defined number of output parameters
if nargout(mfile) == -1,
	error(['''' mfileName ''' has a variable number of output arguments (type ''help StartBatch'' for details). ']);
end

% Open batch file
f = fopen(bfile,'r');
if f == -1, error(['Could not open file ''' bfile '''.']); end

% Parse batch file
b = ParseBatch(bfile);

% Open log file
b.log = -1;
if ~isempty(log),
	% Insert date
	log = InsertDate(log);
	b.log = fopen(log,'w');
	if b.log == -1, error(['Could not open log file ''' log '''.']); end
end

% Set batch function
[~,mfile] = fileparts(mfileName);
b.mfile = mfile;

b.hide = strcmp(hide,'on');
if b.hide,
	warning('Figures will be hidden (type ''help Hide'' for details).');
end

% Start timer
batch = timer('TimerFcn',{@RunBatch,b},'StartDelay',delay*60,'Tag','BatchJob');
start(batch);


%FtsZPolymerization process test case
%
% Author: Jayodita Sanghvi, jayodita@stanford.edu
% Author: Jared Jacobs, jmjacobs@stanford.edu
% Affilitation: Covert Lab, Department of Bioengineering, Stanford University
% Last updated: 9/1/2010
classdef FtsZPolymerization_Test < edu.stanford.covert.cell.sim.ProcessTestCase
    methods
        function this = FtsZPolymerization_Test(name)
            this = this@edu.stanford.covert.cell.sim.ProcessTestCase(name);
        end

        function setUp(this)
            this.setUp@edu.stanford.covert.cell.sim.ProcessTestCase();
            warning('off', 'WholeCell:warning');
        end

        function loadTestFixture(this)
            this.loadTestFixture@edu.stanford.covert.cell.sim.ProcessTestCase();
            m = this.process;
            m.geometry.volume = 1e-18;
        end
    end

    %tests
    methods
        function testInitializedToSteadyState(this)
            m = this.process;

            m.enzymes(:) = 0;
            m.enzymes(m.enzymeIndexs_FtsZ) = 1000;
            m.boundEnzymes(:);
            this.setGTP(1e5);
            this.setGDP(1e4);

            m.initializeState();
            initialEnzymes = m.enzymes;

            for i = 1:5
                m.evolveState();
            end
            assertNonnegativeCounts(m.enzymes);
            assertEqual(countFtsZ(initialEnzymes), countFtsZ(m.enzymes));
            assertElementsAlmostEqual(initialEnzymes', m.enzymes', 'relative', 0.2, 3);
        end

        function testFtsZAndGtpBalance(this)
            m = this.process;
            this.setGTP(400);
            this.setGDP(0);
            m.enzymes(m.enzymeIndexs_FtsZ) = 300;
            m.enzymes(m.enzymeIndexs_FtsZ_GDP) = 100;
            m.enzymes(m.enzymeIndexs_FtsZ_activated) = [200 101 0 0 0 0 0 0 22]';

            for i = 1:20
                m.evolveState();
                assertNonnegativeCounts(m.enzymes);
                assertEqual(1000, countFtsZ(m.enzymes));
                assertEqual(...
                    m.substrates(m.substrateIndexs_gtp),...
                    sum(m.enzymes([m.enzymeIndexs_FtsZ m.enzymeIndexs_FtsZ_GDP])));
            end
        end

        function testNoFtsZ(this)
            m = this.process;
            m.enzymes(:) = 0;
            this.setGTP(1e5);

            m.evolveState();
            assertEqual(zeros(size(m.enzymes)), m.enzymes);
            assertEqual(1e5, m.substrates(m.substrateIndexs_gtp));
        end

        function testNoGtp(this)
            m = this.process;
            m.enzymes(:) = 0;
            m.enzymes(m.enzymeIndexs_FtsZ) = 1000;
            m.substrates(:) = 1e6;
            this.setGTP(0);
            this.setGDP(1e5);

            m.evolveState();

            assertTrue(all(m.substrates >= 0));
            assertTrue(all(m.enzymes >= 0));
            assertEqual(1000, [1 1 1:9] * m.enzymes);
            assertAllEqual(0, m.enzymes(m.enzymeIndexs_FtsZ_activated));
            assertEqual(0, m.substrates(m.substrateIndexs_gtp));
        end

        function testNoGdp(this)
            m = this.process;
            m.enzymes(:) = 0;
            m.enzymes(m.enzymeIndexs_FtsZ) = 1000;
            m.substrates(:) = 1e6;
            this.setGTP(1e5);
            this.setGDP(0);

            m.evolveState();

            assertTrue(all(m.substrates >= 0));
            assertTrue(all(m.enzymes >= 0));
            assertEqual(1000, [1 1 1:9] * m.enzymes);
            assertAllEqual(0, m.enzymes(m.enzymeIndexs_FtsZ_GDP));
            assertEqual(0, m.substrates(m.substrateIndexs_gdp));
        end

        function testNoWater(this)
            m = this.process;
            m.enzymes(:) = 0;
            m.enzymes(m.enzymeIndexs_FtsZ) = 1000;
            m.substrates(:) = 1e6;
            m.substrates(m.substrateIndexs_water) = 0;
            this.setGDP(0);

            m.evolveState();

            assertTrue(all(m.substrates >= 0));
            assertTrue(all(m.enzymes >= 0));
            assertEqual(1000, [1 1 1:9] * m.enzymes);
            assertAllEqual(0, m.enzymes(m.enzymeIndexs_FtsZ_GDP));
        end

        function testForwardActivationOnly(this)
            m = this.process;
            m.enzymes(:) = [1000 0 0 0 0 0 0 0 0 0 0]';  %inactive monomer only
            this.setGTP(1000);

            this.setAllReactionRatesToZero();
            m.activationFwd = 10;

            m.evolveState();
            assertEqual(0, m.substrates(m.substrateIndexs_gtp));
            assertEqual(0, m.enzymes(m.enzymeIndexs_FtsZ));
            assertEqual(0, m.enzymes(m.enzymeIndexs_FtsZ_GDP));
            assertEqual([1000 0 0 0 0 0 0 0 0], m.enzymes(m.enzymeIndexs_FtsZ_activated)');
        end

        function testReverseActivationOnly(this)
            m = this.process;
            m.enzymes(:) = [0 0 1000 0 0 0 0 0 0 0 0]';  %activated monomer only
            this.setGTP(0);

            this.setAllReactionRatesToZero();
            m.activationRev = 10;

            m.evolveState();
            assertEqual(1000, m.substrates(m.substrateIndexs_gtp));
            assertEqual(1000, m.enzymes(m.enzymeIndexs_FtsZ));
            assertEqual(0, m.enzymes(m.enzymeIndexs_FtsZ_GDP));
            assertEqual([0 0 0 0 0 0 0 0 0], m.enzymes(m.enzymeIndexs_FtsZ_activated)');
        end

        function testForwardExchangeOnly(this)
            m = this.process;
            m.enzymes(:) = [0 1000 0 0 0 0 0 0 0 0 0]';  %deactivated monomer only
            this.setGTP(1000);
            this.setGDP(0);

            this.setAllReactionRatesToZero();
            m.exchangeFwd = 1e6;

            m.evolveState();
            assertEqual(0, m.substrates(m.substrateIndexs_gtp));
            assertEqual(1000, m.substrates(m.substrateIndexs_gdp));
            assertEqual(0, m.enzymes(m.enzymeIndexs_FtsZ));
            assertEqual(0, m.enzymes(m.enzymeIndexs_FtsZ_GDP));
            assertEqual([1000 0 0 0 0 0 0 0 0], m.enzymes(m.enzymeIndexs_FtsZ_activated)');
        end

        function testReverseExchangeOnly(this)
            m = this.process;
            m.enzymes(:) = [0 0 1000 0 0 0 0 0 0 0 0]';  %activated monomer only
            this.setGTP(0);
            this.setGDP(1000);

            this.setAllReactionRatesToZero();
            m.exchangeRev = 1e6;

            m.evolveState();
            assertEqual(1000, m.substrates(m.substrateIndexs_gtp));
            assertEqual(0, m.substrates(m.substrateIndexs_gdp));
            assertEqual(0, m.enzymes(m.enzymeIndexs_FtsZ));
            assertEqual(1000, m.enzymes(m.enzymeIndexs_FtsZ_GDP));
            assertEqual([0 0 0 0 0 0 0 0 0], m.enzymes(m.enzymeIndexs_FtsZ_activated)');
        end

        function testForwardNucleationOnly(this)
            m = this.process;
            m.enzymes = [0 0 1000 0 0 0 0 0 0 0 0]';  %activated monomer only

            this.setAllReactionRatesToZero();
            m.nucleationFwd = 1e6;

            m.evolveState();
            assertElementsAlmostEqual(...
                [0 0 0 500 0 0 0 0 0 0 0], m.enzymes', 'absolute', 2);
            assertEqual(1000, countFtsZ(m.enzymes));
        end

        function testReverseNucleationOnly(this)
            m = this.process;
            m.enzymes = [0 0 0 500 0 0 0 0 0 0 0]';  %activated dimer only

            this.setAllReactionRatesToZero();
            m.nucleationRev = 10;

            m.evolveState();
            assertEqual([0 0 1000 0 0 0 0 0 0 0 0], m.enzymes');
        end

        function testForwardElongationOnly(this)
            m = this.process;
            m.enzymes = [0 0 700 100 0 0 0 0 0 0 0]';  %activated monomers and dimers

            this.setAllReactionRatesToZero();
            m.elongationFwd = 1e7;

            m.evolveState();
            assertElementsAlmostEqual(...
                [0 0 0 0 0 0 0 0 0 0 100], m.enzymes', 'absolute', 9);
            assertEqual(900, countFtsZ(m.enzymes));
        end

        function testReverseElongationOnly(this)
            m = this.process;
            m.substrates(:) = 1e6;
            m.enzymes = [0 0 0 0 0 0 0 0 0 0 100]';  %9mers only

            this.setAllReactionRatesToZero();
            m.elongationRev = 100;

            m.evolveState();
            assertElementsAlmostEqual(...
                [0 0 700 100 0 0 0 0 0 0 0], m.enzymes', 'absolute', 2);
            assertEqual(900, countFtsZ(m.enzymes));
        end

        function testApplySubstrateLimits(this)
            m = this.process;

            %% Case 1: ODEs trying to make FtsZ-GTP polymers from FtsZ-GDP, but unable to because no GTP
            %state before running ODEs
            m.enzymes(:) = 0;
            m.enzymes(m.enzymeIndexs_FtsZ_GDP) = sum(1:9);
            m.substrates(:) = 1e6;
            m.substrates(m.substrateIndexs_gtp) = 0;

            %state after running ODEs
            substrates = m.substrates;
            enzymes = zeros(size(m.enzymes));
            enzymes(m.enzymeIndexs_FtsZ_activated) = 1;

            %final state
            [finEnzymes, finSubstrates] = m.applySubstrateLimits(enzymes, substrates);

            %assertions
            assertTrue(all(finSubstrates >= 0)); %counts are non-negative
            assertTrue(all(finEnzymes >= 0)); %counts are non-negative
            assertEqual([1 1 1:9] * m.enzymes, [1 1 1:9] * finEnzymes); %number of FtsZ monomer is constant
            assertEqual([0 1 1:9] * m.enzymes + [1 1] * m.substrates([m.substrateIndexs_gdp; m.substrateIndexs_gtp]), ... %number of guanosine (free + bound) is constant
                [0 1 1:9] * finEnzymes + [1 1] * finSubstrates([m.substrateIndexs_gdp; m.substrateIndexs_gtp]));
            assertIn([0 0 1:9] * finEnzymes + [0 1] * finSubstrates([m.substrateIndexs_gdp; m.substrateIndexs_gtp]), ... %number of GTP molecules (free + bound) doesn't increase / energy is not created
                [0 [0 0 1:9] * m.enzymes + [0 1] * m.substrates([m.substrateIndexs_gdp; m.substrateIndexs_gtp])]);
            assertEqual(m.substrates' * m.substrateMolecularWeights + m.enzymes' * m.enzymeMolecularWeights, ... %mass is conserved
                finSubstrates' * m.substrateMolecularWeights + finEnzymes' * m.enzymeMolecularWeights);

            %% Case 2: Making FtsZ-GDP from free FtsZ and GTP
            %state before running ODEs
            m.enzymes(:) = 0;
            m.enzymes(m.enzymeIndexs_FtsZ) = 100;
            m.substrates(:) = 1e6;
            m.substrates(m.substrateIndexs_gdp) = 0;

            %state after running ODEs
            substrates = m.substrates;
            enzymes = zeros(size(m.enzymes));
            enzymes(m.enzymeIndexs_FtsZ_GDP) = 100;

            %final state
            [finEnzymes, finSubstrates] = m.applySubstrateLimits(enzymes, substrates);

            %assertions
            assertTrue(all(finSubstrates >= 0)); %counts are non-negative
            assertTrue(all(finEnzymes >= 0)); %counts are non-negative
            assertEqual([1 1 1:9] * m.enzymes, [1 1 1:9] * finEnzymes); %number of FtsZ monomer is constant
            assertEqual([0 1 1:9] * m.enzymes + [1 1] * m.substrates([m.substrateIndexs_gdp; m.substrateIndexs_gtp]), ... %number of guanosine (free + bound) is constant
                [0 1 1:9] * finEnzymes + [1 1] * finSubstrates([m.substrateIndexs_gdp; m.substrateIndexs_gtp]));
            assertIn([0 0 1:9] * finEnzymes + [0 1] * finSubstrates([m.substrateIndexs_gdp; m.substrateIndexs_gtp]), ... %number of GTP molecules (free + bound) doesn't increase / energy is not created
                [0 [0 0 1:9] * m.enzymes + [0 1] * m.substrates([m.substrateIndexs_gdp; m.substrateIndexs_gtp])]);
            assertElementsAlmostEqual(m.substrates' * m.substrateMolecularWeights + m.enzymes' * m.enzymeMolecularWeights, ... %mass is conserved
                finSubstrates' * m.substrateMolecularWeights + finEnzymes' * m.enzymeMolecularWeights, ...
                'relative', 1e-8, 0);
        end

        function testGeneEssentiality(this)
            m = this.process;
            m.enzymes(:) = 0;
            m.enzymes(m.enzymeIndexs_FtsZ) = 100;
            m.boundEnzymes(:) = 0;
            this.setGTP(1e5);
            this.setGDP(1e4);
            this.helpTestGeneEssentiality({
                'MG_224'},...  %ftsZ
                @(m,i) m.enzymes(m.enzymeIndexs_FtsZ_9mer) > ...
                       i.enzymes(m.enzymeIndexs_FtsZ_9mer), ...
                       struct('lengthSec', 20));
        end
    end

    %helper methods
    methods
        function setAllReactionRatesToZero(this)
            m = this.process;
            m.activationFwd = 0;
            m.activationRev = 0;
            m.exchangeFwd = 0;
            m.exchangeRev = 0;
            m.nucleationFwd = 0;
            m.nucleationRev = 0;
            m.elongationFwd = 0;
            m.elongationRev = 0;
        end

        function setGTP(this, numMolecules)
            m = this.process;
            m.substrates(m.substrateIndexs_gtp) = numMolecules;
        end

        function setGDP(this, numMolecules)
            m = this.process;
            m.substrates(m.substrateIndexs_gdp) = numMolecules;
        end
    end
end

function assertNonnegativeCounts(values)
    assertTrue(all(values >= 0));
    assertEqual(round(values), values);
end

function result = countFtsZ(counts)
    result = [1 1 1:length(counts)-2] * counts;
end


function [ctf] = ctf_read_res4(folder,VERBOSE,COEFS);

% ctf_read_res4 - Read a CTF .res4 file
%
% ctf = ctf_read_res4( [folder], [verbose], [coefs])
%
% This function reads the resource information from a CTF .ds folder.  This
% resource information must be read before reading the .meg4 data file.
% All input arguments are optional.
%
% INPUTS
%
%   folder: the .ds directory containing the data to be read.  With
%           no input, it will prompt with a gui folder locator
%           (called by ctf_folder).
%
%   verbose: If verbose = 1, display 'ctf.setup' structure (default)
%            If verbose = 0, do not display 'ctf.setup' structure
%
%   coefs: an option to read the MEG sensor and reference coefficients,
%          which give the weights for calculation of synthetic 2nd or 3rd
%          order gradiometers.
%          If coefs = 1, read the sensor coefficients
%          If coefs = 0, do not read the sensor coefficients (this is the
%          default because it is assumed that data preprocessing with
%          CTF tools has already applied AND saved a synthetic
%          gradiometer transformation for the meg4 data file).  The
%          ctf.sensor.info(x).grad_order_no will indicate the gradient
%          order of the data (this only applies to MEG sensors, other
%          channels have zero values).
%
% OUTPUTS
%
%   ctf.folder - path of the .ds folder
%
%   ctf.res4.file - data file path/name
%   ctf.res4.header - data format header
%
%   ctf.setup - a header structure consisting of date, time, run name, run
%   title, subject, run description, operator, number of channels, number
%   of samples, sample rate, number of trials, duration, pretrigger_samples,
%   sensor filename, head zeroing, and number of filters.
%
%   ctf.sensor.index - a sensor structure consisting of EEG sensors, MEG
%   sensors, reference sensors, and other sensors.
%
%   ctf.sensor.info - a structure with gain and offset information consisting
%   of proper gain, Q gain, io gain, io offset, and index.
%
% The proper gain is the channel-wide constant quotient of a value in raw
% units and a value in user units. (For EEG this is one because there is
% no conversion; i.e., the values are in volts, for MEG there is conversion
% between phi0 and teslas).
% proper gain = raw value / user value
%
% The Q-gain is used to convert the 32-bit integers produced by the
% electronics to the raw values stored in the data file.
% raw value = 32 bit value / Q gain
%
% The IO-gain is essentially a correction factor that is applied to the
% raw values before they are stored to disk.
%
%
%      <>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
%      <                                                      >
%      <                    DISCLAIMER:                       >
%      <                                                      >
%      < THIS PROGRAM IS INTENDED FOR RESEARCH PURPOSES ONLY. >
%      < THIS PROGRAM IS IN NO WAY INTENDED FOR CLINICAL OR   >
%      <                    OFFICIAL USE.                     >
%      <                                                      >
%      <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>
%

% $Revision: 1.1 $ $Date: 2009-01-30 03:49:27 $

% Copyright (C) 2003  Darren L. Weber
%
% 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.

% Modified: 11/2003, Darren.Weber_at_radiology.ucsf.edu
%                    - modified from NIH code readresfile.m
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

ver = '$Revision: 1.1 $';
fprintf('\nCTF_READ_RES4 [v %s]\n',ver(11:15)); tic;

if ~exist('folder','var'),
    ctf = ctf_folder;
else
    ctf = ctf_folder(folder);
end
%[folderPath,folderName,folderExt] = fileparts(ctf.folder);
ctf.res4.file = findres4file(ctf.folder);

if ~exist('VERBOSE','var'), VERBOSE = 1; end
if ~exist('COEFS','var'), COEFS = 0; end

%----------------------------------------------------------------
% open the data file

[fid,message] = fopen(ctf.res4.file,'rb','ieee-be.l64');
if fid < 0, error('cannot open .res4 file'); end

%-------------------------------------------------------------
% READ HEADER

fseek(fid,0,-1);
ctf.res4.header = char(fread(fid,8,'char'))';

% check for the right format
if strmatch('MEG41RS',ctf.res4.header),
    % OK, we can handle this format
else
    msg = sprintf('This function is designed to read MEG41RS format.\nIt may not read "%s" format correctly',ctf.res4.header);
    warning(msg);
end

%-------------------------------------------------------------
% READ SETUP

%---DATE/TIME
fseek(fid, 778,-1);
ctf.setup.time = char(fread(fid,255,'char'))';
fseek(fid,1033,-1);
ctf.setup.date = char(fread(fid,255,'char'))';

%---NUMBER OF SAMPLES
fseek(fid,1288,-1);
ctf.setup.number_samples = (fread(fid,1,'int32')');

%---NUMBER OF CHANNELS
fseek(fid,1292,-1);
ctf.setup.number_channels = (fread(fid,1,'int16')');

%---SAMPLE RATE
fseek(fid,1296,-1);
ctf.setup.sample_rate = fread(fid,1,'double')';
ctf.setup.sample_msec = 1000 / ctf.setup.sample_rate;
ctf.setup.sample_sec  =    1 / ctf.setup.sample_rate;

%---NUMBER OF TRIALS
fseek(fid,1312,-1);
ctf.setup.number_trials = fread(fid,1,'int16')';
fseek(fid, 776,-1);
ctf.setup.number_trials_averaged = fread(fid,1,'int16');

%---DURATION
fseek(fid,1304,-1);
ctf.setup.duration_total = fread(fid,1,'double');
ctf.setup.duration_trial = ctf.setup.duration_total / ctf.setup.number_trials;

%---PRE_TRIG POINTS
fseek(fid,1316,-1);
ctf.setup.pretrigger_samples = fread(fid,1,'int32');
ctf.setup.pretrigger_msec = (ctf.setup.pretrigger_samples / ctf.setup.sample_rate) * 1000;

%---HEAD ZEROING
fseek(fid,1348,-1);
h_zero = fread(fid,1,'int32')';
no_yes = {'no','yes'};
ctf.setup.head_zero = no_yes{h_zero+1};

%---RUN NAME
fseek(fid,1360,-1);
ctf.setup.run_name = char(fread(fid,32,'char'))';

%---RUN TITLE
fseek(fid,1392,-1);
ctf.setup.run_title = char(fread(fid,256,'char'))';

%---SUBJECT
fseek(fid,1712,-1);
ctf.setup.subject = char(fread(fid,32,'char'))';

%---OPERATOR
fseek(fid,1744,-1);
ctf.setup.operator = char(fread(fid,32,'char'))';

%---SENSOR FILE NAME
fseek(fid,1776,-1);
ctf.setup.sensor_file_name = char(fread(fid,60,'char'))';

%---RUN DESCRIPTION & FILTERS
fseek(fid,1836,-1);
run_size = fread(fid,1,'int32');
fseek(fid,1844,-1);
ctf.setup.run_description = char(fread(fid,run_size,'char'));

ctf.setup.number_filters = fread(fid,1,'int16');

for i = 1:ctf.setup.number_filters,
    ctf.setup.filters(i).freq     = fread(fid,1,'double');
    ctf.setup.filters(i).class    = fread(fid,1,'int32');
    ctf.setup.filters(i).type     = fread(fid,1,'int32');
    ctf.setup.filters(i).numparam = fread(fid,1,'int16');
    ctf.setup.filters(i).params   = fread(fid,ctf.setup.filters(i).numparam,'double');
end

if(COEFS)
    b = 1846 + run_size;
    if(ctf.setup.number_filters == 0)
        np = 0;
    else
        np = sum([ctf.setup.filters.numparam]);
        warning('3rd gradient + hardware filter parameters not fully tested! let''s see what happens... :)');
    end
    nf = ctf.setup.number_filters;
    f = ( nf * 18 ) + ( np * 8 );
    offset = b + f + ctf.setup.number_channels * 1360;
end

%-------------------------------------------------------------
% CREATE TIME ARRAYS

% the time arrays must be based on increments of the sample_msec
ctf.setup.time_msec = [0:ctf.setup.number_samples - 1]' * ctf.setup.sample_msec;
ctf.setup.time_msec = ctf.setup.time_msec - ctf.setup.pretrigger_msec;

% adjust the sample point closest to zero so that it is zero, if it
% is reasonably close to zero, say within 3 decimal places for msec timing
zero_index = find(abs(ctf.setup.time_msec) == min(abs(ctf.setup.time_msec)));
zero_value = ctf.setup.time_msec(zero_index);
if (-0.0001 < zero_value) & (zero_value < 0.0001),
    ctf.setup.time_msec(zero_index) = 0;
end

ctf.setup.start_msec = ctf.setup.time_msec(1);
ctf.setup.end_msec   = ctf.setup.time_msec(end);

ctf.setup.time_sec  = ctf.setup.time_msec / 1000;
ctf.setup.start_sec = ctf.setup.time_sec(1);
ctf.setup.end_sec   = ctf.setup.time_sec(end);

%-------------------------------------------------------------
% PRINT SETUP
if VERBOSE,
    ctf_print_setup(ctf);
end

%-------------------------------------------------------------
% READ SENSOR INFORMATION

ctf.sensor.info = struct(...
    'proper_gain',[],...
    'q_gain',[],...
    'io_gain',[],...
    'io_offset',[],...
    'index',[],...
    'extra',[],...
    'label',[],...
    'grad_order_no',[]);

% read channel names
for chan = 1:ctf.setup.number_channels,
    temp = fread(fid,32,'char');
    ctf.sensor.info(chan).label = parse_sensor_label(temp);
end

for chan = 1:ctf.setup.number_channels,

    % The proper gain is the channel-wide constant quotient of a value in raw
    % units and a value in user units. (For EEG this is one because there is
    % no conversion; i.e., the values are in volts, for MEG there is conversion
    % between phi0 and teslas).
    % proper gain = raw value / user value
    %
    % The Q-gain is used to convert the 32-bit integers produced by the
    % electronics to the raw values stored in the data file.
    % raw value = 32 bit value / Q gain
    %
    % The IO-gain is essentially a correction factor that is applied to the
    % raw values before they are stored to disk.

    %ftell(fid);

    ctf.sensor.info(chan).index = fread(fid,1,'int16');
    ctf.sensor.info(chan).extra = fread(fid,1,'int16');
    id = fread(fid,1,'int32')+1;
    ctf.sensor.info(chan).proper_gain = fread(fid,1,'double');
    ctf.sensor.info(chan).q_gain = fread(fid,1,'double');
    ctf.sensor.info(chan).io_gain = fread(fid,1,'double');
    ctf.sensor.info(chan).io_offset = fread(fid,1,'double');
    fread(fid,1,'int16');
    ctf.sensor.info(chan).grad_order_no = fread(fid,1,'int16');
    fread(fid,1,'int32');

    %fseek(fid,ftell(fid)+6,0);

    for pos = 1:8,
        ctf.sensor.info(chan).coil(pos).position.x = fread(fid,1,'double');
        ctf.sensor.info(chan).coil(pos).position.y = fread(fid,1,'double');
        ctf.sensor.info(chan).coil(pos).position.z = fread(fid,1,'double');
        fread(fid,1,'double');
        ctf.sensor.info(chan).coil(pos).orient.x = fread(fid,1,'double');
        ctf.sensor.info(chan).coil(pos).orient.y = fread(fid,1,'double');
        ctf.sensor.info(chan).coil(pos).orient.z = fread(fid,1,'double');
        fread(fid,1,'double');
        fread(fid,1,'int16');
        fread(fid,1,'int32');
        fread(fid,1,'int16');
        fread(fid,1,'double');

        %fseek(fid,ftell(fid)+56,0);
        %fseek(fid,ftell(fid)-80,0);
    end

    for pos = 1:8,
        ctf.sensor.info(chan).hcoil(pos).position.x = fread(fid,1,'double');
        ctf.sensor.info(chan).hcoil(pos).position.y = fread(fid,1,'double');
        ctf.sensor.info(chan).hcoil(pos).position.z = fread(fid,1,'double');
        fread(fid,1,'double');
        ctf.sensor.info(chan).hcoil(pos).orient.x = fread(fid,1,'double');
        ctf.sensor.info(chan).hcoil(pos).orient.y = fread(fid,1,'double');
        ctf.sensor.info(chan).hcoil(pos).orient.z = fread(fid,1,'double');
        fread(fid,1,'double');
        fread(fid,1,'int16');
        fread(fid,1,'int32');
        fread(fid,1,'int16');
        fread(fid,1,'double');

        %fseek(fid,ftell(fid)+56,0);
        %fseek(fid,ftell(fid)+80,0);
    end
    %fseek(fid,ftell(fid)+1288,-1);
end

%-------------------------------------------------------------
% Find channel types and define channel sets, see the
% System Administrators .pdf, 'Channel Sets Configuration'

ctf = ctf_channel_sets(ctf);

%-------------------------------------------------------------
% Channel coordinates, in centimeters, in subject head space

for chan = 1:ctf.setup.number_channels,

    switch ctf.sensor.info(chan).index,

        case {0,1,5},
            %0=Reference Magnetometers
            %1=Reference Gradiometers
            %5=MEG Channels

            coord = [ctf.sensor.info(chan).hcoil(1:2).position];
            ctf.sensor.info(chan).location = [coord.x; coord.y; coord.z];

            orient = [ctf.sensor.info(chan).hcoil(1).orient];
            ctf.sensor.info(chan).orientation = [orient.x; orient.y; orient.z];

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % This ensures that the orientation of the sensor is away from the
            % center of a sphere.  It uses the sign of the dot product between
            % the orientation vector and the location vector.
            tmp = ctf.sensor.info(chan).orientation' * ctf.sensor.info(chan).location;
            tmp = sign(tmp(1));
            ctf.sensor.info(chan).orientation = tmp * ctf.sensor.info(chan).orientation;
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

        case 9,
            %EEG Channels

            coord = [ctf.sensor.info(chan).hcoil(1:2).position];
            ctf.sensor.info(chan).location = [coord.x; coord.y; coord.z];
            ctf.sensor.info(chan).orientation = [];

    end
end

%-------------------------------------------------------------------------
% assign sensor locations and orientations
% simplify information in the ctf.sensor.info struct

ctf.sensor.location = zeros(3,ctf.setup.number_channels);
ctf.sensor.orientation = zeros(3,ctf.setup.number_channels);

ctf.sensor.label = [];
ctf.sensor.location = [];
ctf.sensor.orientation = [];

for c = 1:ctf.setup.number_channels,

    % All channels have a label
    ctf.sensor.label{1,c} = strtok(ctf.sensor.info(c).label,'-');

    % All channels have a location
    % EEG channels do not have any orientation
    if length(ctf.sensor.type.meg_ref) > 1, ind2 = 2; else ind2 = 1; end;

    switch ctf.sensor.info(c).index,

        case {ctf.sensor.type.meg_sens, ctf.sensor.type.meg_ref(1), ctf.sensor.type.meg_ref(ind2)},
        % modification above based on bug 438 of EEGLAB - Arnaud Delorme, August 2007
            %0=Reference Channels,
            %1=More Reference Channels,
            %5=MEG Channels

            % MEG channels are radial gradiometers, so they have an inner (1) and
            % an outer (2) location - it might be better to take the average of
            % their locations
            if ~isempty(ctf.sensor.info(c).location),
                ctf.sensor.location(:,c) = ctf.sensor.info(c).location(:,1);
            end

            if ~isempty(ctf.sensor.info(c).orientation),
                ctf.sensor.orientation(:,c) = ctf.sensor.info(c).orientation(:,1);
            end

        case ctf.sensor.type.eeg_sens,
            %9=EEG Channels

            if ~isempty(ctf.sensor.info(c).location),
                ctf.sensor.location(:,c) = ctf.sensor.info(c).location(:,1);
            end

    end
end

%%%%%% Coefficient reading appended by SSD %%%%%%

if(COEFS)
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % NUMBER OF COEFFICIENTS, byte offset = b+f+nc*1360, byte size = 1

    fseek(fid,offset,-1);
    ctf.res4.numberCoefficients = fread(fid,1,'int16');

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % SENSOR COEFFICIENT RECORDS, byte offset = b+f+nc*1360+2, byte size = 1992

    if VERBOSE & ctf.res4.numberCoefficients,
        fprintf('...reading %d coefficients\n',ctf.res4.numberCoefficients);
    end

    SENSOR_LABEL  = 31;
    MAX_NUM_COEFS = 50;
    MAX_BALANCING = MAX_NUM_COEFS;

    hexadef = {'00000000','47314252','47324252','47334252','47324f49','47334f49'};
    strdef = {'NOGRAD','G1BR','G2BR','G3BR','G2OI','G3OI'};

    for i = 1:ctf.res4.numberCoefficients,

        % read the sensor name (channel label)
        temp = fread(fid,[1,32],'char');
        sensorName = parse_sensor_label(temp);
        sensorIndex = strmatch( sensorName, {ctf.sensor.info.label} );

        % read the coefficient type
        coefType = fread(fid,1,'bit32');

        padding = fread(fid,1,'int32'); % not sure why this is needed???

        % read the coefficient record
        numberCoefs = fread(fid,1,'int16');

        if numberCoefs > MAX_NUM_COEFS,
            msg = sprintf('numberCoefs > MAX_NUM_COEFS\n');
            warning(msg);
        end

        sensor_list = char(fread(fid,[SENSOR_LABEL,MAX_BALANCING],'uchar')');
        % clean-up the sensor_list
        sensor_list = sensor_list(1:numberCoefs,:);
        for j=1:numberCoefs,
            temp = strtok(sensor_list(j,:),char(0));
            temp = strtok(temp,'-');
            % check if this sensor is a reference
            refLabels = {ctf.sensor.info(ctf.sensor.index.meg_ref).label};
            refIndex = strmatch(temp,refLabels);
            if refIndex,
                % ensure this one has the same label
                temp = refLabels{refIndex};
            end

            new_sensor_list(j) = refIndex;
        end
        sensor_list = ctf.sensor.index.meg_ref(:,new_sensor_list)';

        coefs_list = fread(fid,MAX_BALANCING,'double');
        % clean-up the coefs_list
        coefs_list = coefs_list(1:numberCoefs,:)';

        % allocate the coefficient parameters into the ctf struct
        ctf.res4.sensorCoef(i).sensorName = sensorName;
        ctf.res4.sensorCoef(i).coefType   = coefType;
        ctf.res4.sensorCoef(i).coefRec.numberCoefs = numberCoefs;
        ctf.res4.sensorCoef(i).coefRec.sensor_list = sensor_list;
        ctf.res4.sensorCoef(i).coefRec.coefs_list  = coefs_list;

        % DLW:
        % This is a brainstorm variable, note the use of coefType
        % Not clear why this is checked and allocated as such
        coefType = find( hex2dec(hexadef) == coefType );
        if coefType,
            CoefInfo{sensorIndex,coefType-1}.numberCoefs = numberCoefs;
            CoefInfo{sensorIndex,coefType-1}.sensor_list = sensor_list;
            CoefInfo{sensorIndex,coefType-1}.coefs = coefs_list;
        end
    end

    % Channel Gains
    gain_chan = zeros(size(ctf.setup.number_channels,1),1);
    gain_chan(ctf.sensor.index.meg_sens) = ([ctf.sensor.info(ctf.sensor.index.meg_sens).proper_gain]'.*[ctf.sensor.info(ctf.sensor.index.meg_sens).q_gain]');
    gain_chan(ctf.sensor.index.meg_ref) = ([ctf.sensor.info(ctf.sensor.index.meg_ref).proper_gain]'.*[ctf.sensor.info(ctf.sensor.index.meg_ref).q_gain]');
    %  gain_chan(ieegsens) = 1./([SensorRes(ieegsens).qGain]'*1e-6);
    %     gain_chan(ieegsens) = 1./([SensorRes(ieegsens).qGain]');
    %     gain_chan(iothersens) = ([SensorRes(iothersens).qGain]'); % Don't know exactly which gain to apply here

    % Calculus of the matrix for nth-order gradient correction
    % Coefficients for unused reference channels are weigthed by zeros in
    % the correction matrix.
    Gcoef = zeros(length(ctf.sensor.index.meg_sens),length(min(ctf.sensor.index.meg_ref):max(ctf.sensor.index.meg_ref)));
    grad_order_no = 3*ones(306,1);
    for k = 1:length(ctf.sensor.index.meg_sens)

        % Reference coils for channel k
        if grad_order_no(ctf.sensor.index.meg_sens(k)) == 0
            %Data is saved as RAW
            %Save 3rd order gradient sensor-list for subsequent correction if requested later by the user
            [refs] = (CoefInfo{ctf.sensor.index.meg_sens(k),3}.sensor_list);
            Gcoef(k,refs-min(ctf.sensor.index.meg_ref)+1) = CoefInfo{ctf.sensor.index.meg_sens(k),3}.coefs ...
                .* gain_chan(refs)'/gain_chan(ctf.sensor.index.meg_sens(k));
        else
            [refs] = (CoefInfo{ctf.sensor.index.meg_sens(k),grad_order_no(ctf.sensor.index.meg_sens(k))}.sensor_list);
            Gcoef(k,refs-min(ctf.sensor.index.meg_ref)+1) = CoefInfo{ctf.sensor.index.meg_sens(k),grad_order_no(ctf.sensor.index.meg_sens(k))}.coefs ...
                .* gain_chan(refs)/gain_chan(ctf.sensor.index.meg_sens(k));
        end
        ctf.sensor.info(ctf.sensor.index.meg_sens(k)).Gcoef = Gcoef(k,:);
    end
end   %% end COEF block

fclose(fid);

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

return

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% -------------------------------------------------------
function res4name = findres4file( folder )

% find file name if truncated or with uppercase extension
% added by Arnaud Delorme June 15, 2004

res4name = dir([ folder filesep '*.res4' ]);
if isempty(res4name)
    res4name = dir([ folder filesep '*.RES4' ]);
end

if isempty(res4name)
    error('No file with extension .res4 or .RES4 in selected folder');
else
    res4name = [ folder filesep res4name(1).name ];
end;
return

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% -------------------------------------------------------
function sensorName = parse_sensor_label(temp)

% sensorName = parse_sensor_label(temp)
% parse sensor label names

temp(temp>127) = 0;
temp(temp<0) = 0;
temp = strtok(temp,char(0));
temp = strtok(temp,'-');
sensorName = char(temp)';

return


classdef Metric < handle
    properties (Access = public)
        flops = 0;
        memory = 0;
        outputSize = 0;
    end

    methods (Access = public)
        function obj = Metric(flops,memory,outputSize)
            if nargin > 0
                obj.flops = flops;
                obj.memory = memory;
                obj.outputSize = outputSize;
            end
        end
    end
end


% The COBRAToolbox: testMgPipe.m
%
% Purpose:
%     - test the mappingInfo function of the Microbiome Toolbox
%
% Authors:
%     - original version: Federico Baldini & Laurent Heirendt, April 2018
%       - Almut Heinken, December 2020: adapted to recent changes in mgPipe
%

global CBTDIR

% define the features required to run the test
requiredToolboxes = { 'distrib_computing_toolbox' };

% test Requirements
prepareTest('requiredToolboxes',requiredToolboxes, 'requiredSolvers', {'ibm_cplex'});

% save the current path
currentDir = pwd;

% initialize the test
cd(fileparts(which(mfilename)));
modPath = [CBTDIR filesep 'test' filesep 'models' filesep 'mat'];

% path to the file with abundance information.
abunFilePath='testData_normCoverageReduced.csv';

% path to the file with dietary information
dietFilePath='AverageEuropeanDiet';

% path to the file with information on individuals (default=none)
indInfoFilePath='';

% to define whether uptake and secretion profiles should be recrutied
computeProfiles = true;

resPath = [pwd filesep 'Results'];

% number of cores dedicated for parallelization
numWorkers = 2;

% name of objective function of organisms
objre={'EX_biomass(e)'};

% if documentations on patient health status is provided (0 not 1 yes)
patStat = 0;

% to enable also rich diet simulations (default=false)
rDiet = 0;

% to enable personalized diet simulayions (default=false)
pDiet = 0;

% set the lower bound on biomass in community models (default=0)
lowerBMBound=0;

% set if diet should be adapted with human-derived metabolites
% (default=true)
adaptDiet=1;

% set if existing results should be overwritten (default=false)
repeatSim=1;

% turn all warnings off
warning('off', 'all')

% test getIndividualSizeName
[sampNames, organisms, exMets] = getIndividualSizeName(abunFilePath,modPath);

assert(length(sampNames) == 4)
assert(length(organisms) == 5)

%% run the complete pipeline
% path to the file with abundance information.
abunFilePath='testData_normCoverageReduced.csv';

% test complete pipeline run
[init, netSecretionFluxes, netUptakeFluxes, Y] = initMgPipe(modPath, abunFilePath, computeProfiles, 'dietFilePath', dietFilePath, 'numWorkers', numWorkers, 'resPath', resPath);

assert(init)

% test detectOutput
mapP = detectOutput(resPath, 'mapInfo.mat');
assert(~isempty(mapP));

%% test each function separately

load(['Results' filesep 'mapInfo.mat']);

% load the models
[sampNames, organisms] = getIndividualSizeName(abunFilePath,modPath);
models = loadUncModels(modPath, organisms, objre);

assert(length(models) == 5);

assert(verifyCobraFunctionError('loadUncModels', 'inputs', {}));
assert(verifyCobraFunctionError('loadUncModels', 'inputs', {modPath, ''}));

warning('off', 'all');
loadUncModels(modPath, organisms);
assert(~isempty(lastwarn()))
warning('on', 'all');

% test if the function throws an error when no arguments are provided
assert(verifyCobraFunctionError('initMgPipe'))

% test with only the path to the models (throws an error that the abundance file is missing)
assert(verifyCobraFunctionError('initMgPipe', 'inputs',{modPath}));

% cleanup
delete simRes.mat
delete rDiet_allFlux.csv

% testing with rich diet
[init, netSecretionFluxes, netUptakeFluxes, Y] = initMgPipe(modPath, abunFilePath, computeProfiles, 'resPath',resPath,'numWorkers',numWorkers, 'rDiet', true);
assert(init)

% cleanup
delete simRes.mat
clear ID fvaCt nsCt presol inFesMat
cd(resPath)
delete *.mat

%% verify that mgPipe also works with a single microbiome model with one strain

abunFilePath='testData_normCoverage_one_model.csv';

[init, netSecretionFluxes, netUptakeFluxes, Y] = initMgPipe(modPath, abunFilePath, computeProfiles, 'resPath',resPath,'numWorkers',numWorkers,'resPath',resPath);

assert(init)

% cleanup
rmdir(resPath,'s')


function planC = translateDose(doseNum,xyzT,doseName,planC)
%
% function planC = translateDose(structNum,xyzT,structName,planC)
%
% This function creates a new dose matrix by translating doseNum by amounts
% xT, yT and zT in x,y,z directions respectively. Note that xyzT = [xT yT zT].
% doseName must be a string to name the new dose matrix.
%
% Example:
%   planC = translateDose(1,[-1 0 0],'moveDose1');
%
% Emiliano Spezi, 11 Sep 2013
%
% See also GETUNIFORMSTR MASKTOCERRSTRUCTURE
%
% 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/>.

if ~exist('planC')
    global planC
end

indexS = planC{end};
planC{indexS.dose}(end+1) = planC{indexS.dose}(doseNum);
planC{indexS.dose}(end).fractionGroupID = doseName;
planC{indexS.dose}(end).doseUID = createUID('dose');

planC{indexS.dose}(end).coord1OFFirstPoint = planC{indexS.dose}(doseNum).coord1OFFirstPoint + xyzT(1);
planC{indexS.dose}(end).coord2OFFirstPoint = planC{indexS.dose}(doseNum).coord2OFFirstPoint + xyzT(2);
planC{indexS.dose}(end).zValues = [planC{indexS.dose}(doseNum).zValues] + xyzT(3);


function PTKRemoveOrphanedCoronalSlices(mask_1, mask_2)
    % PTKRemoveOrphanedCoronalSlices.
    %
    %
    %     Licence
    %     -------
    %     Part of the TD Pulmonary Toolkit. https://github.com/tomdoel/pulmonarytoolkit
    %     Author: Tom Doel, 2013.  www.tomdoel.com
    %     Distributed under the GNU GPL v3 licence. Please see website for details.
    %

    for coronal_index = 1 : mask_1.ImageSize(1);
        slice_1 = mask_1.GetSlice(coronal_index, PTKImageOrientation.Coronal);
        slice_2 = mask_2.GetSlice(coronal_index, PTKImageOrientation.Coronal);
        if any(slice_1(:)) && ~any(slice_2(:))
            slice_1(:) = 0;
            mask_1.ReplaceImageSlice(slice_1, coronal_index, PTKImageOrientation.Coronal);
        end
        if any(slice_2(:)) && ~any(slice_1(:))
            slice_2(:) = 0;
            mask_2.ReplaceImageSlice(slice_2, coronal_index, PTKImageOrientation.Coronal);
        end
    end
end


function sydney_add_labels_to_list(source_list_file,dest_list_file,rotation_start_index,all_singlerots_to_zero)

list = textread(source_list_file,'%s');
parfor i = 1 : numel(list)
    [class_name,rotation] = sydney_extract_classname_and_rot_from_filename(list{i});
    [class_label,pose_label] = sydney_generate_class_and_pose_labels(class_name,rotation-rotation_start_index,all_singlerots_to_zero);
    list{i} = sprintf('%s %d %d %d' ,list{i},1,class_label,pose_label);
end

%--- write the modified list to the destination file
fp = fopen(dest_list_file,'wt');
fprintf(fp,'%s\n',list{:});
fclose(fp);


function plot_DR30_fa(plot_obj,plot_args,legend_string)
% Plots the point on the minDCF curve at which the number of false
% alarms falls below 30 (see Doddington's Rule of 30).
% Inputs:
%   plot_args: A cell array of arguments to be passed to 'plot' that control
%     the appearance of the curve. See Matlab's help on 'plot' for information.
%   legend_string: Optional. A string to describe this curve in the legend.
%     This string will be prepended with the system name.

if ischar(plot_args)
    plot_args = {plot_args};
end

figure(plot_obj.fh);
assert(iscell(plot_args))
lh = plot(plot_obj.plo(plot_obj.dr30FA),plot_obj.minDCF(plot_obj.dr30FA),plot_args{:});

if exist('legend_string','var') && ~isempty(legend_string)
    assert(ischar(legend_string))
    plot_obj.add_legend_entry(lh,legend_string,true);
end

end