function [cfg, artifact] = ft_artifact_eog(cfg, data)

% FT_ARTIFACT_EOG scans data segments of interest for EOG artifacts.
%
% Use as
%   [cfg, artifact] = ft_artifact_eog(cfg)
% with the configuration options
%   cfg.dataset    = string with the filename
% or
%   cfg.headerfile = string with the filename
%   cfg.datafile   = string with the filename
% and optionally
%   cfg.headerformat
%   cfg.dataformat
%
% Alternatively you can use it as
%   [cfg, artifact] = ft_artifact_eog(cfg, data)
% where the input data is a structure as obtained from FT_PREPROCESSING.
%
% In both cases the configuration should also contain
%   cfg.trl        = structure that defines the data segments of interest, see FT_DEFINETRIAL
%   cfg.continuous = 'yes' or 'no' whether the file contains continuous data
%
% Prior to artifact detection, the data is preprocessed (again) with the following
% configuration parameters, which are optimal for identifying EOG artifacts.
%   cfg.artfctdef.eog.bpfilter   = 'yes'
%   cfg.artfctdef.eog.bpfilttype = 'but'
%   cfg.artfctdef.eog.bpfreq     = [1 15]
%   cfg.artfctdef.eog.bpfiltord  = 4
%   cfg.artfctdef.eog.hilbert    = 'yes'
%
% Artifacts are identified by means of thresholding the z-transformed value
% of the preprocessed data.
%   cfg.artfctdef.eog.channel      = Nx1 cell-array with selection of channels, see FT_CHANNELSELECTION for details
%   cfg.artfctdef.eog.cutoff       = z-value at which to threshold (default = 4)
%   cfg.artfctdef.eog.trlpadding   = number in seconds (default = 0.5)
%   cfg.artfctdef.eog.fltpadding   = number in seconds (default = 0.1)
%   cfg.artfctdef.eog.artpadding   = number in seconds (default = 0.1)
%
% The output argument "artifact" is a Nx2 matrix comparable to the "trl" matrix of
% FT_DEFINETRIAL. The first column of which specifying the beginsamples of an
% artifact period, the second column contains the endsamples of the artifactperiods.
%
% To facilitate data-handling and distributed computing, you can use
%   cfg.inputfile   =  ...
% to read the input data from a *.mat file on disk. This mat files should contain
% only a single variable named 'data', corresponding to the input structure.
%
% See also FT_REJECTARTIFACT, FT_ARTIFACT_CLIP, FT_ARTIFACT_ECG, FT_ARTIFACT_EOG,
% FT_ARTIFACT_JUMP, FT_ARTIFACT_MUSCLE, FT_ARTIFACT_THRESHOLD, FT_ARTIFACT_ZVALUE

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

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

% do the general setup of the function
ft_defaults
ft_preamble init

% ft_preamble provenance is not needed because just a call to ft_artifact_zvalue
% ft_preamble loadvar data is not needed because ft_artifact_zvalue will do this

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

% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'renamed',    {'datatype', 'continuous'});
cfg = ft_checkconfig(cfg, 'renamedval', {'continuous', 'continuous', 'yes'});
cfg = ft_checkconfig(cfg, 'forbidden',  {'padding', 'pretim', 'psttim'});

% set default rejection parameters
cfg.artfctdef            = ft_getopt(cfg,               'artfctdef', []);
cfg.artfctdef.eog        = ft_getopt(cfg.artfctdef,     'eog',       []);
cfg.artfctdef.eog.method = ft_getopt(cfg.artfctdef.eog, 'method',    'zvalue');

if isfield(cfg.artfctdef.eog, 'artifact')
  fprintf('eog artifact detection has already been done, retaining artifacts\n');
  artifact = cfg.artfctdef.eog.artifact;
  return
end

if ~strcmp(cfg.artfctdef.eog.method, 'zvalue')
  ft_error('EOG artifact detection only works with method=''zvalue''');
end

% for backward compatibility
cfg.artfctdef.eog = ft_checkconfig(cfg.artfctdef.eog, 'renamed', {'sgn',     'channel'});
cfg.artfctdef.eog = ft_checkconfig(cfg.artfctdef.eog, 'renamed', {'passbnd', 'bpfreq'});
cfg.artfctdef.eog = ft_checkconfig(cfg.artfctdef.eog, 'renamed', {'padding', 'trlpadding'});

% settings for preprocessing
cfg.artfctdef.eog.bpfilter    = ft_getopt(cfg.artfctdef.eog, 'bpfilter',   'yes');
cfg.artfctdef.eog.bpfreq      = ft_getopt(cfg.artfctdef.eog, 'bpfreq',     [1 15]);
cfg.artfctdef.eog.bpfiltord   = ft_getopt(cfg.artfctdef.eog, 'bpfiltord',   4);
cfg.artfctdef.eog.bpfilttype  = ft_getopt(cfg.artfctdef.eog, 'bpfilttype', 'but');
cfg.artfctdef.eog.hilbert     = ft_getopt(cfg.artfctdef.eog, 'hilbert',    'yes');

% settings for the zvalue subfunction
cfg.artfctdef.eog.channel    = ft_getopt(cfg.artfctdef.eog, 'channel',    'EOG');
cfg.artfctdef.eog.cutoff     = ft_getopt(cfg.artfctdef.eog, 'cutoff',     4);
cfg.artfctdef.eog.trlpadding = ft_getopt(cfg.artfctdef.eog, 'trlpadding', 0.5);
cfg.artfctdef.eog.fltpadding = ft_getopt(cfg.artfctdef.eog, 'fltpadding', 0.1);
cfg.artfctdef.eog.artpadding = ft_getopt(cfg.artfctdef.eog, 'artpadding', 0.1);

% construct a temporary configuration that can be passed onto FT_ARTIFACT_ZVALUE
tmpcfg                  = cfg;
tmpcfg.artfctdef.zvalue = cfg.artfctdef.eog;
tmpcfg.artfctdef        = rmfield(tmpcfg.artfctdef, 'eog');

% call the zvalue artifact detection function, where the data is either passed
% into the function by the user or read from file with cfg.inputfile
hasdata = exist('data', 'var');
if ~hasdata
  tmpcfg             = ft_checkconfig(tmpcfg, 'dataset2files', 'yes');
  tmpcfg             = ft_checkconfig(tmpcfg, 'required', {'headerfile', 'datafile'});
  [tmpcfg, artifact] = ft_artifact_zvalue(tmpcfg);
else
  tmpcfg.artfctdef.zvalue.trlpadding = 0;
  tmpcfg.artfctdef.zvalue.fltpadding = 0;
  ft_warning('trlpadding and fltpadding are set to zero to avoid filter problems with NaN, see bug3193 for details');
  [tmpcfg, artifact] = ft_artifact_zvalue(tmpcfg, data);
end
cfg.artfctdef.eog  = tmpcfg.artfctdef.zvalue;


function [xRgb,yRgb,zRgb,xform,xAxes,yAxes,zAxes] = dtiGetCurSlices(handles)
% Determine current slices and return them (and potentially a lot of other info)
%
%  [xRgb,yRgb,zRgb,xform,xAxes,yAxes,zAxes] = dtiGetCurSlices(handles)
%
% The potential background and overlay images are stored in the background
% slots, dtiH.bg().  This is a structure in which the image is always
% between 0 and 1, but the values of the original are stored in the
% .bg(1).minVal and .bg(1).maxVal slots.
%
% Note: mmPerVox and xform are for the current space that the slices are
% in, NOT the original image space. This is typically 1mm isotropic, ac-pc
% aligned space.
%
% HISTORY:
%  ?????: Dougherty & Wandell wrote it.
%  2005.06.09 RFD: minor code optimizations to make refreshes closer to 'realtime'.
%  2006.11.08 RFD: default interpolation method is now nearest-neighbor
%    rather than trilinear. We should make this a use option.
%  2006.11.10 RFD: interpolation method is now an option.
%  2009.06.16 RFD: added 0.5 offset to xAxes,yAxes,zAxes to make the axes
%    tick marks line up with the voxel centers.
%
% Brian and Bob (c) Stanford VISTA Team 2005

%% Get key parameters from the GUI
curPosition   = dtiGet(handles,'acpcpos');
overlayThresh = dtiGet(handles,'cur overlay thresh');
overlayAlpha  = dtiGet(handles,'cur overlay alpha');
mmPerVox      = dtiGet(handles,'render mm');
bb            = dtiGet(handles,'default Bounding Box');
anat          = dtiGet(handles,'current anatomy data');      % anat      = handles.bg(curBgNum).img;
anatXform     = dtiGet(handles,'bg img2acpc xform');
dispRange     = dtiGet(handles,'display range');
% handles.bg(curBgNum).displayValueRange;
% dispRange     = dtiGet(handles,handles.bg(curBgNum).displayValueRange;

%% Here we get the anatomical slices transformed into image space
[zRgb,x,y,z] = dtiGetSlice(anatXform, anat, 3, curPosition(3), bb, handles.interpType, mmPerVox, dispRange);
zAxes = [x(1), x(end); y(1), y(end)]+0.5;
[yRgb,x,y,z] = dtiGetSlice(anatXform, anat, 2, curPosition(2), bb, handles.interpType, mmPerVox, dispRange);
yAxes = [x(1), x(end); z(1), z(end)]+0.5;
[xRgb,x,y,z] = dtiGetSlice(anatXform, anat, 1, curPosition(1), bb, handles.interpType, mmPerVox, dispRange);
xAxes = [y(1), y(end); z(1), z(end)]+0.5;
%  mrvNewGraphWin; imagesc(flipud(zRgb)); axis image; colormap(gray)

if(ndims(anat) == 3)
    % Turn the images into RGB
    xRgb = repmat(xRgb,[1 1 3]);
    yRgb = repmat(yRgb,[1 1 3]);
    zRgb = repmat(zRgb,[1 1 3]);
elseif(ndims(anat)==4)
    % 3d views can only handle grayscale anatomy, so we'll just use a
    % luminance map from the RGB data.
    % We are not sure this works all that well. - FP
    anat = mean(anat,4);
end
%  mrvNewGraphWin; image(zRgb); axis image; colormap(gray)

% Force the background image to be between 0 and 1
xRgb(xRgb<0) = 0; xRgb(xRgb>1) = 1;
yRgb(yRgb<0) = 0; yRgb(yRgb>1) = 1;
zRgb(zRgb<0) = 0; zRgb(zRgb>1) = 1;

if(overlayAlpha>0)
    %[overlayImg, oMmPerVoxel, oXform] = dtiGetCurAnat(handles,1);
    n          = dtiGet(handles,'overlay number');
    overlayImg = dtiGet(handles,'overlay image',n);
    oXform     = dtiGet(handles,'overlay xform',n);
    dispRange  = dtiGet(handles,'overlay display range',n);

    % Get the color map and add a black where we place NaNs.
    cmap = handles.cmaps(get(handles.popupOverlayCmap,'Value')).rgb;
    cmap = [cmap; 0 0 0];

    interpType = dtiGet(handles,'interp type');

    if(ndims(overlayImg)==3)
        % Get the transformed overlay slices and combine them with the
        % background slices.
        % The disp range we send in is used to return an image between 0
        % and 1.  We need to remember the display range, somehow. The
        % spatial interpolation type is set somewhere.  Not sure where. It
        % is a string, by default n, which I think means nearest neighbor.
        ox = dtiGetSlice(oXform, overlayImg, 1, curPosition(1), bb, interpType, mmPerVox, dispRange);
        oy = dtiGetSlice(oXform, overlayImg, 2, curPosition(2), bb, interpType, mmPerVox, dispRange);
        oz = dtiGetSlice(oXform, overlayImg, 3, curPosition(3), bb, interpType, mmPerVox, dispRange);

        % Find the locations above the threshold
        mx = ox > overlayThresh;
        my = oy > overlayThresh;
        mz = oz > overlayThresh;

        % Here we merge the overlay image (which is colorful) with the
        % background image created above, which is gray scale.
        % We do this by taking every value in the overlay image and
        % assigning it an index into the color map, cmap.
        %
        % The cmap rows are 1:256.  We require the overlay to be between 0
        % and 1. That way the entires of the overlay, e.g., oz, map into a
        % row of cmap.  So, oz*255 + 1 is a an integer between 1 and 256.

        % We set the NaNs to the 257th entry of the table, which is 0 0 0.
        oz(isnan(oz)) = (256 / 255);
        oy(isnan(oy)) = (256 / 255);
        ox(isnan(ox)) = (256 / 255);

        % This makes a list of cmap values that is a vector, and we reshape
        % it to an RGB image.
        oz = reshape(cmap(round(oz*255+1),:), [size(mz) 3]);
        oy = reshape(cmap(round(oy*255+1),:), [size(my) 3]);
        ox = reshape(cmap(round(ox*255+1),:), [size(mx) 3]);

        % Now, we mix the background gray image with the overlay image at
        % the locations specified by mx.
        mx = repmat(mx,[1,1,3]);
        my = repmat(my,[1,1,3]);
        mz = repmat(mz,[1,1,3]);
        xRgb(mx) = (1-overlayAlpha).*xRgb(mx) + overlayAlpha.*ox(mx);
        yRgb(my) = (1-overlayAlpha).*yRgb(my) + overlayAlpha.*oy(my);
        zRgb(mz) = (1-overlayAlpha).*zRgb(mz) + overlayAlpha.*oz(mz);

    elseif(ndims(overlayImg)==4)
        % If there is a 4th dim, then this overlay is an RGB map.
        % dtiGetSlice will return an XxYx3 RGB slice.
        % BROKEN! _ See Franco P
        ox = dtiGetSlice(oXform, overlayImg, 1, curPosition(1), bb, interpType, mmPerVox, dispRange);
        oy = dtiGetSlice(oXform, overlayImg, 2, curPosition(2), bb, interpType, mmPerVox, dispRange);
        oz = dtiGetSlice(oXform, overlayImg, 3, curPosition(3), bb, interpType, mmPerVox, dispRange);
        % We'll treat these differnt from the intensity image overlay case.
        % Rather than use a fixed transparency everywhere, we'll set the
        % transparency per-voxel by modulating it with the luminaince
        % component.
        if(overlayAlpha>1)
            % alpha>1 applies a gamma to the transparency map
            g = 1/overlayAlpha;
            overlayAlpha = 1;
        else
            g = 1;
        end
        lumz = repmat(mean(oz,3).^g,[1,1,3]);
        lumy = repmat(mean(oy,3).^g,[1,1,3]);
        lumx = repmat(mean(ox,3).^g,[1,1,3]);
        mz = lumz>overlayThresh;
        my = lumy>overlayThresh;
        mx = lumx>overlayThresh;
        xRgb(mx) = (1-lumx(mx).*overlayAlpha).*xRgb(mx) + lumx(mx).*overlayAlpha.*ox(mx);
        yRgb(my) = (1-lumy(my).*overlayAlpha).*yRgb(my) + lumy(my).*overlayAlpha.*oy(my);
        zRgb(mz) = (1-lumz(mz).*overlayAlpha).*zRgb(mz) + lumz(mz).*overlayAlpha.*oz(mz);
    end
end

%% Set up the return transform from acpc

% The xform that we return is from acpc to the space that we put the slices
% in, which is ac-pc aligned on the specific sample grid (mmPerVox,
% typically 1mm isotropic) and filling the bounding box. The xform is
% returned and used in say, dtiRefreshFigure.
xform = [[diag(mmPerVox) bb(1,:)'-1];[0 0 0 1]];

return;


classdef UnfittedMeshPlotter < handle

    properties (Access = private)
        uMesh
        faceColor
        edgeAlpha
        faceAlpha
    end

    methods (Access = public)

        function obj = UnfittedMeshPlotter(cParams)
            obj.init(cParams)
        end

        function plotDomainInColor(obj,color)
            obj.faceColor = color;
            obj.edgeAlpha = 0;
            obj.faceAlpha = 1;
            obj.plotInner();
            obj.plotInnerCut();
        end

        function plotDomain(obj)
            obj.faceColor = [];
            obj.edgeAlpha = [];
            obj.faceAlpha = [];
            obj.plotAll();
            obj.addLighting();
        end

        function plotBoundary(obj)
            obj.plotBackground();
            obj.plotBoundaryCutMesh();
            obj.plotUnfittedBoundaryMesh();
            obj.addLighting();
        end

        function plotAll(obj)
            obj.plotBackground();
            obj.plotInner();
            obj.plotInnerCut();
            obj.plotBoundaryCutMesh();
            obj.plotUnfittedBoundaryMesh();
        end

    end

    methods (Access = private)

        function init(obj,cParams)
            obj.uMesh = cParams.uMesh;
        end

        function plotBackground(obj)
            s.mesh = obj.uMesh.backgroundMesh;
            s.isBackground = true;
            obj.plotMesh(s);
        end

        function plotInner(obj)
            m = obj.uMesh.innerMesh;
            obj.plotSubMesh(m);
        end

        function plotInnerCut(obj)
            m = obj.uMesh.innerCutMesh;
            obj.plotSubMesh(m);
        end

        function plotBoundaryCutMesh(obj)
            m = obj.uMesh.boundaryCutMesh;
            obj.plotSubMesh(m);
        end

        function plotUnfittedBoundaryMesh(obj)
            uB = obj.uMesh.unfittedBoundaryMesh;
            if ~isempty(uB)
                uBoundaryMeshes = uB.getActiveMesh();
                for imesh = 1:numel(uBoundaryMeshes)
                    uM = uBoundaryMeshes{imesh};
                    uM.plotAll();
                end
            end
        end

        function plotSubMesh(obj,uM)
            if ~isempty(obj.faceColor)
                s.faceColor = obj.faceColor;
            end
            if ~isempty(obj.edgeAlpha)
                s.edgeAlpha = obj.edgeAlpha;
            end
            if ~isempty(obj.faceAlpha)
                s.faceAlpha = obj.faceAlpha;
            end
            if ~isempty(uM)
                s.mesh = uM.mesh;
                obj.plotMesh(s);
            end
        end

        function addLighting(obj)
            delete(findall(gcf,'Type','light'))
            if ~isempty(obj.uMesh.innerMesh)
                if obj.uMesh.innerMesh.mesh.ndim == 3
                    l = lightangle(120,30);
                    material metal
                    l = lightangle(240,30);
                    material metal
                    l = lightangle(90,180);
                    material metal
                    l = lightangle(90,0);
                    material metal
                    view([1 1 1])
                end
            end
        end

    end

    methods (Access = private, Static)

        function plotMesh(s)
            s = SettingsMeshPlotter(s);
            if ~isempty(s.mesh)
                mP = MeshPlotter(s);
                mP.plot();
            end
        end
    end

end


function OBJ = objFVN(FV, N)
%Convert Matlab faces/vertices/normals to a wavefront OBJ format
%
%  This appears to work for a cortical mesh.
%  We have also made it work for a fiber produced by AFQ.
%
% Example
%  OBJ = objFVN(FV,N);
%  objWrite(OBJ,fname);
%
% LMP/BW Vistasoft Team, 2015

%% Check FV and Normals

%% Figure out the general things to do here

% Need to understand more about the OBJ file format to make this work
% better.

% Make material structures.
% Ka is the ambient illuminant
% Kd is the diffuse reflectance
% Ks is the specular reflectance
% I don't know what the values in illum (presumably illuminant) and Ns (not
% sure what that is) refer to.
material(1).type='newmtl';
material(1).data='skin';
material(2).type='Ka';
material(2).data=[0.8 0.4 0.4];
material(3).type='Kd';
material(3).data=[0.8 0.4 0.4];
material(4).type='Ks';
material(4).data=[1 1 1];
material(5).type='illum';
material(5).data = 2;
material(6).type = 'Ns';
material(6).data = 27;

%% Make alias wavefront object structure

% This is the OBJ data structure that will be written out by the routine
% objWrite
clear OBJ
OBJ.material = material;

% Vertices and normals
OBJ.vertices = FV.vertices;
OBJ.vertices_normal = N;

% No idea what these control.  They don't seem aligned with the materials
% above.  Maybe they should be and then something good would happen.
OBJ.objects(1).type='g';
OBJ.objects(1).data='skin';

OBJ.objects(2).type='usemtl';
OBJ.objects(2).data='skin';

OBJ.objects(3).type='f';
OBJ.objects(3).data.vertices=  FV.faces;
OBJ.objects(3).data.normal  =  FV.faces;

end


function disp(F)
%DISP   Display a DISKFUN to the command line.
%
% See also DISKFUN/DISPLAY.

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

loose = strcmp( get(0, 'FormatSpacing'), 'loose' );

% Get display style and remove trivial empty DISKFUN case.
if ( isempty(F) )
    fprintf('    empty diskfun\n')
    if ( loose )
        fprintf('\n');
    end
    return
end

% Get information that we want to display:
len = length(F);                          % Numerical rank
vscl = vscale(F);                         % vertical scale

% Display the information:
disp('     diskfun object ')
fprintf('       domain        rank    vertical scale\n');
fprintf('      unit disk   %6i          %3.2g\n', len, vscl);

if ( loose )
    fprintf('\n');
end

end


function varargout = loadBiGGModel( model_ids, format, multichoice)
% Reads from url http://bigg.ucsd.edu/api/v2/models
%
% USAGE:
%
%    model = listBiGGModel(model_ids)
%
% OPTIONAL INPUTS:
%
%    model_ids:     The BiGG ID(s) of the model(s). If no ID is provided (either empty or no arguments) a gui
%                   will ask for the model to load.
%    format:        The format from Bigg to load. Either 'sbml' or ('mat').
%                   Default('mat')
%    multichoice:   Whether multiple models can be loaded if no ids are
%                   given. (Default: true)
% OUTPUT:
%    varargout:     The models in the order of the selected ids. If
%                   multiple models are selected in the dialog, a struct
%                   is provided with the models being fields of that
%                   struct.
%
% EXAMPLES:
%
%    1) Load a specific model from the mat file of the BiGG database
%           model = loadBiGGModel('e_coli_core');
%
%    2) Load several models from the BiGG database at once
%           [model1, model2, ... = loadBiGGModel({'bigg_id1','bigg_id2',...});
%
%    3) Load model from the BiGG database sbml files
%           model = loadBiGGModel('iJR904','sbml');
%
%
% .. Authors:
%       - Thomas Pfau Sep 2017

BIGG_FolderName = tempname;
mkdir(BIGG_FolderName);
finishup = onCleanup(@() rmdir(BIGG_FolderName,'s'));

if ~exist('format','var')
    format = 'mat';
end

if ~exist('multichoice','var')
    multichoice = true;
end
if multichoice
    selectionMode = 'multichoice';
else
    selectionMode = 'single';
end

if strcmp(format,'sbml')
    format = 'xml';
end

%Do not create a struct for multiple ids
structout = false;

modelfiles = {};

modelOptions = webread('http://bigg.ucsd.edu/api/v2/models');
modelOptions = modelOptions.results;
modelIDs = {modelOptions.bigg_id}';

if nargin == 0 || isempty(model_ids)
    orgNames = cellfun(@(x,y) strcat(x,' [',y,']'), {modelOptions.organism}, {modelOptions.bigg_id}, 'UniformOutput',0)' ;
    [sortedNames, order] = sort(orgNames);
    maxNameSize = max(cellfun(@numel,orgNames));

    if usejava('desktop')
        [s,v] = listdlg('Name','Model Selection','PromptString','Select Model(s) to download','SelectionMode',selectionMode, ...
                    'OKString','Load', 'ListString', sortedNames, 'ListSize', [maxNameSize*7,160] );
    else
        for i = 1:numel(sortedNames)
            fprintf('%i: %s\n',i,sortedNames{i});
        end
        s = input(['Please select a model (e.g. type 3 for ' sortedNames{3} '):'],'s');
        s = str2num(s);
    end

    model_ids = modelIDs(order(s));
    if numel(model_ids) > 1
        structout = true;
    end
else
    if ~iscell(model_ids)
        model_ids = {model_ids};
    end

    present = ismember(model_ids,modelIDs);
    if any(~present)
        nonpresent = model_ids(~present);
        model_ids = model_ids(present);
        fprintf('Could not find the following model ids in BiGG:\n');
        for i = 1:numel(nonpresent)
            fprintf('%s\n',nonpresent{i});
        end
    end
end

%If nothing is selected return nothing.
if numel(model_ids) == 0
       warning('No model selected.')
       varargout = {};
       return
end

varargout = cell(numel(model_ids),1);
for i = 1:numel(model_ids)
    filename = [BIGG_FolderName filesep model_ids{i} '.' format];
    url = ['http://bigg.ucsd.edu/static/models/' model_ids{i} '.' format];
    websave(filename,url);
    varargout{i} = readCbModel(filename);
end

if structout
    outstruct = struct();
    for i = 1:numel(model_ids)
        fieldID = regexprep(model_ids{i},'[^a-zA-Z0-9_]','');
        if isempty(regexp(fieldID,'^[a-zA-Z]'))
            fieldID = ['M_' fieldID];
        end
        outstruct.(fieldID) = varargout{i};
    end
    varargout = {outstruct};
end


function hfig = tightfig(hfig)
% tightfig: Alters a figure so that it has the minimum size necessary to
% enclose all axes in the figure without excess space around them.
%
% Note that tightfig will expand the figure to completely encompass all
% axes if necessary. If any 3D axes are present which have been zoomed,
% tightfig will produce an error, as these cannot easily be dealt with.
%
% Input
%
% hfig - handle to figure, if not supplied, the current figure will be used
%   instead.
%
%

    if nargin == 0
        hfig = gcf;
    end

    % There can be an issue with tightfig when the user has been modifying
    % the contnts manually, the code below is an attempt to resolve this,
    % but it has not yet been satisfactorily fixed
%     origwindowstyle = get(hfig, 'WindowStyle');
    set(hfig, 'WindowStyle', 'normal');

    % 1 point is 0.3528 mm for future use

    % get all the axes handles note this will also fetch legends and
    % colorbars as well
    hax = findall(hfig, 'type', 'axes');
    % TODO: fix for modern matlab, colorbars and legends are no longer axes
    hcbar = findall(hfig, 'type', 'colorbar');
    hleg = findall(hfig, 'type', 'legend');

    % get the original axes units, so we can change and reset these again
    % later
    origaxunits = get(hax, 'Units');

    % change the axes units to cm
    set(hax, 'Units', 'centimeters');

    pos = [];
    ti = [];

    % get various position parameters of the axes
    if numel(hax) > 1
%         fsize = cell2mat(get(hax, 'FontSize'));
        ti = cell2mat(get(hax,'TightInset'));
        pos = [pos; cell2mat(get(hax, 'Position')) ];
    else
%         fsize = get(hax, 'FontSize');
        ti = get(hax,'TightInset');
        pos = [pos; get(hax, 'Position') ];
    end

    if ~isempty (hcbar)

        set(hcbar, 'Units', 'centimeters');

        % colorbars do not have tightinset property
        for cbind = 1:numel(hcbar)
            %         fsize = cell2mat(get(hax, 'FontSize'));
            [cbarpos, cbarti] = colorbarpos (hcbar);

            pos = [pos; cbarpos];
            ti = [ti; cbarti];
        end
    end

    if ~isempty (hleg)

        set(hleg, 'Units', 'centimeters');

        % legends do not have tightinset property
        if numel(hleg) > 1
            %         fsize = cell2mat(get(hax, 'FontSize'));
            pos = [pos; cell2mat(get(hleg, 'Position')) ];
        else
            %         fsize = get(hax, 'FontSize');
            pos = [pos; get(hleg, 'Position') ];
        end
        ti = [ti; repmat([0,0,0,0], numel(hleg), 1); ];
    end

    % ensure very tiny border so outer box always appears
    ti(ti < 0.1) = 0.15;

    % we will check if any 3d axes are zoomed, to do this we will check if
    % they are not being viewed in any of the 2d directions
    views2d = [0,90; 0,0; 90,0];

    for i = 1:numel(hax)

        set(hax(i), 'LooseInset', ti(i,:));
%         set(hax(i), 'LooseInset', [0,0,0,0]);

        % get the current viewing angle of the axes
        [az,el] = view(hax(i));

        % determine if the axes are zoomed
        iszoomed = strcmp(get(hax(i), 'CameraViewAngleMode'), 'manual');

        % test if we are viewing in 2d mode or a 3d view
        is2d = all(bsxfun(@eq, [az,el], views2d), 2);

        if iszoomed && ~any(is2d)
           error('TIGHTFIG:haszoomed3d', 'Cannot make figures containing zoomed 3D axes tight.')
        end

    end

    % we will move all the axes down and to the left by the amount
    % necessary to just show the bottom and leftmost axes and labels etc.
    moveleft = min(pos(:,1) - ti(:,1));

    movedown = min(pos(:,2) - ti(:,2));

    % we will also alter the height and width of the figure to just
    % encompass the topmost and rightmost axes and lables
    figwidth = max(pos(:,1) + pos(:,3) + ti(:,3) - moveleft);

    figheight = max(pos(:,2) + pos(:,4) + ti(:,4) - movedown);

    % move all the axes
    for i = 1:numel(hax)

        set(hax(i), 'Position', [pos(i,1:2) - [moveleft,movedown], pos(i,3:4)]);

    end

    for i = 1:numel(hcbar)

        set(hcbar(i), 'Position', [pos(i+numel(hax),1:2) - [moveleft,movedown], pos(i+numel(hax),3:4)]);

    end

    for i = 1:numel(hleg)

        set(hleg(i), 'Position', [pos(i+numel(hax)+numel(hcbar),1:2) - [moveleft,movedown], pos(i+numel(hax)+numel(hcbar),3:4)]);

    end

    origfigunits = get(hfig, 'Units');

    set(hfig, 'Units', 'centimeters');

    % change the size of the figure
    figpos = get(hfig, 'Position');

    set(hfig, 'Position', [figpos(1), figpos(2), figwidth, figheight]);

    % change the size of the paper
    set(hfig, 'PaperUnits','centimeters');
    set(hfig, 'PaperSize', [figwidth, figheight]);
    set(hfig, 'PaperPositionMode', 'manual');
    set(hfig, 'PaperPosition',[0 0 figwidth figheight]);

    % reset to original units for axes and figure
    if ~iscell(origaxunits)
        origaxunits = {origaxunits};
    end

    for i = 1:numel(hax)
        set(hax(i), 'Units', origaxunits{i});
    end

    set(hfig, 'Units', origfigunits);

%      set(hfig, 'WindowStyle', origwindowstyle);

end

function [pos, ti] = colorbarpos (hcbar)

    % 1 point is 0.3528 mm

    pos = hcbar.Position;
    ti = [0,0,0,0];

    if ~isempty (strfind (hcbar.Location, 'outside'))

        if strcmp (hcbar.AxisLocation, 'out')

            tlabels = hcbar.TickLabels;

            fsize = hcbar.FontSize;

            switch hcbar.Location

                case 'northoutside'

                    % make exta space a little more than the font size/height
                    ticklablespace_cm = 1.1 * (0.3528/10) * fsize;

                    ti(4) = ti(4) + ticklablespace_cm;

                case 'eastoutside'

                    maxlabellen = max ( cellfun (@numel, tlabels, 'UniformOutput', true) );

                    % 0.62 factor is arbitrary and added because we don't
                    % know the width of every character in the label, the
                    % fsize refers to the height of the font
                    ticklablespace_cm = (0.3528/10) * fsize * maxlabellen * 0.62;

                    ti(3) = ti(3) + ticklablespace_cm;

                case 'southoutside'

                    % make exta space a little more than the font size/height
                    ticklablespace_cm = 1.1 * (0.3528/10) * fsize;

                    ti(2) = ti(2) + ticklablespace_cm;

                case 'westoutside'

                    maxlabellen = max ( cellfun (@numel, tlabels, 'UniformOutput', true) );

                    % 0.62 factor is arbitrary and added because we don't
                    % know the width of every character in the label, the
                    % fsize refers to the height of the font
                    ticklablespace_cm = (0.3528/10) * fsize * maxlabellen * 0.62;

                    ti(1) = ti(1) + ticklablespace_cm;

            end

        end

    end

end


function [s,r] = medit(varargin)
  % MEDIT wrapper to launch medit to visualize a given tetmesh
  %
  % [s,r] = medit(V,T,F)
  % [s,r] = medit(V,T,F,'ParameterName',ParameterValue)
  %
  % Inputs:
  %   V  #V by 3 list of vertex positions
  %   T  #T by 4|5 list of tetrahedron findices (additional column is color
  %     index)
  %   F  #F by 3|4 list of face indices (additional column is color index)
  %   Optional:
  %     'Wait'  followed by boolean whether to wait for completion {true}
  %     'Data'  followed by #V|#F+#T data values {[]}
  %     'Subplot' follwed by [x y i] defining subplot window (see subplot)
  %     'Edges' follwed by E list of edges to write along with mesh (see
  %       writeMESH)
  %     'Palette'  followed by list (up to 5) iso values
  % Outputs:
  %   s,r  result of system call
  %
  % Example:
  %   % tet mesh in (V,T), per-tet data in D
  %   [F,J] = boundary_faces(T);
  %   % Display data at tets and also on boundary facets
  %   medit(V,T,F,'Data',[D(J);D])
  %
  % See also: tetramesh
  %

  V = varargin{1};
  T = varargin{2};
  if nargin<3
    F = [];
  else
    F = varargin{3};
  end
  wait = true;
  D = [];
  E = [];
  subp = [];
  palette = [];

  ii = 4;
  while(ii<=nargin)
    switch varargin{ii}
    case 'Wait'
      ii = ii + 1;
      assert(ii<=nargin);
      wait = varargin{ii};
    case 'Data'
      ii = ii + 1;
      assert(ii<=nargin);
      D = varargin{ii};
    case 'Edges'
      ii = ii + 1;
      assert(ii<=nargin);
      E = varargin{ii};
    case 'Subplot'
      ii = ii + 1;
      assert(ii<=nargin);
      subp = varargin{ii};
    case 'Palette'
      ii = ii + 1;
      assert(ii<=nargin);
      palette = varargin{ii};
    otherwise
      error(['Unsupported parameter: ' varargin{ii}]);
    end
    ii = ii+1;
  end

  assert(numel(palette)<=5);

  if ~exist('wait','var')
    wait = true;
  end

  % Change these paths accordingly
  MEDIT_PATH = path_to_medit;
  TEMP_MESH_FILE  = '/var/tmp/temp.mesh';
  TEMP_MEDIT_FILE = '/var/tmp/temp.medit';
  TEMP_BB_FILE = '/var/tmp/temp.bb';

  % write default medit options
  f = fopen(TEMP_MEDIT_FILE,'w');
  fprintf(f, [...
    'BackgroundColor 1 1 1\n' ...
    'LineColor 0 0 0\n']);
  if size(F,2) > 3 || size(T,2) > 4
    fprintf(f,['NbMaterials\n']);
    %% collect indices
    %I = [F(:,3:end);T(:,4:end)];
    %count = max(I) - min(I);
    %fprintf(f,'%d\n',count);
    fprintf(f,'RenderMode colorshadinglines\n');
  else
    fprintf(f,'RenderMode shading + lines\n');
  end

  if isempty(subp)
    fprintf(f,'WindowSize 1024 800\n');
  else
    ss = get(0,'ScreenSize');
    w = ss(3)/subp(2);
    h = ss(4)/subp(1);
    [i j] = ind2sub(subp(1:2),subp(3));
    j = (j-1)*w;
    i = (i-1)*h;
    fprintf(f,'WindowSize %d %d\n',round(w),round(h));
    fprintf(f,'WindowPosition %d %d\n',round(j),round(i));
  end

  if ~isempty(palette)
    fprintf(f,'Palette');
    fprintf(f,' %g',palette(:));
    fprintf(f,'\n');
  end

  fclose(f);

  % write bb file
  if isempty(D)
    if exist(TEMP_BB_FILE,'file')
      delete(TEMP_BB_FILE);
    end
  else
    switch size(D,1)
    case size(F,1)
      warning('Appending junk for tet data since medit needs it.');
      D = [D;mean(D)*ones(size(T,1),1)];
      type = 1;
    case size(T,1)
      if size(F,1) == 0
        allF = [ ...
          T(:,2) T(:,4) T(:,3); ...
          T(:,1) T(:,3) T(:,4); ...
          T(:,1) T(:,4) T(:,2); ...
          T(:,1) T(:,2) T(:,3); ...
          ];
        [I,C] = on_boundary(T);
        DF = repmat(D,4,1);
        F = allF(C(:),:);
        DF = DF(C(:),:);
        D = [DF;D];
      else
        warning('Appending junk for face data since medit needs it.');
        D = [mean(D)*ones(size(F,1),1);D];
      end
      type = 1;
    case size(V,1)
      type = 2;
    case size(F,1)+size(T,1)
      type = 1;
    otherwise
      error('size(Data,1) %d should be == size(V,1) (%d) or size(T,1) + size(F,1) (%d + %d = %d)',size(D,1),size(V,1),size(T,1),size(F,1),size(T,1)+size(F,1));
    end
    writeBB(TEMP_BB_FILE,D,type);
  end

  % write temporary mesh
  writeMESH(TEMP_MESH_FILE,V,T,F,E);

  command = [MEDIT_PATH ' ' TEMP_MESH_FILE ' ' TEMP_MEDIT_FILE];
  command
  if ~wait
    command = [command ' &'];
  end
  [s,r] = system(command);
  if(s ~= 0)
      error(r);
  end
end


function [success,msg] = ConvertCtrax2JAABA(varargin)

success = false;
msg = {};

[inmoviefile,intrxfile,annfile,...
  expdir,moviefilestr,trxfilestr,perframedirstr,...
  arenatype,arenacenterx,arenacentery,...
  arenaradius,arenawidth,arenaheight,roi2,...
  pxpermm,fps,overridefps,overridearena,...
  dosoftlink,...
  dofliplr,doflipud,dotransposeimage,...
  inperframedir,frameinterval] = myparse(varargin,...
  'inmoviefile','','intrxfile','','annfile','',...
  'expdir','','moviefilestr','movie.ufmf','trxfilestr','trx.mat','perframedirstr','perframe',...
  'arenatype','None','arenacenterx',0,'arenacentery',0,...
  'arenaradius',123,'arenawidth',123,'arenaheight',123,'roi2',[],...
  'pxpermm',1,'fps',30,...
  'overridefps',false,'overridearena',false,...
  'dosoftlink',false,...
  'fliplr',false,...
  'flipud',false,...
  'dotransposeimage',false,...
  'inperframedir','',...
  'frameinterval',[1,inf]);

% note that if flipping, transposing trajectories and input arena is given,
% the input arena is NOT flipped

wingunits = struct(...
  'nwingsdetected',parseunits('unit'),...
  'wing_areal',parseunits('px^2'),...
  'wing_arear',parseunits('px^2'),...
  'wing_trough_angle',parseunits('rad'));

%% check that required inputs are given
ismoviefile = ~isempty(inmoviefile);
if isempty(intrxfile),
  msg = 'Input trx mat file is empty';
  return;
end
% if isempty(annfile),
%   msg = 'Input ann file is empty';
%   return;
% end

% output file locations
if ismoviefile,
  moviefile = fullfile(expdir,moviefilestr);
else
  moviefile = '';
end
trxfile = fullfile(expdir,trxfilestr);
perframedir = fullfile(expdir,perframedirstr);

%% load in the trx
try
  [trx,~,success1,timestamps] = load_tracks(intrxfile,moviefile,...
    'dosave',false,'annname',annfile,'verbose',false);
  if ~success1,
    msg = sprintf('Could not load trx from file %s',intrxfile);
    return;
  end
catch ME,
  msg = sprintf('Could not load trx from file %s: %s',intrxfile,getReport(ME));
  return;
end

if all(isnan(timestamps)),
  timestamps = [];
end

iswings = isfield(trx,'xwingl');

if iswings,
  if isempty(inperframedir),
    msg = 'Per-frame directory containing wing data is not set';
    return;
  end
  fns = fieldnames(wingunits);
  for i = 1:numel(fns),
    fn = fns{i};
    filename = fullfile(inperframedir,[fn,'.mat']);
    if ~exist(filename,'file'),
      msg = sprintf('Wing feature file %s does not exist.',filename);
      return;
    end
  end
end

if dofliplr || doflipud,

  if ~ismoviefile,
    msg = 'Cannot flip without input movie file';
    return;
  end

  try
    % get image size
    [readframe,~,fid] = get_readframe_fcn(inmoviefile);
    im = readframe(1);
    [imheight,imwidth,~] = size(im);
    if fid > 1,
      fclose(fid);
    end
  catch ME,
    msg = getReport(ME);
    return;
  end
end

% flip if necessary
if dofliplr,
  for i = 1:numel(trx),
    trx(i).x = imwidth-trx(i).x+1;
    trx(i).theta = modrange(pi - trx(i).theta,-pi,pi);
    if iswings,
      trx(i).xwingl = imwidth-trx(i).xwingl+1;
      trx(i).xwingr = imwidth-trx(i).xwingr+1;
    end
  end
  if isfield(trx,'arena') && isfield(trx(1).arena,'x'),
    for i = 1:numel(trx),
      trx(i).arena.x = imwidth-trx(i).arena.x+1;
    end
  end
  msg{end+1} = 'Flipped the trajectories left-right.';
end
if doflipud,
  for i = 1:numel(trx),
    trx(i).y = imheight-trx(i).y+1;
    trx(i).theta = modrange(-trx(i).theta,-pi,pi);
    if iswings,
      trx(i).ywingl = imheight-trx(i).xwingl+1;
      trx(i).ywingr = imheight-trx(i).xwingr+1;
    end
  end
  if isfield(trx,'arena') && isfield(trx(1).arena,'y'),
    for i = 1:numel(trx),
      trx(i).arena.y = imheight-trx(i).arena.y+1;
    end
  end
  msg{end+1} = 'Flipped the trajectories up-down.';
end
if dotransposeimage,
  for i = 1:numel(trx),
    tmp = trx(i).x;
    trx(i).x = trx(i).y;
    trx(i).y = tmp;
    c = cos(trx(i).theta);
    s = sin(trx(i).theta);
    trx(i).theta = atan2(c,s);
    if iswings,
      tmp = trx(i).xwingl;
      trx(i).xwingl = trx(i).ywingl;
      trx(i).ywingl = tmp;
      tmp = trx(i).xwingr;
      trx(i).xwingr = trx(i).ywingr;
      trx(i).ywingr = tmp;
    end
  end
  if isfield(trx,'arena') && isfield(trx(1).arena,'x') && isfield(trx(1).arena,'y'),
    for i = 1:numel(trx),
      tmp = trx(i).arena.x;
      trx(i).arena.x = trx(i).arena.y;
      trx(i).arena.y = tmp;
    end
  end
  msg{end+1} = 'Switched x and y in trajectories.';
end

% Clip the tracks if necessary
outofrange = [];
clipranges = zeros(length(trx),2);
if (frameinterval(1)>1) || ~isinf(frameinterval(end)),
  fields2clip = {'x','y','a','b','theta','timestamps',...
                 'wing_anglel','wing_angler',...
                 'xwingl','ywingl','xwingr','ywingr'};
  for fly = 1:length(trx),
    if trx(fly).endframe<frameinterval(1) || ...
        trx(fly).firstframe>frameinterval(2),
      outofrange(end+1) = fly;  %#ok<AGROW>
      continue;
    end
    tstart = max(1,frameinterval(1)-trx(fly).firstframe+1);
    tend = min(trx(fly).nframes,frameinterval(2)-trx(fly).endframe+trx(fly).nframes);
    clipranges(fly,1) = tstart;
    clipranges(fly,2) = tend;
    trx(fly).firstframe = max(frameinterval(1),trx(fly).firstframe);
    trx(fly).endframe = min(frameinterval(2),trx(fly).endframe);
    trx(fly).nframes = trx(fly).endframe-trx(fly).firstframe+1;
    for fieldndx = 1:numel(fields2clip)
      if isfield(trx(fly),fields2clip{fieldndx});
        trx(fly).(fields2clip{fieldndx}) = trx(fly).(fields2clip{fieldndx})(tstart:tend);
      end
    end
  end

end
trx(outofrange) = [];

% compute frame rate if not overriding and possible
if ~overridefps && ~isempty(timestamps),
  fps = 1/nanmedian(diff(timestamps));
  msg{end+1} = sprintf('Computed frame rate = %f fps from timestamps.',fps);
else
  msg{end+1} = sprintf('Using input frame rate %f fps to set timestamps',fps);
end

if ~overridearena && isfield(trx,'pxpermm'),
  tmp = nanmean([trx.pxpermm]);
  if ~isnan(tmp),
    pxpermm = tmp;
    msg{end+1} = sprintf('Read pxpermm = %f from trxfile.',pxpermm);
  end
else
  msg{end+1} = sprintf('Using input pixels-per-mm scaling = %f.',pxpermm);
end

if ~overridearena && isfield(trx,'arena') && all(isfield(trx(1).arena,{'x','y','r'})) && ...
  ~isempty(trx(1).arena.r) && ~isempty(trx(1).arena.x) && ~isempty(trx(1).arena.y),
  arenaradius = trx(1).arena.r;
  arenacenterx = trx(1).arena.x;
  arenacentery = trx(1).arena.y;
  arenatype = 'Circle';
  msg{end+1} = sprintf('Read circle arena parameters from trxfile. Center = (%.1f, %.1f) px, radius = %.1f px.',arenacenterx,arenacentery,arenaradius);
elseif strcmpi(arenatype,'None'),
  arenacenterx = 0;
  arenacentery = 0;
end

if ~isempty(roi2),
  roi2(:,1) = (roi2(:,1)-arenacenterx) / pxpermm;
  roi2(:,2) = (roi2(:,2)-arenacentery) / pxpermm;
  roi2(:,3) = roi2(:,3) / pxpermm;
  roi2(:,4) = roi2(:,4) / pxpermm;
end

% apply to trajectories
for fly = 1:length(trx),

  % scale and translate position
  trx(fly).x_mm = (trx(fly).x - arenacenterx) / pxpermm;
  trx(fly).y_mm = (trx(fly).y - arenacentery) / pxpermm;
  trx(fly).a_mm = trx(fly).a / pxpermm;
  trx(fly).b_mm = trx(fly).b / pxpermm;
  trx(fly).theta_mm = trx(fly).theta;

  % add in dt
  if overridefps || isempty(timestamps),
    trx(fly).dt = repmat(1/fps,[1,trx(fly).nframes-1]);
  else
    trx(fly).dt = diff(timestamps(trx(fly).firstframe:trx(fly).endframe));
    trx(fly).dt(isnan(trx(fly).dt)) = 1/fps;
  end

  trx(fly).fps = 1/median(trx(fly).dt);
  trx(fly).pxpermm = pxpermm;

end

msg{end+1} = sprintf('Read trx for %d flies, frame range [%d,%d]',numel(trx),min([trx.firstframe]),max([trx.endframe]));

if isempty(timestamps) || overridefps,
  timestamps = (0:max([trx.endframe]))*1/fps; %#ok<NASGU>
end

% set landmark parameters
arenaradius_mm = arenaradius / pxpermm;
arenawidth_mm = arenawidth / pxpermm;
arenaheight_mm = arenaheight / pxpermm;
arenacenterx_mm = 0;
arenacentery_mm = 0;
trx = SetLandmarkParameters(trx,arenatype,arenacenterx_mm,arenacentery_mm,...
  arenaradius_mm,arenawidth_mm,arenaheight_mm,roi2);
trx = SetLandmarkParameters_px(trx,arenatype,arenacenterx,arenacentery,arenaradius);

if strcmpi(arenatype,'Circle')
  msg{end+1} = sprintf('Set circular arena parameters. Center = (0,0) mm, radius = %.1f mm.',arenaradius_mm);
elseif strcmpi(arenatype,'Rectangle'),
  msg{end+1} = sprintf('Set rectangular arena parameters. Center = (0,0) mm, width= %.1f mm, height = %.1f mm',arenawidth_mm,arenaheight_mm);
end
msg{end+1} = sprintf('x_mm ranges within [%.1f,%.1f], y_mm ranges within [%.1f,%.1f]',...
  min([trx.x_mm]),max([trx.x_mm]),min([trx.y_mm]),max([trx.y_mm]));

% create the experiment directory
if ~exist(expdir,'dir'),
  [success1,msg1] = mkdir(expdir);
  if ~success1,
    msg = msg1;
    return;
  end
  msg{end+1} = sprintf('Created experiment directory %s.',expdir);
end

% save the trx file
try
  save(trxfile,'trx','timestamps');
catch ME,
  msg = sprintf('Could not save to file %s: %s',trxfile,getReport(ME));
  return;
end
if ~exist(trxfile,'file'),
  msg = sprintf('Failed to save trx to file %s',trxfile);
  return;
end
msg{end+1} = sprintf('Saved trx to file %s.',trxfile);

% copy/soft-link movie
if ismoviefile,
  if strcmp(fullfile(inmoviefile),fullfile(moviefile)),
    msg{end+1} = 'Input and out movie files are the same, not copying/linking.';
  else

    if dosoftlink,
      if exist(moviefile,'file'),
        delete(moviefile);
      end
      if isunix,
        cmd = sprintf('ln -s %s %s',inmoviefile,moviefile);
        unix(cmd);
        % test to make sure it worked
        [status,result] = unix(sprintf('readlink %s',moviefile));
        result = strtrim(result);
        if status ~= 0 || ~strcmp(result,inmoviefile),
          res = questdlg(sprintf('Failed to make soft link. Copy %s to %s instead?',inmoviefile,moviefile));
          if ~strcmpi(res,'Yes'),
            msg = sprintf('Failed to make soft link from %s to %s.',inmoviefile,moviefile);
            return;
          end
          dosoftlink = false;
        end
      elseif ispc,
        if exist([moviefile,'.lnk'],'file'),
          delete([moviefile,'.lnk']);
        end
        cmd = sprintf('mkshortcut.vbs /target:"%s" /shortcut:"%s"',inmoviefile,moviefile);
        fprintf('Making a Windows shortcut file at "%s" with target "%s"\n',inmoviefile,moviefile);
        system(cmd);
        % test to make sure that worked
        [equalmoviefile,didfind] = GetPCShortcutFileActualPath(moviefile);
        if ~didfind || ~strcmp(equalmoviefile,inmoviefile),
          res = questdlg(sprintf('Failed to make shortcut. Copy %s to %s instead?',inmoviefile,moviefile));
          if ~strcmpi(res,'Yes'),
            msg = sprintf('Failed to make shortcut from %s to %s.',inmoviefile,moviefile);
            return;
          end
          dosoftlink = false;
        end
      else
        res = questdlg(sprintf('Unknown OS, cannot soft-link movie file %s. Copy instead?',inmoviefile));
        if ~strcmpi(res,'Yes'),
          msg = sprintf('Failed to make softlink from %s to %s.',inmoviefile,moviefile);
          return;
        end
        dosoftlink = false;
      end
      if dosoftlink,
        msg{end+1} = sprintf('Made a link to movie file %s at %s',inmoviefile,moviefile);
      end
    end

    if ~dosoftlink,
      if ispc,
        if exist([moviefile,'.lnk'],'file'),
          delete([moviefile,'.lnk']);
        end
      end
      if exist(moviefile,'file'),
        delete(moviefile);
      end
      [success1,msg1] = copyfile(inmoviefile,moviefile);
      if ~success1,
        msg = msg1;
        success = false;
        return;
      end
      msg{end+1} = sprintf('Copied movie file %s to %s',inmoviefile,moviefile);
    end

  end

end

% make per-frame directory
if ~exist(perframedir,'dir'),
  [success1,msg1] = mkdir(perframedir);
  if ~success1,
    msg = msg1;
    return;
  end
end

% copy over wing features
if iswings,
  fns = fieldnames(wingunits);
  doclip = false;
  docopy = false;
  fn = fns{1};
  filenamein = fullfile(inperframedir,[fn,'.mat']);
  filenameout = fullfile(perframedir,[fn,'.mat']);
  if (frameinterval(1)>1) || ~isinf(frameinterval(end)),
    if strcmp(filenamein,filenameout),
      qstr = sprintf('Input and output file locations for %s/%s.mat (and possibly others) are the same. The original file will be overwritten with newer version with fewer frames. Overwrite?',...
        perframedir,fn);
      res = questdlg(qstr,'Overwrite perframe file','Yes','No','No');
      if strcmpi(res,'Yes'),doclip = true; end
    else
      doclip = true;
    end
  else
    if strcmp(filenamein,filenameout),
      msg{end+1} = fprintf('%s/%s.mat input and output file locations are the same, not copying.',perframedir,fn);
    else
      docopy = true;
    end
  end
  for i = 1:numel(fns),
    fn = fns{i};
    filenamein = fullfile(inperframedir,[fn,'.mat']);
    filenameout = fullfile(perframedir,[fn,'.mat']);

    if docopy
      if exist(filenameout,'file'),
        delete(filenameout);
      end
      [success1,msg1] = copyfile(filenamein,filenameout);
      if ~success1,
        msg = msg1;
        return;
      end
    end
    if doclip
      Q = load(filenamein);
      for fly = 1:numel(Q.data)
        if ismember(fly,outofrange),
          continue;
        end
        Q.data{fly} = Q.data{fly}(clipranges(fly,1):clipranges(fly,2));
      end
      Q.data(outofrange) = [];

      save(filenameout,'-struct','Q');

    end
  end
end

success = true;


function [rcnn_model, rcnn_k_fold_model] = ...
    rcnn_train(imdb, varargin)
% [rcnn_model, rcnn_k_fold_model] = rcnn_train(imdb, varargin)
%   Trains an R-CNN detector for all classes in the imdb.
%
%   Keys that can be passed in:
%
%   svm_C             SVM regularization parameter
%   bias_mult         Bias feature value (for liblinear)
%   pos_loss_weight   Cost factor on hinge loss for positives
%   layer             Feature layer to use (either 5, 6 or 7)
%   k_folds           Train on folds of the imdb
%   checkpoint        Save the rcnn_model every checkpoint images
%   crop_mode         Crop mode (either 'warp' or 'square')
%   crop_padding      Amount of padding in crop
%   net_file          Path to the Caffe CNN to use
%   cache_name        Path to the precomputed feature cache

% AUTORIGHTS
% ---------------------------------------------------------
% Copyright (c) 2014, Ross Girshick
%
% This file is part of the R-CNN code and is available
% under the terms of the Simplified BSD License provided in
% LICENSE. Please retain this notice and LICENSE if you use
% this file (or any portion of it) in your project.
% ---------------------------------------------------------

% TODO:
%  - allow training just a subset of classes

ip = inputParser;
ip.addRequired('imdb', @isstruct);
ip.addParamValue('svm_C',           10^-3,  @isscalar);
ip.addParamValue('bias_mult',       10,     @isscalar);
ip.addParamValue('pos_loss_weight', 2,      @isscalar);
ip.addParamValue('layer',           7,      @isscalar);
ip.addParamValue('k_folds',         2,      @isscalar);
ip.addParamValue('checkpoint',      0,      @isscalar);
ip.addParamValue('crop_mode',       'warp', @isstr);
ip.addParamValue('crop_padding',    16,     @isscalar);
ip.addParamValue('net_file', ...
    './data/caffe_nets/finetune_voc_2007_trainval_iter_70k', ...
    @isstr);
ip.addParamValue('cache_name', ...
    'v1_finetune_voc_2007_trainval_iter_70000', @isstr);

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

opts.net_def_file = './model-defs/rcnn_batch_256_output_fc7.prototxt';

conf = rcnn_config('sub_dir', imdb.name);

% Record a log of the training and test procedure
timestamp = datestr(datevec(now()), 'dd.mmm.yyyy:HH.MM.SS');
diary_file = [conf.cache_dir 'rcnn_train_' timestamp '.txt'];
diary(diary_file);
fprintf('Logging output in %s\n', diary_file);

fprintf('\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n');
fprintf('Training options:\n');
disp(opts);
fprintf('~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n');

% ------------------------------------------------------------------------
% Create a new rcnn model
rcnn_model = rcnn_create_model(opts.net_def_file, opts.net_file, opts.cache_name);
rcnn_model = rcnn_load_model(rcnn_model, conf.use_gpu);
rcnn_model.detectors.crop_mode = opts.crop_mode;
rcnn_model.detectors.crop_padding = opts.crop_padding;
rcnn_model.classes = imdb.classes;
% ------------------------------------------------------------------------

% ------------------------------------------------------------------------
% Get the average norm of the features
opts.feat_norm_mean = rcnn_feature_stats(imdb, opts.layer, rcnn_model);
fprintf('average norm = %.3f\n', opts.feat_norm_mean);
rcnn_model.training_opts = opts;
% ------------------------------------------------------------------------

% ------------------------------------------------------------------------
% Get all positive examples
% We cache only the pool5 features and convert them on-the-fly to
% fc6 or fc7 as required
save_file = sprintf('./feat_cache/%s/%s/gt_pos_layer_5_cache.mat', ...
    rcnn_model.cache_name, imdb.name);
try
  load(save_file);
  fprintf('Loaded saved positives from ground truth boxes\n');
catch
  [X_pos, keys_pos] = get_positive_pool5_features(imdb, opts);
  save(save_file, 'X_pos', 'keys_pos', '-v7.3');
end
% Init training caches
caches = {};
for i = imdb.class_ids
  fprintf('%14s has %6d positive instances\n', ...
      imdb.classes{i}, size(X_pos{i},1));
  X_pos{i} = rcnn_pool5_to_fcX(X_pos{i}, opts.layer, rcnn_model);
  X_pos{i} = rcnn_scale_features(X_pos{i}, opts.feat_norm_mean);
  caches{i} = init_cache(X_pos{i}, keys_pos{i});
end
% ------------------------------------------------------------------------

% ------------------------------------------------------------------------
% Train with hard negative mining
first_time = true;
% one pass over the data is enough
max_hard_epochs = 1;

for hard_epoch = 1:max_hard_epochs
  for i = 1:length(imdb.image_ids)
    fprintf('%s: hard neg epoch: %d/%d image: %d/%d\n', ...
            procid(), hard_epoch, max_hard_epochs, i, length(imdb.image_ids));

    % Get hard negatives for all classes at once (avoids loading feature cache
    % more than once)
    [X, keys] = sample_negative_features(first_time, rcnn_model, caches, ...
        imdb, i);

    % Add sampled negatives to each classes training cache, removing
    % duplicates
    for j = imdb.class_ids
      if ~isempty(keys{j})
        if ~isempty(caches{j}.keys_neg)
          [~, ~, dups] = intersect(caches{j}.keys_neg, keys{j}, 'rows');
          assert(isempty(dups));
        end
        caches{j}.X_neg = cat(1, caches{j}.X_neg, X{j});
        caches{j}.keys_neg = cat(1, caches{j}.keys_neg, keys{j});
        caches{j}.num_added = caches{j}.num_added + size(keys{j},1);
      end

      % Update model if
      %  - first time seeing negatives
      %  - more than retrain_limit negatives have been added
      %  - its the final image of the final epoch
      is_last_time = (hard_epoch == max_hard_epochs && i == length(imdb.image_ids));
      hit_retrain_limit = (caches{j}.num_added > caches{j}.retrain_limit);
      if (first_time || hit_retrain_limit || is_last_time) && ...
          ~isempty(caches{j}.X_neg)
        fprintf('>>> Updating %s detector <<<\n', imdb.classes{j});
        fprintf('Cache holds %d pos examples %d neg examples\n', ...
                size(caches{j}.X_pos,1), size(caches{j}.X_neg,1));
        [new_w, new_b] = update_model(caches{j}, opts);
        rcnn_model.detectors.W(:, j) = new_w;
        rcnn_model.detectors.B(j) = new_b;
        caches{j}.num_added = 0;

        z_pos = caches{j}.X_pos * new_w + new_b;
        z_neg = caches{j}.X_neg * new_w + new_b;

        caches{j}.pos_loss(end+1) = opts.svm_C * opts.pos_loss_weight * ...
                                    sum(max(0, 1 - z_pos));
        caches{j}.neg_loss(end+1) = opts.svm_C * sum(max(0, 1 + z_neg));
        caches{j}.reg_loss(end+1) = 0.5 * new_w' * new_w + ...
                                    0.5 * (new_b / opts.bias_mult)^2;
        caches{j}.tot_loss(end+1) = caches{j}.pos_loss(end) + ...
                                    caches{j}.neg_loss(end) + ...
                                    caches{j}.reg_loss(end);

        for t = 1:length(caches{j}.tot_loss)
          fprintf('    %2d: obj val: %.3f = %.3f (pos) + %.3f (neg) + %.3f (reg)\n', ...
                  t, caches{j}.tot_loss(t), caches{j}.pos_loss(t), ...
                  caches{j}.neg_loss(t), caches{j}.reg_loss(t));
        end

        % store negative support vectors for visualizing later
        SVs_neg = find(z_neg > -1 - eps);
        rcnn_model.SVs.keys_neg{j} = caches{j}.keys_neg(SVs_neg, :);
        rcnn_model.SVs.scores_neg{j} = z_neg(SVs_neg);

        % evict easy examples
        easy = find(z_neg < caches{j}.evict_thresh);
        caches{j}.X_neg(easy,:) = [];
        caches{j}.keys_neg(easy,:) = [];
        fprintf('  Pruning easy negatives\n');
        fprintf('  Cache holds %d pos examples %d neg examples\n', ...
                size(caches{j}.X_pos,1), size(caches{j}.X_neg,1));
        fprintf('  %d pos support vectors\n', numel(find(z_pos <  1 + eps)));
        fprintf('  %d neg support vectors\n', numel(find(z_neg > -1 - eps)));
      end
    end
    first_time = false;

    if opts.checkpoint > 0 && mod(i, opts.checkpoint) == 0
      save([conf.cache_dir 'rcnn_model'], 'rcnn_model');
    end
  end
end
% save the final rcnn_model
save([conf.cache_dir 'rcnn_model'], 'rcnn_model');
% ------------------------------------------------------------------------

% ------------------------------------------------------------------------
if opts.k_folds > 0
  rcnn_k_fold_model = rcnn_model;
  [W, B, folds] = update_model_k_fold(rcnn_model, caches, imdb);
  rcnn_k_fold_model.folds = folds;
  for f = 1:length(folds)
    rcnn_k_fold_model.detectors(f).W = W{f};
    rcnn_k_fold_model.detectors(f).B = B{f};
  end
  save([conf.cache_dir 'rcnn_k_fold_model'], 'rcnn_k_fold_model');
else
  rcnn_k_fold_model = [];
end
% ------------------------------------------------------------------------

% ------------------------------------------------------------------------
function [X_neg, keys] = sample_negative_features(first_time, rcnn_model, ...
                                                  caches, imdb, ind)
% ------------------------------------------------------------------------
opts = rcnn_model.training_opts;

d = rcnn_load_cached_pool5_features(opts.cache_name, ...
    imdb.name, imdb.image_ids{ind});

class_ids = imdb.class_ids;

if isempty(d.feat)
  X_neg = cell(max(class_ids), 1);
  keys = cell(max(class_ids), 1);
  return;
end

d.feat = rcnn_pool5_to_fcX(d.feat, opts.layer, rcnn_model);
d.feat = rcnn_scale_features(d.feat, opts.feat_norm_mean);

neg_ovr_thresh = 0.3;

if first_time
  for cls_id = class_ids
    I = find(d.overlap(:, cls_id) < neg_ovr_thresh);
    X_neg{cls_id} = d.feat(I,:);
    keys{cls_id} = [ind*ones(length(I),1) I];
  end
else
  zs = bsxfun(@plus, d.feat*rcnn_model.detectors.W, rcnn_model.detectors.B);
  for cls_id = class_ids
    z = zs(:, cls_id);
    I = find((z > caches{cls_id}.hard_thresh) & ...
             (d.overlap(:, cls_id) < neg_ovr_thresh));

    % Avoid adding duplicate features
    keys_ = [ind*ones(length(I),1) I];
    if ~isempty(caches{cls_id}.keys_neg) && ~isempty(keys_)
      [~, ~, dups] = intersect(caches{cls_id}.keys_neg, keys_, 'rows');
      keep = setdiff(1:size(keys_,1), dups);
      I = I(keep);
    end

    % Unique hard negatives
    X_neg{cls_id} = d.feat(I,:);
    keys{cls_id} = [ind*ones(length(I),1) I];
  end
end

% ------------------------------------------------------------------------
function [w, b] = update_model(cache, opts, pos_inds, neg_inds)
% ------------------------------------------------------------------------
solver = 'liblinear';
liblinear_type = 3;  % l2 regularized l1 hinge loss
%liblinear_type = 5; % l1 regularized l2 hinge loss

if ~exist('pos_inds', 'var') || isempty(pos_inds)
  num_pos = size(cache.X_pos, 1);
  pos_inds = 1:num_pos;
else
  num_pos = length(pos_inds);
  fprintf('[subset mode] using %d out of %d total positives\n', ...
      num_pos, size(cache.X_pos,1));
end
if ~exist('neg_inds', 'var') || isempty(neg_inds)
  num_neg = size(cache.X_neg, 1);
  neg_inds = 1:num_neg;
else
  num_neg = length(neg_inds);
  fprintf('[subset mode] using %d out of %d total negatives\n', ...
      num_neg, size(cache.X_neg,1));
end

switch solver
  case 'liblinear'
    ll_opts = sprintf('-w1 %.5f -c %.5f -s %d -B %.5f', ...
                      opts.pos_loss_weight, opts.svm_C, ...
                      liblinear_type, opts.bias_mult);
    fprintf('liblinear opts: %s\n', ll_opts);
    X = sparse(size(cache.X_pos,2), num_pos+num_neg);
    X(:,1:num_pos) = cache.X_pos(pos_inds,:)';
    X(:,num_pos+1:end) = cache.X_neg(neg_inds,:)';
    y = cat(1, ones(num_pos,1), -ones(num_neg,1));
    llm = liblinear_train(y, X, ll_opts, 'col');
    w = single(llm.w(1:end-1)');
    b = single(llm.w(end)*opts.bias_mult);

  otherwise
    error('unknown solver: %s', solver);
end

% ------------------------------------------------------------------------
function [W, B, folds] = update_model_k_fold(rcnn_model, caches, imdb)
% ------------------------------------------------------------------------
opts = rcnn_model.training_opts;
num_images = length(imdb.image_ids);
folds = create_folds(num_images, opts.k_folds);
W = cell(opts.k_folds, 1);
B = cell(opts.k_folds, 1);

fprintf('Training k-fold models\n');
for i = imdb.class_ids
  fprintf('\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n');
  fprintf('Training folds for class %s\n', imdb.classes{i});
  fprintf('~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n');
  for f = 1:length(folds)
    fprintf('Holding out fold %d\n', f);
    [pos_inds, neg_inds] = get_cache_inds_from_fold(caches{i}, folds{f});
    [new_w, new_b] = update_model(caches{i}, opts, ...
        pos_inds, neg_inds);
    W{f}(:,i) = new_w;
    B{f}(i) = new_b;
  end
end

% ------------------------------------------------------------------------
function [pos_inds, neg_inds] = get_cache_inds_from_fold(cache, fold)
% ------------------------------------------------------------------------
pos_inds = find(ismember(cache.keys_pos(:,1), fold) == false);
neg_inds = find(ismember(cache.keys_neg(:,1), fold) == false);

% ------------------------------------------------------------------------
function [X_pos, keys] = get_positive_pool5_features(imdb, opts)
% ------------------------------------------------------------------------
X_pos = cell(max(imdb.class_ids), 1);
keys = cell(max(imdb.class_ids), 1);

for i = 1:length(imdb.image_ids)
  tic_toc_print('%s: pos features %d/%d\n', ...
                procid(), i, length(imdb.image_ids));

  d = rcnn_load_cached_pool5_features(opts.cache_name, ...
      imdb.name, imdb.image_ids{i});

  for j = imdb.class_ids
    if isempty(X_pos{j})
      X_pos{j} = single([]);
      keys{j} = [];
    end
    sel = find(d.class == j);
    if ~isempty(sel)
      X_pos{j} = cat(1, X_pos{j}, d.feat(sel,:));
      keys{j} = cat(1, keys{j}, [i*ones(length(sel),1) sel]);
    end
  end
end

% ------------------------------------------------------------------------
function cache = init_cache(X_pos, keys_pos)
% ------------------------------------------------------------------------
cache.X_pos = X_pos;
cache.X_neg = single([]);
cache.keys_neg = [];
cache.keys_pos = keys_pos;
cache.num_added = 0;
cache.retrain_limit = 2000;
cache.evict_thresh = -1.2;
cache.hard_thresh = -1.0001;
cache.pos_loss = [];
cache.neg_loss = [];
cache.reg_loss = [];
cache.tot_loss = [];


function DoPlotVObj(handles)

%--------Display Parameters
fieldname=fieldnames(handles.Attrh2);
for i=1:length(fieldname)/2
    try
        eval(['SD.' fieldname{i*2} '=' get(handles.Attrh2.(fieldname{i*2}),'String') ';']);
    catch me
        TAttr=get(handles.Attrh2.(fieldname{i*2}),'String');
        eval(['SD.' fieldname{i*2} '=''' TAttr{get(handles.Attrh2.(fieldname{i*2}),'Value')}  ''';']);
    end
end
handles.SD=SD;
%--------End

DoWriteXML2m(DoParseXML(handles.VObjXMLFile),[handles.VObjXMLFile(1:end-3) 'm']);
clear functions;  % remove the M-functions from the memory
[pathstr,name,ext]=fileparts(handles.VObjXMLFile);
eval(['[Obj, VObj]=' name '(0);']);

axes(handles.VObj_axes);
[az,el] = view(gca);
cla(handles.VObj_axes);
for i=1:length(Obj(1,:))
    p(i)=patch(Obj{1,i});
    if isempty(str2num(Obj{2,i}))
        set(p(i),'FaceColor',Obj{2,i},'FaceAlpha',Obj{3,i});
    else
        set(p(i),'FaceColor',str2num(Obj{2,i}),'FaceAlpha',Obj{3,i});
    end
end
view([az,el]);
box(SD.Box);
eval(['grid ' SD.Grid]);
cameratoolbar(SD.CameraTool);
axis image;
set(gca,'XLim',[(-(VObj.XDim-1)/2)*VObj.XDimRes ((VObj.XDim-1)/2)*VObj.XDimRes],...
        'YLim',[(-(VObj.YDim-1)/2)*VObj.YDimRes ((VObj.YDim-1)/2)*VObj.YDimRes],...
        'ZLim',[(-(VObj.ZDim-1)/2)*VObj.ZDimRes ((VObj.ZDim-1)/2)*VObj.ZDimRes]);
set(gca,'YDir','reverse','ZDir','reverse');
xlabel('X');
ylabel('Y');
zlabel('Z');
lighting phong;

end


function iceweb2017(thissubnet, ds, ChannelTagList, ...
    snum, enum, nummins, products, PARAMS, paths)
    debug.printfunctionstack('>');

    % load state
    statefile = sprintf('iceweb_%s_state.mat',thissubnet);
    if exist(statefile, 'file') && ~strcmp(PARAMS.runmode, 'test')
        load(statefile)
    end

    % end time
    if enum==0
        enum = utnow - delaymins/1440;
    end

    % loop one day at a time
    for dnum = floor(snum):ceil(enum)
        disp(datestr(dnum))

%         % figure out which channeltags are active for this day?
%         ctags_thisday = get_channeltags_thisday(ds, ctags, dnum); % subset to channeltags valid
%         if isempty(todaysites)
%             continue;
%         end

%         % subset to sites for which the files pointed to by the
%         % wfdisc table, actually exist
%         m = listMiniseedFiles(ds, chantag, dnum, dnum+1);
%         ctags_thisday = ctags_thisday([m.exists]==2);

        % get timewindow for this day
        timewindows = get_timewindow(min([enum dnum+1]), nummins, max([dnum snum]));

        if ~strcmp(PARAMS.runmode,'test')
            % loop over timewindows
            for count = 1:length(timewindows.start)
                this_timewindow.start = timewindows.start(count);
                this_timewindow.stop = timewindows.stop(count);
                iceweb_helper(paths, PARAMS, newsubnets, this_timewindow, ds, products);
            end
        end
    end
    debug.printfunctionstack('<');
end

function iceweb_helper(paths, PARAMS, subnets, tw, ds, products)
    debug.printfunctionstack('>');

    MILLISECOND_IN_DAYS = (1 / 86400000);

    makeSamFiles = false;
    makeSoundFiles = true;

    snum = tw.start;
    enum = tw.stop - MILLISECOND_IN_DAYS; % try to skip last sample

    % load state
    statefile = sprintf('iceweb_%s_state.mat',subnet);
    if exist(statefile, 'file')
        load(statefile)
        if snum < snum0
            continue
        end
    end

    % save state
    ds0=ds; sites0=sites; snum0=snum; enum0=enum; subnet0 = subnet;
    save(statefile, 'ds0', 'sites0', 'snum0', 'enum0', 'subnet0');
    clear ds0 sites0 snum0 enum0 subnet0

    %% Save raw waveform data to 1 hour MAT file
    dv = datevec(snum);
    yyyy = dv(1);
    jjj = floor(snum - datenum(yyyy,1,1) + 1);
    hh = dv(4);
    wavematdir = 'wavemat';
    wavrawmat = sprintf('%s/%s/%04d/%03d/%s.%04d.%03d.%02d.raw.mat',wavematdir,subnet,yyyy,jjj,subnet,yyyy,jjj,hh);
    if ~exist(wavrawmat,'file')
        %% Get waveform data
        debug.print_debug(0, sprintf('%s %s: Getting waveforms for %s from %s to %s at %s',mfilename, datestr(utnow), subnet , datestr(snum), datestr(enum)));
        w = waveform_wrapper(ds, ChannelTagList, snum, enum);
        mkdir(fileparts(wavmatfile));
        disp(sprintf('Saving waveform data to %s',wavrawmat));
        save(wavrawmat);
    end

    % continue

    % Save the cleaned waveform data to MAT file
    wavcleanmat = sprintf('%s/%s/%04d/%03d/%s.%04d.%03d.%02d.clean.mat',wavematdir,subnet,yyyy,jjj,subnet,yyyy,jjj,hh);
    if ~exist(wavcleanmat,'file')

        % Eliminate empty waveform objects
        w = waveform_remove_empty(w);
        if numel(w)==0
            debug.print_debug(0, 'No waveform data returned - skipping');
            continue
        end

        % Clean the waveforms
        w = fillgaps(w, 'interp');
        w = detrend(w);

        % Apply calibs which should be stored within sites structure to
        % waveform objects to convert from counts to real physical
        % units
        w = apply_calib(w, sites);

        % Pad all waveforms to same start/end
        [wsnum wenum] = gettimerange(w); % assume gaps already filled, signal
        w = pad(w, min([snum wsnum]), max([enum wenum]), 0);

        % Apply filter to all signals
        w = apply_filter(w, PARAMS);

        mkdir(fileparts(wavmatfile));
        disp(sprintf('Saving waveform data to %s',wavcleanmat));
        save(wavcleanmat);
    end

    %% ICEWEB PRODUCTS

    % WAVEFORM PLOT
    if products.waveform_plot.doit
        close all
        plot_panels(w)
        [spdir,spbase,spext] = fileparts(spectrogramFilename);
        fname = fullfile('plots', 'waveforms', subnet, sprintf('%s.png',datestr(snum,31)) );
        orient tall;
        saveImageFile(fname, 72); % this should make directory tree too
    end

    % RSAM
    if products.rsam.doit
        for measure = products.rsam.measures
            rsamobj = waveform2rsam(w);
            rsamobj.save_to_bob_file(fullfile('data', 'rsam', subnet, 'SSSS.CCC.YYYY.MMMM.bob'));
        end
    end

    % CREATE & SAVE HELICORDER PLOT
    if products.helicorders.doit
        close all
        for wi=1:numel(w)
            heliplot = helicorder(w(wi),'mpl',3);
            build(heliplot)
            ct = get(w(wi),'channeltag');
            helicorderFilename = fullfile(spdir, sprintf('heli_%s_%s.%s.%s',spbase,ct.station,ct.channel,spext));
            orient tall;
            saveImageFile(helicorderFilename, 72);
            clear ct helicorderFilename
        end
        clear wi
    end

    % PLOT SPECTROGRAMS
    if products.spectrograms.doit
        debug.print_debug(1, sprintf('Creating %s',spectrogramFilename))
        waveSp = w;

		% restrict spectrogram to Z channels only
		w2=[];
		for wi=1:numel(w)
			ct=get(w(wi),'channeltag');
			if strfind(ct.channel,'Z')
				w2 = [w2 w(wi)];
			end
        end
        waveSp = w2;

        % split into 10-minute timewindows
        for someloopover10mintimewindows
            % make 10-minute spectrogram
            close all
            spectrogramFraction = 0.75;
            [sgresult, Tcell, Fcell, Ycell] = spectrogram_iceweb(PARAMS.spectralobject, waveSp, spectrogramFraction, extended_spectralobject_colormap);
            clear w2 wi ct

            if sgresult > 0 % sgresult = number of waveforms for which a spectrogram was successfully plotted
                % SAVE SPECTROGRAM PLOT TO IMAGE FILE AND CREATE THUMBNAIL
                orient tall;

                if saveImageFile(spectrogramFilename, 72)

                    fileinfo = dir(spectrogramFilename); % getting a weird Index exceeds matrix dimensions error here.
                    debug.print_debug(0, sprintf('%s %s: spectrogram PNG size is %d',mfilename, datestr(utnow), fileinfo.bytes));

                    % make thumbnails
                    makespectrogramthumbnails(spectrogramFilename, spectrogramFraction);

                end

                % save spectral data
                if products.spectral_data.doit
                    frequency_index_divider = 5.0; % Hz
                    for spi = 1:numel(Fcell)
                        thisY = Ycell{spi};
                        thisF = Fcell{spi};
                        thisT = Tcell{spi};
                        sta = get(w(spi),'station');
                        chan = get(w(spi),'channel');

                        % peakF and meanF for each spectrogram window
                        [Ymax,imax] = max(thisY);
                        PEAK_F = thisF(imax);
                        MEAN_F = (thisF' * thisY)./sum(thisY);

                        % frequency index for each spectrogram window
                        fUpperIndices = find(thisF > frequency_index_divider);
                        fLowerIndices = find(thisF < frequency_index_divider);
                        fupper = sum(thisY(fUpperIndices,:));
                        flower = sum(thisY(fLowerIndices,:));
                        F_INDEX = log2(fupper ./ flower);

                        % Peak spectral value in each frequency bin - or in
                        % each minute?

                        % Now downsample to 1 sample per minute
                        dnum = unique(floorminute(thisT));
                        for k=1:length(dnum)
                            p = find(floorminute(thisT) == dnum(k));
                            downsampled_peakf(k) = nanmean(PEAK_F(p));
                            downsampled_meanf(k) = nanmean(MEAN_F(p));
                            downsampled_findex(k) = nanmean(F_INDEX(p));
                            suby = thisY(:,p);
                            if size(suby,2) == 1
                                            max_in_each_freq_band(:,k) = suby;
                            else
                                            max_in_each_freq_band(:,k) = max(suby');
                            end
                        end

        %                 % Plot for verification
        %                 close all
        %                 subplot(2,1,1),plot(dnum,downsampled_peakf,':');datetick('x');
        %                 hold on
        %                 plot(dnum,downsampled_meanf);datetick('x');
        %                 subplot(2,1,2),plot(dnum,downsampled_findex);datetick('x');
        %                 anykey = input('Press any key to continue');

                        % Save data
                        r1 = rsam(dnum, downsampled_peakf, 'sta', sta, ...
                            'chan', chan, 'measure', 'peakf', ...
                            'units', 'Hz', 'snum', min(dnum), 'enum', max(dnum));
                        r1.save_to_bob_file(fullfile('spectrograms', subnet, 'SSSS.CCC.YYYY.peakf'))

                        r2 = rsam(dnum, downsampled_meanf, 'sta', sta, ...
                            'chan', chan, 'measure', 'meanf', ...
                            'units', 'Hz', 'snum', min(dnum), 'enum', max(dnum));
                        r2.save_to_bob_file(fullfile('spectrograms', subnet, 'SSSS.CCC.YYYY.meanf'))

                        r3 = rsam(dnum, downsampled_findex, 'sta', sta, ...
                            'chan', chan, 'measure', 'findex', ...
                            'units', 'none', 'snum', min(dnum), 'enum', max(dnum));
                        r3.save_to_bob_file(fullfile('spectrograms', subnet, 'SSSS.CCC.YYYY.findex'))

                        specdatafilename = fullfile('data', 'spectral', subnet, datestr(min(dnum),'yyyy/mm/dd'), sprintf( '%s_%s_%s.mat', datestr(min(dnum),30), sta, chan) );
                        specdatadir = fileparts(specdatafilename); % make the directory in case it does not exist
                        mkdir(specdatadir); % make the directory in case it does not exist
                        save(specdatafilename, 'dnum', 'max_in_each_freq_band')
                        clear r1 r2 r3 k p   downsampled_peakf downsampled_meanf ...
                            downsampled_findex fUpperIndices fLowerIndices flower ...
                            fupper F_INDEX PEAK_F MEAN_F Ymax imax thisY thisF thisT ...
                            suby max_in_each_freq_band

                    end
                end
            end
        end
    end

    % SOUND FILES
    if products.soundfiles.doit
        try
            % 20120221 Added a "sound file" like 201202211259.sound which simply records order of stachans in waveform object so
            % php script can match spectrogram panel with appropriate wav file
            % 20121101 GTHO COmment: Could replace use of bnameroot below with strrep, since it is just used to change file extensions
            % e.g. strrep(spectrogramFilename, '.png', sprintf('_%s_%s.wav', sta, chan))
            soundfileroot = fullfile('soundfiles', subnet, datestr(get(w(c),'start'),31),
            [dname, bnameroot, bnameext] = fileparts(soundfileroot);
            soundfilelist = sprintf('%s.sound',soundfileroot);
            fsound = fopen(soundfilelist,'a');
            for c=1:length(w)
                soundfilename = fullfile('soundfiles', sprintf('%s_%s_%s.wav',bnameroot, get(w(c),'station'), get(w(c), 'channel')  ) );
                fprintf(fsound,'%s\n', soundfilename);
                debug.print_debug(0, sprintf('Writing to %s',soundfilename));
                isSuccessful = waveform2sound(w(c), 60, soundfilename)
            end
            fclose(fsound);
        end
    end

    debug.printfunctionstack('<');
end


%% isclose
% Below is a demonstration of the features of the |isclose| function

%%
clear; close all; clc;

%% Syntax
% |L=isclose(A,B,toleranceLevel);|

%% Description
% UNDOCUMENTED
%% Examples
%
%%
%
% <<gibbVerySmall.gif>>
%
% _*GIBBON*_
% <www.gibboncode.org>
%
% _Kevin Mattheus Moerman_, <gibbon.toolbox@gmail.com>
%%
% _*GIBBON footer text*_
%
% License: <https://github.com/gibbonCode/GIBBON/blob/master/LICENSE>
%
% GIBBON: The Geometry and Image-based Bioengineering add-On. A toolbox for
% image segmentation, image-based modeling, meshing, and finite element
% analysis.
%
% Copyright (C) 2006-2022 Kevin Mattheus Moerman and the GIBBON contributors
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.