function mtPaths_ = dtiLoadMetrotracPathsFromStrVer3(str,i, numstats, mtPaths,xform)
% Loads version 3 pdb file
%
%   mtPaths_ = dtiLoadMetrotracPathsFromStrVer3(str,i, numstats,mtPaths,xform)
%
% str is the string buffer
% i is the current position in the string buffer
% numstats is the number of statistics in the pdb
% It appears that xform is never used.
%
% HISTORY:
% 2010.08.01
%
% Stanford VISTA
%
%%

% profile on

% Boolean.  Only Matlab transform what?  Appears to always be false and
% thus the code below is not used.

% How many bytes for int32 and double
int32size   = size(typecast(int32(0),'uint8'),2);
doublesize = size(typecast(double(0.0),'uint8'),2);

numpaths = typecast( uint8(str(i:i+int32size-1)),'uint32');
i = i + int32size;
points_per_fiber = typecast( uint8(str(i:i+int32size*numpaths-1)),'int32'); i = i + int32size*numpaths;

% compute the total number of points
total_pts = 0;
for k = 1:numpaths
    total_pts = total_pts + points_per_fiber(k);
end
total_pts = uint32(total_pts);

% read all the fiber points
fiber_points = typecast( uint8(str(i:i+doublesize*total_pts*3-1)),'double');
i = i + doublesize*total_pts*3;
points_read = 0;

%% Reading each pathway
for p = 1:numpaths
    numnodes = points_per_fiber(p);
    mtPaths.pathwayInfo(p).algo_type = 0; % algo type
    mtPaths.pathwayInfo(p).seed_point_index = 1; % seedpointindex ???

    % Reading path positions
    mtPaths.pathways{p,1} = fiber_points( 1+points_read*3 : (points_read+numnodes)*3);
    mtPaths.pathways{p,1} = reshape(mtPaths.pathways{p,1},3,numnodes);
    points_read = points_read + numnodes;

    if mod(p,10000)==0
        disp(['Loaded ' num2str(p) ' of ' num2str(numpaths)]);
    end
end

% Transform all fiber coords in one go (much faster!)
% I don't know why this offset is still necessary, isn't ACPC space all
% ACPC space??
bOnlyMatlabXForm=0;   % It appears that this is always false and could be deleted.
if( ~bOnlyMatlabXForm )
    xformToMatlab = eye(4);
    xformToAcpc = xformToMatlab*mtPaths.xform;
    if isfield(mtPaths,'pathways')
        mtPaths.pathways = dtiXformFiberCoords(mtPaths.pathways, xformToAcpc);
    else
        mtPaths.pathways = [];
        mtPaths.pathwayInfo = [];
    end
end

%% Read per fiber stats
for as = 1:numstats
    per_fiber_stat = typecast( uint8(str(i:i+doublesize*numpaths-1)),'double');
    i = i + doublesize*numpaths;
    for p = 1:numpaths
        mtPaths.pathwayInfo(p).pathStat(as) = per_fiber_stat(p);

    end
end
%% Read per point stats
for as = 1:numstats
    if(mtPaths.statHeader(as).is_computed_per_point)
        per_point_stat = typecast( uint8(str(i:i+doublesize*total_pts-1)),'double');
        i = i + doublesize*total_pts;
    end

    points_read = 0;
    % Assign per point stats value
    for p = 1:numpaths
        if(mtPaths.statHeader(as).is_computed_per_point)
            mtPaths.pathwayInfo(p).point_stat_array(as,:) = ...
                per_point_stat(points_read+1 : points_read+points_per_fiber(p));
            points_read = points_read + points_per_fiber(p);
        end
    end
end
mtPaths_ = mtPaths;

% profile off

end


function decision = cutBlocks(structIn,cnst,currImName)
    blockIn = structIn.data;

    currSize = size(blockIn);
    if (currSize(1) == currSize(2)) % discard non-square blocks

    % keep only non-background blocks, according to Coudray et al., Nat Med 2018
    % criterion
    bwIm = mean(blockIn,3); % black and white image
    if sum(sum(bwIm>=220))<(0.5*numel(bwIm))

    % resize block to 512x512
    blockIn = imresize(blockIn,[cnst.finalBlockSize,cnst.finalBlockSize]);

    if cnst.verbose
    imshow(blockIn);
    drawnow
    end

    % clean up file name
    currDot = strfind(currImName,'.');
    currImName = currImName(1:(currDot(1)-1));

    % identify failed (black) images
    if sum(sum(bwIm<=1))<(numel(bwIm))
    % save image
        imwrite(blockIn,[cnst.outDir,currImName,'-blk-',randseq(12,'alphabet','amino'),'.png']);
        decision = 4; % code for success
    else
        warning('moving block to failures');
        imwrite(blockIn,[cnst.failDir,currImName,'-blk-',randseq(12,'alphabet','amino'),'.png']);
        decision = 1; % code for fail
    end
    else
        if cnst.verbose
        disp('discarding background block');
        end
        decision = 2; % code for background
    end
    else
        if cnst.verbose
        disp('discarding edge');
        end
        decision = 3; % code for edge
    end

end


function [ni,canXform] = niftiApplyCannonicalXform(ni, canXform, phaseDir)
% Reorient NIFTI data and metadata fields into RAS space
%
%    [ni,canXform] = ...
%       niftiApplyCannonicalXform(ni, ...
%          [canXform=mrAnatComputeCannonicalXformFromDicomXform], phaseDir=[])
%
% Inputs:
%       ni:  Nifti-1 data structure.  (Should allow a filename in future).
% canXform:  Canonical transform from current form to RAS (Right-left,
%            Anterior-posterior, Superior-inferior).  If not passed in,
%            then the transform is estimated
% phaseDir:  ????  Phase encode direction.  (Columns, rows ... ???)
%
%  ** PLEASE EXPLAIN WHY WE NEED TO KNOW PHASE ENCODE DIRECTION ***
%
% Returns:
%       ni:  New nifti data set
% canXform:  Canonical transform
%
% Given a NIFTI-1 file ni- reorients the data and adjusts relevant metadata
% fields (like quaternion, pixfdim, freq/phase/slice dims, etc.) to be
% consistent.
%
% The transformation is specified by canXform (canonical transform). If the
% canXform isn't provided, it is estimated using
%
%     mrAnatComputeCannonicalXformFromDicomXform
%
% You can also pass the phase dir to overwrite a bad phase_dir field in
% your nifti header. Note that you should specify the phase_dir of the
% input data *before* the cannonical xform is applied. The value should be
% 1, 2, or 3 to specify phase-encoding along the first, second or third
% image dimension.  ** PLEASE EXPLAIN WHICH FUNCTION USES THIS INFORMATION
%
% See also: mrAnatComputeCannonicalXformFromDicomXform, applyCannonicalXform
%
% HISTORY:
% 2007.05.17 RFD: wrote it.
% 2008.08.15 RFD: fixed dim order bug.
%
% (c) Stanford VISTALAB

if(nargin<1), help(mfile); end

% Do a sanity-check on the nifti transform
% ni = niftiCheckQto(ni);
ni = niftiSet(ni,'Check Qto',''); %The final value is not necessary here

if(~exist('canXform','var') || isempty(canXform))
    canXform = mrAnatComputeCannonicalXformFromDicomXform(ni.qto_xyz, ni.dim(1:3));
end
%canXform(:,4) = [0 0 0 1]';
if(exist('phaseDir','var') && ~isempty(phaseDir))
    ni.phase_dim = phaseDir;
end

if(all(all(canXform == eye(4))))
 fprintf('[%s]: Data are already in canonical orientation.\n', mfilename)
else

    % Apply the transform
    [ni.data,newPixdim,dimOrder] = ...
        applyCannonicalXform(ni.data, canXform, ni.pixdim(1:3), false);

    % Fill the NIFTI image slots
    ni = niftiSet(ni,'dim',size(niftiGet(ni,'data')));
    ni.pixdim(1:3) = newPixdim;

    % Fill the transform slots
    ni = niftiSet(ni, 'qto', inv(canXform * niftiGet(ni,'qto_ijk')));
    if(any(ni.sto_xyz(:)>0))
        ni.sto_ijk = canXform*ni.sto_ijk;
        ni.sto_xyz = inv(ni.sto_ijk);
    end

    if(ni.freq_dim>0 && ni.freq_dim<4)
        ni.freq_dim = dimOrder(ni.freq_dim);
    else
        disp('freq_dim not set correctly in NIFTI header.');
    end

    if(ni.phase_dim>0 && ni.phase_dim<4)
        ni.phase_dim = dimOrder(ni.phase_dim);
    else
        disp('phase_dim not set correctly in NIFTI header.');
    end

    if(ni.slice_dim>0 && ni.slice_dim<4)
        ni.slice_dim = dimOrder(ni.slice_dim);
    end
end

end


function outW = extract(w, method, startV, endV)
   %EXTRACT creates a waveform with a subset of another's data.
   %   waveform = extract(waveform, 'TIME', startTime, endTime)
   %       returns a waveform with the subset of data from startTime to
   %       endTime.  Both times are matlab formatted (string or datenum)
   %
   %   waveform = extract(waveform, 'INDEX', startIndex, endIndex)
   %       returns a waveform with the subset of data from StartIndex to
   %       EndIndex.  this is roughly equivelent to grabbing the waveform's
   %       data into an array, as in D = get(W,'data'), then returning a
   %       waveform with the subset of data,
   %       ie. waveform = set(waveform,'data', D(startIndex:endIndex));
   %
   %   waveform = extract(waveform, 'INDEX&DURATION', startIndex, duration)
   %       a hybrid method that starts from data index startIndex, and then
   %       returns a specified length of data as indicated by duration.
   %       Duration is a matlab formatted time (string or datenum).
   %
   %   waveform = extract(waveform, 'TIME&SAMPLES', startTime, Samples)
   %       a hybrid method that starts from data index startIndex, and then
   %       returns a specified length of data as indicated by duration.
   %       Duration is a matlab formatted time (string or datenum).
   %
   %   Input Arguments:
   %       WAVEFORM: waveform object        N-DIMENSIONAL
   %       METHOD: 'TIME', or 'INDEX', or 'INDEX&DURATION'
   %           TIME: starttime and endtime are absolute times
   %                   (include the date)
   %           INDEX: startt and endt are the offset (index) within the data
   %           INDEX&DURATION: first value is an offset (index), the next says
   %                           how much data to retrieve...
   %           TIME&SAMPLES: grab first value at time startTime, and grab
   %                         Samplength data points
   %       STARTTIME:  Start time (matlab or text format)
   %       ENDTIME:    End time (matlab or text format)
   %       STARTINDEX: position within data array to begin extraction
   %       ENDINDEX:   final grabbed position within data array
   %       DURATION:   matlab format time indicating duration of data to grab
   %       SAMPLES: the number of data points to grab.
   %
   %   the output waveform will have the new, appropriate start time.
   %   if the times are outside the range of the waveform object, then the
   %   output waveform will contain only the portion of the data that is
   %   appropriate.
   %
   %   *MULTIPLE EXTRACTIONS* can be received if the time values are vectors.
   %   Both starttime/startindex and endtime/endindex/endduration/samples must
   %   have the same number of elements.  In this case the output waveforms
   %   will be reshaped with each waveform represented by row, and each
   %   extracted time represented by column.  that is...
   %
   %  The output of this function, for multiple waveforms and times will be:
   %         t1   t2  t3 ... tn
   %    -----------------------
   %    w1 |
   %    w2 |
   %    w3 |
   %     . |
   %     . |
   %    wn |
   %
   %
   %%   examples:
   %       % say that Win is a waveform that starts 1/5/2007 04:00, and
   %       % contains 1 hour of data at 100 Hz (360000 samples)
   %
   %       % grab samples between 4:15 and 4:20
   %       Wout = extract(Win, 'TIME', '1/5/2007 4:15:00','1/5/2007 4:20:00');
   %
   %       % grab 3 minutes, starting at the 10000th sample
   %       Wout = extract(Win, 'INDEX&DURATION', 10000 , '0/0/0 00:03:00');
   %
   %
   %%     example of multiple extract:
   %  % declare the times we're interested in
   %         firstsnippet = datenum('6/20/2003 00:00:00');
   %         lastsnippet = datenum('6/20/2003 24:00:00');
   %
   %         % divide the day into 1-hour segments.
   %         % note, 25 peices. equivelent to 0:1:24, including both midnights
   %         alltimes = linspace(firstsnippet, lastsnippet, 25);
   %         starttimes = alltimes(1:end-1);
   %         endtimes = alltimes(2:end);
   %
   %         % grab each hour of time, and shove it into wHours
   %         wHours = extract(wDay, 'time',starttimes, endtimes);
   %
   %         scaleFactor = 4 * std(double(wDay));
   %         wHours = wHours ./ scaleFactor;
   % %
   %          for n = 1:length(wHours)
   %            wHours(n) = -wHours(n) + n; %add offset for plotting
   %          end
   %          plot(wHours,'xunit','m','b'); %plot it in blue with at nm scaling
   %          axis ([0 60 1 25])
   %          set(gca,'ytick',[0:2:24],'xgrid', 'on','ydir','reverse');
   %          ylabel('Hour');
   %
   %   See also WAVEFORM/SET -- Sample_Length

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

   %% Set up condition variables, and ensure validity of input
   MULTIPLE_WAVES = ~isscalar(w);

   %if either of our times are strings, it's 'cause they're actually dates
   if ischar(startV)
      startV = datenum(startV);
   end
   if ischar(endV)
      endV = datenum(endV);
   end

   if numel(startV) ~= numel(endV)
      error('Waveform:extract:indexMismatch',...
         'Number of start times (or indexes) must equal number of end times')
   end

   % are we getting a series of extractions from each waveform?
   MULTIPLE_EXTRACTION = numel(endV) > 1;

   if MULTIPLE_WAVES && MULTIPLE_EXTRACTION
      w = w(:);
   end

   %%
   if numel(w)==0 || numel(startV) ==0
      warning('Waveform:extract:emptyWaveform','no waveforms to extract');
      return
   end

   % changed by glenn thompson 20161006 as it was losing calibration
   % information
   %outW(numel(w),numel(startV)) = waveform; % removed
   outW = w; % added

   for m = 1: numel(startV) %loop through the number of extractions
      for n=1:numel(w); %loop through the waveforms
         inW = w(n);
         myData = inW.data;

         switch lower(method)
            case 'time'

               % startV and endV are both matlab formated dates
               %sampleTimes = get(inW,'timevector');

               %   ensure the format of our times
               if startV(m) > endV(m)
                  warning('Waveform:extract:reversedValues',...
                     'Start time prior to end time.  Flipping.');
                  [startV(m), endV(m)] = swap(startV(m), endV(m));
               end

               %if requested data is outside the existing waveform, change the
               %start time, and clear out the data.
               startsAfterWave = startV(m) > get(inW,'end') ;
               endsBeforeWave = endV(m) < get(inW,'start');
               if startsAfterWave || endsBeforeWave
                  myStart = startV(m);
                  myData = [];
               else
                  %some aspect of this data must be represented by the waveform
                  [myStartI myStartTime] = time2offset(inW,startV(m));
                  [myEndI] = time2offset(inW,endV(m)) - 1;
                  if isempty(myStartTime)
                     %waveform starts sometime after requested start
                     myStartTime = get(inW,'start');
                     myStartI = 1;
                  end

                  if myEndI > numel(myData)
                     myEndI = numel(myData);
                  end
                  myData = myData(myStartI:myEndI);
                  myStart = myStartTime;
               end
            case 'index'
               %startV and endV are both indexes into the data

               if startV(m) > numel(myData)
                  warning('Waveform:extract:noDataFound',...
                     'no data after start index');
                  return
               end;
               if endV(m) > numel(myData)
                  endV(m) = length(myData);
                  warning('Waveform:extract:truncatingData',...
                     'end index too long, truncating to match data');
               end

               if startV(m) > endV(m)
                  warning('Waveform:extract:reversedValues',...
                     'Start time prior to end time.  Flipping.');
                  [startV(m), endV(m)] = swap(startV(m), endV(m));
               end

               myData = myData(startV(m):endV(m));
               sampTimes = get(inW,'timevector'); % grab individual sample times
               myStart = sampTimes(startV(m));

            case 'index&duration'
               % startV is an index into the data, endV is a matlab date
               myData = myData(startV(m):end); %grab the data starting at our index

               sampTimes = get(inW,'timevector'); % grab individual sample times
               sampTimes = sampTimes(startV(m):end); % truncate to match data

               myStart = sampTimes(1); %grab our starting date before hacking it

               sampTimes = sampTimes - sampTimes(1); %set first time to zero
               count = sum(sampTimes <= endV(m)) -1;
               myData = myData(1:count);

            case 'time&samples'
               % startV is a matlab date, while endV is an index into the data
               sampTimes = get(inW,'timevector'); % grab individual sample times

               index_to_times = sampTimes >= startV(m); %mask of valid times
               goodTimes = find(index_to_times,endV(m));%first howevermany of these good times

               myData = myData(goodTimes); % keep matching samples

               try
                  myStart = sampTimes(goodTimes(1)); %first sample time is new waveform start
               catch
                  warning('Waveform:extract:NoDataFound',...
                     'no data');
                  myStart = startV(1);
               end

            otherwise
               error('Waveform:extract:unknownMethod','unknown method: %s', method);
         end

         if MULTIPLE_EXTRACTION
            outW(n,m) = set(inW,'start',myStart, 'data', myData);
         else
            outW(n) = set(inW,'start',myStart, 'data', myData);
         end
      end % n-loop (looping through waveforms)
   end % m-loop (looping through extractions)
end

function  [B,A] = swap(A,B)
   %do nothing, just flip inputs & outputs
end


function [vardistx, model] = vargplvmPartExpand(model, x, update)
% VARGPLVMPARTEXPAND
% This function is used when the model is using jointly the test and
% training data (possibly with missing values from the first) to optimise
% and in that case only part of the parameters are being optimised. This
% function, thus, expands the model by taking into account only the
% optimised parameters (the old ones are being kept fixed).

% In this version of the function the params that are considered to be
% optimised again are the var. distribution, dyn. kernel hyperparams and
% inducing points. The rest (base kernel and beta) are being kept fixed.

% x = [mu_bar lambda theta_t X_u] where mu_bar and lambda are augmented to
% include the vardist. of the test points.

% Note on the order of the parameters:
% [(N*Q*2)              |theta_t| Q*k |theta_f| 1] -> vargplvmExtractParam(model)
% [(N*Q*2)+(Nstar*Q*2)  |theta_t| Q*k]             -> x
%
% COPYRIGHT: Andreas C. Damianou, Michalis K. Titsias 2011
% VARGPLVM

% The model params before optimisation
paramsOrig = vargplvmExtractParam(model);

% The optimised parameters x here are not only the vardist. but also
% gDynKern and inducing points.
paramsNew = x;

% x will now hold only the vardist. params.
vardistxNparams=length(model.dynamics.t_star) * model.q * 2;
x = paramsNew(1:(model.dynamics.vardist.nParams + vardistxNparams));

% now separate the variational disribution into the training part and the
% testing part and update the original training model (only with the new training
% variational distribution) and the test variational distribution
% this is doing the expand
vardistNumData = size(x,2)/(model.q*2);
x = reshape(x, vardistNumData, model.dynamics.q*2);
xtrain = x(1:model.N,:);
xtest = x(model.N+1:end,:);
%model.dynamics.vardist = vardistExpandParam(model.dynamics.vardist, xtrain);
vardistx = vardistExpandParam(model.vardistx, xtest(:)');

% The model will be expanded with the origParams vector where all the
% new values (optimised) will overwrite the old ones.
params = paramsOrig;

startVal = 1;
endVal = model.dynamics.vardist.nParams;

% This will overwrite the vardist original params with the newly optimised
% ones
params(startVal:endVal) = xtrain(:)';

% This will overwrite the dyn.kern params and the inducing points.
startVal = endVal+1;
endVal = endVal + model.dynamics.kern.nParams + model.q * model.k;
params(startVal:endVal) = paramsNew((startVal+vardistxNparams):(endVal+vardistxNparams));

% Update the model
if exist('update') && update
    model = vargplvmExpandParam(model, params);
else
    % Update the model withoug calling update stats (this is used in the
    % optimisation likelihood and wrapper functions which do all the
    % precomputations themselves)
    model = vargplvmExpandParamNoUpdate(model, params);
end

%-----------

% This is copy-paste of vargplvmExpandParam without the last call to
% vargplvmUpdateStats (because this is done inside the objective and gradient functions)
function model = vargplvmExpandParamNoUpdate(model,params)
startVal = 1;
if isfield(model, 'dynamics') & ~isempty(model.dynamics)
    % variational parameters (reparametrized) AND dyn.kernel's parameters
    endVal = model.dynamics.nParams;
    model.dynamics = modelExpandParam(model.dynamics, params(startVal:endVal));
else
    % variational parameters (means and covariances), original ones
    endVal = model.vardist.nParams;
    model.vardist = modelExpandParam(model.vardist, params(startVal:endVal));
end

% inducing inputs
startVal = endVal+1;
if model.fixInducing
    if isfield(model, 'dynamics') & ~isempty(model.dynamics)
        Kt = kernCompute(model.dynamics.kern, model.dynamics.t);%%%%%%%%%%%%5
        model.X_u = Kt*model.dynamics.vardist.means; %dynamics
        model.X_u = model.X_u(model.inducingIndices,:);
    else
        model.X_u = model.vardist.means(model.inducingIndices, :); % static
    end
    % X_u values are taken from X values.
    % model.X_u = model.X(model.inducingIndices, :);
else
    % Parameters include inducing variables.
    endVal = endVal + model.q*model.k;
    model.X_u = reshape(params(startVal:endVal),model.k,model.q);
end

% kernel hyperparameters
startVal = endVal+1;
endVal = endVal + model.kern.nParams;
model.kern = kernExpandParam(model.kern, params(startVal:endVal));

% likelihood beta parameters
if model.optimiseBeta
    startVal = endVal + 1;
    endVal = endVal + prod(size(model.beta));
    fhandle = str2func([model.betaTransform 'Transform']);
    model.beta = fhandle(params(startVal:endVal), 'atox');
end

model.nParams = endVal;


function [varargout] = read_plexon_nex(filename, varargin)

% READ_PLEXON_NEX reads header or data from a Plexon *.nex file, which
% is a file containing action-potential (spike) timestamps and waveforms
% (spike channels), event timestamps (event channels), and continuous
% variable data (continuous A/D channels).
%
% LFP and spike waveform data that is returned by this function is
% expressed in microVolt.
%
% Use as
%   [hdr] = read_plexon_nex(filename)
%   [dat] = read_plexon_nex(filename, ...)
%   [dat1, dat2, dat3, hdr] = read_plexon_nex(filename, ...)
%
% Optional arguments should be specified in key-value pairs and can be
%   header      structure with header information
%   feedback    0 or 1
%   tsonly      0 or 1, read only the timestamps and not the waveforms
%   channel     number, or list of numbers (that will result in multiple outputs)
%   begsample   number (for continuous only)
%   endsample   number (for continuous only)
%
% See also READ_PLEXON_PLX, READ_PLEXON_DDT

% Copyright (C) 2007, 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$

% parse the optional input arguments
hdr       = ft_getopt(varargin, 'header');
channel   = ft_getopt(varargin, 'channel');
feedback  = ft_getopt(varargin, 'feedback', false);
tsonly    = ft_getopt(varargin, 'tsonly', false);
begsample = ft_getopt(varargin, 'begsample', 1);
endsample = ft_getopt(varargin, 'endsample', inf);

% start with empty return values and empty data
varargout = {};

% read header info from file, use Matlabs for automatic byte-ordering
fid = fopen_or_error(filename, 'r', 'ieee-le');
fseek(fid, 0, 'eof');
siz = ftell(fid);
fseek(fid, 0, 'bof');

if isempty(hdr)
  if feedback, fprintf('reading header from %s\n', filename); end
  % a NEX file consists of a file header, followed by a number of variable headers
  % sizeof(NexFileHeader) = 544
  % sizeof(NexVarHeader) = 208
  hdr.FileHeader = NexFileHeader(fid);
  if hdr.FileHeader.NumVars<1
    ft_error('no channels present in file');
  end
  hdr.VarHeader = NexVarHeader(fid, hdr.FileHeader.NumVars);
end

for i=1:length(channel)
  chan = channel(i);
  vh = hdr.VarHeader(chan);
  clear buf
  fseek(fid, vh.DataOffset, 'bof');
  switch vh.Type
    case 0
      % Neurons, only timestamps
      buf.ts = fread(fid, [1 vh.Count], 'int32=>int32');

    case 1
      % Events, only timestamps
      buf.ts = fread(fid, [1 vh.Count], 'int32=>int32');

    case 2
      % Interval variables
      buf.begs = fread(fid, [1 vh.Count], 'int32=>int32');
      buf.ends = fread(fid, [1 vh.Count], 'int32=>int32');

    case 3
      % Waveform variables
      buf.ts = fread(fid, [1 vh.Count], 'int32=>int32');
      if ~tsonly
        buf.dat = fread(fid, [vh.NPointsWave vh.Count], 'int16');
        % convert the AD values to miliVolt, subsequently convert from miliVolt to microVolt
        buf.dat = buf.dat * (vh.ADtoMV * 1000);
      end

    case 4
      % Population vector
      ft_error('population vectors are not supported');

    case 5
      % Continuously recorded variables
      buf.ts   = fread(fid, [1 vh.Count], 'int32=>int32');
      buf.indx = fread(fid, [1 vh.Count], 'int32=>int32');
      if vh.Count>1 && (begsample~=1 || endsample~=inf)
        ft_error('reading selected samples from multiple AD segments is not supported');
      end
      if ~tsonly
        numsample = min(endsample - begsample + 1, vh.NPointsWave);
        fseek(fid, (begsample-1)*2, 'cof');
        buf.dat  = fread(fid, [1 numsample], 'int16');
        % convert the AD values to miliVolt, subsequently convert from miliVolt to microVolt
        buf.dat = buf.dat * (vh.ADtoMV * 1000);
      end

    case 6
      % Markers
      buf.ts = fread(fid, [1 vh.Count], 'int32=>int32');
      for j=1:vh.NMarkers
        buf.MarkerNames{j,1} = fread(fid, [1 64], 'uint8=>char');
        for k=1:vh.Count
          buf.MarkerValues{j,k} = fread(fid, [1 vh.MarkerLength], 'uint8=>char');
        end
      end

    otherwise
      ft_error('incorrect channel type');
  end % switch channel type

  % return the data of this channel
  varargout{i} = buf;
end % for channel

% always return the header as last
varargout{end+1} = hdr;

fclose(fid);
return

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function hdr = NexFileHeader(fid)
hdr.NexFileHeader  = fread(fid,4,'uint8=>char')';    % string NEX1
hdr.Version        = fread(fid,1,'int32');
hdr.Comment        = fread(fid,256,'uint8=>char')';
hdr.Frequency      = fread(fid,1,'double');         % timestamped freq. - tics per second
hdr.Beg            = fread(fid,1,'int32');          % usually 0
hdr.End            = fread(fid,1,'int32');          % maximum timestamp + 1
hdr.NumVars        = fread(fid,1,'int32');          % number of variables in the first batch
hdr.NextFileHeader = fread(fid,1,'int32');          % position of the next file header in the file, not implemented yet
Padding = fread(fid,256,'uint8=>char')';             % future expansion

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function hdr = NexVarHeader(fid, numvar)
for varlop=1:numvar
  hdr(varlop).Type         = fread(fid,1,'int32');        % 0 - neuron, 1 event, 2- interval, 3 - waveform, 4 - pop. vector, 5 - continuously recorded
  hdr(varlop).Version      = fread(fid,1,'int32');        % 100
  hdr(varlop).Name         = fread(fid,64,'uint8=>char')'; % variable name
  hdr(varlop).DataOffset   = fread(fid,1,'int32');        % where the data array for this variable is located in the file
  hdr(varlop).Count        = fread(fid,1,'int32');        % number of events, intervals, waveforms or weights
  hdr(varlop).WireNumber   = fread(fid,1,'int32');        % neuron only, not used now
  hdr(varlop).UnitNumber   = fread(fid,1,'int32');        % neuron only, not used now
  hdr(varlop).Gain         = fread(fid,1,'int32');        % neuron only, not used now
  hdr(varlop).Filter       = fread(fid,1,'int32');        % neuron only, not used now
  hdr(varlop).XPos         = fread(fid,1,'double');       % neuron only, electrode position in (0,100) range, used in 3D
  hdr(varlop).YPos         = fread(fid,1,'double');       % neuron only, electrode position in (0,100) range, used in 3D
  hdr(varlop).WFrequency   = fread(fid,1,'double');       % waveform and continuous vars only, w/f sampling frequency
  hdr(varlop).ADtoMV       = fread(fid,1,'double');       % waveform continuous vars only, coeff. to convert from A/D values to Millivolts
  hdr(varlop).NPointsWave  = fread(fid,1,'int32');        % waveform only, number of points in each wave
  hdr(varlop).NMarkers     = fread(fid,1,'int32');        % how many values are associated with each marker
  hdr(varlop).MarkerLength = fread(fid,1,'int32');        % how many characters are in each marker value
  Padding = fread(fid,68,'uint8=>char')';
end


function ctf_plot(ctf,CHAN,TIME,TRIALS,Xhair)

% ctf_plot - plot ctf.data
%
% ctf_plot(ctf,CHAN,TIME,TRIALS,Xhair)
%
% CHAN - see ctf_channel_select for options
% TIME - see ctf_read for options (given in msec)
% TRIALS - select 1 trial to plot (the default is trial = 1)
%
% Xhair - 1 to use the crosshair GUI features (default), 0 otherwise
%
%      <>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
%      <                                                      >
%      <                    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) 2004  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: 02/2004, Darren.Weber_at_radiology.ucsf.edu
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

if ~exist('CHAN','var'), CHAN = 'all'; end
if ~exist('TIME','var'), TIME = 'all'; end
if ~exist('TRIALS','var'), TRIALS = 1; end
if ~exist('Xhair','var'), Xhair = 1; end

if isempty(CHAN), CHAN = 'all'; end
if isempty(TIME), TIME = 'all'; end
if isempty(TRIALS), TRIALS = 1; end
if isempty(Xhair), Xhair = 1; end

% This function calls ctf_channel_select
[CHAN,type] = ctf_channel_select(ctf,CHAN);

switch num2str(TIME),
  case 'all',
    TIME = ctf.setup.time_msec;
    TIME_index = 1:ctf.setup.number_samples;
otherwise
    fprintf('...sorry, time restrictions not implemented correctly (03/2004)\n');
    TIME = ctf.setup.time_msec;
    TIME_index = 1:ctf.setup.number_samples;

    %     % assume the input is a range of times in sec
    %     % check the range
    %     if TIME(1) > ctf.setup.time_msec(1),
    %       fprintf('...setting TIME(1) = ctf.setup.time_msec(1)\n');
    %       TIME(1) = ctf.setup.time_msec(1);
    %     end
    %     if TIME(end) > ctf.setup.time_msec(end),
    %       fprintf('...setting TIME(end) = ctf.setup.time_msec(end)\n');
    %       TIME(end) = ctf.setup.time_msec(end);
    %     end
    %     % now find the nearest indices into the samples matrix
    %     TIME_index = interp1(ctf.setup.time_msec,1:ctf.setup.number_samples,TIME,'nearest');
    %     % now ensure that the TIME array is consistent with ctf.setup.time_sec
    %     TIME = ctf.setup.time_msec(TIME_index);
end
TIME = sort(TIME);

switch num2str(TRIALS),
  case 'all',
    TRIALS = 1:ctf.setup.number_trials;
  otherwise
    % assume the input is an array of trials
end
TRIALS = unique(sort(TRIALS));

% check the input data for more than 1 trial
trials = size(ctf.data,3);
if trials > 1,
    fprintf('...plotting trial %d of %d trials in ctf.data\n',TRIALS,trials);
    data = ctf.data(:,:,TRIALS);
else
    data = ctf.data;
end

% now plot the data
switch type,

    case 'all',

        if isempty(ctf.sensor.index.meg_sens),
            fprintf('...no meg sensors\n');
        else
            figure('Name','MEG sensors');
            MEGCHAN = ctf_channel_select(ctf,'meg');
            plot(ctf.setup.time_msec(TIME_index),data(TIME_index,MEGCHAN))
            axis tight
            if exist('crosshair') & Xhair,
                crosshair;
            end
        end

        if isempty(ctf.sensor.index.eeg_sens),
            fprintf('...no eeg sensors\n');
        else
            figure('Name','EEG sensors');
            EEGCHAN = ctf_channel_select(ctf,'eeg');
            plot(ctf.setup.time_msec(TIME_index),data(TIME_index,EEGCHAN))
            axis tight
        end

    case 'meg',

        if isempty(CHAN),
            fprintf('...no meg sensors\n');
        else
            figure('Name','MEG sensors');
            plot(ctf.setup.time_msec(TIME_index),data(TIME_index,CHAN))
            axis tight
        end

    case 'eeg',

        if isempty(CHAN),
            fprintf('...no eeg sensors\n');
        else
            figure('Name','EEG sensors');
            plot(ctf.setup.time_msec(TIME_index),data(TIME_index,CHAN))
            axis tight
        end

    case 'ref',

        if isempty(CHAN),
            fprintf('...no ref sensors\n');
        else
            figure('Name','MEG REF sensors');
            plot(ctf.setup.time_msec(TIME_index),data(TIME_index,CHAN))
            axis tight
        end

    case 'others',

        if isempty(CHAN),
            fprintf('...no other sensors\n');
        else
            figure('Name','Other sensors');
            plot(ctf.setup.time_msec(TIME_index),data(TIME_index,CHAN))
            axis tight
        end

    otherwise

        figure;
        plot(ctf.setup.time_msec(TIME_index),data(TIME_index,CHAN))
        axis tight

end

if exist('crosshair') & Xhair,
    crosshair;
end

return


% COMPDSP - Display standard info figures for a data decomposition
%             Creates four figure windows showing: Component amplitudes,
%             scalp maps, activations and activation spectra.
% Usage:
%      >> compdsp(data,weights,locfile,[srate],[title],[compnums],[amps],[act]);
%
% Inputs:
%   data     = data matrix used to train the decomposition
%   weights  = the unmixing matrix (e.g., weights*sphere from RUNICA)
%
% Optional:
%   locfile  = 2-d electrode locations file (as in >> topoplot example)
%              {default|[]: default locations file given in icadefs.m
%   srate    = sampling rate in Hz {default|[]: as in icadefs.m}
%   title    = optional figure title text
%              {default|'': none}
%   compnums = optional vector of component numbers to display
%              {default|[] -> all}
%   amps     = all component amplitudes (from RUNICA)
%              {default|[]->recompute}
%   act      = activations matrix (from RUNICA)
%              {default|[]->recompute}
%
% Author: Scott Makeig, SCCN/INC/UCSD, La Jolla, 2000

% Copyright (C) 12/16/00 Scott Makeig, SCCN/INC/UCSD, scott@sccn.ucsd.edu
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.

% 02-01-01  replaced STD with RMS -sm
% 02-10-01  made srate optional -sm
% 01-25-02 reformated help & license -ad

function compdsp(data,unmix,locfile,srate,titl,compnums,amps,act)

minHz = 2; % frequency display limits
maxHz = 40;
ACTS_PER_EEGPLOT = 32;

%
%%%%%%%%%%%%%%%%%%%%%% Read and test arguments %%%%%%%%%%%%%%%%%%%%%%%%
%
icadefs % read BACKCOLOR, DEFAULT_SRATE

if nargin<2
  help compdsp
  return
end

chans = size(data,1);
frames = size(data,2);
ncomps = size(unmix,1);

if ncomps < 1
   error('Unmixing matrix must have at least one component');
end
if chans < 2
   error('Data must have at least two channels');
end
if frames < 2
   error('Data must have at least two frames');
end
if size(unmix,2) ~= chans
   error('Sizes of unmix and data do not match');
end
if nargin<3
 locfile=[];
end
if isempty(locfile)
   locfile = DEFAULT_ELOC; % from icsdefs.m
end
if ~exist(locfile)
   error('Cannot find electrode locations file');
end

if nargin<4
  srate = 0; % from icadefs
end
if isempty(srate) || srate==0
  srate = DEFAULT_SRATE; % from icadefs
end
if nargin<5
  titl = '';    % default - no title text
end
if isempty(titl)
  titl = '';
end
if nargin<6
  compnums = 0; % default - all components
end
if isempty(compnums)
  compnums = 0;
end
if nargin<7
  amps = NaN; % default - recompute amps
end
if isempty(amps)
  amps = NaN;
end
if ~isnan(amps) && length(amps) ~= ncomps
   error('Supplied amps does not match the number of unmixed components');
end

if compnums(1) == 0
  compnums = 1:ncomps;
end
if min(compnums) < 1
   error('Compnums must be positive');
end
if min(compnums) > ncomps
   error('Some compnum > number of components in unmix');
end

if nargin<8
   act = NaN; % default - recompute act
end

%
%%%%%%%%%%%%%%%%%%%%%%%%%%% Create plots %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%

if chans == ncomps
  winv = inv(unmix);
elseif chans < ncomps
   error('More components than channels?');
else
  winv = pinv(unmix);
end

if isnan(act)
  fprintf('Computing activations ...')
  act = unmix(compnums,:)*data;
  fprintf('\n');
elseif size(act,2) ~= frames
  error('Supplied activations do not match data length');
elseif size(act,1) ~=  ncomps && size(act,1) ~= length(compnums)
  error('Number of supplied activations matrix does not match data or weights');
elseif size(act,1) ==  ncomps
  act = act(compnums,:); % cut down matrix to desired components
end

%
%%%%%%%%%%%%%%%%%%%%% I. Plot component amps %%%%%%%%%%%%%%%%%%%%%%%%%%%
%
pos = [40,520,550,400]; figure('Position',pos);
if isnan(amps)
  fprintf('Computing component rms amplitudes ');
  amps = zeros(1,length(compnums));
  for j=1:length(compnums)
     amps(j) = rms(winv(:,compnums(j)))*rms(act(j,:)');
     fprintf('.')
  end
  fprintf('\n');
else
  amps = amps(compnums); % truncate passed amps to desired compnums
end
plot(compnums,amps,'k');
hold on
plot(compnums,amps,'r^');
xl=xlabel('Component numbers');
yl=ylabel('RMS Amplitudes');
tl=title([titl ' Amplitudes']);
ax = axis;
axis([min(compnums)-1 max(compnums)+1 0 ax(4)]);

%
%%%%%%%%%%%%%%%%%%%%%%%%% II. Plot component maps %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
pos = [40,40,550,400]; figure('Position',pos);

fprintf('Plotting component scalp maps ...') % compmaps() may make multiple figures
compmap(winv,locfile,compnums,[titl ' Scalp Maps'],0,compnums);
fprintf('\n');

%
%%%%%%%%%%%%%%%%%%%%%% III. EEGPLOT activations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
if frames/srate < 10
  dispsecs = ceil(frames/srate);
else
  dispsecs = 10; % defaults - display 10s data per screen
end
range = 0.8*max(max(act')-min(act'));

stact=1;
lact=ACTS_PER_EEGPLOT;
if lact>size(act,1)
   lact = size(act,1);
end

pos = [620,520,550,400]; figure('Position',pos);
while stact <= size(act,1)
  % eegplot(data,srate,spacing,eloc_file,windowlength,title,posn)
  eegplot(act(stact:lact,:),srate,range,compnums(stact:lact),...
               dispsecs,[titl ' Activations'],pos);
  pos = pos + [.02 .02 0 0];
  stact = stact+ACTS_PER_EEGPLOT;
  lact  = lact +ACTS_PER_EEGPLOT;
  if lact>size(act,1)
   lact = size(act,1);
  end
end

%
%%%%%%%%%%%%%%%%%%%%%%% IV. PLOTDATA spectra %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
pos = [620,40,550,400]; figure('Position',pos);

if frames > 2048
  windw = 512;
elseif frames > 1024
  windw = 256;
else
  windw = 128;
end
fprintf('Computing component spectra ')
for j = 1:length(compnums)
  % [Pxx,F] = PSD(X,NFFT,Fs,WINDOW,NOVERLAP)
  [spec,freqs] = psd(act(j,:),1024,srate,windw,ceil(windw*0.5));
  if ~exist('specs')
     specs = zeros(length(compnums),length(freqs));
  end
  specs(j,:) = spec';
  fprintf('.')
end
fprintf('\n');
specs = 10*log10(specs);

tmp = ceil(sqrt(length(compnums)));
tmp2 = ceil(length(compnums)/tmp);
for j=1:length(compnums)
  sbplot(tmp2,tmp,j)
  plot(freqs,specs(j,:))
  set(gca,'box','off')
  set(gca,'color',BACKCOLOR);
  ax=axis;
  axis([minHz,maxHz,ax(3),ax(4)]);
  tl = title(int2str(compnums(j)));
end
xl=xlabel('Frequency (Hz)');
yl=ylabel('Power (dB)');
set(gca,'YAxisLocation','right');
txl=textsc([titl ' Activation Spectra'],'title');
axcopy % pop-up axes on mouse click

% plottopo(specs,[tmp2 tmp],0,[2,70 min(min(specs)) max(max(specs))],...
%                 [titl ' Activation Power Spectra']);

function rmsval = rms(column)
   rmsval = sqrt(mean(column.*column)); % don't remove mean


function [ output_meta ] = meta2sicd_s1product( domnode, eof_domnode )
%META2SICD_S1PRODUCT Converts Sentinel-1 annotation "product" XML file into SICD format
%
% Written by: Wade Schwartzkopf, NGA Research
%
% We do not populate the SICD Antenna field.  An antenna pattern is given
% in the metadata, but its not clear what you would use this for, so we
% haven't bothered to convert to the SICD structure yet.
%
% //////////////////////////////////////////
% /// CLASSIFICATION: UNCLASSIFIED       ///
% //////////////////////////////////////////

%% Setup
SECONDS_IN_A_DAY = 24*60*60;
xp=javax.xml.xpath.XPathFactory.newInstance.newXPath();

%% CollectionInfo
common_meta.CollectionInfo.CollectorName = char(xp.evaluate(...
    'product/adsHeader/missionId',domnode));
common_meta.CollectionInfo.CollectType='MONOSTATIC';
common_meta.CollectionInfo.RadarMode.ModeID = char(xp.evaluate(...
    'product/adsHeader/mode',domnode));
if common_meta.CollectionInfo.RadarMode.ModeID(1)=='S'
    common_meta.CollectionInfo.RadarMode.ModeType='STRIPMAP';
else
    % Actually TOPSAR.  Not what we normally think of for DYNAMIC STRIPMAP,
    % but it is definitely not SPOTLIGHT (actually counter to the spotlight
    % beam motion), and it isn't STRIPMAP with a constant angle between the
    % beam and direction of travel either, so we use DYNAMIC STRIPMAP as a
    % catchall.
    common_meta.CollectionInfo.RadarMode.ModeType='DYNAMIC STRIPMAP';
end
common_meta.CollectionInfo.Classification='UNCLASSIFIED';

%% ImageData
% For SLC, the following test should always hold true:
if strcmpi(xp.evaluate('product/imageAnnotation/imageInformation/pixelValue',domnode),'Complex') && ...
    strcmpi(xp.evaluate('product/imageAnnotation/imageInformation/outputPixels',domnode),'16 bit Signed Integer')
    common_meta.ImageData.PixelType = 'RE16I_IM16I';
else
    warning('META2SICD_S1PRODUCT:UNRECOGNIZED_SLC','SLC data should be 16-bit complex.');
end
num_bursts = str2double(xp.evaluate(...
    'count(product/swathTiming/burstList/burst)',domnode));
% These two definitions of NumRows should always be the same for
% non-STRIPMAP data (For STRIPMAP, samplesPerBurst is set to zero.)  Number
% of rows in burst should be the same as the full image.  Both of these
% numbers also should match the ImageWidth field of the measurement TIFF.
if num_bursts>0
    common_meta.ImageData.NumRows = uint32(str2double(char(xp.evaluate(...
        'product/swathTiming/samplesPerBurst',domnode))));
else % STRIPMAP
    common_meta.ImageData.NumRows = uint32(str2double(xp.evaluate(...
        'product/imageAnnotation/imageInformation/numberOfSamples',domnode)));
end
% These NumCols definition will be different.  Since each burst is its own
% coherent data period, and thus SICD, we set the SICD metadata to describe
% each individual burst.
if num_bursts>0
    % Ths in the number of coumns in a single burst.
    common_meta.ImageData.NumCols = uint32(str2double(char(xp.evaluate(...
        'product/swathTiming/linesPerBurst',domnode))));
else % STRIPMAP
    % This in the number of columns in the full TIFF measurement file, even
    % if it contains multiple bursts.
    common_meta.ImageData.NumCols = uint32(str2double(xp.evaluate(...
        'product/imageAnnotation/imageInformation/numberOfLines',domnode)));
end

common_meta.ImageData.FirstRow = uint32(0);
common_meta.ImageData.FirstCol = uint32(0);
common_meta.ImageData.FullImage.NumRows = common_meta.ImageData.NumRows;
common_meta.ImageData.FullImage.NumCols = common_meta.ImageData.NumCols;
% SCP pixel within entire TIFF
center_cols = round((-0.5 + (1:max(num_bursts,1))) * double(common_meta.ImageData.NumCols)) - 1;
center_rows = repmat(round(double(common_meta.ImageData.NumRows)/2 - 1),size(center_cols));
% SCP pixel within single burst image is the same for all burst, since east
% burst is the same size
common_meta.ImageData.SCPPixel.Col = center_cols(1);
common_meta.ImageData.SCPPixel.Row = center_rows(1);

%% GeoData
common_meta.GeoData.EarthModel = 'WGS_84';
% Initially, we just seed this with a rough value.  Later we will put
% in something more precise.
num_grid_points=str2double(xp.evaluate(...
    'count(product/geolocationGrid/geolocationGridPointList/geolocationGridPoint)',domnode));
[scp_col, scp_row, x, y, z] = deal(zeros(num_grid_points,1));
for j = 1:num_grid_points
    scp_col(j) = str2double(xp.evaluate(...
        ['product/geolocationGrid/geolocationGridPointList/geolocationGridPoint[' num2str(j) ']/line'],...
        domnode));
    scp_row(j) = str2double(xp.evaluate(...
        ['product/geolocationGrid/geolocationGridPointList/geolocationGridPoint[' num2str(j) ']/pixel'],...
        domnode));
    lat = str2double(xp.evaluate(...
        ['product/geolocationGrid/geolocationGridPointList/geolocationGridPoint[' num2str(j) ']/latitude'],...
        domnode));
    lon = str2double(xp.evaluate(...
        ['product/geolocationGrid/geolocationGridPointList/geolocationGridPoint[' num2str(j) ']/longitude'],...
        domnode));
    hgt = str2double(xp.evaluate(...
        ['product/geolocationGrid/geolocationGridPointList/geolocationGridPoint[' num2str(j) ']/height'],...
        domnode));
    % Can't interpolate across international date line -180/180 longitude,
    % so move to ECF space from griddata interpolation
    [x(j), y(j), z(j)] = geodetic_to_ecf(lat, lon, hgt);
end
scp_x = griddata(scp_col, scp_row, x, center_cols, center_rows);
scp_y = griddata(scp_col, scp_row, y, center_cols, center_rows);
scp_z = griddata(scp_col, scp_row, z, center_cols, center_rows);

%% Grid
common_meta.Grid.ImagePlane = upper(strtok(char(xp.evaluate(...
    'product/generalAnnotation/productInformation/projection',domnode))));
common_meta.Grid.Type = 'RGZERO';
delta_tau_s = 1/str2double(char(xp.evaluate(...
    'product/generalAnnotation/productInformation/rangeSamplingRate',domnode)));
common_meta.Grid.Row.SS = (SPEED_OF_LIGHT/2) * delta_tau_s;
% common_meta.Grid.Row.SS = str2double(xp.evaluate(... % Another way to do it
%     'product/imageAnnotation/imageInformation/rangePixelSpacing',domnode));
common_meta.Grid.Row.Sgn = -1;
% Justification for Sgn:
% 1) "Sentinel-1 Level 1 Detailed Algorithm Definition" shows last step in
% image formation as IFFT, which would mean a forward FFT (-1 Sgn) would be
% required to transform back.
% 2) The forward FFT of a sliding window shows the Doppler centroid
% increasing as you move right in the image, which must be the case for the
% TOPSAR collection mode which starts in a rear squint and transitions to a
% forward squint (and are always right looking).
fc = str2double(char(xp.evaluate(...
    'product/generalAnnotation/productInformation/radarFrequency',domnode)));
common_meta.Grid.Row.KCtr = 2*fc/SPEED_OF_LIGHT;
common_meta.Grid.Row.DeltaKCOAPoly=0;
spp_str = 'product/imageAnnotation/processingInformation/swathProcParamsList/swathProcParams/';
common_meta.Grid.Row.ImpRespBW=2*str2double(xp.evaluate([spp_str 'rangeProcessing/processingBandwidth'],domnode))/SPEED_OF_LIGHT;
common_meta.Grid.Row.WgtType.WindowName=upper(char(xp.evaluate(...
    [spp_str 'rangeProcessing/windowType'],domnode)));
if strcmpi(common_meta.Grid.Row.WgtType.WindowName,'NONE')
    common_meta.Grid.Row.WgtType.WindowName = 'UNIFORM';
elseif strcmpi(common_meta.Grid.Row.WgtType.WindowName,'HAMMING') % The usual Sentinel weighting
    common_meta.Grid.Row.WgtType.Parameter.name = 'COEFFICIENT';
    common_meta.Grid.Row.WgtType.Parameter.value = char(xp.evaluate(...
        [spp_str 'rangeProcessing/windowCoefficient'],domnode));
    a = str2double(common_meta.Grid.Row.WgtType.Parameter.value); % Generalized Hamming window parameter
    common_meta.Grid.Row.WgtFunct = raised_cos_fun(512,a);
    % Computation of broadening factor for uniform window is:
    % 2 * fzero(@(x) (sin(pi*x)/(pi*x)) - (1/sqrt(2)), .1)
    row_broadening_factor = 2*fzero(@(x) ...
        a*(sin(pi*x)/(pi*x)) + ((1-a)*(sin(pi*(x-1))/(pi*(x-1)))/2) + ...
        ((1-a)*(sin(pi*(x+1))/(pi*(x+1)))/2) - a/sqrt(2), .1);
    common_meta.Grid.Row.ImpRespWid = row_broadening_factor/common_meta.Grid.Row.ImpRespBW;
end
common_meta.Grid.Col.SS = str2double(xp.evaluate(...
    'product/imageAnnotation/imageInformation/azimuthPixelSpacing',domnode));
common_meta.Grid.Col.Sgn = -1; % Must be the same as Row.Sgn
common_meta.Grid.Col.KCtr = 0;
dop_bw=str2double(xp.evaluate([spp_str 'azimuthProcessing/processingBandwidth'],domnode)); % Doppler bandwidth
ss_zd_s=str2double(xp.evaluate(... % Image column spacing in zero doppler time (seconds)
    'product/imageAnnotation/imageInformation/azimuthTimeInterval',...
    domnode)); % Sentinel-1 is always right-looking, so should always be positive
common_meta.Grid.Col.ImpRespBW=dop_bw*ss_zd_s/common_meta.Grid.Col.SS; % Convert to azimuth spatial bandwidth (cycles per meter)
common_meta.Grid.Col.WgtType.WindowName=upper(char(xp.evaluate(...
    [spp_str 'azimuthProcessing/windowType'],domnode)));
if strcmpi(common_meta.Grid.Col.WgtType.WindowName,'NONE')
    common_meta.Grid.Col.WgtType.WindowName = 'UNIFORM';
elseif strcmpi(common_meta.Grid.Col.WgtType.WindowName,'HAMMING') % The usual Sentinel weighting
    common_meta.Grid.Col.WgtType.Parameter.name = 'COEFFICIENT';
    common_meta.Grid.Col.WgtType.Parameter.value = char(xp.evaluate(...
        [spp_str 'azimuthProcessing/windowCoefficient'],domnode));
    a = str2double(common_meta.Grid.Col.WgtType.Parameter.value); % Generalized Hamming window parameter
    common_meta.Grid.Col.WgtFunct = raised_cos_fun(512,a);
    col_broadening_factor = 2*fzero(@(x) ...
        a*(sin(pi*x)/(pi*x)) + ((1-a)*(sin(pi*(x-1))/(pi*(x-1)))/2) + ...
        ((1-a)*(sin(pi*(x+1))/(pi*(x+1)))/2) - a/sqrt(2), .1);
    common_meta.Grid.Col.ImpRespWid = col_broadening_factor/common_meta.Grid.Col.ImpRespBW;
end
% We will compute Grid.Col.DeltaKCOAPoly separately per-burst later.
% The following Grid fields will be computed later in derived_sicd_fields
% Grid.Row/Col.WgtFunct
% Grid.Row/Col.ImpRespWid
% Grid.Row/Col.DeltaK1
% Grid.Row/Col.DeltaK2

%% Timeline
prf=str2double(xp.evaluate(...
    'product/generalAnnotation/downlinkInformationList/downlinkInformation/prf',domnode));
% Because of calibration pulses, it is unlikely this PRF was maintained
% through this entire period, but we don't currently include that detail.
common_meta.Timeline.IPP.Set.IPPPoly=[0; prf];
[azimuth_time_first_line, azimuth_time_frac] = datenum_w_frac(char(xp.evaluate(...
    'product/imageAnnotation/imageInformation/productFirstLineUtcTime',...
    domnode))); % Always the left-most SICD column (of first bursts or entire STRIPMAP dataset), since Sentinel-1 is always right-looking
eta_mid = ss_zd_s * double(common_meta.ImageData.SCPPixel.Col); % Offset in zero Doppler time from first column to SCP column

%% Position
% Compute state vectors
num_state_vectors=str2double(xp.evaluate(...
    'count(product/generalAnnotation/orbitList/orbit)',domnode));
state_vector_T  = zeros(num_state_vectors,1);
state_vector_T_frac  = zeros(num_state_vectors,1);
state_vector_pos  = zeros(num_state_vectors,3);
state_vector_vel = zeros(num_state_vectors,3);
for i=1:num_state_vectors
    timeStamp = char(xp.evaluate(...
        ['product/generalAnnotation/orbitList/orbit[' num2str(i) ']/time'],...
        domnode));
    [state_vector_T(i), state_vector_T_frac(i)] = datenum_w_frac(timeStamp);

    state_vector_pos(i,1) = str2double(xp.evaluate(...
        ['product/generalAnnotation/orbitList/orbit[' num2str(i) ']/position/x'],...
        domnode));
    state_vector_pos(i,2) = str2double(xp.evaluate(...
        ['product/generalAnnotation/orbitList/orbit[' num2str(i) ']/position/y'],...
        domnode));
    state_vector_pos(i,3) = str2double(xp.evaluate(...
        ['product/generalAnnotation/orbitList/orbit[' num2str(i) ']/position/z'],...
        domnode));
    state_vector_vel(i,1) = str2double(xp.evaluate(...
        ['product/generalAnnotation/orbitList/orbit[' num2str(i) ']/velocity/x'],...
        domnode));
    state_vector_vel(i,2) = str2double(xp.evaluate(...
        ['product/generalAnnotation/orbitList/orbit[' num2str(i) ']/velocity/y'],...
        domnode));
    state_vector_vel(i,3) = str2double(xp.evaluate(...
        ['product/generalAnnotation/orbitList/orbit[' num2str(i) ']/velocity/z'],...
        domnode));
end
% If external orbit file was passed in, use it instead of orbit state files
% in SLC annotation file
common_meta.CollectionInfo.Parameter{4}.name = 'ORBIT_SOURCE';
if exist('eof_domnode','var')
    % Use same time range as was used in SLC annotation file plus some
    % buffer to assure enough state vectors are selected, since we are
    % fitting to 5th order polynomial.  EOF files have state vectors every
    % 10 seconds.
    buffer = max(5, (70-(max(state_vector_T)-min(state_vector_T))*SECONDS_IN_A_DAY)/2);
    timerange = [min(state_vector_T - (buffer/SECONDS_IN_A_DAY)) ...
        max(state_vector_T + (buffer/SECONDS_IN_A_DAY))];
    [state_vector_T, state_vector_T_frac, state_vector_pos, state_vector_vel] = ...
        get_osv_from_eof(eof_domnode, timerange);

    common_meta.CollectionInfo.Parameter{4}.value = char(xp.evaluate(... % Orbit file type
        'Earth_Explorer_File/Earth_Explorer_Header/Fixed_Header/File_Type',eof_domnode));
else
    common_meta.CollectionInfo.Parameter{4}.value = 'SLC_INTERNAL';
end

%% RadarCollection
pol = char(xp.evaluate('product/adsHeader/polarisation',domnode));
common_meta.RadarCollection.TxPolarization = pol(1);
common_meta.RadarCollection.TxFrequency.Min = fc + str2double(xp.evaluate(...
    'product/generalAnnotation/downlinkInformationList/downlinkInformation/downlinkValues/txPulseStartFrequency',domnode));
common_meta.RadarCollection.Waveform.WFParameters.TxFreqStart = ...
    common_meta.RadarCollection.TxFrequency.Min;
common_meta.RadarCollection.Waveform.WFParameters.TxPulseLength=str2double(xp.evaluate(...
    'product/generalAnnotation/downlinkInformationList/downlinkInformation/downlinkValues/txPulseLength',domnode));
common_meta.RadarCollection.Waveform.WFParameters.TxFMRate=str2double(xp.evaluate(...
    'product/generalAnnotation/downlinkInformationList/downlinkInformation/downlinkValues/txPulseRampRate',domnode));
bw = common_meta.RadarCollection.Waveform.WFParameters.TxPulseLength*...
    common_meta.RadarCollection.Waveform.WFParameters.TxFMRate;
common_meta.RadarCollection.TxFrequency.Max = common_meta.RadarCollection.TxFrequency.Min + bw;
common_meta.RadarCollection.Waveform.WFParameters.TxRFBandwidth=bw;
common_meta.RadarCollection.Waveform.WFParameters.RcvDemodType='CHIRP';
common_meta.RadarCollection.Waveform.WFParameters.RcvFMRate=0; % True for RcvDemodType='CHIRP'
common_meta.RadarCollection.Waveform.WFParameters.ADCSampleRate=str2double(char(xp.evaluate(...
    'product/generalAnnotation/productInformation/rangeSamplingRate',domnode))); % Raw not decimated
% After decimation would be:
% output_meta.RadarCollection.Waveform.WFParameters.ADCSampleRate=str2double(xp.evaluate(...
%         'product/generalAnnotation/downlinkInformationList/downlinkInformation/downlinkValues/rangeDecimation/samplingFrequencyAfterDecimation',domnode));
num_swl = str2double(xp.evaluate(...
        'count(product/generalAnnotation/downlinkInformationList/downlinkInformation/downlinkValues/swlList/swl)',domnode));
for i = 1:num_swl % We could have multiple receive window lengths across the collect
    common_meta.RadarCollection.Waveform.WFParameters(i) = common_meta.RadarCollection.Waveform.WFParameters(1);
    common_meta.RadarCollection.Waveform.WFParameters(i).RcvWindowLength=str2double(xp.evaluate(...
        ['product/generalAnnotation/downlinkInformationList/downlinkInformation/downlinkValues/swlList/swl[' num2str(i) ']/value'],domnode));
end

%% ImageFormation
common_meta.ImageFormation.RcvChanProc=struct('NumChanProc',1,'PRFScaleFactor',1);
common_meta.ImageFormation.TxRcvPolarizationProc=[pol(1) ':' pol(2)];
% RcvChanProc.ChanIndex must be populated external to this since it depends
% on how the polarization were ordered in manifest file.
% Assume image formation uses all data
common_meta.ImageFormation.TStartProc=0;
common_meta.ImageFormation.TxFrequencyProc.MinProc=...
    common_meta.RadarCollection.TxFrequency.Min;
common_meta.ImageFormation.TxFrequencyProc.MaxProc=...
    common_meta.RadarCollection.TxFrequency.Max;
common_meta.ImageFormation.ImageFormAlgo='RMA';
% From the Sentinel-1 Level 1 Detailed Algorithm Definition document
if common_meta.CollectionInfo.RadarMode.ModeID(1)=='S'
    common_meta.ImageFormation.STBeamComp='GLOBAL'; %STRIPMAP
else
    common_meta.ImageFormation.STBeamComp='SV'; %TOPSAR
end
common_meta.ImageFormation.ImageBeamComp='SV';
common_meta.ImageFormation.AzAutofocus='NO';
common_meta.ImageFormation.RgAutofocus='NO';

%% RMA
% "Sentinel-1 Level 1 Detailed Algorithm Definition" document seems to most
% closely match the RangeDoppler algorithm (with accurate secondary range
% compression or "option 2" as described in the Cumming and Wong book).
common_meta.RMA.RMAlgoType = 'RG_DOP';
common_meta.RMA.ImageType = 'INCA';
tau_0 = str2double(char(xp.evaluate(... % tau_0 is notation from ESA deramping paper
    'product/imageAnnotation/imageInformation/slantRangeTime',domnode)));
common_meta.RMA.INCA.R_CA_SCP = (SPEED_OF_LIGHT/2) * (tau_0 + ...
        (double(common_meta.ImageData.SCPPixel.Row) * delta_tau_s));
common_meta.RMA.INCA.FreqZero = fc;
% If we use the Doppler Centroid as defined directly in the manifest.safe
% metadata, then the center of frequency support Col.DeltaKCOAPoly does not
% correspond to RMA.INCA.DopCentroidPoly.  However, we will compute
% TimeCOAPoly later to match a newly computed Doppler Centroid based off of
% DeltaKCOAPoly, assuming that the the COA is at the peak signal (fdop_COA
% = fdop_DC).
common_meta.RMA.INCA.DopCentroidCOA = true;

%% Doppler centroid
% Get common (non-burst specific) parameters we will need for Doppler
% centroid and rate computations later
num_dc_estimates_az=str2double(xp.evaluate(...
    'count(product/dopplerCentroid/dcEstimateList/dcEstimate)',domnode));
for i = 1:num_dc_estimates_az
    [dc_az_time_s(i), dc_az_time_frac(i)] = datenum_w_frac(char(xp.evaluate(...
        ['product/dopplerCentroid/dcEstimateList/dcEstimate[' num2str(i) ']/azimuthTime'],...
        domnode)));
    % How to interpret the various representations of Doppler Centroid in
    % the Sentinel 1 XML:
    % This implementation allows for estimates at different azimuth times
    % to have different numbers of fineDce values, but I don't know if that
    % ever actually happens.
    % num_dc_estimates_rg=str2double(xp.evaluate(...
    %     ['count(product/dopplerCentroid/dcEstimateList/dcEstimate[' num2str(i) ']/fineDceList/fineDce)'],domnode));
    % [tSR, freq] = deal(zeros(num_dc_estimates_rg,1));
    % for j = 1:num_dc_estimates_rg
    %     tSR(j) = str2double(xp.evaluate(... % Two-way slant range (s)
    %         ['product/dopplerCentroid/dcEstimateList/dcEstimate[' num2str(i) ']/fineDceList/fineDce[' num2str(j) ']/slantRangeTime'],...
    %         domnode));
    %     freq(j) = str2double(xp.evaluate(... % Doppler estimate
    %         ['product/dopplerCentroid/dcEstimateList/dcEstimate[' num2str(i) ']/fineDceList/fineDce[' num2str(j) ']/frequency'],...
    %         domnode));
    % end
    dc_t0(i) = str2double(xp.evaluate(...
        ['product/dopplerCentroid/dcEstimateList/dcEstimate[' num2str(i) ']/t0'],...
        domnode));
    % % data_dc_poly should be equal to:
    % % polyfit(tSR-dc_t0, freq, 2)
    data_dc_poly{i} = str2num(xp.evaluate(...
        ['product/dopplerCentroid/dcEstimateList/dcEstimate[' num2str(i) ']/dataDcPolynomial'],...
        domnode));
    % % rms_dc_poly should be equal to:
    % % rms(freq - polyval(polyfit(tSR-dc_t0, freq, 2), tSR-dc_t0))
    % rms_dc_poly = str2num(xp.evaluate(...
    %     ['product/dopplerCentroid/dcEstimateList/dcEstimate[' num2str(i) ']/dataDcRmsError'],...
    %     domnode));
    % % An alternative method for computing the Doppler polynomial.  Uses the
    % % Doppler centroid estimate from orbit geometry.  Much smoother.
    % geom_dc_poly = str2num(xp.evaluate(...
    %     ['product/dopplerCentroid/dcEstimateList/dcEstimate[' num2str(i) ']/geometryDcPolynomial'],...
    %     domnode));
    % freq=polyval(geom_dc_poly(end:-1:1),tSR-dc_t0); % Samples at the same points as fineDceList

    % Of all these options, we will use dataDcPolynomial, since that's what
    % they use in the ESA document on TOPS SLC deramping.  The data-derived
    % samples are not always particularly smooth, nor are they sampled very
    % densely, so its not clear how accurate this polynomial representation
    % is.
end
num_azfm_rates=str2double(xp.evaluate(...
    'count(product/generalAnnotation/azimuthFmRateList/azimuthFmRate)',...
    domnode));
for i = 1:num_azfm_rates
    [az_t_s(i), az_t_frac(i)] = datenum_w_frac(char(xp.evaluate(...
        ['product/generalAnnotation/azimuthFmRateList/azimuthFmRate[' ...
        num2str(i) ']/azimuthTime'],domnode)));
    az_t0(i) = str2double(xp.evaluate(...
        ['product/generalAnnotation/azimuthFmRateList/azimuthFmRate[' ...
        num2str(i) ']/t0'],domnode)).';
    % Two different ways we have seen in XML for storing the FM Rate polynomial
    if strcmpi('true',xp.evaluate(...
            ['boolean(product/generalAnnotation/azimuthFmRateList/azimuthFmRate[' ...
            num2str(i) ']/azimuthFmRatePolynomial)'],domnode))
        k_a_poly{i} = str2num(xp.evaluate(...
            ['product/generalAnnotation/azimuthFmRateList/azimuthFmRate[' ...
            num2str(i) ']/azimuthFmRatePolynomial'],domnode)).';
    else
        for j=1:3 % Assume hard coded to 3
            k_a_poly{i}(j)=str2double(xp.evaluate(... % Doppler FM rate with regard to raw time
                ['product/generalAnnotation/azimuthFmRateList/azimuthFmRate[' ...
                num2str(i) ']/c' num2str(j-1) ],...
                domnode));
        end
    end
end

% Azimuth steering rate (constant, not dependent on burst or range)
k_psi = str2double(char(xp.evaluate(...
    'product/generalAnnotation/productInformation/azimuthSteeringRate',domnode)));
k_psi = k_psi*pi/180; % Convert from degrees/sec into radians/sec

%% Compute per/burst metadata
output_meta = cell(num_bursts,1);
for i = 1:max(num_bursts,1)
    output_meta{i} = common_meta;

    %% CollectionInfo
    % These values are needed to generate CoreName later, but we will be
    % have to wait until after Timeline to fully generate that string,
    % since we include date in it.
    if strcmpi('true',xp.evaluate('boolean(product/imageAnnotation/imageInformation/sliceNumber)',domnode))
        slice = str2double(xp.evaluate(...
            'product/imageAnnotation/imageInformation/sliceNumber',domnode));
    else
        slice = 0;
    end
    swath = char(xp.evaluate('product/adsHeader/swath',domnode));
    % Sensor specific portions of metadata
    output_meta{i}.CollectionInfo.Parameter{1}.name = 'SLICE';
    output_meta{i}.CollectionInfo.Parameter{1}.value = num2str(slice);
    output_meta{i}.CollectionInfo.Parameter{2}.name = 'SWATH';
    output_meta{i}.CollectionInfo.Parameter{2}.value = swath;
    output_meta{i}.CollectionInfo.Parameter{3}.name = 'BURST';
    output_meta{i}.CollectionInfo.Parameter{3}.value = num2str(i);

    %% ImageData.ValidData
    % Get valid bounds of burst from metadata.  Assume a rectangular valid
    % area-- not totally true, but all that seems to be defined by the
    % product XML metadata.
    if num_bursts>0 % Valid data does not seem to be defined for STRIPMAP data...
        firstSamples = str2num(char(xp.evaluate(...
            ['product/swathTiming/burstList/burst[' num2str(i) ']/firstValidSample'],domnode)));
        lastSamples = str2num(char(xp.evaluate(...
            ['product/swathTiming/burstList/burst[' num2str(i) ']/lastValidSample'],domnode)));
        valid_cols = (firstSamples>=0)&(lastSamples>=0);
        first_col = find(valid_cols,1,'first') - 1; % SICD zero-based vs MATLAB one-based
        last_col = find(valid_cols,1,'last') - 1; % SICD zero-based vs MATLAB one-based
        first_row = max(firstSamples(valid_cols)); % Sentinel XML and SICD both zero-based, no -1 needed
        last_row = min(lastSamples(valid_cols)); % Sentinel XML and SICD both zero-based
        % From SICD spec: Vertices ordered clockwise with vertex 1
        % determined by: (1) minimum row index, (2) minimum column index if
        % 2 vertices exist with minimum row index.
        output_meta{i}.ImageData.ValidData.Vertex(1).Row = first_row;
        output_meta{i}.ImageData.ValidData.Vertex(1).Col = first_col;
        output_meta{i}.ImageData.ValidData.Vertex(2).Row = first_row;
        output_meta{i}.ImageData.ValidData.Vertex(2).Col = last_col;
        output_meta{i}.ImageData.ValidData.Vertex(3).Row = last_row;
        output_meta{i}.ImageData.ValidData.Vertex(3).Col = last_col;
        output_meta{i}.ImageData.ValidData.Vertex(4).Row = last_row;
        output_meta{i}.ImageData.ValidData.Vertex(4).Col = first_col;
    end

    %% Timeline
    if num_bursts>0
        % This is the first and last zero doppler times of the columns in
        % the burst.  This isn't really what we mean by CollectStart and
        % CollectDuration in SICD (really we want first and last pulse
        % times), but its all we have.
        [start_s, start_frac] = datenum_w_frac(char(xp.evaluate(...
            ['product/swathTiming/burstList/burst[' num2str(i) ']/azimuthTime'],domnode)));
        first_line_relative_start = 0; % CollectStart is zero Doppler time of first column
    else % STRIPMAP
        [start_s, start_frac] = datenum_w_frac(char(xp.evaluate(...
            'product/generalAnnotation/downlinkInformationList/downlinkInformation/firstLineSensingTime',domnode)));
        % Maybe CollectStart/CollectDuration should be set by
        % product/imageAnnotation/imageInformation/productFirstLineUtcTime
        % and productLastLineUtcTime.  This would make it consistent with
        % non-stripmap which just defines first and last zero doppler
        % times, but is not really consistent with what SICD generally
        % means by CollectStart/CollectDuration.
        [stop_s, stop_frac] = datenum_w_frac(char(xp.evaluate(...
            'product/generalAnnotation/downlinkInformationList/downlinkInformation/lastLineSensingTime',domnode)));
        output_meta{i}.Timeline.CollectStart = start_s + (start_frac/SECONDS_IN_A_DAY);
        output_meta{i}.Timeline.CollectDuration = ...
            round((stop_s - start_s)*SECONDS_IN_A_DAY) + (stop_frac-start_frac);
        first_line_relative_start = ... % Convert from UTC to relative to start
            round((azimuth_time_first_line-start_s)*SECONDS_IN_A_DAY) + ... % Convert to seconds
            (azimuth_time_frac-start_frac); % Handle fractional seconds
    end

    % After we have start_s, we can generate CoreName
    output_meta{i}.CollectionInfo.CoreName = [...
        ... % Prefix with the NGA CoreName standard format
        upper(datestr(start_s,'ddmmmyy')) ...
        common_meta.CollectionInfo.CollectorName ...
        ... % The following core name is unique within all Sentinel-1
        ...% coherent data periods:
        num2str(str2double(xp.evaluate(...
        'product/adsHeader/missionDataTakeId',domnode)),'%07u') '_' ... % Is 7 digits enough for the life of the mission?
        num2str(slice, '%02u') '_' ... % Slice
        swath '_' ... % Swath
        num2str(i, '%02u')]; % Burst

    %% Position
    % Polynomial is computed with respect to time from start of burst
    state_vector_T_burst = round((state_vector_T-start_s)*SECONDS_IN_A_DAY) + ... % Convert from days to secs
        (state_vector_T_frac-start_frac); % Handle fractional seconds
    % sv2poly.m shows ways to determine best polynomial order, but 5th is almost always best
    polyorder = min(5, numel(state_vector_T_burst) - 1);
    P_x = polyfit(state_vector_T_burst, state_vector_pos(:,1), polyorder);
    P_y = polyfit(state_vector_T_burst, state_vector_pos(:,2), polyorder);
    P_z = polyfit(state_vector_T_burst, state_vector_pos(:,3), polyorder);
    output_meta{i}.Position.ARPPoly.X = P_x(end:-1:1).';
    output_meta{i}.Position.ARPPoly.Y = P_y(end:-1:1).';
    output_meta{i}.Position.ARPPoly.Z = P_z(end:-1:1).';

    %% RMA
    % Sentinel-1 is always right-looking, so TimeCAPoly should never have
    % to be "flipped" for left-looking cases.
    output_meta{i}.RMA.INCA.TimeCAPoly = first_line_relative_start + eta_mid; % SCP zero Doppler time relative to start
    output_meta{i}.RMA.INCA.TimeCAPoly(2,1) = ss_zd_s/common_meta.Grid.Col.SS; % Convert zero doppler spacing from sec/pixels to sec/meters

    %% Doppler centroid
    % We choose the single Doppler centroid polynomial closest to the
    % center of the current burst.
    dc_est_times = round((dc_az_time_s-start_s) * SECONDS_IN_A_DAY) + ... % Convert from days to secs
        (dc_az_time_frac-start_frac); % Handle fractional seconds
    [~,dc_poly_ind] = min(abs(dc_est_times - output_meta{i}.RMA.INCA.TimeCAPoly(1)));
    % Shift polynomial from origin at dc_t0 (reference time for Sentinel
    % polynomial) to SCP time (reference time for SICD polynomial)
    range_time_scp = common_meta.RMA.INCA.R_CA_SCP*2/SPEED_OF_LIGHT;
    % The Doppler centroid field in the Sentinel-1 metadata is not
    % complete, so we cannot use it directly.  That description of Doppler
    % centroid by itself does not vary by azimuth although the
    % Col.DeltaKCOAPoly we see in the data definitely does. We will define
    % DopCentroidPoly differently later down in the code.  We also comment
    % out any definition of TimeCOAPoly based off of this DopCentroidPoly
    % definition, since it is wrong without some further adjustment.
    % dop_rate_poly_rg_shifted = polyshift(data_dc_poly{dc_poly_ind}(:), ...
    %     range_time_scp - dc_t0(dc_poly_ind));
    % % Scale 1D polynomial to from Hz/s^n to Hz/m^n
    % output_meta{i}.RMA.INCA.DopCentroidPoly = dop_rate_poly_rg_shifted.*...
    %     (2/SPEED_OF_LIGHT).^(0:(length(dop_rate_poly_rg_shifted)-1)).';

    %% Doppler rate
    % Total Doppler rate is a combination of the Doppler FM rate and the
    % Doppler rate introduced by the scanning of the antenna.
    % We pick a single velocity magnitude at closest approach to represent
    % the entire burst.  This is valid, since the magnitude of the velocity
    % changes very little.
    pos_coefs = [P_x(:) P_y(:) P_z(:)];
    % Velocity is derivate of position.
    vel_coefs=pos_coefs(1:end-1,:).*repmat(((size(pos_coefs,1)-1):-1:1)',[1 3]);
    vel_x = polyval(vel_coefs(:,1), output_meta{i}.RMA.INCA.TimeCAPoly(1));
    vel_y = polyval(vel_coefs(:,2), output_meta{i}.RMA.INCA.TimeCAPoly(1));
    vel_z = polyval(vel_coefs(:,3), output_meta{i}.RMA.INCA.TimeCAPoly(1));
    vm_ca = sqrt(vel_x.^2 + vel_y.^2 + vel_z.^2); % Magnitude of the velocity at SCP closest approach
    % Compute FM Doppler Rate, k_a
    % We choose the single azimuth FM rate polynomial closest to the
    % center of the current burst.
    az_rate_times = round((az_t_s-start_s) * SECONDS_IN_A_DAY) + ... % Convert from days to secs
        (az_t_frac-start_frac); % Handle fractional seconds
    [~,az_rate_poly_ind] = min(abs(az_rate_times - output_meta{i}.RMA.INCA.TimeCAPoly(1)));
    % SICD's Doppler rate seems to be FM Doppler rate, not total Doppler rate
    % Shift polynomial from origin at az_t0 (reference time for Sentinel
    % polynomial) to SCP time (reference time for SICD polynomial)
    DR_CA = polyshift(k_a_poly{az_rate_poly_ind}(:), ...
        range_time_scp - az_t0(az_rate_poly_ind));
    % Scale 1D polynomial to from Hz/s^n to Hz/m^n
    DR_CA = DR_CA.*(2/SPEED_OF_LIGHT).^(0:(numel(DR_CA)-1)).';
    % Polynomial representing range as a function of range distance from SCP
    r_ca = [output_meta{i}.RMA.INCA.R_CA_SCP; 1];
    % RMA.INCA.DRateSFPoly is a function of Doppler rate.
    output_meta{i}.RMA.INCA.DRateSFPoly = - conv(DR_CA, r_ca) * ...
        SPEED_OF_LIGHT / (2 * fc * (vm_ca^2)); % Assumes a SGN of -1

    %% TimeCOAPoly
    % TimeCOAPoly = TimeCA + (DopCentroid/dop_rate); % True if DopCentroidCOA = true
    % Since we don't know how to evaluate this equation analytically, we
    % could evaluate samples of it across our image and fit a 2D polynomial
    % to it later.
    POLY_ORDER = 2; % Same order of native metadata polynomials to describe Doppler Centroid, azimuth fm rate, etc.
    grid_samples = POLY_ORDER + 1; % in each dimension
    % For debugging, this lets us compute phase for every pixel:
    % cols = linspace(1, double(output_meta{i}.ImageData.NumCols), output_meta{i}.ImageData.NumCols);
    % rows = linspace(1, double(output_meta{i}.ImageData.NumRows), output_meta{i}.ImageData.NumRows);
    cols = round(linspace(1, double(output_meta{i}.ImageData.NumCols), grid_samples));
    rows = round(linspace(1, double(output_meta{i}.ImageData.NumRows), grid_samples));
    coords_az_m = double(cols - (output_meta{i}.ImageData.SCPPixel.Col+1)) * ...
        output_meta{i}.Grid.Col.SS;
    coords_rg_m = double(rows - (output_meta{i}.ImageData.SCPPixel.Row+1)) * ...
        output_meta{i}.Grid.Row.SS;
    timeca_sampled = sicd_polyval2d(output_meta{i}.RMA.INCA.TimeCAPoly(:).',coords_az_m,coords_rg_m);
    % dopcentroid_sampled = sicd_polyval2d(output_meta{i}.RMA.INCA.DopCentroidPoly,coords_az_m,coords_rg_m);
    doprate_sampled = sicd_polyval2d(DR_CA,coords_az_m,coords_rg_m);
    % timecoa_sampled = timeca_sampled + (dopcentroid_sampled./doprate_sampled);

    %% Grid.Col.DeltaKCOAPoly
    % Reference: Definition of the TOPS SLC deramping function for products
    % generated by the S-1 IPF, COPE-GSEG-EOPG-TN-14-0025
    tau = tau_0 + delta_tau_s * (0:double(common_meta.ImageData.NumRows-1)); % Range time for each sample
    % The vm_ca used here is slightly different than the ESA deramp
    % document, since the document interpolates the velocity values given
    % rather than the position values, which is what we do here.
    k_s = (2 * vm_ca / SPEED_OF_LIGHT) * fc * k_psi; % Doppler rate as introduced by antenna steering, k_s
    k_a = polyval(k_a_poly{az_rate_poly_ind}(end:-1:1), tau - az_t0(az_rate_poly_ind)); % Doppler FM rate
    k_t = (k_a * k_s)./(k_a - k_s); % Total Doppler Centroid Rate
    f_eta_c= polyval(data_dc_poly{dc_poly_ind}(end:-1:1), tau - dc_t0(dc_poly_ind)); % Doppler Centroid
    % This old implementation was imprecise.  The limits were one more than
    % the spacing.  Should have been a -1 in here somewhere.  New
    % formulation is clearer (at least to me). -WCS
    % eta = linspace(-double(output_meta{i}.ImageData.NumCols)*ss_zd_s/2, ...
    %     double(output_meta{i}.ImageData.NumCols)*ss_zd_s/2, ...
    %     double(output_meta{i}.ImageData.NumCols));
    eta = (-double(output_meta{i}.ImageData.SCPPixel.Col) * ss_zd_s) + ...
        (0:double(output_meta{i}.ImageData.NumCols-1))*ss_zd_s;
    eta_c = - f_eta_c./k_a; % Beam center crossing time.  TimeCOA in SICD terminology
    eta_ref = eta_c - eta_c(1);
    [eta_grid, eta_ref_grid] = ndgrid(eta(cols), eta_ref(rows));
    eta_arg = eta_grid - eta_ref_grid;
    k_t_grid = repmat(k_t(rows),numel(cols),1);
    f_eta_c_grid = repmat(f_eta_c(rows),numel(cols),1);
    deramp_phase = k_t_grid.*(eta_arg.^2)/2;
    demod_phase = f_eta_c_grid.*eta_arg;
    total_phase = deramp_phase + demod_phase; % Sampled phase correction for deramping and demodding

    %% Least squares fit for 2D polynomials
    % A*x = b
    [coords_az_m_2d, coords_rg_m_2d] = ndgrid(coords_az_m, coords_rg_m);
    a = zeros(numel(total_phase), (POLY_ORDER+1)^2);
    for k = 0:POLY_ORDER
        for j = 0:POLY_ORDER
            a(:,k*(POLY_ORDER+1)+j+1) = (coords_rg_m_2d(:).^j).*(coords_az_m_2d(:).^k);
        end
    end
    % b_coa = zeros((POLY_ORDER+1)^2,1);
    b_phase = zeros((POLY_ORDER+1)^2,1);
    for k=1:((POLY_ORDER+1)^2)
       % b_coa(k)=sum(timecoa_sampled(:).*a(:,k)); % center of aperture
       b_phase(k)=sum(total_phase(:).*a(:,k)); % phase to deramp in azimuth
    end
    A=zeros((POLY_ORDER+1)^2);
    for k=1:((POLY_ORDER+1)^2)
        for j=1:((POLY_ORDER+1)^2)
            A(k,j)=sum(a(:,k).*a(:,j));
        end
    end
    % MATLAB often flags these as badly scaled, but results still appear valid
    old_warning_state=warning('off','MATLAB:nearlySingularMatrix');
    % x_coa=A\b_coa;
    x_phase=A\b_phase;
    warning(old_warning_state);
    phase=reshape(x_phase, POLY_ORDER+1, POLY_ORDER+1);
    % output_meta{i}.Grid.TimeCOAPoly=reshape(x_coa, POLY_ORDER+1, POLY_ORDER+1);
    % DeltaKCOAPoly is derivative of phase in Col direction
    output_meta{i}.Grid.Col.DeltaKCOAPoly = phase(:,2:end) .* ...
        repmat((1:(size(phase,2)-1)),size(phase,1),1);

    %% DopCentroidPoly/TimeCOAPoly
    % Another way to derive the Doppler Centroid, which is back-calculated
    % from the ESA-documented azimuth deramp phase function.
    output_meta{i}.RMA.INCA.DopCentroidPoly = output_meta{i}.Grid.Col.DeltaKCOAPoly * ...
        common_meta.Grid.Col.SS / ss_zd_s;
    dopcentroid2_sampled = sicd_polyval2d(output_meta{i}.RMA.INCA.DopCentroidPoly, coords_az_m, coords_rg_m);
    timecoa_sampled = timeca_sampled + (dopcentroid2_sampled./doprate_sampled);
    % Convert sampled TimeCOA to polynomial
    b_coa = zeros((POLY_ORDER+1)^2,1);
    for k=1:((POLY_ORDER+1)^2)
       b_coa(k)=sum(timecoa_sampled(:).*a(:,k)); % center of aperture
    end
    old_warning_state=warning('off','MATLAB:nearlySingularMatrix');
    x_coa=A\b_coa;
    warning(old_warning_state);
    output_meta{i}.Grid.TimeCOAPoly=reshape(x_coa, POLY_ORDER+1, POLY_ORDER+1);

    %% Timeline
    % We don't know the precise start and stop time of each burst (as in
    % the times of first and last pulses), so we use the min and max COA
    % time, which is a closer approximation than the min and max zero
    % Doppler times.  At least COA times will not overlap between bursts.
    if num_bursts>0 % STRIPMAP case uses another time origin different from first zero Doppler time
        time_offset = min(timecoa_sampled(:));
        % Datetime more precise than serial date number.  This precision is
        % important for TOPSAR burst relationships.  Datetime introduced in
        % MATLAB 2014b.
        output_meta{i}.Timeline.CollectStart = ... % Without fractional seconds
            datetime(start_s,'ConvertFrom','datenum');
        output_meta{i}.Timeline.CollectStart.Second = ... % With fraction seconds
            output_meta{i}.Timeline.CollectStart.Second + start_frac + time_offset;
        output_meta{i}.Timeline.CollectDuration = ...
            max(timecoa_sampled(:)) - min(timecoa_sampled(:));
        % Adjust all SICD fields that were dependent on start time
        % Time is output of polynomial:
        output_meta{i}.Grid.TimeCOAPoly(1) = ...
            output_meta{i}.Grid.TimeCOAPoly(1) - time_offset;
        output_meta{i}.RMA.INCA.TimeCAPoly(1) = ...
            output_meta{i}.RMA.INCA.TimeCAPoly(1) - time_offset;
        % Time is input of polynomial:
        for j = 'XYZ'
            output_meta{i}.Position.ARPPoly.(j) = ...
                polyshift(output_meta{i}.Position.ARPPoly.(j),time_offset);
        end
    end
    output_meta{i}.Timeline.IPP.Set.TStart=0;
    output_meta{i}.Timeline.IPP.Set.TEnd=output_meta{i}.Timeline.CollectDuration;
    output_meta{i}.Timeline.IPP.Set.IPPStart=uint32(0);
    output_meta{i}.Timeline.IPP.Set.IPPEnd=uint32(floor(output_meta{i}.Timeline.CollectDuration*prf)); % An approximation
    output_meta{i}.ImageFormation.TEndProc=output_meta{i}.Timeline.CollectDuration;

    %% GeoData
    % Rough estimate of SCP (interpolated from metadata geolocation grid)
    % to bootstrap (point_image_to_ground uses it only to find tangent to
    % ellipsoid.)  Then we will immediately replace it with a more precise
    % value from point_image_to_ground and the SICD sensor model.
    output_meta{i}.GeoData.SCP.ECF.X = scp_x(i);
    output_meta{i}.GeoData.SCP.ECF.Y = scp_y(i);
    output_meta{i}.GeoData.SCP.ECF.Z = scp_z(i);
    % Note that blindly using the heights in the geolocationGridPointList
    % can result in some confusing results.  Since the scenes can be
    % extremely large, you could easily be using a height in your
    % geolocationGridPointList that is very high, but still have ocean
    % shoreline in your scene. Blindly projecting the the plane tangent to
    % the inflated ellipsoid at SCP could result in some badly placed
    % geocoords in Google Earth.  Of course, one must always be careful
    % with ground projection and height variability, but probably even more
    % care is warranted in this data than even usual due to large scene
    % sizes and frequently steep graze angles.
    % Note also that some Sentinel-1 data we have see has different heights
    % in the geolocation grid for polarimetric channels from the same
    % swath/burst!?!
    llh = ecf_to_geodetic([output_meta{i}.GeoData.SCP.ECF.X ...
        output_meta{i}.GeoData.SCP.ECF.Y output_meta{i}.GeoData.SCP.ECF.Z]);
    output_meta{i}.GeoData.SCP.LLH.Lat = llh(1);
    output_meta{i}.GeoData.SCP.LLH.Lon = llh(2);
    output_meta{i}.GeoData.SCP.LLH.HAE = llh(3);
    % Now that SCP has been populated, populate GeoData.SCP more precisely.
    ecf = point_image_to_ground([common_meta.ImageData.SCPPixel.Row; common_meta.ImageData.SCPPixel.Col], output_meta{i});
    output_meta{i}.GeoData.SCP.ECF.X=ecf(1);
    output_meta{i}.GeoData.SCP.ECF.Y=ecf(2);
    output_meta{i}.GeoData.SCP.ECF.Z=ecf(3);
    llh=ecf_to_geodetic([output_meta{i}.GeoData.SCP.ECF.X output_meta{i}.GeoData.SCP.ECF.Y output_meta{i}.GeoData.SCP.ECF.Z]);
    output_meta{i}.GeoData.SCP.LLH.Lat=llh(1);
    output_meta{i}.GeoData.SCP.LLH.Lon=llh(2);
    output_meta{i}.GeoData.SCP.LLH.HAE=llh(3);

    %% SCPCOA (and other stuff)
    output_meta{i} = derived_sicd_fields(output_meta{i});
end

end

% Reproduces hamming functionality from the MATLAB Signal Processing
% Toolbox, but allows for arbitrary coefficients of raised cosine.
function w = raised_cos_fun(n, coef)
    w = coef - (1-coef)*cos(2*pi*(0:ceil(n/2)-1)'/(n-1));
    if ~rem(n,2)
        w = [w; w(end:-1:1)];
    else
        w = [w; w(end-1:-1:1)];
    end
end

% MATLAB's datenum function won't handle precise times down under a
% millisecond, because 1) It won't accept a format with more than 3 .FFF in
% the string description of the date format and 2) the resulting serial
% date number is stored in days from 00-JAN-0000 and just doesn't have
% enough bits to handle fractional seconds to the level we want.  Here we
% handle the fractional seconds separately so we can read date with the
% precision we need.
function [datenum_s, datenum_frac] = datenum_w_frac(datestring)
    datenum_s = datenum(datestring,'yyyy-mm-ddTHH:MM:SS');
    datenum_frac = str2double(regexp(datestring,'\.\d*','match'));
    if isnan(datenum_frac), datenum_frac = 0; end;
end

% Works on both restituted orbit files and precise orbit ephemerides orbit
% files.  Time range is a two-element vector of MATLAB date numbers.
% Returned state vectors will be within a second of these bounds.
function [ times_sec, times_frac, pos, vel ] = get_osv_from_eof( eof_domnode, time_range )

SECONDS_IN_A_DAY = 24*60*60;

osv_list = eof_domnode.getElementsByTagName('OSV');
[times_sec, times_frac] = deal(zeros(osv_list.getLength,1));
[pos, vel] = deal(zeros(osv_list.getLength,3));
valid = false(osv_list.getLength,1);
for i = 1:osv_list.getLength
    % Orbit files have 3 times (TAI, UTC, and UT1)
    % We choose UTC here, since 1) that is what is used in the orbit state
    % vectors in the SLC annotation file and 2) that is the SICD standard.
    % Restituted orbit files seem to have state vectors as the exact same
    % UTC times as those in the state vectors in the SLC annotation files,
    % while precise orbit files have state vectors on whole UTC seconds.
    time_reference = 'UTC';
    time_date_str = char(osv_list.item(i-1).getElementsByTagName(time_reference).item(0).getFirstChild().getData());
    time_date_str(1:strfind(time_date_str,'='))=''; % Remove 'UTC='
    [times_sec(i), times_frac(i)]= datenum_w_frac(time_date_str);
    if (nargin < 2) || ... % If no time ranges were passed in, read in all
            (((times_sec(i) - time_range(2)) - (1/SECONDS_IN_A_DAY) <= 0) && ...
            ((times_sec(i) - time_range(1)) + (1/SECONDS_IN_A_DAY) >= 0))
        pos(i,1) = str2double(osv_list.item(i-1).getElementsByTagName('X').item(0).getFirstChild().getData());
        pos(i,2) = str2double(osv_list.item(i-1).getElementsByTagName('Y').item(0).getFirstChild().getData());
        pos(i,3) = str2double(osv_list.item(i-1).getElementsByTagName('Z').item(0).getFirstChild().getData());
        vel(i,1) = str2double(osv_list.item(i-1).getElementsByTagName('VX').item(0).getFirstChild().getData());
        vel(i,2) = str2double(osv_list.item(i-1).getElementsByTagName('VY').item(0).getFirstChild().getData());
        vel(i,3) = str2double(osv_list.item(i-1).getElementsByTagName('VZ').item(0).getFirstChild().getData());
        valid(i) = true;
        if ~strcmpi('NOMINAL', osv_list.item(i-1).getElementsByTagName('Quality').item(0).getFirstChild().getData())
            warning('META2SICD_S1PRODUCT:DEGRADED_EPHEMERIS', ...
                'Ephemeris found in requested file is of degraded quality.');
        end
    end
end
if ~any(valid)
    error('META2SICD_S1PRODUCT:NON_OVERLAPPING_EOF','EOF file does not span the same times as SLC.');
end
pos = pos(valid,:);
vel = vel(valid,:);
times_sec = times_sec(valid);
times_frac = times_frac(valid);

% XPath version.  Goes MUCH slower!  Don't use.
% xp=javax.xml.xpath.XPathFactory.newInstance.newXPath();
% base_xpath = 'Earth_Explorer_File/Data_Block/List_of_OSVs/OSV';
% num_grid_points=str2double(xp.evaluate(['count(' base_xpath ')'],dom_node));
% for i = 1:num_grid_points
%     time_date_str = char(xp.evaluate([base_xpath '[' num2str(i) ']/UTC'],dom_node));
%     time_date_str(1:strfind(time_date_str,'='))=''; % Remove 'UTC='
%     [times_sec(i), times_frac(i)]= datenum_w_frac(time_date_str);
%     pos(i,1) = str2double(xp.evaluate([base_xpath '[' num2str(i) ']/X'],dom_node));
%     pos(i,2) = str2double(xp.evaluate([base_xpath '[' num2str(i) ']/Y'],dom_node));
%     pos(i,3) = str2double(xp.evaluate([base_xpath '[' num2str(i) ']/Z'],dom_node));
% end

end

% //////////////////////////////////////////
% /// CLASSIFICATION: UNCLASSIFIED       ///
% //////////////////////////////////////////


function [TruePositives, FalseNegatives] = testPutrefactionPathways(model, microbeID, biomassReaction, database)
% Performs an FVA and reports those putrefaction pathway end reactions (exchange reactions)
% that can carry flux in the model and should carry flux according to
% data (true positives) and those putrefaction pathway end reactions that
% cannot carry flux in the model but should be secreted according to in
% vitro data (false negatives).
%
% INPUT
% model             COBRA model structure
% microbeID         Microbe ID in carbon source data file
% biomassReaction   Biomass objective functions (low flux through BOF
%                   required in analysis)
% database          Structure containing rBioNet reaction and metabolite
%                   database
%
% OUTPUT
% TruePositives     Cell array of strings listing all putrefaction reactions
% that can carry flux in the model and in in vitro data.
% FalseNegatives    Cell array of strings listing all putrefaction reactions
% that cannot carry flux in the model but should carry flux according to in
% vitro data.
%
% .. Author:
%      Almut Heinken, Dec 2017
%                     March 2022 - changed code to string-matching to make
%                     it more robust

global CBT_LP_SOLVER
if isempty(CBT_LP_SOLVER)
    initCobraToolbox
end

% read putrefaction product tables
dataTable = readInputTableForPipeline('PutrefactionTable.txt');

corrRxns = {'Histidine degradation (histidine -> glutamate)','GLUFORT';'THF production (histidine -> tetrahydrofolate)','FTCD';'Glutamate production (glutamate -> acetate + pyruvate)','CITMALL';'Putrescine_1','EX_ptrc(e)';'Putrescine_2','EX_ptrc(e)';'Putrescine_3','EX_ptrc(e)';'Spermidine/ Spermine production (methionine -> spermidine)','EX_spmd(e)';'Cadaverine production (lysine -> cadaverine)','EX_15dap(e)';'Cresol production (tyrosine -> cresol)','EX_pcresol(e)';'Indole production (tryptophan -> indole)','EX_indole(e)';'Phenol production (tyrosine -> phenol)','EX_phenol(e)';'H2S_1','EX_h2s(e)';'H2S_2','EX_h2s(e)';'H2S_3','EX_h2s(e)';'H2S_4','EX_h2s(e)';'H2S_5','EX_h2s(e)'};

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

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

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

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

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

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

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

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

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

end