function [elec, grad] = out_fieldtrip_channel(ChannelFile, isIncludeRef)
% OUT_FIELDTRIP_CHANNEL: Converts a channel file into elec/grad structures
%
% USAGE:  [elec, grad] = out_fieldtrip_channel(ChannelFile, isIncludeRef=1)
%         [elec, grad] = out_fieldtrip_channel(ChannelMat)

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2016-2019

% ===== PARSE INPUT =====
if (nargin < 2) || isempty(isIncludeRef)
    isIncludeRef = 1;
end
if isstruct(ChannelFile)
    ChannelMat  = ChannelFile;
    ChannelFile = [];
else
    ChannelMat = [];
end

% ===== LOAD CHANNEL FILE =====
% Load channel file
if ~isempty(ChannelFile) && isempty(ChannelMat)
    ChannelMat = in_bst_channel(ChannelFile);
end
% Make sure that the channel file is defined
if isempty(ChannelMat)
    error('No channel file available.');
end
% Find MEG and EEG sensors
iEeg = channel_find(ChannelMat.Channel, 'EEG,SEEG,ECOG');
iMeg = channel_find(ChannelMat.Channel, 'MEG');
iRef = channel_find(ChannelMat.Channel, 'MEG REF');
if isIncludeRef
    iMegAll = [iMeg, iRef];
else
    iMegAll = iMeg;
end

% ===== COMPUTE PROJECTOR =====
% Compute selected SSP projectors
if ~isempty(ChannelMat.Projector)
    % Rebuild projector in the expanded form (I-UUt)
    Proj = process_ssp2('BuildProjector', ChannelMat.Projector, [1 2]);
else
    Proj = [];
end

% ===== EEG =====
if ~isempty(iEeg)
    % Create electrode structure
    elec = struct();
    elec.label = {ChannelMat.Channel(iEeg).Name};
    elec.unit  = 'm';
    % Electrode position
    elec.chanpos = zeros(length(iEeg),3);
    for i = 1:length(iEeg)
        if all(size(ChannelMat.Channel(iEeg(i)).Loc) >= [3,1])
            elec.chanpos(i,:) = ChannelMat.Channel(iEeg(i)).Loc(:,1);
        else
            elec.chanpos(i,:) = [0;0;0];
        end
    end
    elec.elecpos = elec.chanpos;
    % Default montage
    elec.tra = eye(length(iEeg));
    % Apply projectors (SSP or ICA)
    if ~isempty(Proj)
        elec.tra = Proj(iEeg,iEeg) * elec.tra;
    end
else
    elec = [];
end

% ===== MEG =====
if ~isempty(iMeg)
    % Average all the intergration points of the various coils
    chantype = cell(1,length(iMegAll));
    for i = 1:length(iMegAll)
        switch (ChannelMat.Channel(iMegAll(i)).Type)
            case 'MEG'
                chantype{i} = 'megaxial';
            case 'MEG MAG'
                chantype{i} = 'megmag';
            case 'MEG GRAD'
                chantype{i} = 'megplanar';
            case 'MEG REF'
                chantype{i} = 'megref';
        end
    end

    % Create sensor structure
    grad = struct();
    grad.label    = {ChannelMat.Channel(iMegAll).Name};
    grad.unit     = 'm';
    grad.chantype = chantype;
    % Channels positions
    grad.chanpos = figure_3d('GetChannelPositions', ChannelMat, iMegAll);
    % Coils positions
    grad.coilpos = [ChannelMat.Channel(iMegAll).Loc]';
    grad.coilori = [ChannelMat.Channel(iMegAll).Orient]';
    % Correspondance channel-coil
    grad.tra = sparse(length(iMegAll), length(grad.coilpos));
    k = 1;
    for i = 1:length(iMegAll)
        % Dealing with the multiple coils and integration points
        grad.chanori(i,:) = ChannelMat.Channel(iMegAll(i)).Orient(:,1)';
        nCoils = size(ChannelMat.Channel(iMegAll(i)).Weight,2);
        grad.tra(i,k+(0:nCoils-1)) = ChannelMat.Channel(iMegAll(i)).Weight;
        k = k + nCoils;
    end
    % Add MegRefCoef (CTF/4D 3rd order gradient compensation)
    if isIncludeRef && ~isempty(iRef)
        % Error: Not all the sensors are selected
        if (size(ChannelMat.MegRefCoef,1) ~= length(iMeg)) || (size(ChannelMat.MegRefCoef,2) ~= length(iRef))
            error('CTF compensation can be used only when using all the MEG sensors.');
        end
        % Apply compensation matrix to ".tra" matrix
        GradComp = eye(length(ChannelMat.Channel));
        GradComp(iMeg,iRef) = -ChannelMat.MegRefCoef;
        grad.tra = GradComp(iMegAll,iMegAll) * grad.tra;
        % grad.tra is later applied to the leadfield in ft_compute_leadfield:
        % lf = sens.tra * lf;
    end
    % Apply projectors (SSP or ICA)
    if ~isempty(Proj)
        grad.tra = Proj(iMegAll,iMegAll) * grad.tra;
    end
else
    grad = [];
end


classdef CelestialBodySunRelStateDataCache < matlab.mixin.SetGet
    %CelestialBodySunRelStateDataCache Summary of this class goes here
    %   Detailed explanation goes here

    properties
        bodyInfo KSPTOT_BodyInfo

        times(:,1) double = [];
        stateVects(:,6) double = [];
        relFrame AbstractReferenceFrame

        gi %gridded interpolant
    end

    methods
        function obj = CelestialBodySunRelStateDataCache(bodyInfo)
            obj.bodyInfo = bodyInfo;
        end

        function setStateData(obj, times, stateVects, relFrame)
            obj.times = times;
            obj.stateVects = stateVects;
            obj.relFrame = relFrame;

            obj.gi = griddedInterpolant(obj.times, obj.stateVects, 'spline');
        end

        function [rVect, vVect] = getCachedBodyStateAtTime(obj, time)
            if(numel(obj.times) <= 1 || any(time > max(obj.times)) || any(time < min(obj.times)))
                maxUT = max(obj.times);
                boolMaxUT = maxUT - time < 0;
                worstMaxUTViolation = max(time(boolMaxUT));

                minUT = min(obj.times);
                boolMinUT = time - minUT < 0;
                worstMinUTViolation = min(time(boolMinUT));

                if(not(isempty(worstMaxUTViolation)) && not(isempty(worstMinUTViolation)))
                    str = sprintf('Adjust the minimum cache UT to below %0.3f sec and the maximum cache UT to above %0.3f sec.', worstMinUTViolation, worstMaxUTViolation);

                elseif(not(isempty(worstMaxUTViolation)))
                    str = sprintf('Adjust the maximum cache UT to above %0.3f sec.', worstMaxUTViolation);

                elseif(not(isempty(worstMinUTViolation)))
                    str = sprintf('Adjust the minimum cache UT to below %0.3f sec.', worstMinUTViolation);

                end

                msg = sprintf('At least one of the points in time queried are out of the bounds of the numerical integration (%0.3f sec to %0.3f sec).  %s', min(obj.times), max(obj.times), str);

                errordlg(msg, 'Body State Cache Bounds', 'modal');
                error(msg);
            else
                vq = obj.gi(time);

                rVect = vq(:,1:3)';
                vVect = vq(:,4:6)';
            end
        end
    end
end


function out = spm_shoot_norm(job)
% Spatially normalise and smooth fMRI/PET data to MNI space, using Shoot deformation fields
% FORMAT out = spm_shoot_norm(job)
% job - a structure generated by the configuration file
%   job.template - Shoot template for aligning to MNI space. Aligns to population
%                  average if no template is provided.
%   job.subj(n)  - Subject n
%       subj(n).def    - Shoot deformation field
%       subj(n).images - Images for this subject
%   job.vox      - Voxel sizes for spatially normalised images
%   job.bb       - Bounding box for spatially normalised images
%   job.preserve - How to transform
%                  0 = preserve concentrations
%                  1 = preserve integral (cf "modulation")
%
% Normally, Shoot generates deformations that align with the average-
% shaped template.  This routine includes the option to compose the
% shoot deformations with an affine transform derived from an affine
% registration of the template (the final one generated by Shoot),
% with the TPM data released with SPM.
%
% Note that trilinear interpolation is used, and no masking is done.  It
% is therefore essential that the images are realigned and resliced
% before they are spatially normalised.  Alternatively, contrast images
% generated from unsmoothed native-space fMRI/PET data can be spatially
% normalised for a 2nd level analysis.
%
% Two "preserve" options are provided.  One of them should do the
% equavalent of generating smoothed "modulated" spatially normalised
% images.  The other does the equivalent of smoothing the modulated
% normalised fMRI/PET, and dividing by the smoothed Jacobian determinants.
%
%__________________________________________________________________________
% Copyright (C) 2009 Wellcome Trust Centre for Neuroimaging

% John Ashburner
% $Id: spm_shoot_norm.m 7496 2018-11-23 11:14:43Z john $

% Hard coded stuff, that should maybe be customisable
tpm  = fullfile(spm('Dir'),'tpm','TPM.nii');
Mmni = spm_get_space(tpm);

% Shoot template
if ~isempty(job.template{1})
    Nt     = nifti(job.template{1});
    do_aff = true;
else
    Nt     = nifti(tpm);
    do_aff = false;
end

% Deal with desired bounding box and voxel sizes.
%--------------------------------------------------------------------------
bb   = job.bb;
vox  = job.vox;
Mt   = Nt.mat;
dimt = size(Nt.dat);

if any(isfinite(bb(:))) || any(isfinite(vox))
    [bb0,vox0] = spm_get_bbox(Nt, 'old');

    msk = ~isfinite(vox); vox(msk) = vox0(msk);
    msk = ~isfinite(bb);   bb(msk) =  bb0(msk);

    bb  = sort(bb);
    vox = abs(vox);

    % Adjust bounding box slightly - so it rounds to closest voxel.
    bb(:,1) = round(bb(:,1)/vox(1))*vox(1);
    bb(:,2) = round(bb(:,2)/vox(2))*vox(2);
    bb(:,3) = round(bb(:,3)/vox(3))*vox(3);
    dim = round(diff(bb)./vox+1);
    of  = -vox.*(round(-bb(1,:)./vox)+1);
    mat = [vox(1) 0 0 of(1) ; 0 vox(2) 0 of(2) ; 0 0 vox(3) of(3) ; 0 0 0 1];
    if det(Mt(1:3,1:3)) < 0
        mat = mat*[-1 0 0 dim(1)+1; 0 1 0 0; 0 0 1 0; 0 0 0 1];
    end
else
    dim = dimt(1:3);
    mat = Mt;
end

if isfield(job.data,'subj') || isfield(job.data,'subjs')
    if do_aff
        [pth,nam,ext] = fileparts(Nt.dat.fname); %#ok
        if exist(fullfile(pth,[nam '_2mni.mat']),'file')
            load(fullfile(pth,[nam '_2mni.mat']),'mni');
        else
            % Affine registration of Shoot Template with MNI space.
            %--------------------------------------------------------------
            fprintf('** Affine registering "%s" with MNI space **\n', nam);
            clear mni
            mni.affine = Mmni/spm_klaff(Nt,tpm,1);
            mni.code   = 'MNI152';
            save(fullfile(pth,[nam '_2mni.mat']),'mni', spm_get_defaults('mat.format'));
        end
        M = mat\mni.affine/Mt;
        mat_intent = mni.code;
    else
        M = mat\eye(4);
        mat_intent = 'Aligned';
    end
    fprintf('\n');

    if isfield(job.data,'subjs')
        % Re-order data
        %------------------------------------------------------------------
        subjs = job.data.subjs;
        subj  = struct('deformation',cell(numel(subjs.deformations),1),...
                       'images',   cell(numel(subjs.deformations),1));
        for i=1:numel(subj)
            subj(i).deformation = {subjs.deformations{i}};
            subj(i).images   = cell(numel(subjs.images),1);
            for j=1:numel(subjs.images)
                subj(i).images{j} = subjs.images{j}{i};
            end
        end
    else
        subj = job.data.subj;
    end

    % Loop over subjects
    %----------------------------------------------------------------------
    out = cell(1,numel(subj));
    for i=1:numel(subj)
        % Spatially normalise data from this subject
        [pth,nam,ext] = fileparts(subj(i).deformation{1}); %#ok
        fprintf('** "%s" **\n', nam);
        out{i} = deal_with_subject(subj(i).deformation,subj(i).images, mat,dim,M,job.preserve,job.fwhm,mat_intent);
    end

    if isfield(job.data,'subjs')
        out1 = out;
        out  = cell(numel(subj),numel(subjs.images));
        for i=1:numel(subj)
            for j=1:numel(subjs.images)
                out{i,j} = out1{i}{j};
            end
        end
    end
end
%__________________________________________________________________________

%__________________________________________________________________________
function out = deal_with_subject(Py,PI,mat,dim,M,jactransf,fwhm,mat_intent)
% Py         - Filename of shoot deformation.
% PI         - Filenames of images to spatially normalise.
% mat        - Voxel-to-world matrix for header of warped images.
% dim        - Dimensions of warped images.
% M          - Matrix for adjusting the deformation fields (eg when using
%              different bounding boxes or origins, or when an additional
%              affine transform is included.
% jactransf  - Whether or not to "modulate".
% fwhm       - FWHM for smoothing.
% mat_intent - Something for the header to indicate average space or MNI
%              space.
%
% Generate deformation, which is the inverse of the usual one (it is for "pushing"
% rather than the usual "pulling"). This deformation is affine transformed to
% allow for different voxel sizes and bounding boxes, and also to incorporate
% the affine mapping between MNI space and the population average shape.
%--------------------------------------------------------------------------
NY  = nifti(Py{1});
M   = M*NY.mat;
y   = single(squeeze(NY.dat(:,:,:,:,:)));
d   = size(y);

if norm(M-eye(4))>1e-3 || any(d(1:3) ~= dim(1:3))
    y0  = zeros([dim(1:3),3],'single');
    for d=1:3
        yd = y(:,:,:,d);
        for x3=1:dim(3)
            y0(:,:,x3,d) = single(spm_slice_vol(yd,M\spm_matrix([0 0 x3]),dim(1:2),[1 NaN]));
        end
    end
else
    y0 = y;
end

odm = zeros(1,3);
oM  = zeros(4,4);
out = cell(1,numel(PI));
for m=1:numel(PI)

    % Generate headers etc for output images
    %----------------------------------------------------------------------
    [pth,nam,ext,num] = spm_fileparts(PI{m}); %#ok
    NI = nifti(fullfile(pth,[nam ext]));
    NO = NI;
    if jactransf
        if fwhm==0
            NO.dat.fname=fullfile(pth,['mw' nam ext]);
        else
            NO.dat.fname=fullfile(pth,['smw' nam ext]);
        end
        NO.dat.scl_slope = 1.0;
        NO.dat.scl_inter = 0.0;
        NO.dat.dtype     = 'float32-le';
    else
        if fwhm==0
            NO.dat.fname=fullfile(pth,['w' nam ext]);
        else
            NO.dat.fname=fullfile(pth,['sw' nam ext]);
        end
    end
    NO.dat.dim = [dim NI.dat.dim(4:end)];
    NO.mat  = mat;
    NO.mat0 = mat;
    NO.mat_intent  = mat_intent;
    NO.mat0_intent = mat_intent;

    if strcmp(mat_intent,'Aligned'), to_what = 'Average';  else
                                     to_what = mat_intent; end
    if fwhm==0
        NO.descrip = ['Shoot-normed (' to_what ')'];
    else
        NO.descrip = sprintf('Smoothed (%g) Shoot normed (%s)',fwhm, to_what);
    end
    out{m} = NO.dat.fname;
    NO.extras = [];
    create(NO);

    % Smoothing settings
    vx  = sqrt(sum(mat(1:3,1:3).^2));
    krn = max(fwhm./vx,0.1);

    % Loop over volumes within the file
    %----------------------------------------------------------------------
    fprintf('%s',nam); drawnow;
    for j=1:size(NI.dat,4)

        % Check if it is an "imported" image to normalise
        if sum(sum((NI.mat - NY.mat ).^2)) < 0.0001 && ...
           sum(sum((NI.mat - NI.mat0).^2)) > 0.0001
            % No affine transform necessary
            M0 = NI.mat0;
        else
            % Need to resample the mapping by an affine transform
            % so that it maps from voxels in the native space image
            % to voxels in the spatially normalised image.
            %--------------------------------------------------------------
            M0 = NI.mat;
            if ~isempty(NI.extras) && isstruct(NI.extras) && isfield(NI.extras,'mat')
                M1 = NI.extras.mat;
                if size(M1,3) >= j && sum(sum(M1(:,:,j).^2)) ~=0
                    M0 = M1(:,:,j);
                end
            end
        end
        M   = inv(M0);
        dm  = [size(NI.dat),1,1,1,1];
        if ~all(dm(1:3)==odm) || ~all(M(:)==oM(:))
            % Generate new deformation (if needed)
            y   = zeros(size(y0),'single');
            y(:,:,:,1) = M(1,1)*y0(:,:,:,1) + M(1,2)*y0(:,:,:,2) + M(1,3)*y0(:,:,:,3) + M(1,4);
            y(:,:,:,2) = M(2,1)*y0(:,:,:,1) + M(2,2)*y0(:,:,:,2) + M(2,3)*y0(:,:,:,3) + M(2,4);
            y(:,:,:,3) = M(3,1)*y0(:,:,:,1) + M(3,2)*y0(:,:,:,2) + M(3,3)*y0(:,:,:,3) + M(3,4);

            % Generate Jacobian determinants.
            c          = abs(spm_diffeo('jacdet',y)*(det(NI.mat(1:3,1:3))/det(NO.mat(1:3,1:3))));
            c(:,:,[1 end]) = NaN; % Boundary voxels are not handled well - so remove
            c(:,[1 end],:) = NaN;
            c([1 end],:,:) = NaN;
        end
        odm = dm(1:3);
        oM  = M;

        % Write the warped data for this time point.
        %------------------------------------------------------------------
        for k=1:size(NI.dat,5)
            for l=1:size(NI.dat,6)
                f  = single(NI.dat(:,:,:,j,k,l));
                if ~jactransf
                    % Unmodulated
                    f = spm_diffeo('pull',f,y).*c;
                    if fwhm>0
                        spm_smooth(f,f,krn); % Side effects
                        cs = zeros(size(c),'like',c);
                        spm_smooth(c,cs,krn); % Side effects
                        f  = f./max(cs,0.000001);
                    else
                        f  = f./max(c ,0.000001);
                    end
                else
                    % Modulated, by pushing
                    f = spm_diffeo('pull',f,y).*c;
                    spm_smooth(f,f,krn); % Side effects
                end
                NO.dat(:,:,:,j,k,l) = f;
                fprintf('\t%d,%d,%d', j,k,l); drawnow;
            end
        end
    end
    fprintf('\n'); drawnow;
end
%__________________________________________________________________________


classdef PTKAirwayGenerator < handle
    % PTKAirwayGenerator. Creates an artifical airway tree using a volume-filling
    % algorithm.
    %
    %     This class generates an artificial model of the airway tree. You
    %     specify the lung volume to fill, and provide a starting tree (such as
    %     that produced by a CT region-growing algorithm). The airway growing
    %     starts at the endpoints of the starting tree and grows into the
    %     provided volume. The resulting tree includes the starting tree.
    %
    %     You can also choose to grow specific parts of the tree into specific
    %     volumes, so for example a lobar region can be exclusively allocated to
    %     a lobar bronchus and its descendents.
    %
    %     This code has in part been adapted from C++ code by Rafel Bordas which
    %     forms part of the Chaste project at the University of Oxford.
    %     The algorithm is derived from Tawhai et al. (2004), although some
    %     changes have been made to the algorithm.
    %
    %     Syntax:
    %         airway_generator = PTKAirwayGenerator(
    %             lung_mask,              % A binary mask of the whole lung volume
    %             centreline_tree,          % A PTKModelTree produced from PTKAirwayCentreline
    %             point_limit_voxels,     % Branches will terminate if the size of the region they grow into in voxels is less than this limit
    %             approx_number_points,   %
    %             reporting               % A CoreReportingInterface object for error, warning and progress reporting
    %         )
    %
    %     Licence
    %     -------
    %     Part of the TD Pulmonary Toolkit. https://github.com/tomdoel/pulmonarytoolkit
    %     Author: Tom Doel, 2012.  www.tomdoel.com
    %     Distributed under the GNU GPL v3 licence. Please see website for details.
    %

    properties (Constant)
        LengthLimitMm = 1.2 % A branch is terminated if its length is less than this value. Tawhai et al 2004 use value 2.
        PointLimitVoxels = 1 % A branch is terminated if the point cloud of the apex has fewer points than this limit
        NumberOfGenerationsToReallocate = 20  % If ReallocatePointsAtEachGeneration is set to true, this is the number of generations for which points in the volume will be reallocated at each generation
        PointNumberMultiple = 1 % The desired number of grid points is obtained by multiplying approx_number_points by this value
        MaximumGenerationNumber = 25     % All branches of the output tree will terminate if they extend beyond this generation number
        InitialTerminatingBranchLengthLimit = 6
        BranchingFraction = 0.4 % The fraction a branch extends towards the centre of the point cloud. Value 0.4 from Tawhai et al., 2004
        ReallocatePointsAtEachGeneration = true % If true, points will be reassigned to apices at each generation up to the generation number set in NumberOfGenerationsToReallocate
        AngleLimitRadians = 60*(pi/180.0); % Value 60 degrees from Tawhai et al, 2004
        PointDistanceLimit = 5 % Points greather than a voxel distance of this number multiplied by the number of cloud points are removed from the apex cloud
        BranchLengthToParentRatioLimit = 1 % Child branches cannot be longer than this factor multiplied by ther parent's length
        BranchLengthToParentGenerationLimit = 20 % The BranchLengthToParentRatioLimit parameter starts taking effect from this generation number
    end

    properties
        AirwayTree
        InitialApexImage
        GridSpacingMm
    end

    methods
        function obj = PTKAirwayGenerator(lung_mask, centreline_tree, approx_number_points, reporting)

            % Compute the grid spacing. We choose to have more grid points than
            % the minimum required as a finer grid will give better branching
            approx_number_grid_points = PTKAirwayGenerator.PointNumberMultiple*approx_number_points;
            obj.GridSpacingMm = lung_mask.ComputeResamplingGridSpacing(approx_number_grid_points);

            % Initialise airway tree
            initial_airway_tree = obj.CreateInitialTreeFromSegmentation(centreline_tree, PTKAirwayGenerator.MaximumGenerationNumber, reporting);
            obj.RemoveSmallTerminatingAirways(initial_airway_tree, PTKAirwayGenerator.InitialTerminatingBranchLengthLimit, reporting);
            obj.AirwayTree = initial_airway_tree;
        end

        function delete(~)
        end

        % Starting from the initial airway tree generated from AddTree(),
        function GrowTree(obj, growth_volume, starting_segment, reporting)
            obj.InitialApexImage = PTKAirwayGenerator.GrowTreeUsingThisGridSpacing(obj.AirwayTree, growth_volume, starting_segment, obj.GridSpacingMm, reporting);
        end
    end

    methods (Static, Access = private)
        function initial_apex_image = GrowTreeUsingThisGridSpacing(airway_tree, growth_volume, starting_segment, grid_spacing_mm, reporting)

            reporting.PushProgress;

            resampled_volume = PTKAirwayGenerator.CreatePointCloud(growth_volume, grid_spacing_mm);
            disp(['Number of seed points:' int2str(sum(resampled_volume.RawImage(:)))]);

            initial_apex_image = PTKAirwayGenerator.Grow(resampled_volume, airway_tree, starting_segment, reporting);

            reporting.PopProgress;
        end

        % Use CreateInitialTreeFromSegmentation to create an initial airway tree
        % from the airway centreline results
        function airway_tree = CreateInitialTreeFromSegmentation(segmented_centreline_tree, maximum_generation_number, reporting)
            airway_tree = PTKAirwayGrowingTree;
            airway_tree.CentrelineTreeSegment = segmented_centreline_tree;
            segments_to_do = airway_tree;
            while ~isempty(segments_to_do)
                segment = segments_to_do(end);
                segments_to_do(end) = [];

                % Get the first and last voxel coordinates from the segment of
                % the centreline airway tree
                centreline_segment = segment.CentrelineTreeSegment;
                if isempty(segment.Parent)
                    first_point = centreline_segment.Centreline(1);
                else
                    first_point = centreline_segment.Parent.Centreline(end);
                end
                end_point = centreline_segment.Centreline(end);

                segment.StartPoint = first_point;
                segment.EndPoint = end_point;

                if isempty(centreline_segment.GenerationNumber)
                    error('program error');
                end

                if centreline_segment.GenerationNumber >= maximum_generation_number
                    reporting.ShowWarning('PTKAirwayGenerator:SegmentedBranchesExcluded', 'Initial branches have been excluded due to the maximum generation parameter', []);
                else

                    if ~isempty(centreline_segment.Children)
                        % Add a new branch to the tree for each child
                        for child = centreline_segment.Children
                            % Create a new segment
                            new_segment = PTKAirwayGrowingTree(segment);
                            new_segment.CentrelineTreeSegment = child;
                            new_segment.IsGenerated = false;
                            segments_to_do = [segments_to_do, new_segment];
                        end
                    end
                end

            end
        end

        function RemoveSmallTerminatingAirways(initial_airway_tree, length_limit, reporting)
            branches_to_do = initial_airway_tree;
            has_changed = true;
            iteration_number = 0;
            while (has_changed)
                has_changed = false;
                iteration_number = iteration_number + 1;
                while ~isempty(branches_to_do)
                    branch = branches_to_do(end);
                    branches_to_do(end) = [];
                    if isempty(branch.Children)
                        branch_length = MimImageCoordinateUtilities.DistanceBetweenPoints(branch.StartPoint, branch.EndPoint);
                        if branch_length < length_limit
                            if ~isempty(branch.Parent)
                                reporting.ShowWarning('PTKAirwayGenerator:SegmentedBranchesBelowLimit', 'Initial branches have been excluded due to their length being below the limit', []);
                                branch.Parent.RemoveChildren;
                                has_changed = true;
                            end
                        end

                    else
                        branches_to_do = [branches_to_do, branch.Children];
                    end
                end
            end
        end

        function in_volume = IsInsideVolume(point_mm, lung_volume)
            global_coordinates = MimImageCoordinateUtilities.GetGlobalCoordinatesForPoints(point_mm, lung_volume);
            if ~lung_volume.IsPointInImage(global_coordinates)
                in_volume = false;
            else
                in_volume = lung_volume.GetVoxel(global_coordinates);
            end
        end

        function apices = GetApicesBelowThisBranchForTerminalSegments(centreline_branches, airway_tree, generation_number, reporting)
            apices = [];

            % Find the starting segments
            segments_to_do = [];
            for centreline_branch = centreline_branches
                branch = airway_tree.FindCentrelineBranch(centreline_branch, reporting);
                segments_to_do = [segments_to_do, branch];
            end

            % Now search the tree below the starting segments and create an apex
            % for each one at the correct generation number
            while ~isempty(segments_to_do)
                segment = segments_to_do(end);
                segments_to_do(end) = [];
                children = segment.Children;
                segments_to_do = [segments_to_do, children];

                % Now search the tree below the starting segments and create an apex
                % for each one that is a terminal segment
                if isempty(children) && isempty(segment.IsTerminal)
                    % Add apices for airway growing
                    new_apex = PTKAirwayGeneratorApex(segment, PTKCoords, true);
                    apices = [apices new_apex];
                end
            end
        end

        function apices = GetApicesBelowThisBranchForThisGeneration(start_centreline_branches, airway_tree, generation_number, reporting)
            apices = [];

            % Find the starting segments
            segments_to_do = [];
            for centreline_branch = start_centreline_branches
                branch = airway_tree.FindCentrelineBranch(centreline_branch, reporting);
                segments_to_do = [segments_to_do, branch];
            end

            % Now search the tree below the starting segments and create an apex
            % for each one at the correct generation number
            while ~isempty(segments_to_do)
                segment = segments_to_do(end);
                segments_to_do(end) = [];
                children = segment.Children;
                segments_to_do = [segments_to_do, children];

                % Find all branches of this generation which have not been
                % terminaetd by the algorithm. This includes branches which
                % already have child branches - these will not be grown, but
                % points will be allocated to their apices so that they may
                % later be available for their child branches to grow
                if segment.GenerationNumber == generation_number
                    if isempty(segment.IsTerminal)
                        is_growing_apex = isempty(children);
                        new_apex = PTKAirwayGeneratorApex(segment, PTKCoords, is_growing_apex);
                        apices = [apices new_apex];
                    end
                end
            end
        end

        function generation_number = GetMinimumTerminalGeneration(apices)
            generation_number = [];
            for apex = apices
                apex_generation = apex.AirwayGrowingTreeSegment.GenerationNumber;
                if isempty(generation_number)
                    generation_number = apex_generation;
                else
                    generation_number = min(generation_number, apex_generation);
                end
            end
        end

        % Find the smallest generation number of a branch in the airway tree
        % which is still growing. This will return an empty variable if no more
        % airways are growing
        function generation_number = GetMinimumActiveTerminalGeneration(airway_tree, start_centreline_branches, reporting)
            % Find the starting segments
            segments_to_do = [];
            for centreline_branch = start_centreline_branches
                branch = airway_tree.FindCentrelineBranch(centreline_branch, reporting);
                segments_to_do = [segments_to_do, branch];
            end

            generation_number = [];
            while ~isempty(segments_to_do)
                next_branch = segments_to_do(end);
                child_branches = next_branch.Children;

                % A branch is still growing if it has no child branches and it
                % has not been terminated by the growing algorithm
                if isempty(child_branches) && isempty(next_branch.IsTerminal)

                    % We want the smallest generation number for the tree
                    if (isempty(generation_number) || (generation_number > next_branch.GenerationNumber))
                        generation_number = next_branch.GenerationNumber;
                    end
                end

                segments_to_do(end) = [];
                segments_to_do = [segments_to_do child_branches];
            end
        end

        function centre = GetCentreOfMass(point_cloud)
            cloud_points = point_cloud.Coords;
            centre = mean(cloud_points, 1);
        end

        function new_image = GetImageFromApex(template, apices)
            new_image = zeros(template.ImageSize, 'uint16');
            colour = 1;
            for apex = apices
                cloud_points = apex.PointCloud.Coords;
                if ~isempty(cloud_points)
                    cloud_points = MimImageCoordinateUtilities.PTKCoordinatesToCoordinatesMm(cloud_points);
                    point_coord = template.CoordinatesMmToGlobalCoordinates(cloud_points);
                    point_coord = template.GlobalToLocalCoordinates(point_coord);
                    indices = sub2ind(template.ImageSize, point_coord(:,1), point_coord(:,2), point_coord(:,3));
                    new_image(indices) = colour;
                end
                colour = colour + 1;
            end
        end

        function centreline_indices_local = CentrelinePointsToLocalIndices(centreline_points, template_image)
            centreline_indices_global = MimImageCoordinateUtilities.GetGlobalIndicesForPoints(centreline_points, template_image);
            centreline_indices_local = template_image.GlobalToLocalIndices(centreline_indices_global);
        end

        function closest_point = FindClosestPointInCloud(point, cloud)
            point_coords = [point.CoordX, point.CoordY, point.CoordZ];
            num_points_in_cloud = size(cloud.Coords, 1);
            distance = cloud.Coords - repmat(point_coords, [num_points_in_cloud, 1]);
            distance = sqrt(distance(:,1).^2 + distance(:,2).^2 + distance(:,3).^2);
            [~, min_index] = min(distance, [], 1);
            closest_point = cloud.Coords(min_index, :);
        end

        % Compute a normalised direction vector for a plane parallel to the
        % given vector and the direction of the given branch. If the two vectors
        % are parallel then the direction of the parent branch is used. If this
        % does not exist or is still parallel, then we choose a guaranteed
        % non-parallel vector using the null space.
        function plane_normal = GetValidPlaneNormal(vector_to_com, branch)

            if ~isempty(branch.Parent)
                parent_branch_direction = branch.Parent.Direction;
                plane_normal = cross(vector_to_com, parent_branch_direction);
            else
                branch_direction = branch.Direction;
                plane_normal = cross(vector_to_com, branch_direction);
            end

            % This is an implementation of the algorithm described in Tawhai et
            % al., 2004 for finding the plane. However, this does not work since
            % the parent branch will often be collinear with the centre of mass
            % point (since branches are created in the direction of the centre
            % of mass).
%             branch_direction = branch.Direction;
%
%             % Find the normal to the plane which passes through the centre
%             % of mass and the branch start and end points
%             plane_normal = cross(vector_to_com, branch_direction);
%
%             % If the branch direction is parallel to the vector then we try the
%             % parent direction
%             if isequal(plane_normal, [0 0 0])
%                 disp('parallel - correcting');
%                 if ~isempty(branch.Parent)
%                     parent_branch_direction = branch.Parent.Direction;
%                     plane_normal = cross(vector_to_com, parent_branch_direction);
%                 end
%             end

            % If the vectors are still parallel then we use the null space to
            % find another vector which is perpendicular
            if isequal(plane_normal, [0 0 0])
                disp('still parallel - correcting');
                plane_null_space = null(vector_to_com/norm(vector_to_com));
                perpendicular_vector = plane_null_space(:, 1);
                plane_normal = cross(vector_to_com, perpendicular_vector);
            end

            plane_normal = plane_normal / norm(plane_normal);
        end

        % Divides the point cloud in two, based on a plane perpendicular to the
        % direction of the last two branches
        function [this_plane, other_plane] = SplitPointCloud(point_cloud, normal, origin)
            points_coords = point_cloud.Coords;
            p = - dot(normal, origin);

            % Find which axis is most parallel to the plane
            [~, ordered_directions] = sort(normal, 'descend');
            main_direction = ordered_directions(1);
            other_directions = ordered_directions(2:3);
            plane_points = - (p + normal(other_directions(1))*points_coords(:, other_directions(1)) + normal(other_directions(2))*points_coords(:, other_directions(2)))/normal(main_direction);
            in_plane = points_coords(:, main_direction) < plane_points;
            this_plane = PTKCoords(points_coords(in_plane, :));
            other_plane = PTKCoords(points_coords(~in_plane, :));
        end

        function resampled_volume = CreatePointCloud(growth_volume, grid_spacing_mm)
            resampled_volume = growth_volume.Copy;
            grid_spacing = [grid_spacing_mm, grid_spacing_mm, grid_spacing_mm];
            resampled_volume.Resample(grid_spacing, '*nearest')
        end

        % Checks the branch angle of a proposed growth centre and adjusts it within tolerance, if necessary
        function end_point = CheckBranchAngleLengthAndAdjust(start_point, end_coords, parent_direction, parent_length_mm, generation_number)
            % calculate vector from apex start to the centre
            start_coords = [start_point.CoordX, start_point.CoordY, start_point.CoordZ];
            new_direction = end_coords - start_coords;

            % Record the branching length
            branch_length = norm(new_direction);

            new_direction = new_direction / norm(new_direction);

            old_direction = parent_direction;
            old_direction = old_direction / norm(old_direction);

            % determine branch angle
            dot_product = dot(new_direction, parent_direction);
            branch_angle = acos(dot_product/(norm(new_direction)*norm(parent_direction)));

            % If the branch angle is above the limit then set it to the limit
            if (branch_angle > PTKAirwayGenerator.AngleLimitRadians)
                perpendicular_one = cross(new_direction, old_direction);
                perpendicular_one = perpendicular_one/norm(perpendicular_one);
                perpendicular_two = cross(old_direction, perpendicular_one);
                perpendicular_two = perpendicular_two/norm(perpendicular_two);
                old_direction = old_direction*cos(PTKAirwayGenerator.AngleLimitRadians);
                perpendicular_two = perpendicular_two*sin(PTKAirwayGenerator.AngleLimitRadians);
                new_direction = old_direction + perpendicular_two;
            end

            % Reduce the branch length as required
            branch_length = branch_length*PTKAirwayGenerator.BranchingFraction;
            if generation_number >= PTKAirwayGenerator.BranchLengthToParentGenerationLimit
                branch_length = min(branch_length, parent_length_mm*PTKAirwayGenerator.BranchLengthToParentRatioLimit);
            end
            new_direction = new_direction * branch_length;

            % Update the centre
            end_coords = start_coords + new_direction;
            end_point = PTKPoint(end_coords(1), end_coords(2), end_coords(3));
        end

        function new_apex = AddBranchAndApex(parent_branch, cloud, lung_volume, lung_mask, generation_number, image_size, reporting)
            new_apex = [];

            % Determine the centre of the point cloud
            centre_of_mass = PTKAirwayGenerator.GetCentreOfMass(cloud);

            % The new branch starts at the end of the parent branch
            new_branch_start_point = parent_branch.EndPoint;

            % Fetch the director vector of the parent branch
            parent_direction = parent_branch.Direction;

            % Calculate the end point of the new branch. This is subject both to
            % a length limit and an angle limit.
            new_branch_end_point = PTKAirwayGenerator.CheckBranchAngleLengthAndAdjust(new_branch_start_point, centre_of_mass, parent_direction, parent_branch.Length, generation_number);

            if ~PTKAirwayGenerator.IsInsideVolume(new_branch_end_point, lung_mask)
                reporting.ShowWarning('PTKAirwayGenerator:OutsideVolume', 'Branches terminated because they grew outside the lung volume', []);
                parent_branch.IsTerminal = true;
                return;
            end

            % Create the new branch in AirwayTree
            new_branch = PTKAirwayGrowingTree(parent_branch);
            new_branch.StartPoint = new_branch_start_point;
            new_branch.EndPoint = new_branch_end_point;
            new_branch.IsGenerated = true;

            new_branch_length = MimImageCoordinateUtilities.DistanceBetweenPoints(new_branch_end_point, new_branch_start_point);

            % Find closest point in cloud and delete
            closest_point = PTKAirwayGenerator.FindClosestPointInCloud(new_branch_end_point, cloud);
            closest_point_global_coords = MimImageCoordinateUtilities.PTKCoordinatesToCoordinatesMm(closest_point);
            closest_point_global_coords = lung_volume.CoordinatesMmToGlobalCoordinates(closest_point_global_coords);
            lung_volume.SetVoxelToThis(closest_point_global_coords, 0);

            % Check if the number of points in the cloud is less than the
            % required minimum
            if (size(cloud.Coords, 1) <= PTKAirwayGenerator.PointLimitVoxels)
                new_branch.IsTerminal = true;
                reporting.ShowWarning('PTKAirwayGenerator:BelowLengthThreshold', 'Branches terminated because the number of points was below the threshold', []);
            else
                if (new_branch_length < PTKAirwayGenerator.LengthLimitMm)
                    new_branch.IsTerminal = true;
                    reporting.ShowWarning('PTKAirwayGenerator:BelowLengthThreshold', 'Branches terminated because their length was below the threshold', []);
                else
                    % Create new growth branch
                    new_apex = PTKAirwayGeneratorApex(new_branch, cloud, true);
                end
            end
        end

        function apices = AssignPointCloudToApices(apices, resampled_volume, reporting)
            if numel(apices) == 0
               reporting.Error('PTKAirwayGenerator:AssignPointCloudToApices', 'No starting points');
            end
            sample_image = zeros(resampled_volume.ImageSize, 'uint16');
            apex_start_points = zeros(numel(apices), 3);
            for apex_number = 1 : numel(apices)
                apex = apices(apex_number);
                end_point = apex.AirwayGrowingTreeSegment.EndPoint;
                end_point_global_coordinates = MimImageCoordinateUtilities.GetGlobalCoordinatesForPoints(end_point, resampled_volume);
                end_point = resampled_volume.GlobalToLocalCoordinates(end_point_global_coordinates);
                apex_start_points(apex_number, :) = [end_point(1), end_point(2), end_point(3)];
                current_value = sample_image(end_point(1), end_point(2), end_point(3));
                if current_value ~= 0
                    reporting.ShowWarning('PTKAirwayGenerator:ApexAlreadyUsed', 'The start point for allocating voxels to apices could not be assigned as the point has already been used by another apex', []);
                end
                sample_image(end_point(1), end_point(2), end_point(3)) = apex_number;
            end

            bw_image = sample_image > 0;

            [~, IDX] = bwdist(bw_image);
            mapped_image = sample_image(IDX);
            mapped_image_masked = zeros(size(bw_image), 'uint16');
            point_indices = find(resampled_volume.RawImage);
            mapped_image_masked(point_indices) = mapped_image(point_indices);

            for apex_number = 1 : numel(apices)
                local_indices = find(mapped_image_masked == apex_number);
                if ~isempty(local_indices)
                    [di, dj, dk] = ind2sub(size(mapped_image_masked), local_indices);

                    % Calculate the distance from these new points to the start
                    % point
                    root_point = apex_start_points(apex_number, :);
                    di = di - root_point(1);
                    dj = dj - root_point(2);
                    dk = dk - root_point(3);
                    dist_voxels = sqrt(di.^2 + dj.^2 + dk.^2);
                    points_too_far_away = dist_voxels > PTKAirwayGenerator.PointDistanceLimit*numel(local_indices);

                    % This is a check for distance from root point compared to
                    % parent length
%                     di = (di - root_point(1))*resampled_volume.VoxelSize(1);
%                     dj = (dj - root_point(2))*resampled_volume.VoxelSize(2);
%                     dk = (dk - root_point(3))*resampled_volume.VoxelSize(3);
%                     dist_mm = sqrt(di.^2 + dj.^2 + dk.^2);
%                     last_branch_length = apices(apex_number).AirwayGrowingTreeSegment.Length;
%                     points_too_far_away = dist_mm > PTKAirwayGenerator.PointDistanceLimit*last_branch_length;

                    number_of_points_to_remove = sum(uint8(points_too_far_away));
                    if number_of_points_to_remove > 0
                        disp([int2str(number_of_points_to_remove) ' points removed as they were too far away (generation' int2str(apices(apex_number).AirwayGrowingTreeSegment.GenerationNumber) ')']);
                    end
                    local_indices = local_indices(~points_too_far_away, :);

                    if ~isempty(local_indices)
                        global_indices =  resampled_volume.LocalToGlobalIndices(local_indices);
                        [ic, jc, kc] = resampled_volume.GlobalIndicesToCoordinatesMm(global_indices);
                        [ptk_x, ptk_y, ptk_z] = MimImageCoordinateUtilities.CoordinatesMmToPTKCoordinates(ic, jc, kc);

                        apices(apex_number).PointCloud.Coords = [ptk_x, ptk_y, ptk_z];
                    else
                        apices(apex_number).PointCloud.Coords = [];
                    end
                else
                    apices(apex_number).PointCloud.Coords = [];
                end
            end
        end

        function [apex_1, apex_2] = GrowApex(current_apex, lung_volume, lung_mask, generation_number, image_size, reporting)
            branch = current_apex.AirwayGrowingTreeSegment;

            if isempty(current_apex.PointCloud.Coords)
                branch.IsTerminal = true;
                reporting.ShowWarning('PTKAirwayGenerator:EmptyPointCloud', 'Branches terminated because they had an empty point cloud', []);
                apex_1 = [];
                apex_2 = [];
            elseif size(current_apex.PointCloud.Coords, 1) < 2
                branch.IsTerminal = true;
                reporting.ShowWarning('PTKAirwayGenerator:TooFewPointsInCloud', 'Branches terminated because there was only one point in the point cloud', []);
                apex_1 = [];
                apex_2 = [];
            else

                % Find the end coordinates for this branch
                branch_end_point = branch.EndPoint;
                branch_end_coords = [branch_end_point.CoordX, branch_end_point.CoordY, branch_end_point.CoordZ];

                % Find the centre of mass for the point cloud assigned to this
                % apex
                centre_of_mass = PTKAirwayGenerator.GetCentreOfMass(current_apex.PointCloud);

                % Create a vector from the end branch point to the centre of
                % mass
                vector_to_com = centre_of_mass - branch_end_coords;

                % Get a normalised vector perpendicular to the plane passing
                % through the centre of pass and the start and end points of the
                % branch
                plane_normal = PTKAirwayGenerator.GetValidPlaneNormal(vector_to_com, branch);

                % Split the point cloud into two, creating a new apex for each if there are enough points
                [cloud1, cloud2] = PTKAirwayGenerator.SplitPointCloud(current_apex.PointCloud, plane_normal, branch_end_coords);

                if isempty(cloud1.Coords) || isempty(cloud2.Coords)
                    reporting.ShowWarning('PTKAirwayGenerator:EmptyPointCloud', 'Branches terminated because the point clouds could not be divided into two nonempty clouds', []);
                    branch.IsTerminal = true;
                    apex_1 = [];
                    apex_2 = [];
                    return;
                end

                % Create new branches
                apex_1 = PTKAirwayGenerator.AddBranchAndApex(branch, cloud1, lung_volume, lung_mask, generation_number, image_size, reporting);
                apex_2 = PTKAirwayGenerator.AddBranchAndApex(branch, cloud2, lung_volume, lung_mask, generation_number, image_size, reporting);
            end
        end

        function [apices, initial_apex_image] = Grow(lung_volume, airway_tree, starting_segment, reporting)

            % We need to keep a copy of the lung mask for checking that airways
            % are inside. The original image will have points removed as airways are grown.
            lung_mask = lung_volume.Copy;

            reporting.ShowProgress('Growing branches');
            reporting.UpdateProgressValue(0);
            first_run = true;
            image_size = lung_volume.ImageSize;

            % Set this to something non-empty for the first run
            apices = 1;
            generation_number = 1;

            % Step through generations one by one and terminate when there are
            % no active growing branches left
            while ~isempty(generation_number)

                % Find the lowest generation to grow
                generation_number = PTKAirwayGenerator.GetMinimumActiveTerminalGeneration(airway_tree, starting_segment, reporting);
                if (first_run)
                    min_generation_number = generation_number;
                end

                % Terminate when there are no active growing branches left
                if isempty(generation_number)
                    return;
                end

                % Terminate when we have exceeded a generation threshold, and
                % report how many branches were incomplete
                if generation_number >= PTKAirwayGenerator.MaximumGenerationNumber
                    reporting.ShowMessage('PTKAirwayGenerator:IncompleteApices', [num2str(length(apices)) ' branches were incomplete when the terminal generation was reached']);
                    return
                end

                % Progress reporting
                reporting.ShowMessage('PTKAirwayGenerator:GenerationNumber', ['Generation:' int2str(generation_number)]);
                reporting.UpdateProgressValue(round(100*(generation_number - min_generation_number)/(PTKAirwayGenerator.MaximumGenerationNumber - min_generation_number)));

                % Allocate/reallocate points to the apices
                if (first_run) || ((generation_number < PTKAirwayGenerator.NumberOfGenerationsToReallocate) && (PTKAirwayGenerator.ReallocatePointsAtEachGeneration))

                    is_this_the_last_reallocation = (generation_number + 1 == PTKAirwayGenerator.NumberOfGenerationsToReallocate) || (~PTKAirwayGenerator.ReallocatePointsAtEachGeneration);

                    if is_this_the_last_reallocation
                        apices = PTKAirwayGenerator.GetApicesBelowThisBranchForTerminalSegments(starting_segment, airway_tree, generation_number, reporting);
                    else
                        apices = PTKAirwayGenerator.GetApicesBelowThisBranchForThisGeneration(starting_segment, airway_tree, generation_number, reporting);
                    end

                    num_apices = numel(apices);
                    apices = PTKAirwayGenerator.AssignPointCloudToApices(apices, lung_volume, reporting);
%                     disp(['Generation:' int2str(generation_number) ' Number of apices:' int2str(num_apices) ' Apices post-allocation:' int2str(numel(apices))]);
                    if (first_run)
                        initial_apex_image = PTKAirwayGenerator.GetImageFromApex(lung_volume, apices);
                        first_run = false;
                    end
                end

                apices_next_generation = PTKAirwayGeneratorApex.empty(50000,0);
                for apex = apices

                    % Ignore non-growing apices - they are just there to help
                    % with the point reallocation
                    if (apex.IsGrowingApex)
                        % After the final point reallocation, we may have apices
                        % with higher generation numbers than the current one - this
                        % will happen if the segmented tree has a higher number of
                        % generations than the number of generations to reallocate.
                        % We ignore any apices with a higher generation number than
                        % the current one, but save these for processing later
                        if (generation_number == apex.AirwayGrowingTreeSegment.GenerationNumber)
                            [new_apex_1, new_apex_2] = PTKAirwayGenerator.GrowApex(apex, lung_volume, lung_mask, generation_number, image_size, reporting);
                            if ~isempty(new_apex_1)
                                apices_next_generation(end+1) = new_apex_1;
                            end
                            if ~isempty(new_apex_2)
                                apices_next_generation(end+1) = new_apex_2;
                            end
                        else
                            apices_next_generation(end + 1) = apex;
                        end
                    end
                end

                % The next generation of apices is used when point reallocation
                % no longer happens (either the point reallocation generation
                % has been exceeded, or the reallocation flag is switched off).
                % The list of apices includes newly generated apices, plus any
                % terminal branhces from the segmented airways which are waiting
                % to be grown
                apices = apices_next_generation;
            end
        end
    end
end


function bounds = GetBounds(model, vel_lb, vel_ub, T, x0, xf)

    if nargin < 2
        vel_lb = [0.0,0];
        vel_ub = [0.0,0];
    end

    if nargin < 4
        T = 0.4;
    end

    if nargin < 5
        x0 = zeros(model.numState*2,1);
    end

    if nargin < 6
        xf = zeros(model.numState*2,1);
    end

    %% first get the model specific boundary values
    model_bounds = model.getLimits(); % x, dx, ddx, u

    model_bounds.options.enforceVirtualConstraints = true;

    % fixed joint constraint wrench
    model_bounds.inputs.ConstraintWrench.ffourBar.lb = -1000;
    model_bounds.inputs.ConstraintWrench.ffourBar.ub = 1000;

    % fixed joint constraints
    model_bounds.params.pfourBar.lb = zeros(4,1);
    model_bounds.params.pfourBar.ub = zeros(4,1);

    model_bounds.constrBounds.yaw_initial.lb = 0;
    model_bounds.constrBounds.yaw_initial.ub = 0;

    % feedback control gain for virtual constraints
    model_bounds.gains.kp = 100;
    model_bounds.gains.kd = 20;

    %%% Constraint bounds
    %     vel = [0.0, -0.8].';
    %     T   = 0.4;

    %%% Step Duration
    model_bounds.time.duration.lb = T;
    model_bounds.time.duration.ub = T;

    model_bounds.time.t0.lb = 0;
    model_bounds.time.t0.ub = 0;
    model_bounds.time.t0.x0 = 0;
    model_bounds.time.tf.lb = 0;
    model_bounds.time.tf.ub = T;
    model_bounds.time.tf.x0 = T;

    model_bounds.constrBounds.foot_clearance_1.lb = 0.03;
    model_bounds.constrBounds.foot_clearance_1.ub = 0.3;
    model_bounds.constrBounds.foot_clearance_2.lb = 0.15;
    model_bounds.constrBounds.foot_clearance_2.ub = 0.3;
    model_bounds.constrBounds.foot_clearance_3.lb = 0.05;
    model_bounds.constrBounds.foot_clearance_3.ub = 0.3;
    model_bounds.constrBounds.averageVelocity.lb = vel_lb;
    model_bounds.constrBounds.averageVelocity.ub = vel_ub;

    %     model_bounds.states.x.lb(1:2) = vel_lb;
    %     model_bounds.states.x.ub(1:2) = vel_ub;

    %     model_bounds.constrBounds.stepLength.lb = vel(1)*T;
    %     model_bounds.constrBounds.stepLength.ub = vel(1)*T;
    %
    %     model_bounds.constrBounds.stepWidth.lb = vel(1)*T;
    %     model_bounds.constrBounds.stepWidth.ub = vel(1)*T;

    model_bounds.constrBounds.footVelocityBeginning.lb = [-0.15, -0.15, 0]';
    model_bounds.constrBounds.footVelocityBeginning.ub = [0.15, 0.15, 0.3]';
    model_bounds.constrBounds.footVelocityEnd.lb = [-0.15, -0.15, -0.25]';
    model_bounds.constrBounds.footVelocityEnd.ub = [0.15, 0.15, -0.05]';

    %%% Common Virtual Constraints
    model_bounds.params.aoutput.lb = -2*pi;
    model_bounds.params.aoutput.ub = 2*pi;

    model_bounds.params.poutput.lb = [0, 0];
    model_bounds.params.poutput.ub = [0, T];
    model_bounds.params.poutput.x0 = [0, T];

    model_bounds.params.pRightFoot.lb = [-10, -10, 0, 0];
    model_bounds.params.pRightFoot.ub = [10, 10, 0, 0];

    model_bounds.params.pLeftFoot.lb = [-10, -10, 0, 0];
    model_bounds.params.pLeftFoot.ub = [10, 10, 0, 0];

    model_bounds.inputs.ConstraintWrench.fRightFoot.lb = [-1000,-1000,300,-1000]';
    model_bounds.inputs.ConstraintWrench.fRightFoot.ub = [1000,1000,1000,1000]';

    model_bounds.inputs.ConstraintWrench.fLeftFoot.lb = [-1000,-1000,300,-1000]';
    model_bounds.inputs.ConstraintWrench.fLeftFoot.ub = [1000,1000,1000,1000]';

    %% construct the boundary values for each domain
    bounds = struct();

    wt = 0.3662;
    vx_lb = vel_lb(1);
    vy_lb = vel_lb(2);
    vx_ub = vel_ub(1);
    vy_ub = vel_ub(2);

    %%
    bounds.RightStance1 = model_bounds;
    bounds.RightStance1.constrBounds.knee.lb = [deg2rad(50),deg2rad(50)];
    bounds.RightStance1.constrBounds.knee.ub = [deg2rad(60),deg2rad(150)];

    bounds.RightStance1.constrBounds.stepWidth.lb = -wt - vx_ub*T;
    bounds.RightStance1.constrBounds.stepWidth.ub = -wt - vx_lb*T;

    bounds.RightStance1.constrBounds.stepLength.lb = -vy_ub*T;
    bounds.RightStance1.constrBounds.stepLength.ub = -vy_lb*T;

    bounds.RightStance1.constrBounds.full_state.lb = x0;
    bounds.RightStance1.constrBounds.full_state.ub = x0;
    idx = [3:9, 12:14, 19:25, 28:30];
    bounds.RightStance1.constrBounds.actuated_state.lb = x0(idx);
    bounds.RightStance1.constrBounds.actuated_state.ub = x0(idx);
    %%
    bounds.LeftImpact.states.x = model_bounds.states.x;
    bounds.LeftImpact.states.xn = model_bounds.states.x;
    bounds.LeftImpact.states.dx = model_bounds.states.dx;
    bounds.LeftImpact.states.dxn = model_bounds.states.dx;

    bounds.LeftImpact.inputs = struct();
    bounds.LeftImpact.inputs.ConstraintWrench.ffourBar.lb = -20;
    bounds.LeftImpact.inputs.ConstraintWrench.ffourBar.ub = 20;
    bounds.LeftImpact.inputs.ConstraintWrench.fLeftFoot.lb = [-20,-20,0,-10]';
    bounds.LeftImpact.inputs.ConstraintWrench.fLeftFoot.ub = [20,20,150,10]';
    bounds.LeftImpact.params = struct();

    %% Right Stance
    bounds.LeftStance1 = model_bounds;

    bounds.LeftStance1.constrBounds.knee.lb = [deg2rad(50),deg2rad(50)];
    bounds.LeftStance1.constrBounds.knee.ub = [deg2rad(150),deg2rad(60)];

    bounds.LeftStance1.midState_QFit = zeros(6,3);
    bounds.LeftStance1.midState_dQFit = zeros(6,3);

    bounds.LeftStance1.constrBounds.stepWidth.lb = -wt + vx_lb*T;
    bounds.LeftStance1.constrBounds.stepWidth.ub = -wt + vx_ub*T;

    bounds.LeftStance1.constrBounds.stepLength.lb = vy_lb*T;
    bounds.LeftStance1.constrBounds.stepLength.ub = vy_ub*T;

    %%
    bounds.RightImpact.states.x = model_bounds.states.x;
    bounds.RightImpact.states.xn = model_bounds.states.x;
    bounds.RightImpact.states.dx = model_bounds.states.dx;
    bounds.RightImpact.states.dxn = model_bounds.states.dx;
    bounds.RightImpact.inputs = struct();
    bounds.RightImpact.inputs.ConstraintWrench.ffourBar.lb = -20;
    bounds.RightImpact.inputs.ConstraintWrench.ffourBar.ub = 20;
    bounds.RightImpact.inputs.ConstraintWrench.fRightFoot.lb = [-20,-20,0,-10]'';
    bounds.RightImpact.inputs.ConstraintWrench.fRightFoot.ub = [20,20,150,10]';
    bounds.RightImpact.params = struct();

    %%
    bounds.RightStance2 = bounds.RightStance1;

    bounds.RightStance2.midState_QFit = zeros(6,3);
    bounds.RightStance2.midState_dQFit = zeros(6,3);

    bounds.LeftStance2  = bounds.LeftStance1;

    bounds.LeftStance2.constrBounds.full_state.lb = xf;
    bounds.LeftStance2.constrBounds.full_state.ub = xf;

    bounds.LeftStance2.constrBounds.actuated_state.lb = xf(idx);
    bounds.LeftStance2.constrBounds.actuated_state.ub = xf(idx);
end


function STResNet_stage2(varargin)

if ~isempty(gcp('nocreate'))
    delete(gcp)
end

opts.train.gpus = [ 1 ] ;

% addpath('../network_surgery');
opts = cnn_setup_environment(opts);

% opts.dataSet = 'hmdb51';
opts.dataSet = 'ucf101';

opts.dropOutRatio = NaN;
opts.backpropFuseFrom = 1;
opts.nSplit =  1 ;
opts.addPool3D = 1 ;
opts.singleSoftMax = 0;
opts.nFrames = 11 ;
% opts.train.backpropDepth = 'pool5';
opts.train.learningRate =  1*[ 1e-4*ones(1,1) 1e-5*ones(1,1)]  ;
opts.train.augmentation = 'f25noCtr';

opts.train.epochFactor = 1 ;

opts.train.batchSize = 128  ;
opts.train.numSubBatches = 32 ;
opts.train.cheapResize = 0 ;
opts.poolMethod = 'max';
opts.poolSz=5 ;
opts.poolStride=2 ;

model = ['ST-ResNet50-final-split=' num2str(opts.nSplit)];

opts.train.numSubBatches =  ceil(opts.train.numSubBatches / max(numel(opts.train.gpus),1));

opts.train.memoryMapFile = fullfile(tempdir, 'ramdisk', ['matconvnet' num2str(opts.nSplit ) '.bin']) ;
opts.dataDir = fullfile(opts.dataPath, opts.dataSet) ;
opts.splitDir = 'ucf101_splits'; nClasses = 101;
opts.imdbPath = fullfile(opts.dataDir, [opts.dataSet '_split' num2str(opts.nSplit) 'imdb.mat']);

opts.model = fullfile(opts.modelPath, [opts.dataSet '-ST-ResNet50-split=' num2str(opts.nSplit) '.mat']) ;

if strcmp(opts.dataSet, 'hmdb51')
  opts.splitDir = 'hmdb51_splits'; nClasses = 51;
  opts.flowDir = strrep(opts.flowDir, 'ucf101','hmdb51');
  opts.imdbPath = fullfile(opts.dataDir, ['hmdb_split' num2str(opts.nSplit) 'imdb.mat']);
end

opts.expDir = fullfile(opts.dataDir, [opts.dataSet '-' model]) ;
[opts, varargin] = vl_argparse(opts, varargin) ;

opts.train.saveAllPredScores = 1;
opts.train.denseEval = 1;
opts.train.plotDiagnostics = 0 ;
opts.train.continue = 1 ;
opts.train.prefetch = 1 ;
opts.train.expDir = opts.expDir ;
opts.train.numAugments = 1;
opts.train.frameSample = 'random';
opts.train.nFramesPerVid = 1;

opts = vl_argparse(opts, varargin) ;

% -------------------------------------------------------------------------
%                                                   Database initialization
% -------------------------------------------------------------------------
if exist(opts.imdbPath)
  imdb = load(opts.imdbPath) ;
  imdb.flowDir = opts.flowDir;
else
  imdb = cnn_setup_data(opts) ;
  save(opts.imdbPath, '-struct', 'imdb', '-v6') ;
end

% -------------------------------------------------------------------------
%                                                    Network initialization
% -------------------------------------------------------------------------
if ~exist(opts.model,'file')
  [~, baseModel] = fileparts(opts.model);
  fprintf('Downloading base model file: %s ...\n', baseModel);
  mkdir(fileparts(opts.model)) ;
  urlwrite(...
  ['http://ftp.tugraz.at/pub/feichtenhofer/st-res/stage1/' baseModel '.mat'], ...
    opts.model) ;
end
net = load(opts.model) ;

if isfield(net, 'net'), net=net.net;end
net = dagnn.DagNN.loadobj(net);

if opts.addPool3D

%   poolLayers = {'res2a', 'res3a',  'res4a', 'res5a'; };
%   poolLayers = {'pool5'; };
  poolLayers = {'res5c_relu'; };

  for j=1:numel(poolLayers)
    for s = {'spatial', 'temporal'}
      i_pool = find(strcmp({net.layers.name},[poolLayers{j} '_' char(s)]));

      block = dagnn.PoolTime() ;
      block.poolSize = [1 opts.poolSz];
      block.pad = [0 0 0 0];
      block.stride = [1 opts.poolStride];
      block.method = opts.poolMethod;

      name = [poolLayers{j} '_pool_' char(s)];

      disp(['injecting ' name ' as PoolTime'])

      net.addLayerAt(i_pool, name, block, ...
                    [net.layers(i_pool).outputs], {name}) ;

      % chain input of l that has layer as input
      for l = 1:numel(net.layers)
          if ~strcmp(net.layers(l).name, name)
            sel = find(strcmp(net.layers(l).inputs, net.layers(i_pool).outputs{1})) ;
            if any(sel)
                net.layers(l).inputs{sel} = name;
            end;
          end
      end

    end
  end
end % add pool3d

if opts.addPool3D
  opts.train.augmentation = 'f25noCtr';
  opts.train.frameSample = 'temporalStrideRandom';
  opts.train.nFramesPerVid = opts.nFrames * 1;
  opts.train.temporalStride = 1:15;
  opts.train.valmode = 'temporalStrideRandom';
  opts.train.numValFrames = 25 ;
  opts.train.saveAllPredScores = 1 ;
  opts.train.denseEval = 1;
  opts.train.temporalFullConvTest = 1;
end

net.meta.normalization.rgbVariance = [];
opts.train.train = find(ismember(imdb.images.set, [1])) ;
opts.train.train = repmat(opts.train.train,1,opts.train.epochFactor);

zero_drs =  find(arrayfun(@(x) isa(x.block,'dagnn.DropOut') && x.block.rate == 0, net.layers)) ;
net.removeLayer({net.layers(zero_drs).name});

if ~isnan(opts.dropOutRatio)
  dr_layers = find(arrayfun(@(x) isa(x.block,'dagnn.DropOut'), net.layers)) ;
  if opts.dropOutRatio > 0
    net.layers(dr_layers).block.rate = opts.dropOutRatio;
  else
    net.removeLayer({net.layers(dr_layers).name});
  end
end

if opts.singleSoftMax
  pred_layers = [];
  for l=1:numel(net.layers)
    if isempty( net.layers(l).params ), continue; end;
    if size(net.params(net.getParamIndex(net.layers(l).params{1})).value,4) == nClasses || ...
        size(net.params(net.getParamIndex(net.layers(l).params{1})).value,5) == nClasses % 3D FC layer
          pred_layers = [pred_layers l];
          net.vars(net.layers(l).outputIndexes).precious = 1;
    end
  end
  pred_layers = fliplr(pred_layers) ; % remove the spatial layer
  paramsIdx1 = net.getParamIndex(net.layers(pred_layers(1)).params);
  paramsIdx2 = net.getParamIndex(net.layers(pred_layers(2)).params);
  for p = 1:numel(paramsIdx1)
    sz = size(net.params(paramsIdx1(p)).value);
    if numel(sz) > 2
      net.params(paramsIdx1(p)).value = cat(3,net.params(paramsIdx1(p)).value, net.params(paramsIdx2(p)).value);
    else
      net.params(paramsIdx1(p)).value = net.params(paramsIdx1(p)).value + net.params(paramsIdx2(p)).value;
    end
  end
  block = dagnn.Concat() ;
  newName = ['singleSoftMaxConcat'];
  net.addLayer(newName, block, ...
               [net.layers(pred_layers).inputs], ...
               newName) ;
  net.layers(pred_layers(1)).inputs =    newName ;
  % remove layers of the other prediction
  for l = numel(net.layers):-1:1
    for f = net.layers(pred_layers(2)).outputs
       sel = find(strcmp(f, net.layers(l).inputs )) ;
       if ~isempty(sel)
         fprintf('removing ayer %s \n', net.layers(l).name);
         net.removeLayer({net.layers(l).name});
       end
    end
  end
  net.removeLayer({net.layers(pred_layers(2)).name});
end

net.layers(~cellfun('isempty', strfind({net.layers(:).name}, 'err'))) = [] ;

net.rebuild() ;

opts.train.derOutputs = {} ;
for l=1:numel(net.layers)
  if isa(net.layers(l).block, 'dagnn.Loss') && isempty(strfind(net.layers(l).block.loss, 'err'))
    if opts.backpropFuseFrom || ~isempty(strfind(net.layers(l).name, opts.train.fuseInto ))
      fprintf('setting derivative for layer %s \n', net.layers(l).name);
      opts.train.derOutputs = [opts.train.derOutputs, net.layers(l).outputs, {1}] ;
    end
    net.addLayer(['err1_' net.layers(l).name(end-7:end) ], dagnn.Loss('loss', 'classerror'), ...
             net.layers(l).inputs, 'error') ;
  end
end

net.conserveMemory = 1 ;
fn = getBatchWrapper_rgbflow(net.meta.normalization, opts.numFetchThreads, opts.train) ;
[info] = cnn_train_dag(net, imdb, fn, opts.train) ;


%SingleCellVariation
%
% Author: Jonathan Karr, jkarr@stanford.edu
% Affiliation: Covert Lab, Department of Bioengineering, Stanford University
% Last Updated: 3/23/2011
classdef SingleCellVariation
    methods (Static)
        function run(sim, fileName)
            import edu.stanford.covert.cell.sim.analysis.SingleCellVariation;
            import edu.stanford.covert.cell.sim.util.PlotUtil;
            import edu.stanford.covert.cell.sim.util.PrintUtil;

            %sample growth rate distribution
            initialGrowthFilterWidth = sim.state('MetabolicReaction').initialGrowthFilterWidth;
            sim.state('MetabolicReaction').initialGrowthFilterWidth = Inf;
            growthRates = SingleCellVariation.sampleSingleCellVariationDistribution(sim, 100);
            sim.state('MetabolicReaction').initialGrowthFilterWidth = initialGrowthFilterWidth;
            assertElementsAlmostEqual(sim.state('MetabolicReaction').meanInitialGrowthRate, mean(growthRates), 'relative', 0.50, 0);

            %% excel file
            [content, colLabels, indentation] = SingleCellVariation.printSingleCellVariationDistribution(sim, growthRates);
            if nargin == 1
                PrintUtil.printToStdIO(content, colLabels, struct('indentation', indentation));
            else
                PrintUtil.printToFile(content, colLabels, [fileName '.xls'], 'GrowthRate', struct('indentation', indentation));
            end

            %% plots
            if nargin == 1
                SingleCellVariation.plotSingleCellVariationGrowthRateDistribution(sim, growthRates, PlotUtil.newAxesHandle());
                SingleCellVariation.plotSingleCellVariationDoublingTimeDistribution(sim, growthRates, PlotUtil.newAxesHandle());
                SingleCellVariation.plotExpected_Vs_Simulated_RNA_Weight_Fractions(sim, PlotUtil.newAxesHandle());
                SingleCellVariation.plotExpected_Vs_Simulated_RNA_Expression(sim, PlotUtil.newAxesHandle());
                SingleCellVariation.plotExpected_Vs_Simulated_rRNAExpression(sim, PlotUtil.newAxesHandle());
                SingleCellVariation.plotExpected_Vs_Simulated_sRNA_Expression(sim, PlotUtil.newAxesHandle());
                SingleCellVariation.plotExpected_Vs_Simulated_tRNA_Expression(sim, PlotUtil.newAxesHandle());
                SingleCellVariation.plotExpected_Vs_Simulated_Monomer_Expression(sim, PlotUtil.newAxesHandle());
                SingleCellVariation.plotExpected_Vs_Simulated_NTP_Incorporation(sim, PlotUtil.newAxesHandle());
                SingleCellVariation.plotExpected_Vs_Simulated_AA_Incorporation(sim, PlotUtil.newAxesHandle());
            else
                [axesHandle, figHandle] = PlotUtil.newAxesHandle();

                cla(axesHandle);
                SingleCellVariation.plotSingleCellVariationGrowthRateDistribution(sim, growthRates, axesHandle);
                saveas(figHandle, [fileName '-GrowthRate.pdf']);

                cla(axesHandle);
                SingleCellVariation.plotSingleCellVariationDoublingTimeDistribution(sim, growthRates, axesHandle);
                saveas(figHandle, [fileName '-DoublingTime.pdf']);

                cla(axesHandle);
                SingleCellVariation.plotExpected_Vs_Simulated_RNA_Weight_Fractions(sim, axesHandle);
                saveas(figHandle, [fileName '-Expected_Vs_Simulated_RNA_Weight_Fractions.pdf']);

                cla(axesHandle);
                SingleCellVariation.plotExpected_Vs_Simulated_RNA_Expression(sim, axesHandle);
                saveas(figHandle, [fileName '-Expected_Vs_Simulated_RNA_Expression.pdf']);

                cla(axesHandle);
                SingleCellVariation.plotExpected_Vs_Simulated_rRNAExpression(sim, axesHandle);
                saveas(figHandle, [fileName '-Expected_Vs_Simulated_rRNA_Expression.pdf']);

                cla(axesHandle);
                SingleCellVariation.plotExpected_Vs_Simulated_sRNA_Expression(sim, axesHandle);
                saveas(figHandle, [fileName '-Expected_Vs_Simulated_sRNA_Expression.pdf']);

                cla(axesHandle);
                SingleCellVariation.plotExpected_Vs_Simulated_tRNA_Expression(sim, axesHandle);
                saveas(figHandle, [fileName '-Expected_Vs_Simulated_tRNA_Expression.pdf']);

                cla(axesHandle);
                SingleCellVariation.plotExpected_Vs_Simulated_Monomer_Expression(sim, axesHandle);
                saveas(figHandle, [fileName '-Expected_Vs_Simulated_Monomer_Expression.pdf']);

                cla(axesHandle);
                SingleCellVariation.plotExpected_Vs_Simulated_NTP_Incorporation(sim, axesHandle);
                saveas(figHandle, [fileName '-Expected_Vs_Simulated_NTP_Incorporation.pdf']);

                cla(axesHandle);
                SingleCellVariation.plotExpected_Vs_Simulated_AA_Incorporation(sim, axesHandle);
                saveas(figHandle, [fileName '-Expected_Vs_Simulated_AA_Incorporation.pdf']);

                close(figHandle);
            end
        end
    end

    methods (Static = true)
        function growthRates = sampleSingleCellVariationDistribution(sim, nTrials)
            warnStatus = warning('query', 'WholeCell:warning');
            warning('off', 'WholeCell:warning');

            growthRates = zeros(nTrials, 1);
            r = sim.state('MetabolicReaction');
            for i = 1:nTrials
                %seed rand stream
                sim.applyOptions(struct('seed', i));
                for j = 1:numel(sim.states)
                    o = sim.states{j};
                    o.seed = i;
                end
                for j = 1:numel(sim.processes)
                    o = sim.processes{j};
                    o.seed = i;
                end

                %calculate growth rate
                sim.initializeState();
                growthRates(i) = r.growth;
            end

            warning(warnStatus.state, 'WholeCell:warning');
        end
    end

    %printing
    methods (Static = true)
        function [content, colLabels, indentation] = printSingleCellVariationDistribution(sim, growthRates)
            %import classes
            import edu.stanford.covert.util.ConstantUtil;
            import edu.stanford.covert.cell.sim.Simulation;

            content = cell(0, 4);
            colLabels = {'Trial', 'Growth Rate (cell/s)', 'Doubling Time (hr)'};

            %time
            content = [content;
                num2cell(zeros(size(growthRates)))  cellfun(@(x) num2str(x), num2cell((1:numel(growthRates))'), 'UniformOutput', false)  num2cell(growthRates)  num2cell(1./(growthRates*3600)/sim.state('Mass').timeAveragedCellWeight)
                ];

            content = [content;{
                0 'Mean'          mean(growthRates)  1/(mean(growthRates)*3600)/sim.state('Mass').timeAveragedCellWeight
                0 'Min'           min(growthRates)   1/(max(growthRates)*3600)/sim.state('Mass').timeAveragedCellWeight
                0 'Max'           max(growthRates)   1/(min(growthRates)*3600)/sim.state('Mass').timeAveragedCellWeight
                0 'Experimental'  1/(sim.state('Time').cellCycleLength)/sim.state('Mass').timeAveragedCellWeight sim.state('Time').cellCycleLength/3600
                }];

            %format output
            indentation = cell2mat(content(:, 1));
            content = content(:, 2:end);
        end
    end

    %plotting
    methods (Static = true)
        function plotSingleCellVariationGrowthRateDistribution(sim, growthRates, axesHandle)
            hist(axesHandle, growthRates, 10);
            xlims = xlim(axesHandle);
            ylims = ylim(axesHandle);
            xlabel('Growth (cell/s)', 'FontSize', 12);
            ylabel('Frequency', 'FontSize', 12);

            h2 = line(mean(growthRates) * [1 1], ylims);
            set(h2, 'Color', 'g');

            h3 = line(median(growthRates) * [1 1], ylims);
            set(h3, 'Color', 'c');

            mu = mean(growthRates);
            sigma = std(growthRates);
            h4 = line([mu-sigma; mu-sigma], ylims);
            h5 = line([mu+sigma; mu+sigma], ylims);
            set(h4, 'Color', 'r');
            set(h5, 'Color', 'r');

            mr = sim.state('MetabolicReaction');
            mu = mr.meanInitialGrowthRate;
            wd = mr.initialGrowthFilterWidth;
            h6 = line(mu*(1-wd)*[1 1], ylims);
            h7 = line(mu*(1+wd)*[1 1], ylims);
            set(h6, 'Color', 'y');
            set(h7, 'Color', 'y');

            h = legend([h2 h3 h4 h6], 'Mean', 'Median', 'Mean \pm 1 Std', 'Filter');
            set(h, 'Location', 'NorthWest');

            xlim(axesHandle, xlims);
            ylim(axesHandle, ylims);
        end

        function plotSingleCellVariationDoublingTimeDistribution(sim, growthRates, axesHandle)
            thresh = sim.state('Time').cellCycleLength/3600 * 2;

            doublingTimes = 1./(growthRates * 3600)/sim.state('Mass').timeAveragedCellWeight;
            hist(axesHandle, doublingTimes(doublingTimes <= thresh), 10);
            h1 = findobj(gca, 'Type', 'patch');
            xlabel('Doubling Time (h)', 'FontSize', 12);
            ylabel('Frequency', 'FontSize', 12);
            xlim([floor(min(doublingTimes(doublingTimes <= thresh))) ceil(max(doublingTimes(doublingTimes <= thresh)))]);

            h2 = line(1/(mean(growthRates) * 3600) / sim.state('Mass').timeAveragedCellWeight * [1 1], [0 max(ylim)]);
            set(h2, 'Color', 'g');

            h3 = line(1/(median(growthRates) * 3600) / sim.state('Mass').timeAveragedCellWeight * [1 1], [0 max(ylim)]);
            set(h3, 'Color', 'c');

            h4 = line(sim.state('Time').cellCycleLength * [1 1] / 3600, [0 max(ylim)]);
            set(h4, 'Color', 'r');

            h = legend([h1(1) h2 h3 h4], sprintf('Simulation, growth (%.1f%%)', 100 * sum(doublingTimes <= thresh) / numel(doublingTimes)), 'Sim-Mean', 'Sim-Median', 'Exp-Mean');
            set(h, 'Location', 'NorthEast');
        end

        function plotExpected_Vs_Simulated_RNA_Weight_Fractions(sim, axesHandle)
            import edu.stanford.covert.cell.sim.analysis.Constants;
            import edu.stanford.covert.util.ConstantUtil;

            r = sim.state('Rna');

            weightFractions = [...
                r.molecularWeights(r.matureIndexs(r.matureMRNAIndexs))' * ...
                sum(sum(r.counts(r.matureIndexs(r.matureMRNAIndexs), :, :), 3), 2);...
                r.molecularWeights(r.matureIndexs(r.matureRibosomalRRNAIndexs)) .* ...
                sum(sum(r.counts(r.matureIndexs(r.matureRibosomalRRNAIndexs), :, :), 3), 2);...
                r.molecularWeights(r.matureIndexs(r.matureSRNAIndexs))' * ...
                sum(sum(r.counts(r.matureIndexs(r.matureSRNAIndexs), :, :), 3), 2)
                r.molecularWeights(r.matureIndexs(r.matureTRNAIndexs))' * ...
                sum(sum(r.counts(r.matureIndexs(r.matureTRNAIndexs), :, :), 3), 2)] / ...
                ConstantUtil.nAvogadro;

            plot(axesHandle, r.expectedWeightFractions, weightFractions, '.', 'MarkerSize', 20);
            line([0 1], [0 sum(weightFractions)], 'Parent', axesHandle, 'Color', 'r');
            xlim([min(r.expectedGeneDecayRates) max(r.weightFractions)]);
            ylim([min(weightFractions) max(weightFractions)]);
            xlabel(axesHandle, 'Expected Weight Fraction', 'fontSize', 16);
            ylabel(axesHandle, 'Simulated Weight (g)', 'fontSize', 16);
        end

        function plotExpected_Vs_Simulated_RNA_Expression(sim, axesHandle)
            import edu.stanford.covert.cell.sim.analysis.Constants;

            r = sim.state('Rna');

            expression = r.expression(r.matureIndexs);

            RNAs = sum(sum(...
                r.counts(r.processedIndexs,     :, :) + ...
                r.counts(r.matureIndexs,        :, :) + ...
                r.counts(r.boundIndexs,         :, :) + ...
                r.counts(r.misfoldedIndexs,     :, :) + ...
                r.counts(r.damagedIndexs,       :, :) + ...
                r.counts(r.aminoacylatedIndexs, :, :), ...
                3), 2);

            plot(axesHandle, expression, RNAs, '.', 'MarkerSize', 20);
            line([0 1], [0 sum(RNAs)], 'Parent', axesHandle, 'Color', 'r');
            xlim([min(expression) max(expression)]);
            ylim([min(RNAs) max(RNAs)]);
            xlabel(axesHandle, 'Expected Expression', 'fontSize', 16);
            ylabel(axesHandle, 'Simulated Counts', 'fontSize', 16);
        end

        function plotExpected_Vs_Simulated_rRNAExpression(sim, axesHandle)
            import edu.stanford.covert.cell.sim.analysis.Constants;

            r = sim.state('Rna');
            c = sim.state('ProteinComplex');

            expression = r.geneExpression(sim.gene.rRNAIndexs);
            expression = expression / sum(expression);

            RNAs = sum(sum(...
                r.counts(r.processedIndexs(    r.matureRRNAIndexs), :, :) + ...
                r.counts(r.matureIndexs(       r.matureRRNAIndexs), :, :) + ...
                r.counts(r.boundIndexs(        r.matureRRNAIndexs), :, :) + ...
                r.counts(r.misfoldedIndexs(    r.matureRRNAIndexs), :, :) + ...
                r.counts(r.damagedIndexs(      r.matureRRNAIndexs), :, :) + ...
                r.counts(r.aminoacylatedIndexs(r.matureRRNAIndexs), :, :), ...
                3), 2) + ...
                sum(c.proteinComplexComposition(sim.gene.rRNAIndexs, :, :), 3) * ...
                sum(sum(...
                c.counts(c.matureIndexs,      :, :) + ...
                c.counts(c.inactivatedIndexs, :, :) + ...
                c.counts(c.boundIndexs,       :, :) + ...
                c.counts(c.misfoldedIndexs,   :, :) + ...
                c.counts(c.damagedIndexs,     :, :), 3), 2);

            plot(axesHandle, expression, RNAs, '.', 'MarkerSize', 20);
            line([0 1], [0 sum(RNAs)], 'Parent', axesHandle, 'Color', 'r');
            xlim([min(0.3, min(expression)) max(0.4, max(expression))]);
            ylim([min(RNAs)-1 max(RNAs)+1]);
            xlabel(axesHandle, 'Expected Expression', 'fontSize', 16);
            ylabel(axesHandle, 'Simulated Counts', 'fontSize', 16);
        end

        function plotExpected_Vs_Simulated_sRNA_Expression(sim, axesHandle)
            import edu.stanford.covert.cell.sim.analysis.Constants;

            r = sim.state('Rna');

            expression = r.geneExpression(sim.gene.sRNAIndexs);
            expression = expression / sum(expression);

            RNAs = sum(sum(...
                r.counts(r.processedIndexs(    r.matureSRNAIndexs), :, :) + ...
                r.counts(r.matureIndexs(       r.matureSRNAIndexs), :, :) + ...
                r.counts(r.boundIndexs(        r.matureSRNAIndexs), :, :) + ...
                r.counts(r.misfoldedIndexs(    r.matureSRNAIndexs), :, :) + ...
                r.counts(r.damagedIndexs(      r.matureSRNAIndexs), :, :) + ...
                r.counts(r.aminoacylatedIndexs(r.matureSRNAIndexs), :, :), ...
                3), 2);

            plot(axesHandle, expression, RNAs, '.', 'MarkerSize', 20);
            line([0 1], [0 sum(RNAs)], 'Parent', axesHandle, 'Color', 'r');
            xlim([min(expression) max(expression)]);
            ylim([min(RNAs) max(RNAs)]);
            xlabel(axesHandle, 'Expected Expression', 'fontSize', 16);
            ylabel(axesHandle, 'Simulated Counts', 'fontSize', 16);
        end

        function plotExpected_Vs_Simulated_tRNA_Expression(sim, axesHandle)
            import edu.stanford.covert.cell.sim.analysis.Constants;

            r = sim.state('Rna');

            expression = r.geneExpression(sim.gene.tRNAIndexs);
            expression = expression / sum(expression);

            RNAs = sum(sum(...
                r.counts(r.processedIndexs(    r.matureTRNAIndexs), :, :) + ...
                r.counts(r.matureIndexs(       r.matureTRNAIndexs), :, :) + ...
                r.counts(r.boundIndexs(        r.matureTRNAIndexs), :, :) + ...
                r.counts(r.misfoldedIndexs(    r.matureTRNAIndexs), :, :) + ...
                r.counts(r.damagedIndexs(      r.matureTRNAIndexs), :, :) + ...
                r.counts(r.aminoacylatedIndexs(r.matureTRNAIndexs), :, :), ...
                3), 2);

            plot(axesHandle, expression, RNAs, '.', 'MarkerSize', 20);
            line([0 1], [0 sum(RNAs)], 'Parent', axesHandle, 'Color', 'r');
            xlim([min(expression) max(expression)]);
            ylim([min(RNAs) max(RNAs)]);
            xlabel(axesHandle, 'Expected Expression', 'fontSize', 16);
            ylabel(axesHandle, 'Copy Number', 'fontSize', 16);
        end

        function plotExpected_Vs_Simulated_Monomer_Expression(sim, axesHandle)
            import edu.stanford.covert.cell.sim.analysis.Constants;

            r = sim.state('Rna');
            m = sim.state('ProteinMonomer');
            c = sim.state('ProteinComplex');

            expression = r.geneExpression(sim.gene.mRNAIndexs) ./ m.halfLives(m.matureIndexs);
            expression = expression / sum(expression);

            monomers = ...
                sum(sum(...
                m.counts(m.matureIndexs,      :, :) + ...
                m.counts(m.inactivatedIndexs, :, :) + ...
                m.counts(m.boundIndexs,       :, :) + ...
                m.counts(m.misfoldedIndexs,   :, :) + ...
                m.counts(m.damagedIndexs,     :, :), 3), 2) + ...
                sum(c.proteinComplexComposition(sim.gene.mRNAIndexs, :, :), 3) * ...
                sum(sum(...
                c.counts(c.matureIndexs,      :, :) + ...
                c.counts(c.inactivatedIndexs, :, :) + ...
                c.counts(c.boundIndexs,       :, :) + ...
                c.counts(c.misfoldedIndexs,   :, :) + ...
                c.counts(c.damagedIndexs,     :, :), 3), 2);

            plot(axesHandle, expression, monomers, '.', 'MarkerSize', 10);
            line([0 1], [0 sum(monomers)], 'Parent', axesHandle, 'Color', 'r');
            xlim([min(expression) max(expression)]);
            ylim([min(monomers) max(monomers)]);
            xlabel(axesHandle, 'Expected Expression', 'fontSize', 16);
            ylabel(axesHandle, 'Simulated Counts', 'fontSize', 16);
        end

        function plotExpected_Vs_Simulated_NTP_Incorporation(sim, axesHandle)
            import edu.stanford.covert.cell.sim.analysis.Constants;

            r = sim.state('Rna');
            m = sim.state('Metabolite');

            nmpComposition = sum(m.nmpComposition, 2);

            ntps = sum(sum(multiprod(r.baseCounts(:, sim.state('Metabolite').nmpIndexs)', r.counts, [1 2], [1 2]), 3), 2);

            plot(axesHandle, nmpComposition, ntps, '.', 'MarkerSize', 20);
            line([0 1], [0 sum(ntps)], 'Parent', axesHandle, 'Color', 'r');
            xlim([min(nmpComposition) max(nmpComposition)]);
            ylim([min(ntps) max(ntps)]);
            xlabel(axesHandle, 'Expected Incorporation', 'fontSize', 16);
            ylabel(axesHandle, 'Simulated Counts', 'fontSize', 16);
        end

        function plotExpected_Vs_Simulated_AA_Incorporation(sim, axesHandle)
            import edu.stanford.covert.cell.sim.analysis.Constants;

            met = sim.state('Metabolite');
            m = sim.state('ProteinMonomer');
            c = sim.state('ProteinComplex');

            aaComposition = sum(met.aaComposition, 2);

            aas = multiprod(m.baseCounts(:, sim.state('Metabolite').aminoAcidIndexs)', m.counts, [1 2], [1 2]) + ...
                multiprod(c.baseCounts(:, sim.state('Metabolite').aminoAcidIndexs)', c.counts, [1 2], [1 2]);
            aas = sum(sum(aas, 3), 2);

            plot(axesHandle, aaComposition, aas, '.', 'MarkerSize', 20);
            line([0 1], [0 sum(aas)],'Parent', axesHandle, 'Color', 'r');
            xlim([min(aaComposition) max(aaComposition)]);
            ylim([min(aas) max(aas)]);
            xlabel(axesHandle, 'Expected Incorporation', 'fontSize', 16);
            ylabel(axesHandle, 'Simulated Counts', 'fontSize', 16);
        end
    end
end


% io_loadRFwaveform.m
% Jamie Near, McGill University 2014.
%
% USAGE:
% [RF_struct]=io_loadRFwaveform(filename,type,off_res);
%
% DESCRIPTION:
% Initialize an RF pulse structure to contain an
% RF Pulse waveform as well as its accompanying header
% information.  This function finds the time-bandwidth
% product (tbw) and the time-w1 product (tw1) of the pulse,
% and stores this information in the header fields of the
% output RF structure.
%
% INPUTS:
% filename  = filename of RF pulse waveform text file.  Can be in Siemens
%             format (.pta), Varian/Agilent format (.RF), Bruker format
%             (.inv, .ref or .exc) or a plain text file (.txt, with two,
%             three or four columns (amplitude, phase, time-step
%             (optional),and gradient strength (optional).  Filename can
%             also be the name of a two, three or four column matlab vector
%             specifing the phase, amplitude, time vectors (optionally) and
%             gradient (optionally) of an RF pulse waveform.  Note about
%             Units: RF amplitude waveform [arbitrary units], B1 intensity
%             [kHz],  phase waveform [degrees], time step vector [arbitrary
%             units],  gradient waveform [G/cm].
% type      = Excitation ('exc'), Refocusing ('ref') or Inversion ('inv').
%             Alternatively, 'type' can specify the exact flip angle [in
%             degrees] of the pulse.  This can be useful if you want to use
%             a pulse that does not have an exact flip angle of 90 or 180
%             degrees (Thanks to Eric Plitman for help with this feature).
% off_res   = set to "1" if the centre of the RF pulse is not at 0 Hz.
%             Optional. Default=0.
%
% OUTPUTS:
% RF_struct = RF pulse waveform in FID-A rf pulse structure format.

function RF_struct=io_loadRFwaveform(filename,type,off_res);

if nargin<3
    off_res=0;
end

if isnumeric(type)
    %If 'type' was specified to be exactly 90 degrees, set the type to 'exc';
    if type==90;
        type='exc';
    end

    %If 'type' was specified to be exactly 180 degrees, set the type to 'inv'
    %(functionally there is no real difference between 'ref' and 'inv', so I
    %just chose 'Inv';
    if type==180;
        type='inv';
    end
end

%Now read in the waveform:
if isstr(filename)
    if exist(filename)
        if filename(end-3:end)=='.pta'
            disp('Siemens format .pta RF pulse file detected!! Loading waveform now.');
            rf=io_readpta(filename);
        elseif filename(end-2:end)=='.RF'
            disp('Varian/Agilent format .RF RF pulse file detected!! Loading waveform now.');
            rf=io_readRF(filename);
        elseif filename(end-3:end)=='.inv'
            disp('Bruker format .inv RF pulse file detected!! Loading waveform now.');
            rf=io_readRFBruk(filename);
        elseif filename(end-3:end)=='.rfc'
            disp('Bruker format .pta RF pulse file detected!! Loading waveform now.');
            rf=io_readRFBruk(filename);
        elseif filename(end-3:end)=='.exc'
            disp('Bruker format .exc RF pulse file detected!! Loading waveform now.');
            rf=io_readRFBruk(filename);
        elseif filename(end-3:end)=='.txt'
            disp('Basic .txt format RF pulse file detected!! Loading waveform now.');
            rf=io_readRFtxt(filename);
        else
            error('ERROR:  RF Pulse file not recognized.  Aborting!');
        end
    else
        error('ERROR:  File not found!  Aborting!');
    end
else
    if ismatrix(filename) && ndims(filename)==2 && ( 2 <= size(filename,2) <= 4 )
        disp('Input is an RF waveform already in the matlab workspace.  Loading waveform now.');
        if size(filename,2)==2
            rf=filename;
            rf(:,3)=ones(length(filename(:,1)),1);
        elseif size(filename,2)==3 || size(filename,2)==4
            rf=filename;
        end
    end
end

Tp=0.005;  %assume a 5 ms rf pulse;

%Frequency shifting the pulse to zero if off_res is 1. To be shifted back
%afterwards - PT,2021
if off_res
    %Initializations
    tmp_w1=0.03;
    tmp_min=1;

    %Looping through until a min of <-0.98 (inv/ref) or <0.2 (exc) is reached
    switch type
        case 'exc'
            min_floor = 0.2;
        case {'inv','ref'}
            min_floor = -0.98;
    end
    while tmp_min > min_floor
        [mv,sc]=bes(rf,Tp*1000,'f',tmp_w1,-5,5,1000); %running bes with bare settings
        [tmp_min,tmp_min_pos]=min(mv(3,:)); %finding the min and min position
        freq_shift=sc(tmp_min_pos)*1000;%the freq shift (in Hz) of the pulse @ 5ms
        tmp_w1=tmp_w1+0.02;
    end

    %frequency shifting the pulse to 0 Hz
    N=size(rf,1);
    dt=Tp/N;
    t=[0:dt:Tp-dt];
    phaseRamp=t*-freq_shift*360;
    rf(:,1)=rf(:,1)+phaseRamp';
end

%Find out if the pulse is phase modulated.  If it is not, then we can
%determine the time-w1 product of the pulse quite simply.  If it is phase
%modulated (adiabatic, etc) then the determination of the time-w1 product
%will need to me more interactive.
a=(round(rf(:,1))==180)|(round(rf(:,1))==0);

if sum(a)<length(rf(:,1))
    isPhsMod=true;
else
    isPhsMod=false;
end

%If there are any phase discontinuities in the phase function that are
%equal to a 360 degree jump we can remove these.  This will make it easier
%for rf_resample to do it's job later on:
jumps=diff(rf(:,1));
jumpsAbs=(abs(jumps)>355 & abs(jumps)<365);  %Assume jumps within this range are exactly = 360 degrees.
jumpIndex=find(jumpsAbs);
for n=1:length(jumpIndex)
    rf(jumpIndex(n)+1:end,1)=rf(jumpIndex(n)+1:end,1)-(360*(jumps(jumpIndex(n))/abs(jumps(jumpIndex(n)))));
end

%scale amplitude function so that maximum value is 1:
rf(:,2)=rf(:,2)./max(rf(:,2));

if ~isPhsMod
    %The pulse is not phase modulated, so we can calculate the w1max:
    %find the B1 max of the pulse in [kHz]:
    if isstr(type)
        if type=='exc'
            flipCyc=0.25; %90 degrees is 0.25 cycles;
        elseif type=='ref'
            flipCyc=0.5;  %180 degress is 0.5 cycles;
        elseif type=='inv'
            flipCyc=0.5;  %180 degrees is 0.5 cycles;
        end
    elseif isnumeric(type) %assume that a flip angle (in degrees) was given
        flipCyc=type/360;
    end

    intRF=sum(rf(:,2).*((-2*(rf(:,1)>179))+1))/length(rf(:,2));

    if intRF~=0
        w1max=flipCyc/(intRF*Tp); %w1max is in [Hz]
    else
        w1max=0;
    end
    tw1=Tp*w1max;
else
    %The pulse is phase modulated, so we will need to run some test to find
    %out the w1max;  To do this, we can plot Mz as a function of w1 and
    %find the value of w1 that results in the desired flip angle.
    [mv,sc]=bes(rf,Tp*1000,'b',0,0,5,40000);
    plot(sc,mv(3,:));
    xlabel('w1 (kHz)');
    ylabel('mz');
    w1max=input('Input desired w1max in kHz (for 5.00 ms pulse):  ');
    w1max=w1max*1000; %convert w1max to [Hz]
    tw1=Tp*w1max;
end

%now it's time to find out the time-bandwidth product:
%First make a high resolution plot the pulse profile over a wide bandwidth:
[mv,sc]=bes(rf,Tp*1000,'f',w1max/1000,-5,5,100000);
if isstr(type)
    if type=='exc'
        index=find(abs(mv(1,:)+1i*mv(2,:))>0.5);
        bw=sc(index(end))-sc(index(1))
        %plot(sc(index),mv(3,index),'.-',sc,mv(3,:));
    elseif type=='ref'
        index=find(mv(3,:)<0);
        bw=sc(index(end))-sc(index(1));
        %plot(sc(index),mv(3,index),'.-',sc,mv(3,:));
    elseif type=='inv'
        index=find(mv(3,:)<0);
        bw=sc(index(end))-sc(index(1));
        %plot(sc(index),mv(3,index),'.-',sc,mv(3,:));
    end
elseif isnumeric(type)
    mz=cos(type); %Find out the Mz value immediately following the pulse.
    thr=(1+mz)/2;  %Find out the Mz value mid-way between 1 and the mz (half-max):
    index=find(mv(3,:)<thr);  %Find the indices of the corresponding "full width"
    bw=sc(index(end))-sc(index(1));  %Now find the bandwidth at that point ("Full width at half max").
end

%Now make a very high resolution plot the pulse profile over a narrower bandwidth:
[mv,sc]=bes(rf,Tp*1000,'f',w1max/1000,-bw,bw,100000);
if isstr(type)
    if type=='exc'
        index=find(abs(mv(1,:)+1i*mv(2,:))>0.5);
        bw=sc(index(end))-sc(index(1))
        %plot(sc(index),mv(3,index),'.-',sc,mv(3,:));
    elseif type=='ref'
        index=find(mv(3,:)<0);
        bw=sc(index(end))-sc(index(1));
        %plot(sc(index),mv(3,index),'.-',sc,mv(3,:));
    elseif type=='inv'
        index=find(mv(3,:)<0);
        bw=sc(index(end))-sc(index(1));
        %plot(sc(index),mv(3,index),'.-',sc,mv(3,:));
    end
elseif isnumeric(type)
    mz=cos(type); %Find out the Mz value immediately following the pulse.
    thr=(1+mz)/2;  %Find out the Mz value mid-way between 1 and the mz (half-max):
    index=find(mv(3,:)<thr);  %Find the indices of the corresponding "full width"
    bw=sc(index(end))-sc(index(1));  %Now find the bandwidth at that point ("Full width at half max").
end

%finally, try to estimate the centre point of the RF pulse 'rfCentre'.
%This is a number between 0 and 1 that indicates where the peak of the RF
%amplitude waveform occurs.  If rfCentre = 0.5, then the pulse is symmetric.
%If rfCentre<0.5 then the peak occurs near the beginning of the pulse (i.e.
%a max phase pulse).  If rfCentre >0.5, then the peak occurs near the end
%of the pulse (i.e. min phase pulse).
maxIndex=find(rf(:,2)==max(rf(:,2)));
rfCentre=mean(maxIndex)/length(rf(:,2));

%Now store the output structure;
RF_struct.waveform=rf;
RF_struct.type=type;
RF_struct.f0=0;
if size(rf,2)>3 && any(rf(:,4))
    RF_struct.tbw='N/A - gradient modulated pulse';
    RF_struct.isGM=true;
    RF_struct.tthk=bw*Tp; %This is the time x sliceThickness product for
                         %gradient modulated pulses.  It is in units [cm.s]
else
    RF_struct.tbw=bw*Tp*1000;
    RF_struct.isGM=false;
    RF_struct.tthk='N/A - frequency selective pulse';
end
RF_struct.tw1=tw1;
RF_struct.rfCentre=rfCentre;