function varargout = showSimilarFrames(varargin)
% SHOWSIMILARFRAMES MATLAB code for showSimilarFrames.fig
%      SHOWSIMILARFRAMES, by itself, creates a new SHOWSIMILARFRAMES or raises the existing
%      singleton*.
%
%      H = SHOWSIMILARFRAMES returns the handle to a new SHOWSIMILARFRAMES or the handle to
%      the existing singleton*.
%
%      SHOWSIMILARFRAMES('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in SHOWSIMILARFRAMES.M with the given input arguments.
%
%      SHOWSIMILARFRAMES('Property','Value',...) creates a new SHOWSIMILARFRAMES or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before showSimilarFrames_OpeningFcn gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to showSimilarFrames_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% Edit the above text to modify the response to help showSimilarFrames

% Last Modified by GUIDE v2.5 15-Mar-2012 16:36:51

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @showSimilarFrames_OpeningFcn, ...
                   'gui_OutputFcn',  @showSimilarFrames_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT

% --- Executes just before showSimilarFrames is made visible.
function showSimilarFrames_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to showSimilarFrames (see VARARGIN)

% Choose default command line output for showSimilarFrames
handles.output = hObject;

handles.maxFrames = 20;
handles.numSimilar = 4;
handles.halfSize = 30*5;
handles.imgX = 1024;
handles.imgY = 1024;
handles.frameNo = handles.maxFrames+1;
handles.isPlaying = false;
handles.fwdLimit = 5;
handles.revLimit = 5;
handles.fps = 3;
handles.maxfps = 10;
handles.tsize = 500;
handles.cache = containers.Map('keyType','char','valueType','any');
handles.display = 'manual';

set(handles.frameSlider,'SliderStep',[1/(2*handles.maxFrames) 3/(2*handles.maxFrames)]);

set(handles.frame_range_edit, 'string', '5');
set(handles.fps_edit, 'string', '5');
set(handles.FrameText,'String','frame:0');

sz = handles.halfSize;
tlsz = handles.maxFrames;

linkaxesGroup = [];

for ndx = 1:handles.numSimilar
  tName = sprintf('top%d',ndx);
  mName = sprintf('middle%d',ndx);
  bName = sprintf('bottom%d',ndx);
  handles.hAxes(1,ndx) = initAxes(handles,handles.(tName),sz,1,ndx);
  handles.hAxes(2,ndx) = initAxes(handles,handles.(mName),sz,2,ndx);
  handles.hAxes(3,ndx) = initAxes(handles,handles.(bName),sz,3,ndx);
  handles.hTimeline(1,ndx) = initTimeline(handles.(['tl_',tName]),tlsz,handles.frameNo);
  handles.hTimeline(2,ndx) = initTimeline(handles.(['tl_',mName]),tlsz,handles.frameNo);
  handles.hTimeline(3,ndx) = initTimeline(handles.(['tl_',bName]),tlsz,handles.frameNo);
  linkaxesGroup = [linkaxesGroup, handles.(tName)];
  linkaxesGroup = [linkaxesGroup, handles.(mName)];
  linkaxesGroup = [linkaxesGroup, handles.(bName)];
end

linkaxes(linkaxesGroup,'xy');
%plot(handles.axes_slider_zero,[0,1],[0,0],'y-','LineWidth',5);
% Update handles structure
guidata(hObject, handles);

% UIWAIT makes showSimilarFrames wait for user response (see UIRESUME)
% uiwait(handles.figure_showSimilarFrames);

% --- Outputs from this function are returned to the command line.
function varargout = showSimilarFrames_OutputFcn(hObject, eventdata, handles)
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
varargout{1} = handles.output;

function SetJLabelData(hObject,obj,HJLabel)
handles = guidata(hObject);
handles.JLDobj = obj;
handles.JLabelHandles = HJLabel;
handles.imgX = HJLabel.guidata.movie_width;
handles.imgY = HJLabel.guidata.movie_height;
guidata(hObject,handles);

%{
function loadmovies(hObject)
% Loads the movies.

handles = guidata(hObject);
pointer = [];
movieNames = handles.JLDobj.expdirs;
for movieNum = 1:length(movieNames)
[pointer(movieNum).readframe,pointer(movieNum).nframes,pointer(movieNum).movie_fid,pointer(movieNum).movieheaderinfo] = ...
  get_readframe_fcn(sprintf('%s/movie.ufmf',movieNames{movieNum}),'interruptible',false);
end
handles.movieNames = movieNames;
handles.pointer = pointer;

guidata(hObject,handles);
%}

function CacheTracksLabeled(hObject,exps)
% Load the tracks.

handles = guidata(hObject);
blk = handles.tsize;
off = handles.maxFrames;
fprintf('Loading ');
if nargin<2
  exps = 1:handles.JLDobj.nexps;
end
for expNum = exps
  fprintf('.');
  trxfile = handles.JLDobj.GetFile('trx',expNum);
  trx = load_tracks(trxfile);

  handles.firstframe{expNum} = [trx.firstframe];
  handles.endframe{expNum} = [trx.endframe];

  % Get labels. Store only those parts of tracks that have been labeled.
  for curFly = 1:length(trx)
    labels = handles.JLDobj.GetLabels(expNum,curFly);
    for curNdx = 1:length(labels.t0s)
      trxOffset = handles.firstframe{expNum}(curFly);
      curblockStart = floor( (labels.t0s(curNdx)-off-trxOffset+1)/blk)+1;
      curblockEnd = ceil( (labels.t1s(curNdx)+off-trxOffset+1)/blk);
      for curBlk = curblockStart:curblockEnd
        idStr = sprintf('%d_%d_%d',expNum,curFly,curBlk);
        if ~isKey(handles.cache,idStr),
          tStart = max((curBlk-1)*blk+1,1);
          tEnd = min(curBlk*blk,trx(curFly).endframe-trxOffset+1);
          tSlice = tStart:tEnd;
          trax = [];
          trax.X = trx(curFly).x(tSlice);
          trax.Y = trx(curFly).y(tSlice);
          trax.theta = trx(curFly).theta(tSlice);
          trax.maj = trx(curFly).a(tSlice);
          trax.min = trx(curFly).b(tSlice);
          handles.cache(idStr) = trax;
        end
      end
    end

  end
end
fprintf(' Done loading tracks\n');

guidata(hObject,handles);

function add_prep_list(hObject,exps)
handles = guidata(hObject);
if ~isempty(handles.JLDobj.allperframefns)
  for i = 1:numel(handles.JLDobj.allperframefns),
    handles.timeline_prop_options{i} = handles.JLDobj.allperframefns{i};
  end
  %handles.d = handles.data.allperframefns(1);
  handles.perframepropis = 1;
  set(handles.timeline_properties,'String',handles.timeline_prop_options,'Value',1);
  %handles.timeline_data_ylims = nan(2,numel(handles.data.allperframefns));
end
guidata(hObject,handles);

function CacheTracks(hObject,expNum,curFly,t0,t1)
handles = guidata(hObject);
blk = handles.tsize;
off = handles.maxFrames;
trxfile = handles.JLDobj.GetFile('trx',expNum);
trx = load_tracks(trxfile);

handles.firstframe{expNum} = [trx.firstframe];
handles.endframe{expNum} = [trx.endframe];
trxOffset = handles.firstframe{expNum}(curFly);
curblockStart = floor( (t0-off-trxOffset+1)/blk)+1;
curblockEnd = ceil( (t1+off-trxOffset+1)/blk);

for curBlk = curblockStart:curblockEnd
  idStr = sprintf('%d_%d_%d',expNum,curFly,curBlk);
  if ~isKey(handles.cache,idStr),
    tSlice = ((curBlk-1)*blk+1):(curBlk*blk);
    tSliceValid = tSlice >= 1 & tSlice<=(trx(curFly).endframe-trxOffset+1);
    trax = [];
    trax.X = nan(1,length(tSlice));
    trax.Y = nan(1,length(tSlice));
    trax.theta = nan(1,length(tSlice));
    trax.maj = nan(1,length(tSlice));
    trax.min = nan(1,length(tSlice));

    validNdx = tSlice(tSliceValid);
    trax.X(tSliceValid) = trx(curFly).x(validNdx);
    trax.Y(tSliceValid) = trx(curFly).y(validNdx);
    trax.theta(tSliceValid) = trx(curFly).theta(validNdx);
    trax.maj(tSliceValid) = trx(curFly).a(validNdx);
    trax.min(tSliceValid) = trx(curFly).b(validNdx);
    handles.cache(idStr) = trax;
  end
end
guidata(hObject,handles);

function setFrames(hObject,frameData)
% Sets the frames to be shown.

handles = guidata(hObject);

handles.frames{1} = frameData.posFrames;
handles.frames{2} = frameData.curFrame;
handles.frames{3} = frameData.negFrames;
guidata(hObject,handles);

readFrames(hObject);
handles = guidata(hObject);
updatePlots(hObject,handles,handles.frameNo);

%zoom to the center
limVal = [handles.halfSize*0.8, handles.halfSize*1.2];
set(handles.middle1,'xLim',limVal,'yLim',limVal);

function readFrames(hObject)
handles = guidata(hObject);
handles.isPlaying = false;
guidata(hObject,handles);
curImg = getRotatedFrame(handles,handles.frames{2});
relTrx = getRelTrx(handles,handles.frames{2});
[labels predictions] = getLabels(handles,handles.frames{2});
ptrx = labels.ptrx;
ntrx = labels.ntrx;

plotPredictionsInAutoMode(handles);
poltLabelsInManualMode(handles);

%for row 2
for t = 1:length(ptrx)
    % tranfer data to image for manual timeline
    timeline{2}(1,t,:) = [0.2 0.2 0.2];
    if ~isnan(ptrx(t)); timeline{2}(1,t,:) = [0.7 0.0 0.0]; end
    if ~isnan(ntrx(t)); timeline{2}(1,t,:) = [0.0 0.0 0.7]; end

    % tranfer data to image for automatic timeline
    if isnan(predictions(t)), timeline{2}(2,t,:) = [0.2 0.2 0.2];
    elseif predictions(t)<1.5, timeline{2}(2,t,:) = [0.7 0.0 0.0];
    else timeline{2}(2,t,:) = [0.0 0.0 0.7];
    end
end

for ndx = 1:handles.numSimilar
    handles.im{2,ndx} = curImg;
    handles.trx{2,ndx} = relTrx;
    set(handles.hAxes(2,ndx).labelPos,'visible', 'on');
    set(handles.hAxes(2,ndx).labelNeg,'visible', 'on');
    set(handles.hAxes(2,ndx).predPos,'visible', 'off');
    set(handles.hAxes(2,ndx).predNeg,'visible', 'off');
    set(handles.hAxes(2,ndx).trax,'XData',relTrx.X,'YData',relTrx.Y);
    set(handles.hTimeline(2,ndx).image, 'CData', timeline{2});
    set(handles.hTimeline(2,ndx).hcurr,'XData',[handles.frameNo,handles.frameNo]);
end

%create property plot
%set(handles.axes_timeline_properties,'YLimMode','auto');
hold(handles.axes_timeline_properties,'on');
handles.htimeline_propdata{2} = plot(handles.axes_timeline_properties,nan,nan,'.-');

%choose the first property on the popup list
s = handles.timeline_prop_options{1};
prop = find(strcmpi(s,handles.JLDobj.allperframefns),1);
handles.perframepropis = prop;
handles.perframeprops = s;

[perframedata,T0,T1] = handles.JLDobj.GetPerFrameData(handles.frames{2}.expNum,handles.frames{2}.flyNum,prop);
TStart = handles.frames{2}.curTime - handles.maxFrames;
IStart = TStart - T0 + 1;
TEnd = handles.frames{2}.curTime + handles.maxFrames;
IEnd = TEnd - T0 + 1;
perframedata = perframedata(IStart:IEnd);
set(handles.htimeline_propdata{2},'XData',-handles.maxFrames:handles.maxFrames,'YData',perframedata,'Color','k', 'LineWidth', 2);

%update text_timeline_prop
handles.cur_rb_row = 2;
handles.cur_rb_col = 1;
perframedata = handles.JLDobj.GetPerFrameData1(handles.frames{2}.expNum,handles.frames{2}.flyNum,prop,handles.frames{2}.curTime);
s = sprintf('%.3f',perframedata);
set(handles.text_timeline_prop,'String',s);

%for row 1 and 3
for row = [1 3]
    for ndx = 1:handles.numSimilar
        relTrx = getRelTrx(handles,handles.frames{row}(ndx));
        handles.im{row,ndx} = getRotatedFrame(handles,handles.frames{row}(ndx));

        handles.trx{row,ndx} = relTrx;
        [labels predictions] = getLabels(handles,handles.frames{row}(ndx));
        ptrx = labels.ptrx;
        ntrx = labels.ntrx;

        % plot for manual timeline
        for t = 1:length(ptrx)
            timeline{row,ndx}(1,t,:) = [0.2 0.2 0.2];
            if ~isnan(ptrx(t)); timeline{row,ndx}(1,t,:) = [0.7 0.0 0.0]; end
            if ~isnan(ntrx(t)); timeline{row,ndx}(1,t,:) = [0.0 0.0 0.7]; end

            % plot for automatic timeline
            if isnan(predictions(t)), timeline{row,ndx}(2,t,:) = [0.2 0.2 0.2];
            elseif predictions(t)<1.5, timeline{row,ndx}(2,t,:) = [0.7 0.0 0.0];
            else timeline{row,ndx}(2,t,:) = [0.0 0.0 0.7];
            end
        end

        set(handles.hAxes(row,ndx).labelPos,'visible', 'on');
        set(handles.hAxes(row,ndx).labelNeg,'visible', 'on');
        set(handles.hAxes(row,ndx).predPos,'visible', 'off');
        set(handles.hAxes(row,ndx).predNeg,'visible', 'off');

        set(handles.hAxes(row,ndx).trax,'XData',relTrx.X,'YData',relTrx.Y);
        set(handles.hTimeline(row,ndx).image, 'CData', timeline{row,ndx});
        fprintf('.');

        %create property plots
        if row == 1
            propColor = 'b';
        else
            propColor = 'r';
        end

        handles.htimeline_propdata{row}(ndx) = plot(handles.axes_timeline_properties,nan,nan,'.-','color', propColor);

        [perframedata,T0,T1] = handles.JLDobj.GetPerFrameData(handles.frames{row}(ndx).expNum,handles.frames{row}(ndx).flyNum,prop);
        TStart = handles.frames{row}(ndx).curTime - handles.maxFrames;
        IStart = TStart - T0 + 1;
        TEnd = handles.frames{row}(ndx).curTime + handles.maxFrames;
        IEnd = TEnd - T0 + 1;
        perframedata = perframedata(IStart:IEnd);
        set(handles.htimeline_propdata{row}(ndx),'XData',-handles.maxFrames:handles.maxFrames,'YData',perframedata);
    end
    fprintf('\n');
end

%plot hcurr for axes_timeline_properties
yLimVal=ylim(handles.axes_timeline_properties);
frameNo = handles.frameNo - handles.maxFrames - 1;
handles.hPropOrig= plot(handles.axes_timeline_properties,[frameNo,frameNo],[yLimVal(1),yLimVal(2)],'g--');
handles.hPropCurr= plot(handles.axes_timeline_properties,[frameNo,frameNo],[yLimVal(1),yLimVal(2)],'k-');
guidata(hObject,handles);

function updatePlots(hObject,handles,frameNo)

  for row = 1:3
    for ndx = 1:handles.numSimilar
      set(handles.hAxes(row,ndx).image,'CData',uint8(handles.im{row,ndx}(:,:,:,frameNo)));
      set(handles.hTimeline(row,ndx).hcurr,'XData', [frameNo,frameNo]);
      set(handles.hPropCurr,'XData',[frameNo- handles.maxFrames - 1,frameNo- handles.maxFrames - 1]);
      updatefly(handles.hAxes(row,ndx).fly,handles.hAxes(row,ndx).flyPred,handles.trx{row,ndx},frameNo, handles.display);
    end
    set(handles.FrameText,'String',sprintf('frame:%d',frameNo-handles.maxFrames-1));
  end

  %update the text_timeline_prop value
  curExperiment = handles.frames{handles.cur_rb_row}(handles.cur_rb_col).expNum;
  curFly = handles.frames{ handles.cur_rb_row}(handles.cur_rb_col).flyNum;
  curTime = handles.frames{ handles.cur_rb_row}(handles.cur_rb_col).curTime+handles.frameNo-handles.maxFrames-1;
  curProp= handles.perframepropis;
  perframedata = handles.JLDobj.GetPerFrameData1(curExperiment,curFly,curProp,curTime);
  s = sprintf('%.3f',perframedata);
  set(handles.text_timeline_prop,'String',s);

%   guidata(hObject,handles);

function play(hObject)
  handles = guidata(hObject);
  frameNo = handles.frameNo;
  ticLoop = tic; % for loop
  ticUpdate = tic; % for updates;
  while(handles.isPlaying)
    if(frameNo-handles.maxFrames>handles.fwdLimit)
      frameNo = handles.maxFrames+1-handles.revLimit;
    else
      frameNo = frameNo+1;
    end
    handles.frameNo = frameNo;
    guidata(hObject,handles);
    tmp = toc(ticLoop);
    if tmp < 1/handles.fps
      pause(1/handles.fps-tmp);
    end
    ticLoop = tic;
    updatePlots(hObject,handles,frameNo);
    set(handles.frameSlider,'Value', 1-frameNo/(2*handles.maxFrames+1));

    if(toc(ticUpdate)>0.5)
      handles = guidata(hObject);
    end
  end
  guidata(hObject,handles);

function [labels predictions]= getLabels(handles,curFrame)
% Reads labels and predictions.

  curExp = curFrame.expNum;
  curFly = curFrame.flyNum;
  curTime = curFrame.curTime;
  sz = handles.maxFrames;

  tSlice = curTime-sz:curTime+sz;
  ltrx = nan(1,length(tSlice));
  labels.ptrx = nan(1,length(tSlice));
  labels.ntrx = nan(1,length(tSlice));

  curLabel = handles.JLDobj.GetLabels(curExp,curFly);
%   windowNdx = find( (handles.JLDobj.windowdata.exp == curExp) & ...
%       (handles.JLDobj.windowdata.flies == curFly) & ...
%       (handles.JLDobj.windowdata.t == curTime) ,1);

  idxcurr = handles.JLDobj.windowdata.exp == curExp & ...
    all(bsxfun(@eq,handles.JLDobj.windowdata.flies,curFly),2) & ...
    handles.JLDobj.windowdata.t >= (curTime-sz) & ...
    handles.JLDobj.windowdata.t <= (curTime+sz) & ...
    handles.JLDobj.windowdata.isvalidprediction;

  tNdx = handles.JLDobj.windowdata.t(idxcurr);
  predictions = nan(1,length(tSlice));
  predictions(  (tNdx-curTime+sz+1) ) = handles.JLDobj.windowdata.predicted(idxcurr);
  curT0 = curLabel.t0s;
  curT1 = curLabel.t1s;
  curNames = curLabel.names;

  for bnum = 1:length(curNames)
    if strcmpi(curNames{bnum},'None')
      curVal = -1;
    else
      curVal = 1;
    end
    curBoutSlice = curT0(bnum):(curT1(bnum)-1);
    overlap = ismember(tSlice,curBoutSlice);
    ltrx(overlap)=curVal;

  end
  labels.ptrx(ltrx>0) = 1;
  labels.ntrx(ltrx<0) = 1;

function relTrax = getRelTrx(handles,curFrame)
  curExp = curFrame.expNum;
  curFly = curFrame.flyNum;
  curTime = curFrame.curTime;
  sz = handles.maxFrames;

  tSlice = curTime-sz:curTime+sz;
  trax = readCache(handles,curExp,curFly,curTime);

  % Rotate
  curA = trax.theta(handles.maxFrames+1)-pi/2;
  trax.theta = trax.theta-curA;

  rotMat = [cos(curA) sin(curA) ; -sin(curA) cos(curA) ];
  trax.X = trax.X - trax.X(sz+1);
  trax.Y = trax.Y - trax.Y(sz+1);
  R = rotMat*[trax.X; trax.Y];
  trax.X = R(1,:); trax.Y = R(2,:);
  trax.X = trax.X + handles.halfSize+1;
  trax.Y = trax.Y + handles.halfSize+1;
  [trax.labels trax.predictions] = getLabels(handles,curFrame);
  relTrax = trax;

function im = getRotatedFrame(handles,curFrame)
% Reads frames around curFrame and rotates them so that the fly is vertical
  sz = handles.halfSize;
  curExp = curFrame.expNum;
  curFly = curFrame.flyNum;
  curTime = curFrame.curTime;
  curTrx = readCache(handles,curExp,curFly,curTime);
  curX = round(curTrx.X(handles.maxFrames+1));
  curY = round(curTrx.Y(handles.maxFrames+1));
  curA = double(curTrx.theta(handles.maxFrames+1));
  tt = zeros(handles.imgY+4*sz,handles.imgX+4*sz,1);
  im = zeros(2*sz+1,2*sz+1,1,2*handles.maxFrames+1);
  bBoxX = (curX-2*sz:curX+2*sz)+2*sz;
  bBoxY = (curY-2*sz:curY+2*sz)+2*sz;

  pointer = [];
  curMovie = handles.JLDobj.GetFile('movie',curExp);
  [pointer.readframe, pointer.nframes, pointer.movie_fid, pointer.movieheaderinfo] = ...
    get_readframe_fcn(curMovie,'interruptible',false);

  for offset = -handles.maxFrames:handles.maxFrames
    if curTime+offset<handles.firstframe{curExp}(curFly) || ...
        curTime+offset>handles.endframe{curExp}(curFly)
      tt(2*sz+1:end-2*sz,2*sz+1:end-2*sz,1) = ...
        zeros(pointer.movieheaderinfo.nr,pointer.movieheaderinfo.nc);
    else
      s = pointer.readframe(curTime+offset);
      tt(2*sz+1:end-2*sz,2*sz+1:end-2*sz,1) = s(:,:,1);
    end
    timg = tt(bBoxY,bBoxX,:);
    rotI = imrotate(timg,curA*180/pi-90,'bilinear','crop');
    im(:,:,:,offset+handles.maxFrames+1) = rotI(sz+1:end-sz,sz+1:end-sz,:);
  end

  if pointer.movie_fid,
    fclose(pointer.movie_fid);
  end

function trx = readCache(handles,expNum,flyNum,curT)

  blk = handles.tsize;
  sz = handles.maxFrames;
  trxOffset = handles.firstframe{expNum}(flyNum);
  blkNumStart = floor( (curT-sz-trxOffset+1)/blk)+1;
  blkNumEnd = ceil( (curT+sz-trxOffset+1)/blk);
  off = curT-trxOffset+1 - (blkNumStart-1)*blk - sz;
  trx.X = []; trx.Y = []; trx.theta = []; trx.maj = []; trx.min = [];
  for curBlk = blkNumStart:blkNumEnd
    idStr = sprintf('%d_%d_%d',expNum,flyNum,curBlk);
    if ~isKey(handles.cache,idStr)
      CacheTracks(handles.output,expNum,flyNum,curT,curT);
      handles = guidata(handles.output);
    end
    curTrx = handles.cache(idStr);
    trx.X = [trx.X curTrx.X];
    trx.Y = [trx.Y curTrx.Y];
    trx.theta = [trx.theta curTrx.theta];
    trx.maj = [trx.maj curTrx.maj];
    trx.min = [trx.min curTrx.min];
  end

  trx.X = trx.X(off:(off+2*sz));
  trx.Y = trx.Y(off:(off+2*sz));
  trx.theta = trx.theta(off:(off+2*sz));
  trx.maj = trx.maj(off:(off+2*sz));
  trx.min = trx.min(off:(off+2*sz));

function axesH = initAxes(handles,ax,sz,row,col)
  axesH.image = [];
  hold(ax,'on');
%   set(ax,'XLimMode','manual');  xlim(ax,[1 2*sz+1]);
%   set(ax,'YLimMode','manual');  ylim(ax,[1 2*sz+1]);

%   set(ax,'XLimMode','manual');  xlim(ax,[1 sz/5*2+1]);
%   set(ax,'YLimMode','manual');  ylim(ax,[1 sz/5*2+1]);
  axesH.image = imagesc(zeros(2*sz+1),'ButtonDownFcn',{@displayOnJLabel,handles,row,col},'Parent',ax,[0,255]);
  axesH.labelNeg = plot(ax,nan,nan,'Linestyle','-','Color',[0.0 0.0 0.7],'Linewidth',3);
  axesH.labelPos = plot(ax,nan,nan,'Linestyle','-','Color',[0.7 0.0 0.0],'Linewidth',3);
  axesH.predNeg = plot(ax,nan,nan,'Linestyle','-','Color',[0.0 0.0 0.7],'Linewidth',3);
  axesH.predPos = plot(ax,nan,nan,'Linestyle','-','Color',[0.7 0.0 0.0],'Linewidth',3);
  axesH.trax = plot(ax,nan,nan,'Linestyle','-','Marker','.',...
    'Color',[0.1 0.1 0.1],'MarkerSize',4,'Linewidth',0.1);
  axesH.fly = plot(ax,nan,nan,'Linestyle','-','Color',[0.7 0.2 0.2]);
  axesH.flyPred = plot(ax,nan,nan,'Linestyle','-','Color',[0.7 0.2 0.2],...
    'Linewidth',2);
  colormap(ax,'gray');
  axis(ax,'off','image');

function tlH = initTimeline(tl,sz,tcurr)
  tlH.image = [];
  hold(tl,'on');
  %set(tl,'XLimMode','auto');  xlim(tl,[1 2*sz+1]);
  %set(tl,'YLimMode','auto');  ylim(tl,[1 2*sz+1]);
  tlH.image = imagesc(zeros(2,2*sz+1,3),'Parent',tl,[0,255]);
  tlH.horig = plot(tl,[tcurr,tcurr],[0.5, 2.5],'g--','HitTest','off','linewidth',2);
  tlH.hcurr = plot(tl,[tcurr,tcurr],[0.5, 2.5],'y-','HitTest','off','linewidth',2);
  colormap(tl,'gray');
  axis(tl,'on','image');
  set(tl,'XTick',[]);
  set(tl,'YTick',[]);

function updatefly(h1,h2,trx,t,displayChoice)
% Coped from JCtrax.

% draw an isosceles triangle with center (x,y)
% with rotation theta
% with height maj*4
% with base min*4

x = trx.X(t);
y = trx.Y(t);
theta = trx.theta(t);
maj = trx.maj(t);
min = trx.min(t);

% isosceles triangle not yet rotated or centered
pts = [-maj*2,-min*2,
       -maj*2,min*2,
       maj*2,0];

% rotate
costheta = cos(theta);
sintheta = sin(theta);
R = [costheta,sintheta;-sintheta,costheta];
pts = pts*R;

% translate
pts(:,1) = pts(:,1) + x;
pts(:,2) = pts(:,2) + y;

switch displayChoice
    case 'automatic'
        if isnan(trx.predictions(t)), colr = [0.2 0.2 0.2];
        elseif trx.predictions(t)<1.5, colr = [0.7 0.0 0.0];
        else colr = [0.0 0.0 0.7];
        end
    case 'manual'
        colr = [0.2 0.2 0.2];
        if ~isnan(trx.labels.ptrx(t)); colr = [0.7 0.0 0.0]; end
        if ~isnan(trx.labels.ntrx(t)); colr = [0.0 0.0 0.7]; end
    otherwise
        colr = [0.2 0.2 0.2];
end

% plot for labels.
set(h1,'xdata',pts([1 2],1),'ydata',pts([1 2],2));
set(h1,'Color',colr);
set(h2,'xdata',pts([2 3 1], 1),'ydata',pts([2 3 1],2));
set(h2,'Color',colr);

% --- Executes on slider movement.
function frameSlider_Callback(hObject, eventdata, handles)
% hObject    handle to frameSlider (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider
v = 1-get(hObject,'Value');
handles.frameNo = round(v*(2*handles.maxFrames))+1;
guidata(hObject,handles);
updatePlots(hObject,handles,handles.frameNo);

% --- Executes during object creation, after setting all properties.
function frameSlider_CreateFcn(hObject, eventdata, handles)
% hObject    handle to frameSlider (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end

% --- Executes on button press in pushbutton1.
function pushbutton1_Callback(hObject, eventdata, handles)
% hObject    handle to pushbutton1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% --- Executes on button press in playButton.
function playButton_Callback(hObject, eventdata, handles)
% hObject    handle to playButton (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hint: get(hObject,'Value') returns toggle state of playButton
if(get(hObject,'Value'));
  handles.isPlaying = true;
  guidata(hObject,handles);
  play(hObject);
else
  handles.isPlaying = false;
  guidata(hObject,handles);
end

% --- Executes when user attempts to close figure_showSimilarFrames.
function figure_showSimilarFrames_CloseRequestFcn(hObject, eventdata, handles)
% hObject    handle to figure_showSimilarFrames (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hint: delete(hObject) closes the figure

handles.JLDobj.frameFig = [];
delete(hObject);

function fps_edit_Callback(hObject, eventdata, handles)
% hObject    handle to fps_edit (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of fps_edit as text
%        str2double(get(hObject,'String')) returns contents of fps_edit as a double
v = str2num(get(hObject,'String'));
if (isempty(v))||(v >handles.maxfps|| v < 0)
    error('frame range requires 1 argument that is a number between 0 and maxfps ');
end

handles.fps = round(v);
guidata(hObject,handles);

% --- Executes during object creation, after setting all properties.
function fps_edit_CreateFcn(hObject, eventdata, handles)
% hObject    handle to fps_edit (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

function frame_range_edit_Callback(hObject, eventdata, handles)
% hObject    handle to frame_range_edit (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of frame_range_edit as text
%        str2double(get(hObject,'String')) returns contents of frame_range_edit as a double

frameRanges = str2double(get(hObject,'String'));
if (isempty(frameRanges))||(frameRanges >handles.maxFrames||frameRanges < 0)
    warnStr = sprintf('Frame range requires 1 argument that is a number between 0 and %d, please reinput a value.\n',handles.maxFrames);
    warndlg(warnStr,'frame range is out of range');
else

    handles.revLimit = round(frameRanges);
    handles.fwdLimit = handles.revLimit;

    guidata(hObject,handles);
end

% --- Executes during object creation, after setting all properties.
function frame_range_edit_CreateFcn(hObject, eventdata, handles)
% hObject    handle to frame_range_edit (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes on selection change in timeline_properties.
function timeline_properties_Callback(hObject, eventdata, handles)
% hObject    handle to timeline_properties (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: contents = cellstr(get(hObject,'String')) returns timeline_properties contents as cell array
%        contents{get(hObject,'Value')} returns selected item from
%        timeline_properties

v = get(hObject,'Value');
s = handles.timeline_prop_options{v};

prop = find(strcmpi(s,handles.JLDobj.allperframefns),1);
handles.perframepropis = prop;
handles.perframeprops = s;

[perframedata,T0,T1] = handles.JLDobj.GetPerFrameData(handles.frames{2}.expNum,handles.frames{2}.flyNum,prop);
TStart = handles.frames{2}.curTime - handles.maxFrames;
IStart = TStart - T0 + 1;
TEnd = handles.frames{2}.curTime + handles.maxFrames;
IEnd = TEnd - T0 + 1;
perframedata = perframedata(IStart:IEnd);
set(handles.htimeline_propdata{2},'XData',-handles.maxFrames:handles.maxFrames,'YData',perframedata);

%reset Ymax and Ymin value for current time and original time
set(handles.hPropOrig,'YData',[0,0]);
set(handles.hPropCurr,'XData',[0,0],'YData',[0,0]);

%for row 1 and 3
for row = [1 3]
    for ndx = 1:handles.numSimilar
        [perframedata,T0,T1] = handles.JLDobj.GetPerFrameData(handles.frames{row}(ndx).expNum,handles.frames{row}(ndx).flyNum,prop);
        TStart = handles.frames{row}(ndx).curTime - handles.maxFrames;
        IStart = TStart - T0 + 1;
        TEnd = handles.frames{row}(ndx).curTime + handles.maxFrames;
        IEnd = TEnd - T0 + 1;
        perframedata = perframedata(IStart:IEnd);
        set(handles.htimeline_propdata{row}(ndx),'XData',-handles.maxFrames:handles.maxFrames,'YData',perframedata);
    end
    fprintf('\n');
end

%plot hcurr for axes_timeline_properties
yLimVal=ylim(handles.axes_timeline_properties);
frameNo = handles.frameNo - handles.maxFrames - 1;
set(handles.hPropOrig,'YData',[yLimVal(1),yLimVal(2)]);
set(handles.hPropCurr,'XData',[frameNo,frameNo],'YData',[yLimVal(1),yLimVal(2)]);

%update the text_timeline_prop value
curExperiment = handles.frames{handles.cur_rb_row}(handles.cur_rb_col).expNum;
curFly = handles.frames{ handles.cur_rb_row}(handles.cur_rb_col).flyNum;
curTime = handles.frames{ handles.cur_rb_row}(handles.cur_rb_col).curTime+handles.frameNo-handles.maxFrames-1;
curProp= handles.perframepropis;
perframedata = handles.JLDobj.GetPerFrameData1(curExperiment,curFly,curProp,curTime);
s = sprintf('%.3f',perframedata);
set(handles.text_timeline_prop,'String',s);

guidata(hObject,handles);

% --- Executes during object creation, after setting all properties.
function timeline_properties_CreateFcn(hObject, eventdata, handles)
% hObject    handle to timeline_properties (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes when selected object is changed in uipanel6.
function uipanel6_SelectionChangeFcn(hObject, eventdata, handles)
% hObject    handle to the selected object in uipanel6
% eventdata  structure with the following fields (see UIBUTTONGROUP)
%	EventName: string 'SelectionChanged' (read only)
%	OldValue: handle of the previously selected object or empty if none was selected
%	NewValue: handle of the currently selected object
% handles    structure with handles and user data (see GUIDATA)
switch get(eventdata.NewValue,'Tag') % Get Tag of selected object.
    case 'rb_automatic'
        for row = 1:3
            for ndx = 1:handles.numSimilar

                % Code for when rb_top1 is selected.
                handles.display = 'automatic';
                set(handles.hAxes(row,ndx).predPos,'visible', 'on');
                set(handles.hAxes(row,ndx).predNeg,'visible', 'on');
                set(handles.hAxes(row,ndx).labelPos,'visible', 'off');
                set(handles.hAxes(row,ndx).labelNeg,'visible', 'off');

            end
        end

    case 'rb_manual'
        for row = 1:3
            for ndx = 1:handles.numSimilar
                handles.display = 'manual';
                set(handles.hAxes(row,ndx).labelPos,'visible', 'on');
                set(handles.hAxes(row,ndx).labelNeg,'visible', 'on');
                set(handles.hAxes(row,ndx).predPos,'visible', 'off');
                set(handles.hAxes(row,ndx).predNeg,'visible', 'off');
            end
        end
    otherwise
        % Code for when there is no match.
end
updatePlots(hObject,handles,handles.frameNo);
guidata(hObject,handles);

function displayOnJLabel(obj,eventdata,handles,row,col)

switch get(gcf,'selectiontype')
    case 'normal'
        % LEFT CLICK
    case 'extend'
        % SHIFT-CLICK LEFT (or L/R simultaneous)
    case 'alt'
        % CTRL-CLICK LEFT (or RIGHT-CLICK)
    case 'open'
        % DOUBLE-CLICK

        handles=guidata(obj);
        JLabelHandles = handles.JLabelHandles;
        figure(JLabelHandles.figure_JLabel);
        if row==2
            JLabelHandles.guidata.ts(1) =999; % in order to update plot in SetCurrentFrame
            handles.curExp = handles.frames{2}.expNum;
            [JLabelHandles,~] = JLabel('SetCurrentMovie',JLabelHandles,handles.curExp);
            handles.curFly = handles.frames{2}.flyNum;
            JLabelHandles = JLabel('SetCurrentFlies',JLabelHandles,handles.curFly);
            handles.curFrame = handles.frames{2}.curTime;
            JLabelHandles = JLabel('SetCurrentFrame',JLabelHandles,1,handles.curFrame,obj);
            guidata(obj, handles);
        else
            handles.curExp = handles.frames{row}(col).expNum;
            [JLabelHandles,~] = JLabel('SetCurrentMovie',JLabelHandles,handles.curExp);
            handles.curFly = handles.frames{row}(col).flyNum;
            JLabelHandles = JLabel('SetCurrentFlies',JLabelHandles,handles.curFly);
            handles.curFrame = handles.frames{row}(col).curTime;
            JLabelHandles = JLabel('SetCurrentFrame',JLabelHandles,1,handles.curFrame,obj);
            guidata(obj, handles);
        end

end

% --- Executes when selected object is changed in chooseFrame.
function chooseFrame_SelectionChangeFcn(hObject, eventdata, handles)
% hObject    handle to the selected object in chooseFrame
% eventdata  structure with the following fields (see UIBUTTONGROUP)
%	EventName: string 'SelectionChanged' (read only)
%	OldValue: handle of the previously selected object or empty if none was selected
%	NewValue: handle of the currently selected object
% handles    structure with handles and user data (see GUIDATA)

old_rb_row = handles.cur_rb_row;
old_rb_col = handles.cur_rb_col;

switch get(eventdata.NewValue,'Tag') % Get Tag of selected object.
    case 'rb_top1'
        % Code for when rb_top1 is selected.
        handles.cur_rb_row = 1;
        handles.cur_rb_col = 1;
    case 'rb_top2'
        % Code for when radiobutton2 is selected.
        handles.cur_rb_row = 1;
        handles.cur_rb_col = 2;
    case 'rb_top3'
        handles.cur_rb_row = 1;
        handles.cur_rb_col = 3;

    case 'rb_top4'
        handles.cur_rb_row = 1;
        handles.cur_rb_col = 4;

    case 'rb_middle1'
        handles.cur_rb_row = 2;
        handles.cur_rb_col = 1;
    case 'rb_bottom1'
        handles.cur_rb_row = 3;
        handles.cur_rb_col = 1;
    case 'rb_bottom2'
        handles.cur_rb_row = 3;
        handles.cur_rb_col = 2;

    case 'rb_bottom3'
        handles.cur_rb_row = 3;
        handles.cur_rb_col = 3;
    case 'rb_bottom4'
        handles.cur_rb_row = 3;
        handles.cur_rb_col = 4;

    otherwise
        % Code for when there is no match.
end

%change the color of the old lines to blue
set(handles.htimeline_propdata{old_rb_row}(old_rb_col),'LineWidth',0.5);

%change the color of the new line to red
set(handles.htimeline_propdata{handles.cur_rb_row}(handles.cur_rb_col),'LineWidth',2);

%update the text_timeline_prop value
curExperiment = handles.frames{handles.cur_rb_row}(handles.cur_rb_col).expNum;
curFly = handles.frames{ handles.cur_rb_row}(handles.cur_rb_col).flyNum;
curTime = handles.frames{ handles.cur_rb_row}(handles.cur_rb_col).curTime+handles.frameNo-handles.maxFrames-1;
curProp= handles.perframepropis;
perframedata = handles.JLDobj.GetPerFrameData1(curExperiment,curFly,curProp,curTime);
s = sprintf('%.3f',perframedata);
set(handles.text_timeline_prop,'String',s);

guidata(hObject,handles);

function plotPredictionsInAutoMode(handles)
%for row 2

relTrx = getRelTrx(handles,handles.frames{2});
[labels predictions] = getLabels(handles,handles.frames{2});
ptrx = nan(size(predictions));
ntrx = nan(size(predictions));

for i=1:length(ptrx)
    if predictions(i)<1.5
        ptrx(i) = 1;
    else
        ntrx(i) = 1;
    end
end

%To fill in the gap between the lablePos and labelNeg
diffPred = diff(predictions);
changeIdex = find(diffPred);

for i = 1:length(changeIdex)
    if diffPred(changeIdex(i))<0 %negative -> positive
        ntrx(changeIdex(i)+1) = 1;
    else %positive -> negative
        ptrx(changeIdex(i)+1) = 1;
    end
end

for ndx = 1:handles.numSimilar
    handles.trx{2,ndx} = relTrx;
    set(handles.hAxes(2,ndx).predPos,'XData',relTrx.X.*ptrx,'Ydata',relTrx.Y.*ptrx);
    set(handles.hAxes(2,ndx).predNeg,'XData',relTrx.X.*ntrx,'Ydata',relTrx.Y.*ntrx);
end

%for row 1 and 3
for row = [1 3]
    for ndx = 1:handles.numSimilar
        relTrx = getRelTrx(handles,handles.frames{row}(ndx));
        handles.trx{row,ndx} = relTrx;
        [labels predictions] = getLabels(handles,handles.frames{row}(ndx));

        ptrx = nan(size(predictions));
        ntrx = nan(size(predictions));

        for i=1:length(ptrx)
            if predictions(i)<1.5
                ptrx(i) = 1;
            else
                ntrx(i) = 1;
            end
        end

        diffPred = diff(predictions);
        changeIdex = find(diffPred);

        for i = 1:length(changeIdex)
            if diffPred(changeIdex(i))<0 %negative -> positive
                ntrx(changeIdex(i)+1) = 1;
            else %positive -> negative
                ptrx(changeIdex(i)+1) = 1;
            end
        end

        set(handles.hAxes(row,ndx).predPos,'XData',relTrx.X.*ptrx,'YData',relTrx.Y.*ptrx);
        set(handles.hAxes(row,ndx).predNeg,'XData',relTrx.X.*ntrx,'YData',relTrx.Y.*ntrx);
    end
end

function poltLabelsInManualMode(handles)

%for row 2
relTrx = getRelTrx(handles,handles.frames{2});
[labels predictions] = getLabels(handles,handles.frames{2});
ptrx = labels.ptrx;
ntrx = labels.ntrx;

for ndx = 1:handles.numSimilar
    handles.trx{2,ndx} = relTrx;
    set(handles.hAxes(2,ndx).labelPos,'XData',relTrx.X.*ptrx,'Ydata',relTrx.Y.*ptrx);
    set(handles.hAxes(2,ndx).labelNeg,'XData',relTrx.X.*ntrx,'Ydata',relTrx.Y.*ntrx);
end

%for row 1 and 3
for row = [1 3]
    for ndx = 1:handles.numSimilar
        relTrx = getRelTrx(handles,handles.frames{row}(ndx));
        handles.trx{row,ndx} = relTrx;
        [labels predictions] = getLabels(handles,handles.frames{row}(ndx));
        ptrx = labels.ptrx;
        ntrx = labels.ntrx;

        set(handles.hAxes(row,ndx).labelPos,'XData',relTrx.X.*ptrx,'YData',relTrx.Y.*ptrx);
        set(handles.hAxes(row,ndx).labelNeg,'XData',relTrx.X.*ntrx,'YData',relTrx.Y.*ntrx);
    end
end

% --- Executes on key press with focus on figure_showSimilarFrames and none of its controls.
function figure_showSimilarFrames_KeyPressFcn(hObject, eventdata, handles)
% hObject    handle to figure_showSimilarFrames (see GCBO)
% eventdata  structure with the following fields (see FIGURE)
%	Key: name of the key that was pressed, in lower case
%	Character: character interpretation of the key(s) that was pressed
%	Modifier: name(s) of the modifier key(s) (i.e., control, shift) pressed
% handles    structure with handles and user data (see GUIDATA)
switch eventdata.Key,

  case 'leftarrow',
      go_previous_frame(hObject, handles);

  case 'rightarrow',
      go_next_frame(hObject, handles);

end

function go_next_frame(hObject, handles)
% hObject    handle to menu_go_next_frame (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% TODO: make this work with multiple preview axes

frameNo = handles.frameNo;
if(frameNo-handles.maxFrames>handles.fwdLimit)
    frameNo = handles.maxFrames+1-handles.revLimit;
else
    frameNo = frameNo+1;
end

handles.frameNo = frameNo;

updatePlots(hObject,handles,frameNo);
set(handles.frameSlider,'Value', 1-frameNo/(2*handles.maxFrames+1));

guidata(hObject,handles);

% --------------------------------------------------------------------
function go_previous_frame(hObject, handles)
% hObject    handle to menu_go_previous_frame (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

frameNo = handles.frameNo;
if(handles.maxFrames - frameNo>= handles.revLimit - 1)
    frameNo = handles.maxFrames + handles.fwdLimit + 1;
else
    frameNo = frameNo-1;
end

handles.frameNo = frameNo;

updatePlots(hObject,handles,frameNo);
set(handles.frameSlider,'Value', 1-frameNo/(2*handles.maxFrames+1));

guidata(hObject,handles);

% --- Executes on mouse press over figure background.
function figure_showSimilarFrames_ButtonDownFcn(hObject, eventdata, handles)
% hObject    handle to figure_showSimilarFrames (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% --------------------------------------------------------------------
function zoomOut_ClickedCallback(hObject, eventdata, handles)
% hObject    handle to zoomOut (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
oldLimVal = get(handles.middle1,'xLim');
mag = (oldLimVal(2) - oldLimVal(1))/(handles.halfSize*0.4);

if mag<=4
    mag = mag +1;
else
    mag=5;
end
limVal = [handles.halfSize*(1-mag/5), handles.halfSize*(1+mag/5)];
set(handles.middle1,'xLim',limVal,'yLim',limVal);

% --------------------------------------------------------------------
function zoomIn_ClickedCallback(hObject, eventdata, handles)
% hObject    handle to zoomIn (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
oldLimVal = get(handles.middle1,'xLim');
mag = (oldLimVal(2) - oldLimVal(1))/(handles.halfSize*0.4);

if mag>=2
    mag = mag-1;
else
    mag=1;
end
limVal = [handles.halfSize*(1-mag/5), handles.halfSize*(1+mag/5)];
set(handles.middle1,'xLim',limVal,'yLim',limVal);


classdef MimMarkerMagenta < MimGuiPlugin
    % MimMarkerMagenta. Gui Plugin for switching transparency of zero
    %
    %     You should not use this class within your own code. It is intended to
    %     be used by the gui of the TD MIM Toolkit.
    %
    %     MimMarkerMagenta is a Gui Plugin for the MIM Toolkit.
    %
    %
    %     Licence
    %     -------
    %     Part of the TD MIM Toolkit. https://github.com/tomdoel
    %     Author: Tom Doel, Copyright Tom Doel 2014.  www.tomdoel.com
    %     Distributed under the MIT licence. Please see website for details.
    %

    properties
        ButtonText = 'Magenta'
        SelectedText = 'Magenta'
        ToolTip = 'Place magenta markers'
        Category = 'Marker colour'
        Visibility = 'Dataset'
        Mode = 'Markers'

        HidePluginInDisplay = false
        PTKVersion = '1'
        ButtonWidth = 5
        ButtonHeight = 1
        Icon = 'single_marker.png'
        IconColour = GemMarkerPoint.DefaultColours{5}
        Location = 5
    end

    methods (Static)
        function RunGuiPlugin(gui_app)
            gui_app.ImagePanel.NewMarkerColour = 5;
        end

        function enabled = IsEnabled(gui_app)
            enabled = gui_app.IsDatasetLoaded && isequal(gui_app.GetCurrentModeName, MimModes.MarkerMode);
        end

        function is_selected = IsSelected(gui_app)
            is_selected = gui_app.ImagePanel.NewMarkerColour == 5;
        end
    end
end


function sSubject = db_parse_subject( subjectsDir, subjectSubDir, sizeProgress )
% DB_PARSE_SUBJECT: Parse a subject directory.
%
% USAGE:  sSubject = db_parse_subject(subjectsDir, subjectSubDir, sizeProgress);
%
% INPUT:
%     - subjectsDir   : Top-level subjects directory (protocol/anatomy/)
%     - subjectSubDir : Study subdirectory
%     - sizeProgress : The full process increments the available progress bar by this amount
% OUTPUT:
%     - sSubject : array of Subject structures (one entry for each subdirectory with a brainstormsubject file),
%                  or [] if no brainstormsubject.mat file was found in directory.

% NOTES:
%    In a subject subdirectory there is :
%        - One and only one 'brainstormsubject*.mat' file,
%        - All the files associated with this subject :
%             - Surfaces (cortex, scalp, inner skull, outer skull, ...)
%             - Anatomical MRI
%    All the .MAT files in the directory are associated with the subject.
%    The files that are pointed by the 'Anatomy' and surfaces fields
%        ('Scalp','Cortex',...) in the brainstormsubject.mat file are the
%        current files for each category.
%
% WARNING: This function ignores the <DirDefaultSubject> directories.
%          These directories must be parsed independently.
%
% @=============================================================================
% 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, 2008-2021

%% ===== PARSE INPUTS =====
if ~file_exist(bst_fullfile(subjectsDir, subjectSubDir))
    error('Path in argument does not exist, or is not a directory');
end
if (nargin < 3) || isempty(sizeProgress)
    sizeProgress = [];
end

%% ===== LOOK FOR SUBJECT DEFINITION =====
% List 'brainstormsubject*.mat' files in the subjectSubDir directory
subjectFiles = dir(bst_fullfile(subjectsDir, subjectSubDir, 'brainstormsubject*.mat'));
% If there is no subject definition file : ignore the directory
if isempty(subjectFiles)
    subjMat = [];
% If there is more that one subject definition file : warning and ignore the directory
elseif (length(subjectFiles) > 1)
    subjMat = [];
    bst_error(sprintf('There is more than one brainstormsubject file in directory ''%s'' : ignoring directory.', bst_fullfile(subjectsDir, subjectSubDir)), ...
             'Parsing subject directory', 0);
% Else there is only one file in subjectFiles list
else
    subjectFile = subjectFiles(1);
    % Open file and get subject description
    try
        subjMat = load(bst_fullfile(subjectsDir, subjectSubDir, subjectFile.name));
    catch
        warning('Brainstorm:CannotOpenFile', 'Cannot open file ''%s'' : ignoring subject', bst_fullfile(subjectsDir, subjectSubDir, subjectFile.name));
        subjMat = [];
    end
end

%% ===== READ SUBJECT DESCRIPTION =====
% Copy fields of loaded subject matrix into the sSubject structure (output variable)
% IMPORTANT : All the filenames in the subject description matrix
%             must be relative to the SUBJECTS directory.
if ~isempty(subjMat)
    % Define structure templates
    sSubject = db_template('Subject');
    % Store subject's .MAT filename
    sSubject.FileName = bst_fullfile(subjectSubDir, subjectFile.name);
    sSubject.Name     = subjectSubDir;
    % Comments
    if isfield(subjMat, 'Comments')
        sSubject.Comments = subjMat.Comments;
    end
    % DateOfAcquisition
    if isfield(subjMat, 'DateOfAcquisition')
        sSubject.DateOfAcquisition = subjMat.DateOfAcquisition;
    end
    % UseDefaultAnat
    if isfield(subjMat, 'UseDefaultAnat')
        sSubject.UseDefaultAnat = subjMat.UseDefaultAnat;
    end
    % UseDefaultChannel
    if isfield(subjMat, 'UseDefaultChannel')
        sSubject.UseDefaultChannel = subjMat.UseDefaultChannel;
    end
else
    % Define structure templates
    sSubject = repmat(db_template('Subject'), 0);
end

%% ===== READ ALL FILES IN FOLDER =====
% List all '*.mat' files in the subjectSubDir directory
allFiles = dir(bst_fullfile(subjectsDir, subjectSubDir, '*'));
% Exclude files starting with a '.'
dirFiles = repmat(allFiles,0);
for iFile = 1:length(allFiles)
    if (allFiles(iFile).name(1) ~= '.')
        dirFiles(end+1,1) = allFiles(iFile);
    end
end
if isempty(dirFiles)
    return;
end
% Progress bar?
isProgressBar = bst_progress('isvisible') && ~isempty(sizeProgress);
startValue = bst_progress('get');
% Process all the files
for iFile = 1:length(dirFiles)
    % Increment progress bar
    if isProgressBar
        bst_progress('set',  round(startValue + iFile/length(dirFiles) * sizeProgress));
    end
    % Process sub-directories recursively (excluding the 'DirDefaultSubject' directories)
    if (dirFiles(iFile).isdir) && ~strcmpi(dirFiles(iFile).name, bst_get('DirDefaultSubject'))
        % Append subjects in the subdirectory to the sSubject array
        sSubject = [sSubject, db_parse_subject(subjectsDir, bst_fullfile(subjectSubDir, dirFiles(iFile).name))];
    % Files (only if there is a brainstormsubject file in current directory)
    elseif ~isempty(subjMat)
        % Reconstruct filename
        filenameRelative = bst_fullfile(subjectSubDir, dirFiles(iFile).name);
        filenameFull     = bst_fullfile(subjectsDir, subjectSubDir, dirFiles(iFile).name);
        % Skip "_openmeeg.mat" files (related with the BEM computation)
        if ~isempty(strfind(filenameRelative, '_openmeeg.mat'))
            continue;
        end
        % Determine filetype
        fileType = file_gettype(filenameFull);
        if isempty(fileType)
            continue;
        end
        % Process file
        switch(fileType)
            case 'subjectimage'
                % Try to load Anatomy info
                tempAnatomy = io_getAnatomyInfo(filenameRelative);
                % If an anatomy was identified : add it to the subject structure
                if ~isempty(tempAnatomy)
                    sSubject(1).Anatomy(end+1) = tempAnatomy;
                end
            case {'scalp', 'cortex', 'innerskull', 'outerskull', 'fibers', 'fem', 'tess'}
                tempSurface = io_getSurfaceInfo(filenameRelative);
                if ~isempty(tempSurface)
                    switch(fileType)
                        case 'scalp',       tempSurface.SurfaceType = 'Scalp';
                        case 'cortex',      tempSurface.SurfaceType = 'Cortex';
                        case 'innerskull',  tempSurface.SurfaceType = 'InnerSkull';
                        case 'outerskull',  tempSurface.SurfaceType = 'OuterSkull';
                        case 'fibers',      tempSurface.SurfaceType = 'Fibers';
                        case 'fem',         tempSurface.SurfaceType = 'FEM';
                        case 'tess',        tempSurface.SurfaceType = 'Other';
                    end
                    sSubject(1).Surface(end+1) = tempSurface;
                end
        end
    end
end
% Set progress bar to the end
if isProgressBar
    bst_progress('set', round(startValue + sizeProgress));
end

%% ===== DEFAULT ANATOMY/SURFACES =====
% Sort anatomy files: first T1 and MRI, then volume atlases
if ~isempty(sSubject) && (length(sSubject(1).Anatomy) >= 2)
    iNoAtlas = find(cellfun(@(c)isempty(strfind(c, '_volatlas')), {sSubject(1).Anatomy.FileName}));
    if (length(iNoAtlas) >= 2)
        iT1 = find(~cellfun(@(c)isempty([strfind(lower(c), '_t1'), strfind(lower(c), '_mri')]), {sSubject(1).Anatomy(iNoAtlas).FileName}));
        iNoAtlas = [iNoAtlas(iT1), setdiff(iNoAtlas, iNoAtlas(iT1))];
    end
    iSort = [iNoAtlas, setdiff(1:length(sSubject(1).Anatomy), iNoAtlas)];
    sSubject(1).Anatomy = sSubject(1).Anatomy(iSort);
end
% If there is a brainstormsubject.mat
if ~isempty(subjMat)
    % The brainstormsubject.mat can define what are the defaults files for the different
    % file categories : Anatomy, Scalp, Cortex, InnerSkull, OuterSkull, Fibers, FEM
    % Now we are going to read those brainstormsubject fields look for
    % the pointed files in the sSubject structure

    % ==== ANATOMY ====
    % By default : use the first anatomy in list (which is not a volume atlas)
    if ~isempty(sSubject(1).Anatomy)
        sSubject(1).iAnatomy = 1;
    else
        sSubject(1).iAnatomy = [];
    end
    % Anatomy field : filename of the current/default Anatomy for this subject (subjectimage.mat)
    % => If there are more than one MRI in subject directory,
    %    this one will be used by default for all the processes.
    if (isfield(subjMat, 'Anatomy') && ~isempty(subjMat.Anatomy))
        ind = find(file_compare({sSubject(1).Anatomy.FileName}, subjMat.Anatomy), 1);
        if ~isempty(ind)
            % Reorder anatomy entries
            sSubject(1).Anatomy = sSubject(1).Anatomy([ind, setdiff(1:length(sSubject(1).Anatomy), ind)]);
            sSubject(1).iAnatomy = 1;
        end
    end

    % ==== SURFACE ====
    % Sort surfaces by categories
    subjectSurfaces = db_surface_sort(sSubject(1).Surface);
    % Select one surface in each category
    for surfaceCatergory = {'Scalp', 'Cortex', 'InnerSkull', 'OuterSkull', 'Fibers', 'FEM'}
        % By default : use the last surface in list
        if ~isempty(subjectSurfaces.(surfaceCatergory{1}))
            sSubject(1).(['i' (surfaceCatergory{1})]) = subjectSurfaces.(['Index' surfaceCatergory{1}])(end);
        else
            sSubject(1).(['i' (surfaceCatergory{1})]) = [];
        end
        % If a default was defined in the brainstormsubject*.mat
        if (isfield(subjMat, surfaceCatergory{1}) && ~isempty(subjMat.(surfaceCatergory{1})) && ischar(subjMat.(surfaceCatergory{1})))
            ind = find(file_compare({sSubject(1).Surface.FileName}, subjMat.(surfaceCatergory{1})), 1);
            if (ind > 0)
                sSubject(1).(['i' (surfaceCatergory{1})]) = ind;
            end
        end
    end
end

%% ===================================================================================
%  === HELPER FUNCTIONS ==============================================================
%  ===================================================================================
    % Load an Anatomy from a relative filename
    % Return an Anatomy structure :
    %    |- FileName
    %    |- Comment
    % or an empty structure if anatomy is not or badly defined.
    function Anatomy = io_getAnatomyInfo(relativeFilename)
        Anatomy = repmat(db_template('Anatomy'), 0);
        % Check if the file exists, and load Comment field
        if file_exist(bst_fullfile(subjectsDir, relativeFilename))
            try
                % Load MRI 'Comment' field from SUBJECTIMAGE.MAT
                warning off MATLAB:load:variableNotFound;
                anatomyMat = load(bst_fullfile(subjectsDir, relativeFilename), 'Comment');
                warning on MATLAB:load:variableNotFound;
                % If 'Comment' not defined or empty
                if (length(fieldnames(anatomyMat)) == 0)
                    [fpath, basename] = bst_fileparts(relativeFilename);
                    Anatomy(1).Comment = basename;
                else
                    Anatomy(1).Comment = anatomyMat.Comment;
                end
                Anatomy(1).FileName = relativeFilename;
            catch
                % An error occured during the 'load' operation
                warning('Brainstorm:CannotOpenFile', 'Cannot open anatomy file ''%s''.', bst_fullfile(subjectsDir, relativeFilename));
            end
        else
            % File does not exist
            warning('Brainstorm:FileNotFound', 'Anatomy file ''%s'' was not found.', bst_fullfile(subjectsDir, relativeFilename));
        end
    end

    % Load a surface from a relative filename
    % surfaceType = {'Scalp', 'Cortex', 'InnerSkull', 'OuterSkull', 'Fibers', 'FEM'}
    % Return a Surface structure :
    %    |- FileName
    %    |- Comment
    % or an empty structure if surface is not or badly defined
    function Surface = io_getSurfaceInfo(relativeFilename)
        Surface = repmat(db_template('Surface'), 0);
        % Check if the file exists, and load Comment field
        if file_exist(bst_fullfile(subjectsDir, relativeFilename))
            try
                % Load Surface 'Comment' field from SUBJECTIMAGE.MAT
                surfaceMat = load(bst_fullfile(subjectsDir, relativeFilename), 'Comment');
                % If 'Comment' not defined or empty
                if (isempty(surfaceMat))
                    Surface(1).Comment = '';
                elseif iscell(surfaceMat.Comment)
                    Surface(1).Comment = surfaceMat.Comment{1};
                elseif ischar(surfaceMat.Comment)
                    Surface(1).Comment = surfaceMat.Comment;
                end
                Surface(1).FileName = relativeFilename;
            catch
                % An error occured during the 'load' operation
                warning('Brainstorm:CannotOpenFile', 'Cannot open surface file ''%s''. Adding ''.bak'' to the filename.', relativeFilename);
                fullfilename = bst_fullfile(subjectsDir, relativeFilename);
                file_move(fullfilename, [fullfilename, '.bak']);
            end
        else
            % File does not exist
            warning('Brainstorm:FileNotFound', 'Surface file ''%s'' was not found.', bst_fullfile(subjectsDir, relativeFilename));
        end
    end
end


%PlotBrowser_Test
%
% Author: Jonathan Karr, jkarr@stanford.edu
% Affilitation: Covert Lab, Department of Bioengineering, Stanford University
% Last updated: 8/6/2011
classdef PlotBrowser_Test < TestCase
    properties
        simulation
    end

    methods
        function this = PlotBrowser_Test(name)
            this = this@TestCase(name);
        end
    end

    methods
        function setUp(this)
            this.simulation = edu.stanford.covert.cell.sim.SimulationFixture.load([], true);
        end

        function tearDown(~)
            close('all');
        end
    end

    methods
        function test(this)
            import edu.stanford.covert.cell.sim.analysis.PlotBrowser;
            browser = PlotBrowser(this.simulation);

            %test all plots
            messages = {};
            for i = 1:numel(browser.plotNames)
                try
                    browser.selectPlots(browser.plotNames{i});
                catch exception
                    messages{end+1} = sprintf('%20s: %s', browser.plotFunctions{i}, exception.message); %#ok<AGROW>
                end
            end
            assert(isempty(messages), ...
                sprintf('The following plot methods failed:\n%s',strjoin(sprintf('\n'),messages{:})));

            %close browser
            browser.close();
        end
    end
end


function vw = initHiddenGray(dataType,scan,roi);
%
% function hiddenGray = initHiddenGray([dataType],[scan],[roi])
%
% djh, sometime in 1999
% ras, 04/05, set name to 'hidden'
% ras, 05/05, added 'curScan' field
% ras, 09/05, ensures session is loaded,
% mrGlobals is run; can specify data type,
% scan, and roi at outset.

%TODO: Decide if we want to add a 'loadAnat' to the initHiddenGray

if ~exist('dataType','var') || isempty(dataType)
    dataType = 1;
end

if ~exist('scan','var') || isempty(scan)
    scan = 1;
end

evalin('base','mrGlobals');
evalin('base','HOMEDIR = pwd;');
evalin('base','loadSession');

verbose = prefsVerboseCheck;
if verbose, disp('Initializing HIDDEN Gray view'); end

vw.name     ='hidden';
vw.viewType ='Gray';
vw.subdir   ='Gray';

% Initialize slots for anat co, amp, and ph
vw.anat         = [];
vw.co           = [];
vw.amp          = [];
vw.ph           = [];
vw.map          = [];
vw.mapName      = '';
vw.mapUnits     = '';
vw.tSeriesSlice = [];
vw.loc          = viewGet(vw,'Size') ./ 2;

% Initialize ROIs
vw.ROIs         = [];
vw.selectedROI  = 0;

% Initialize curDataType
if isnumeric(dataType),
    vw.curDataType = dataType;
elseif ischar(dataType),
    vw.curDataType = existDataType(dataType);
end

% Initialize curScan
vw.curScan = scan;

% need to record mmPerVox
vw.mmPerVox = readVolAnatHeader(getVAnatomyPath);

% Compute/load coords
vw = switch2Gray(vw);

% load any ROIs specified
if exist('roi','var') & ~isempty(roi)
    vw = loadROI(vw,roi);
end

% Dummy UI properties, allows loading of contrast maps on meshes w/o
% mrVista GUI open
vw = viewSet(vw, 'displaymode', 'anat');
vw = viewSet(vw, 'initdisplaymodes');

return


% Whole cell simulation class.
% - Runs simulations
% - Downsamples simulations
% - Stores and loads simulation to/from local disk or a database
% - Simulations are stored both on local disk and in the database with the
%   meta data, parameters, and time courses list below. Several dependent
%   time courses are also stored in the database.
% - Opens simulations in dashboard
%
% Options
% - lengthSec
% - stepSizeSec
% - verbosity
% - seed
% - geneticKnockouts
%
% Author: Jonathan Karr, jkarr@stanford.edu
% Affiliation: Covert Lab, Department of Bioengineering, Stanford University
% Last Updated: 9/7/2010
classdef Simulation < handle
    properties (Constant)
        optionNames = {
            'lengthSec'
            'stepSizeSec'
            'verbosity'
            'seed'
            'macromoleculeStateInitialization'
            'geneticKnockouts'
            'stimulus'
            'media'
            };
    end

    properties (SetAccess = private)
        compartment             %compartments
        gene                    %indices of genes

        stateMetadata           %all state IDs, classes, names, etc.
        states                  %cell array of states in alphabetical order
        state_time              %reference to time state
        state_metabolite        %reference to metabolite state
        state_stimulus          %reference to stimulus state

        processMetadata         %all process IDs, classes, names, etc.
        processes               %cell array of processes in alphabetical order
        processInitOrderIndexs  %indices of processes in initialization order
        processEvalOrderIndexs  %indices of processes in state evolution order
        processesInInitOrder    %processes in initialization order
        processesInEvalOrder    %processes in state evolution order
        processIndex_translation        %index of translation process
        processIndex_tRNAAminoacylation %index of tRNA aminoacylation process
    end

    properties (Access = private)
        randStream              %random number stream
    end

    %options. Use applyOptions(name1, value1, ...) to change one or more
    %options before a run.
    properties (SetAccess = private)
        lengthSec   = 50000; %length of simulation (s); ~1.5x 9 h doubling time
        stepSizeSec = 1;     %time scale of simulation (s)
        verbosity   = 1;     %0 = no output, 5 = maximum output
        seed        = [];    %set to any number for reproducible random streams
        macromoleculeStateInitialization = 'multinomial';  %Toggles how inital state is calculated; see initializeState

        geneticKnockouts = {}; %Whole Cell Model IDs of genes to be knocked out
        stimulus         = []; %stimulus set values
        media            = []; %media set values
    end

    methods
        function this = Simulation(kbStates, kbProcesses)
            %construct components
            this.constructRandStream();

            %construct constants
            this.compartment = edu.stanford.covert.cell.sim.constant.Compartment();
            this.gene = edu.stanford.covert.cell.sim.constant.Gene();

            %construct states
            this.states = {};
            if exist('kbStates', 'var')
                sMetadata.wholeCellModelIDs = {kbStates.wholeCellModelID}';
                sMetadata.names             = {kbStates.name}';
                sMetadata.classes           = {kbStates.class}';

                this.stateMetadata = sMetadata;
                this.stateMetadata.indexs = ...
                    this.buildNameIndex(sMetadata.wholeCellModelIDs);
                this.constructStates(sMetadata.wholeCellModelIDs);
            end

            %construct processes
            this.processes = {};
            if exist('kbProcesses', 'var')
                mMetadata.wholeCellModelIDs = {kbProcesses.wholeCellModelID}';
                mMetadata.names             = {kbProcesses.name}';
                mMetadata.classes           = {kbProcesses.class}';
                mMetadata.initOrder         = [kbProcesses.initializationOrder]';
                mMetadata.evalOrder         = [kbProcesses.evaluationOrder]';

                this.processMetadata = mMetadata;
                this.processMetadata.indexs = ...
                    this.buildNameIndex(mMetadata.wholeCellModelIDs);

                this.constructProcesses(mMetadata.wholeCellModelIDs);
            end

            %link processes-states
            for i = 1:numel(this.states)
                this.states{i}.storeObjectReferences(this);
            end

            %link processes-states
            for i = 1:numel(this.processes)
                this.processes{i}.storeObjectReferences(this);
            end
        end
    end

    methods
        function state = state(this, wholeCellModelID)
            i = this.stateIndex(wholeCellModelID);
            if i
                state = this.states{i};
            else
                state = [];
            end
        end

        function process = process(this, wholeCellModelID)
            i = this.processIndex(wholeCellModelID);
            if i
                process = this.processes{i};
            else
                process = [];
            end
        end

        function i = stateIndex(this, wholeCellModelID)
            [~, i] = ismember(wholeCellModelID, this.stateWholeCellModelIDs);
            if i == 0
                [~, i] = ismember(['State_' wholeCellModelID], this.stateWholeCellModelIDs);
            end
        end

        function i = processIndex(this, wholeCellModelID)
            [~,i] = ismember(wholeCellModelID, this.processWholeCellModelIDs);
            if i == 0
                [~, i] = ismember(['Process_' wholeCellModelID], this.processWholeCellModelIDs);
            end
        end

        function result = stateWholeCellModelIDs(this)
            result = cellfun(@(s) s.wholeCellModelID, this.states, 'UniformOutput', false);
        end

        function result = processWholeCellModelIDs(this)
            result = cellfun(@(m) m.wholeCellModelID, this.processes, 'UniformOutput', false);
        end

        function this = applyAllParameters(this, varargin)
            if nargin >= 2 && isstruct(varargin{1})
                values = varargin{1};
            else
                values = cell2struct(varargin(2:2:end), varargin(1:2:end-1), 2);
            end

            this.applyOptions(values);
            this.applyParameters(values);
            this.applyFittedConstants(values);
            this.applyFixedConstants(values);
        end

        function this = applyOptions(this, varargin)
            if nargin >= 2 && isstruct(varargin{1})
                options = varargin{1};
            else
                options = cell2struct(varargin(2:2:end), varargin(1:2:end-1), 2);
            end
            this.setFromStruct(options, 'options', this.optionNames);
            if mod(this.lengthSec, this.stepSizeSec)
                throw(MException('Simulation:error',...
                    sprintf('Simulation length (%d) must be a multiple of step size (%d)',...
                    this.lengthSec, this.stepSizeSec)));
            end
        end

        function this = applyParameters(this, parameters)
            this.setFromStruct(parameters, 'parameters', {});
        end

        %apply perturbations
        %- genetic: adjust constants so that effectively the genes'
        %  products won't be expressed. This is done by setting the gene
        %  products' decay rates to be very high
        function this = applyPerturbations(this)
            %modify constants
            this.applyPerturbationsToConstants();

            %modify initial conditions
            this.applyPerturbationsToState()
        end

        function applyPerturbationsToConstants(this)
            %% genetic knockouts
            %references
            g = this.gene;
            r = this.state('Rna');
            m = this.state('ProteinMonomer');
            c = this.state('ProteinComplex');

            %indices
            [~, geneIdxs] = ismember(this.geneticKnockouts, this.gene.wholeCellModelIDs);
            if ~all(geneIdxs)
                throw(MException('Simulation:error', 'undefined genes'));
            end
            matureRNAIdxs = find(any(r.matureRNAGeneComposition(geneIdxs(~ismember(geneIdxs, g.mRNAIndexs)), :), 1));
            monIdxs = find(ismember(this.gene.mRNAIndexs, geneIdxs));
            cpxIdxs = find(any(any(c.proteinComplexComposition(geneIdxs, :, :), 3), 1));

            %set constants to that effectively the knocked out genes aren't
            %expressed. More specifically we allow genes to be expressed,
            %but then decay their gene products quickly.
            decayRate = 1e6;

            r.decayRates(r.processedIndexs(matureRNAIdxs), :)     = decayRate;
            r.decayRates(r.matureIndexs(matureRNAIdxs), :)        = decayRate;
            r.decayRates(r.boundIndexs(matureRNAIdxs), :)         = decayRate;
            r.decayRates(r.misfoldedIndexs(matureRNAIdxs), :)     = decayRate;
            r.decayRates(r.damagedIndexs(matureRNAIdxs), :)       = decayRate;
            r.decayRates(r.aminoacylatedIndexs(matureRNAIdxs), :) = decayRate;

            m.decayRates(m.nascentIndexs(monIdxs), :)             = decayRate;
            m.decayRates(m.signalSequenceIndexs(monIdxs), :)      = decayRate;
            m.decayRates(m.processedIIndexs(monIdxs), :)          = decayRate;
            m.decayRates(m.processedIIIndexs(monIdxs), :)         = decayRate;
            m.decayRates(m.foldedIndexs(monIdxs), :)              = decayRate;
            m.decayRates(m.matureIndexs(monIdxs), :)              = decayRate;
            m.decayRates(m.inactivatedIndexs(monIdxs), :)         = decayRate;
            m.decayRates(m.boundIndexs(monIdxs), :)               = decayRate;
            m.decayRates(m.misfoldedIndexs(monIdxs), :)           = decayRate;
            m.decayRates(m.damagedIndexs(monIdxs), :)             = decayRate;

            c.decayRates(c.nascentIndexs(cpxIdxs), :)             = decayRate;
            c.decayRates(c.matureIndexs(cpxIdxs), :)              = decayRate;
            c.decayRates(c.inactivatedIndexs(cpxIdxs), :)         = decayRate;
            c.decayRates(c.boundIndexs(cpxIdxs), :)               = decayRate;
            c.decayRates(c.misfoldedIndexs(cpxIdxs), :)           = decayRate;
            c.decayRates(c.damagedIndexs(cpxIdxs), :)             = decayRate;
        end

        function applyPerturbationsToState(this)
            import edu.stanford.covert.cell.sim.constant.Condition;

            %% genetic knockouts
            %references
            g = this.gene;
            r = this.state('Rna');
            m = this.state('ProteinMonomer');
            c = this.state('ProteinComplex');

            %indices
            [~, geneIdxs] = ismember(this.geneticKnockouts, this.gene.wholeCellModelIDs);
            if ~all(geneIdxs)
                throw(MException('Simulation:error', 'undefined genes'));
            end
            matureRNAIdxs = find(any(r.matureRNAGeneComposition(geneIdxs(~ismember(geneIdxs, g.mRNAIndexs)), :), 1));
            monIdxs = find(ismember(this.gene.mRNAIndexs, geneIdxs));
            cpxIdxs = find(any(any(c.proteinComplexComposition(geneIdxs, :, :), 3), 1));

            %modify initial conditions:
            %- set RNAs, protein counts to zero
            %- synchronize molecule counts with other states
            %  - Chromosome: bound proteins
            %  - RNA polmyerase, transcript: No active transcription
            %  - Ribosome, polypeptide: No active translation
            %  - FtsZ ring: No formed ring
            initRNACounts = r.counts;
            r.counts(r.processedIndexs(matureRNAIdxs), :)     = 0;
            r.counts(r.matureIndexs(matureRNAIdxs), :)        = 0;
            r.counts(r.boundIndexs(matureRNAIdxs), :)         = 0;
            r.counts(r.misfoldedIndexs(matureRNAIdxs), :)     = 0;
            r.counts(r.damagedIndexs(matureRNAIdxs), :)       = 0;
            r.counts(r.aminoacylatedIndexs(matureRNAIdxs), :) = 0;
            if any(any(r.updateExternalState(-(initRNACounts - r.counts), true)))
                throw(MException('Simulation:error', 'Unable to properly knockout gene'));
            end

            initMonCounts = m.counts;
            m.counts(m.nascentIndexs(monIdxs), :)        = 0;
            m.counts(m.signalSequenceIndexs(monIdxs), :) = 0;
            m.counts(m.processedIIndexs(monIdxs), :)     = 0;
            m.counts(m.processedIIIndexs(monIdxs), :)    = 0;
            m.counts(m.foldedIndexs(monIdxs), :)         = 0;
            m.counts(m.matureIndexs(monIdxs), :)         = 0;
            m.counts(m.inactivatedIndexs(monIdxs), :)    = 0;
            m.counts(m.boundIndexs(monIdxs), :)          = 0;
            m.counts(m.misfoldedIndexs(monIdxs), :)      = 0;
            m.counts(m.damagedIndexs(monIdxs), :)        = 0;
            if any(any(m.updateExternalState(-(initMonCounts - m.counts), true)))
                throw(MException('Simulation:error', 'Unable to properly knockout gene'));
            end

            initCpxCounts = c.counts;
            c.counts(c.nascentIndexs(cpxIdxs), :)     = 0;
            c.counts(c.matureIndexs(cpxIdxs), :)      = 0;
            c.counts(c.inactivatedIndexs(cpxIdxs), :) = 0;
            c.counts(c.boundIndexs(cpxIdxs), :)       = 0;
            c.counts(c.misfoldedIndexs(cpxIdxs), :)   = 0;
            c.counts(c.damagedIndexs(cpxIdxs), :)     = 0;
            if any(any(c.updateExternalState(-(initCpxCounts - c.counts), true)))
                throw(MException('Simulation:error', 'Unable to properly knockout gene'));
            end

            %% stimulus
            stim = this.state('Stimulus');
            newVals = this.stimulus;
            vals = stim.setValues;
            tfs = true(size(vals, 1), 1);
            for i = 1:size(newVals, 1)
                tfs = tfs & ~any(...
                    ismember(vals(:, Condition.objectCompartmentIndexs), newVals(:, Condition.objectCompartmentIndexs)) & ...
                    ((vals(:, Condition.initialTimeIndexs) >= newVals(:, Condition.initialTimeIndexs) & ...
                    vals(:, Condition.initialTimeIndexs) <= newVals(:, Condition.finalTimeIndexs)) | ...
                    (vals(:, Condition.finalTimeIndexs) >= newVals(:, Condition.initialTimeIndexs) & ...
                    vals(:, Condition.finalTimeIndexs) <= newVals(:, Condition.finalTimeIndexs)) | ...
                    (vals(:, Condition.finalTimeIndexs) <= newVals(:, Condition.initialTimeIndexs) & ...
                    vals(:, Condition.finalTimeIndexs) >= newVals(:, Condition.finalTimeIndexs))));
            end
            stim.setValues = [vals(tfs, :); newVals];

            %% media
            m = this.state('Metabolite');
            newVals = this.media;
            vals = m.setCounts;
            tfs = true(size(vals, 1), 1);
            for i = 1:size(newVals, 1)
                tfs = tfs & ~any(...
                    ismember(vals(:, Condition.objectCompartmentIndexs), newVals(:, Condition.objectCompartmentIndexs)) & ...
                    ((vals(:, Condition.initialTimeIndexs) >= newVals(:, Condition.initialTimeIndexs) & ...
                    vals(:, Condition.initialTimeIndexs) <= newVals(:, Condition.finalTimeIndexs)) | ...
                    (vals(:, Condition.finalTimeIndexs) >= newVals(:, Condition.initialTimeIndexs) & ...
                    vals(:, Condition.finalTimeIndexs) <= newVals(:, Condition.finalTimeIndexs)) | ...
                    (vals(:, Condition.finalTimeIndexs) <= newVals(:, Condition.initialTimeIndexs) & ...
                    vals(:, Condition.finalTimeIndexs) >= newVals(:, Condition.finalTimeIndexs))));
            end
            m.setCounts = [vals(tfs, :); newVals];
        end

        function this = applyFittedConstants(this, value)
            this.setFromStruct(value, 'fittedConstants', {});
        end

        function this = applyFixedConstants(this, value)
            this.setFromStruct(value, 'fixedConstants', {});
        end

        function this = setMetabolicReactionKinetics(this, value)
            %get handle to metabolism submodel
            met = this.process('Metabolism');
            if isempty(met)
                if isempty(value)
                    return;
                else
                    throw(MException('Simulation:error', 'Cannot set kinetic rates'));
                end
            end

            %set kinetics
            rxnIds = fields(value);
            [rxnTfs, rxnIdxs] = ismember(rxnIds, met.reactionWholeCellModelIDs);
            if ~all(rxnTfs)
                throw(MException('Simulation:error', 'Cannot set kinetics of undefined reactions:\n- %s', ...
                    strjoin(sprintf('\n- '), rxnIdxs{~rxnTfs})));
            end

            for i = 1:numel(rxnIds)
                if isfield(value.(rxnIds{i}), 'rev')
                    met.enzymeBounds(rxnIdxs(i), 1) = value.(rxnIds{i}).rev;
                end
                if isfield(value.(rxnIds{i}), 'for')
                    met.enzymeBounds(rxnIdxs(i), 2) = value.(rxnIds{i}).for;
                end
            end

            %copy kinetics over to FBA setup
            met.fbaEnzymeBounds(met.fbaReactionIndexs_metabolicConversion, :) = ...
                met.enzymeBounds(met.reactionIndexs_fba, :);
        end

        function this = applyRandStreamStates(this, value)
            if size(value.simulation(:, 1), 2) > 1
                warning('WholeCell:warning', 'Applying first rand stream state');
            end

            this.randStream.state = value.simulation(:, 1);
            for i = 1:numel(this.states)
                o = this.states{i};
                o.randStream.state = value.states.(o.wholeCellModelID(7:end))(:, 1);
            end
            for i = 1:numel(this.processes)
                o = this.processes{i};
                o.randStream.state = value.processes.(o.wholeCellModelID(9:end))(:, 1);
            end
        end

        function this = applyTimeCourses(this, value)
            this.setFromStruct(value, 'timeCourses', {});
        end

        function constructRandStream(this)
            %seed private stream
            this.randStream = edu.stanford.covert.util.RandStream('mcg16807');

            %seed state private streams
            for i = 1:length(this.states)
                o = this.states{i};
                o.constructRandStream();
            end

            %seed process private streams
            for i = 1:length(this.processes)
                o = this.processes{i};
                o.constructRandStream();
            end
        end

        function this = seedRandStream(this)
            %pick a seed
            if isempty(this.seed)
                this.seed = round(mod(now, 1) * 1e7);
            end

            %seed default stream
            if verLessThan('matlab', '7.12')
                globalStream = RandStream.getDefaultStream(); %#ok<GETRS>
            else
                globalStream = RandStream.getGlobalStream();
            end
            globalStream.reset(1);

            %seed private stream
            this.randStream.reset(this.seed)

            %seed state private streams
            for i = 1:length(this.states)
                o = this.states{i};
                o.seed = this.seed;
                o.seedRandStream();
            end

            %seed process private streams
            for i = 1:length(this.processes)
                o = this.processes{i};
                o.seed = this.seed;
                o.seedRandStream();
            end
        end

        function value = getAllParameters(this)
            import edu.stanford.covert.util.StructUtil;

            value = struct;
            value = StructUtil.catstruct(value, this.getOptions());
            value = StructUtil.catstruct(value, this.getParameters());
            value = StructUtil.catstruct(value, this.getFittedConstants());
            value = StructUtil.catstruct(value, this.getFixedConstants());
        end

        function value = getOptions(this)
            value = this.getAsStruct('options', this.optionNames);
        end

        function value = getParameters(this)
            value = this.getAsStruct('parameters', {});
        end

        function value = getFittedConstants(this)
            value = this.getAsStruct('fittedConstants', {});
        end

        function value = getFixedConstants(this)
            value = this.getAsStruct('fixedConstants', {});
        end

        function value = getMetabolicReactionKinetics(this)
            met = this.process('Metabolism');
            if isempty(met)
                throw(MException('Simulation:error', 'No metabolism process. Cannot get kinetics.'))
            end

            value = struct();
            for i = 1:numel(met.reactionWholeCellModelIDs)
                value.(met.reactionWholeCellModelIDs{i}) = struct(...
                    'rev', met.enzymeBounds(i, 1), ...
                    'for', met.enzymeBounds(i, 2) ...
                    );
            end
        end

        function value = getTimeCourses(this)
            value = this.getAsStruct('timeCourses', {}, true, false);
        end

        function value = getRandStreamStates(this)
            value = struct('simulation', [], 'states', struct(), 'processes', struct());
            value.simulation = this.randStream.state;
            for i = 1:numel(this.states)
                o = this.states{i};
                value.states.(o.wholeCellModelID(7:end)) = o.randStream.state;
            end
            for i = 1:numel(this.processes)
                o = this.processes{i};
                value.processes.(o.wholeCellModelID(9:end)) = o.randStream.state;
            end
        end

        %number of simulation steps
        function value = getNumSteps(this)
            value = this.lengthSec / this.stepSizeSec;
        end

        % Allocate arrays to store values of physical properties over course of
        % simulation. Each of these arrays has one column.
        function this = allocateMemoryForState(this, numTimePoints)
            %states
            for i = 1:numel(this.states)
                this.states{i}.allocateMemory(numTimePoints);
            end

            %processes
            for i = 1:numel(this.processes)
                this.processes{i}.allocateMemoryForState(numTimePoints);
            end
        end
    end

    %loading, allocation, initialization
    methods
        this = initializeConstants(this, knowledgeBase)
        this = initializeState(this)
        this = run(this, varargin)
        [this, requirements, allocations, usages] = evolveState(this, outputDirectory)
        [daughters, state_daughts] = divideState(this)
    end

    %protected helper methods
    methods (Access = protected)
        function this = constructStates(this, stateWholeCellModelIDs)
            if ~exist('stateWholeCellModelIDs', 'var')
                stateWholeCellModelIDs = this.stateMetadata.wholeCellModelIDs;
            end

            this.states = cell(numel(stateWholeCellModelIDs), 1);
            [~, map1] = ismember(stateWholeCellModelIDs, this.stateMetadata.wholeCellModelIDs);
            [~, map2] = ismember(cellfun(@(x) ['State_' x], stateWholeCellModelIDs, 'UniformOutput', false), this.stateMetadata.wholeCellModelIDs);
            map = map1 + map2;

            %construct states
            for i = 1:numel(stateWholeCellModelIDs)
                constructor = str2func(...
                    ['edu.stanford.covert.cell.sim.state.' this.stateMetadata.classes{map(i)}]);
                this.states{i} = constructor(...
                    this.stateMetadata.wholeCellModelIDs{map(i)}, this.stateMetadata.names{map(i)});
            end

            %construct references to states
            this.state_time       = this.state('Time');
            this.state_metabolite = this.state('Metabolite');
            this.state_stimulus   = this.state('Stimulus');
        end

        function this = constructProcesses(this, processWholeCellModelIDs)
            if ~exist('processWholeCellModelIDs', 'var')
                processWholeCellModelIDs = this.processMetadata.wholeCellModelIDs;
            end

            [~, map1] = ismember(processWholeCellModelIDs, this.processMetadata.wholeCellModelIDs);
            [~, map2] = ismember(cellfun(@(x) ['Process_' x], processWholeCellModelIDs, 'UniformOutput', false), this.processMetadata.wholeCellModelIDs);
            map = map1 + map2;

            if ~isempty(map)
                this.processes{length(map),1} = [];
                for i = 1:length(map)
                    constructor = str2func(...
                        ['edu.stanford.covert.cell.sim.process.' this.processMetadata.classes{map(i)}]);
                    this.processes{i} = constructor(...
                        this.processMetadata.wholeCellModelIDs{map(i)}, this.processMetadata.names{map(i)});
                end
            else
                this.processes(:) = [];
            end

            [this.processesInInitOrder, this.processInitOrderIndexs] = this.createProcessOrdering(this.processMetadata.initOrder);
            [this.processesInEvalOrder, this.processEvalOrderIndexs] = this.createProcessOrdering(this.processMetadata.evalOrder);

            this.processIndex_translation = this.processIndex('Translation');
            this.processIndex_tRNAAminoacylation = this.processIndex('tRNAAminoacylation');
        end

        function [processes, indexs] = createProcessOrdering(this, completeOrdering)
            ids = this.processWholeCellModelIDs;
            [~,order] = sort(completeOrdering);
            indexs = zeros(length(this.processes),1,'int8');
            j = 1;
            for i = 1:length(order)
                if completeOrdering(order(i))
                    [~,k] = ismember(this.processMetadata.wholeCellModelIDs(order(i)), ids);
                    if k
                        indexs(j) = k;
                        j = j + 1;
                    end
                end
            end
            indexs = indexs(1:j-1);

            processes = this.processes(indexs);
        end

        function result = buildNameIndex(~, names)
            result = struct;
            for i = 1:length(names)
                result.(names{i}) = i;
            end
        end

        function value = getAsStruct(this, field, fields, statesFlag, processesFlag)
            if nargin < 4
                statesFlag = true;
            end
            if nargin < 5
                processesFlag = true;
            end

            value = struct();
            if statesFlag
                value.states = struct;
            end
            if processesFlag
                value.processes = struct;
            end

            propertyNames = fieldnames(this);
            for i = 1:length(fields)
                if ismember(fields{i}, propertyNames)
                    value.(fields{i}) = this.(fields{i});
                end
            end

            if statesFlag
                for i = 1:length(this.states)
                    id = strrep(this.states{i}.wholeCellModelID, 'State_', '');
                    value.states.(id) = struct;
                    mValue = this.states{i}.(['get' upper(field(1)) field(2:end)]);
                    if ~isempty(fieldnames(mValue))
                        value.states.(id) = mValue;
                    end
                end
            end

            if processesFlag
                for i = 1:length(this.processes)
                    id = strrep(this.processes{i}.wholeCellModelID, 'Process_', '');
                    value.processes.(id) = struct;
                    mValue = this.processes{i}.(['get' upper(field(1)) field(2:end)]);
                    if ~isempty(fieldnames(mValue))
                        value.processes.(id) = mValue;
                    end
                end
            end
        end

        function this = setFromStruct(this, value, field, fields)
            propertyNames = fieldnames(this);
            for i = 1:length(fields)
                if isfield(value, fields{i}) && ismember(fields{i}, propertyNames)
                    try %#ok<TRYNC>
                        this.(fields{i}) = value.(fields{i});
                    end
                end
            end

            if isfield(value, 'states')
                for i = 1:length(this.states)
                    state = this.states{i};
                    id = strrep(state.wholeCellModelID, 'State_', '');
                    if isfield(value.states, id)
                        state.(['set' upper(field(1)) field(2:end)])(value.states.(id));
                    end
                end
            end

            if isfield(value, 'processes')
                for i = 1:length(this.processes)
                    process = this.processes{i};
                    id =  strrep(process.wholeCellModelID, 'Process_', '');
                    if isfield(value.processes, id)
                        process.(['set' upper(field(1)) field(2:end)])(value.processes.(id));
                    end
                end
            end
        end
    end

    %helper methods for testing
    methods
        function value = getForTest(this, propName)
            value = this.(propName);
        end

        function this = setForTest(this, propName, value)
            this.(propName) = value;
        end
    end
end


any(cellfun(@exist,{'lsl_create_outlet', 'lsl_loadlib_'})==3)


% ReadSegy : Reads a SEG Y rev 1 formatted file, and forces Constant Trcae Length
%
% Call :
% [Data,SegyTraceHeaders,SegyHeader]=ReadSegyConstantTraceLength(filename);
%
% See ReadSegy for optional arguments
%
% See also : ReadSegy
%
function [Data,SegyTraceHeaders,SegyHeader]=ReadSegyConstantTraceLength(filename,varargin);

SegyHeader=ReadSegyHeader(filename);
SegyHeader.FixedLengthTraceFlag=1;

[Data,SegyTraceHeaders,SegyHeader]=ReadSegy(filename,varargin);


% in LMM / HGLMM, need to multiply by 10 to avoid numerical issue
LMM_HGLMM_MULT_FACTOR = 10;

global init_flag;
if isempty(init_flag)

    % location of this script
    pathstr = fileparts( mfilename('fullpath') );

    addpath([pathstr '/../utilities']);
    addpath([pathstr '/../FastICA_25']);
    addpath([pathstr '/../cca']);

    if isunix
        addpath([pathstr '/LMM_linux']);
        addpath([pathstr '/HGLMM_linux']);
    else
        addpath([pathstr '/LMM_win']);
        addpath([pathstr '/HGLMM_win']);
    end

    run([pathstr '/../vlfeat-0.9.18/toolbox/vl_setup']);

    clear pathstr;

    init_flag = true;
end


function publish_html(filename, outputDir, stylesheet)

% publish_html - publish a file to HTML format
%
%    publish_html(filename, outputDir, stylesheet);
%
%   Copyright (c) 2008 Gabriel Peyre

if nargin<1
    filename = 'content';
end
if nargin<2
    outputDir = 'html';
end
if nargin<3
    stylesheet = [outputDir '/gpeyre.xsl'];
end

opts.outputDir = outputDir;
opts.stylesheet = stylesheet;
file = publish(filename,opts);

web(file);


%% Copyright (C) 2014, 2015, 2022 Colin B. Macdonald
%%
%% This file is part of OctSymPy.
%%
%% OctSymPy 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 software 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 software; see the file COPYING.
%% If not, see <http://www.gnu.org/licenses/>.

function s = python_header()
%private

  persistent PyStrCache

  if (isempty(PyStrCache))
    thisdir = fileparts (mfilename ('fullpath'));
    pyfile = fullfile (thisdir, 'python_header.py');
    PyStrCache = fileread (pyfile);
  end

  s = PyStrCache;

end


function [val, remaining] = keyval(key, varargin)

% KEYVAL returns the value that corresponds to the requested key in a
% key-value pair list of variable input arguments
%
% Use as
%   [val] = keyval(key, varargin)
%
% See also VARARGIN

% Undocumented option
%   [val] = keyval(key, varargin, default)

% Copyright (C) 2005-2007, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.ru.nl/neuroimaging/fieldtrip
% 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: keyval.m 2885 2011-02-16 09:41:58Z roboos $

% what to return if the key is not found
emptyval = [];

if nargin==3 && iscell(varargin{1})
  emptyval = varargin{2};
  varargin = varargin{1};
end

if nargin==2 && iscell(varargin{1})
  varargin = varargin{1};
end

if mod(length(varargin),2)
  error('optional input arguments should come in key-value pairs, i.e. there should be an even number');
end

% the 1st, 3rd, etc. contain the keys, the 2nd, 4th, etc. contain the values
keys = varargin(1:2:end);
vals = varargin(2:2:end);

% the following may be faster than cellfun(@ischar, keys)
valid = false(size(keys));
for i=1:numel(keys)
  valid(i) = ischar(keys{i});
end

if ~all(valid)
  error('optional input arguments should come in key-value pairs, the optional input argument %d is invalid (should be a string)', i);
end

hit = find(strcmpi(key, keys));
if isempty(hit)
  % the requested key was not found
  val = emptyval;
elseif length(hit)==1
  % the requested key was found
  val = vals{hit};
else
  error('multiple input arguments with the same name');
end

if nargout>1
  % return the remaining input arguments with the key-value pair removed
  keys(hit) = [];
  vals(hit) = [];
  remaining = cat(1, keys(:)', vals(:)');
  remaining = remaining(:)';
end