%
% This program is free software; you can redistribute it and/or
% modify it under the terms of the GNU General Public License
% as published by the Free Software Foundation; either version 2
% of the License, or (at your option) any later version.
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
%


function varargout = poseLabeller(varargin)
% POSELABELLER M-file for poseLabeller.fig
%      POSELABELLER, by itself, creates a new POSELABELLER or raises the existing
%      singleton*.
%
%      H = POSELABELLER returns the handle to a new POSELABELLER or the handle to
%      the existing singleton*.
%
%      POSELABELLER('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in POSELABELLER.M with the given input arguments.
%
%      POSELABELLER('Property','Value',...) creates a new POSELABELLER or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before poseLabeller_OpeningFunction gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to poseLabeller_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% USING THE APPLICATION:
%     This Matlab GUI app helps the user to create a ground-truth track of
%     'critical points' - in our case, certain points on the human body
%     (head, hands and feet). It works on AVI files and allows the user
%     to play the video forwards or backwards at will. There's no reason
%     why additional points can't be added (eg: knees), or even renaming
%     the points entirely. Of course, more points means correspondingly
%     longer to label all of them!
%     The user selects a critical point type (default is Head) from the
%     Label menu and clicks on the place in each frame where the 
%     critical point is. The coordinates are stored in memory ready to 
%     be saved to disk via File >> Save Coords. You can also load
%     earlier (eg: half-done) runs with File >> Load Coords.
%     To reduce the effort involved, we do a template-tracking step to 
%     estimate where in the next frame a particular critical point is 
%     (unless the user has already specified the coordinate in an
%     earlier run - we don't overwrite data).
%     If the critical point is occluded, the user can mark it as such
%     by Ctrl-clicking anywhere on the image.
% 
%     Note that hitting <Space> will step the video forward so that you
%     can correct template tracking without constantly moving the mouse
%     (<Space> is trapped with the KeyPressFcn). We can't rely on tabbing
%     the focus caret to the [>|] button since every time you click
%     in the frame the focus is lost from Step (even though txtFrame
%     is disabled!). So instead <Space> is globally connected to Step.
%
%     It's suggested that you concentrate on labelling one critical point 
%     over the whole video before doing the next one since the template
%     matching isn't *that* good. The exception to this is feet labelling
%     since one foot usually is 'fixed' on the ground when the other is 
%     moving, so it's often quicker to alternate between feet during the
%     same labelling run. Use the keyboard shortcuts (Ctrl-<Num>) to
%     make critical point switching faster (note that Matlab under Unix
%     sometimes ignores menu accelerators - restart the GUI if this occurs)
%
% Limitations and Design Decisions:
%     - Unfortunately, Matlab under Unix doesn't support compressed AVIs.
%       The only options are re-encode the video to RAW (!!) or use Windows
%     - To speed up AVI processing, we preload frames in blocks of 100
%       from the AVI. Search for '99' to change this.
%     - Matlab isn't the fastest, so the program only doubles the size
%       of the AVI frames to make it easier to see. For large (640x480+)
%       images this can actually be bad since we don't provide any
%       image scrollbars (too much trouble in Matlab), so the double-scale
%       image might not fit on your screen properly.
%     - Template matching window is hardcoded to +/-15 and template sizes
%       are hardcord to +/-3 for hands and +/-5 for other critical points
%       (hands tend to be smaller). You may need to tweak these numbers
%       to match your requirements and/or video resolution.
% 
% Coordinate File Format:
%     - Saving coordinates creates two files: *.csv and *.base.
%       The .base file stores the first frame that coordinates started,
%       the .csv stores the actual coordinate data in the following format:
%        <HeadX>,<HeadY>,<RHandX>,<RHandY>,<LHandX>,<LHandY>,<RFootX>,<RFootY>,<LFootX>,<LFootY>
%
%     - All coordinates are 0-based for better integration with C++/Java
%     - A coordinate of (-1,-1) indicates an occlusion of that point.
%       The user can create this by Ctrl-clicking on the image.
%     - A coordinate of (0, 0) indicates 'undefined' and should probably 
%       be treated like (-1,-1). This is admittedly a bit clumsy, but
%       this program uses (0,0) to indicate that we need to track
%       the critical point because the user hasn't set it.
%
% Written by Patrick Peursum, September 2007
% Department of Computing
% Curtin University of Technology
% Bentley, Western Australia
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% Copyright 2002-2003 The MathWorks, Inc.

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

% Last Modified by GUIDE v2.5 08-Oct-2007 21:00:51

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @poseLabeller_OpeningFcn, ...
                   'gui_OutputFcn',  @poseLabeller_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

%
%--------------------------------------------------
%------------------------------------------------------------
% APPLICATION EVENTS
%------------------------------------------------------------
%--------------------------------------------------
%

% --- Executes just before poseLabeller is made visible.
function poseLabeller_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 poseLabeller (see VARARGIN)

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

   % Update handles structure
   guidata(hObject, handles);

   % Add our 'member variables' to handles
   handles = guidata(hObject);
   handles.m_sAVI = '';
   handles.m_iAVINumFrames = 0;
   handles.m_iFrame = 0;
   handles.m_iFrameStep = 1;
   handles.m_imgCachedFrames = [];
   handles.m_iCacheBase = 1;
   handles.m_tmpltImages = cell(5,1); % One for each head, rhand, lhand, rfoot, lfoot
   handles.m_tmpltOffsets = zeros(5, 2);
   handles.m_bPlaying = 0;
   handles.m_iCoordType = 1;  % Head, which is pre-checked in the menus
   handles.m_iCoordData = [];
   handles.m_iCoordBase = 0;     % First frame of coord data
   handles.m_iCoordColours = [255 255 0;  255 128 128;  136 208 255;  128 255 128;  255 128 255];  % Use different colours for each critical point
   handles.m_iZoomFactor = 2;   % For image display. Must be an integer
   guidata(hObject, handles);

   % Initialise form size to default handling a 640x480 AVI
   % We'll allow resizing anyways, so this default isn't a hard constraint
   screenSize = get(0, 'ScreenSize');
   posnFrm = [1, 1, 8+640+5+8, 8+480+5+8+25+8];
   posnFrm(1:2) = [screenSize(3)/2 - posnFrm(3)/2, screenSize(4)/2 - posnFrm(4)/2];
   set(hObject, 'Position', posnFrm);

   % This call probably isn't needed because the above set() invokes resize as well
   frmPoseLabeller_ResizeFcn(hObject, eventdata, handles)

   % UIWAIT makes poseLabeller wait for user response (see UIRESUME)
   % uiwait(handles.frmPoseLabeller);

   

% --- Outputs from this function are returned to the command line.
function varargout = poseLabeller_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;



%
%--------------------------------------------------
%------------------------------------------------------------
% FIGURE frmPoseLabeller EVENTS
%------------------------------------------------------------
%--------------------------------------------------
%
% --- Executes when frmPoseLabeller is resized.
function frmPoseLabeller_ResizeFcn(hObject, eventdata, handles)
% hObject    handle to frmPoseLabeller (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

   % NOTE: Matlab coord system is Cartesian, not image!
   % This makes for some weird-looking resize code.

   posnFrm = get(hObject, 'Position');

   % Set posnFrm(x) so that the buttons are centred
   posn = [div(posnFrm(3) - (8*(45+4)),2)+1, 8, 45, 25];  
   set(handles.btnRewind, 'Position', posn);
   posn = [posn(1) + posn(3) + 4, 8, 45, 25];
   set(handles.btnFRev, 'Position', posn);
   posn = [posn(1) + posn(3) + 4, 8, 45, 25];
   set(handles.btnRevStep, 'Position', posn);
   posn = [posn(1) + posn(3) + 4, 8, 45, 25];
   set(handles.btnRev, 'Position', posn);
   posn = [posn(1) + posn(3) + 4, 8, 45, 25];
   set(handles.btnPlay, 'Position', posn);
   posn = [posn(1) + posn(3) + 4, 8, 45, 25];
   set(handles.btnStep, 'Position', posn);
   posn = [posn(1) + posn(3) + 4, 8, 45, 25];
   set(handles.btnFFwd, 'Position', posn);
   posn = [posn(1) + posn(3) + 4, 8, 45, 25];
   set(handles.btnXFFwd, 'Position', posn);
   % Tack the frame counter label on the end
   posn = [posn(1) + posn(3) + 4, 12, 100, 15];
   set(handles.lblFrameCounter, 'Position', posn);
   
   if (size(handles.m_imgCachedFrames) > 0)
      iHeight = size(handles.m_imgCachedFrames(1).cdata, 1);
      iWidth = size(handles.m_imgCachedFrames(1).cdata, 2);
   else
      iWidth = 320;  % Default for initial GUI display
      iHeight = 240;
   end
   % Need to add 5 to take into account the border of txtFrame
   posn = [div(posnFrm(3),2) - (iWidth+2), 8+25+8, iWidth*handles.m_iZoomFactor+5, iHeight*handles.m_iZoomFactor+5];
   set(handles.txtFrame, 'Position', posn);

   displayFrame(handles.m_iFrame, handles)



   
% --- Executes on key press over frmPoseLabeller with no controls selected.
% Traps <Space> events for video stepping
function frmPoseLabeller_KeyPressFcn(hObject, eventdata, handles)
% hObject    handle to frmPoseLabeller (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   if (get(hObject, 'CurrentCharacter') == ' ')
      btnStep_Callback(hObject, eventdata, handles);
   end
   

%
%--------------------------------------------------
%------------------------------------------------------------
% LABEL txtFrame EVENTS
%------------------------------------------------------------
%--------------------------------------------------
%
% --- If Enable == 'on', executes on mouse press in 5 pixel border.
% --- Otherwise, executes on mouse press in 5 pixel border or over txtFrame.
function txtFrame_ButtonDownFcn(hObject, eventdata, handles)
% hObject    handle to txtFrame (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

   % Detect clicks on txtFrame and updateCoords() accordingly
   % OLD: 
   %hRoot = get(0);
   %hFrmPoseLabeller = get(handles.frmPoseLabeller);
   %mouseXY = hRoot.PointerLocation - hFrmPoseLabeller.Position(1:2) - hTxtFrame.Position(1:2) - 1;
   hTxtFrame = get(hObject);
   mouseXY = get(handles.frmPoseLabeller, 'CurrentPoint') - hTxtFrame.Position(1:2) - 1;


   % Only process clicks if we have a frame and the click is within the boundary of the image
   if (handles.m_iFrame > 0)
      iOffset = handles.m_iFrame - handles.m_iCacheBase + 1;
      [iHeight, iWidth, iDepth] = size(get(handles.txtFrame, 'CData'));

      if ( (mouseXY(1) > 0) & (mouseXY(1) <= iWidth) & (mouseXY(2) > 0) & (mouseXY(2) <= iHeight) )
      %		msgbox( sprintf('Got a click, at: (%d,%d)', mouseXY(1), mouseXY(2)) );

            % Flip the mouse position due to Matlab's Cartesian coordinates, 
            % then rescale it down to the true image size to get true coords
            mouseXY(2) = iHeight - mouseXY(2);
            mouseXY(1) = mouseXY(1) - 1;
            mouseXY = div(mouseXY, 2) + 1;   % Need to convert from 0-based to 1-based

            % Matlab won't tell us about right-clicks, so we use Ctrl-click
            % to indicate "delete this coord point" (eg: if it's occluded)
            selType = get(handles.frmPoseLabeller, 'SelectionType');
            if (strcmp(selType, 'alt'))  % alt == ctrl (!!), plus strcmp returns 1 if equal (!!!)
               mouseXY = [-1 -1];
            end
            handles = updateCoords(mouseXY, handles);
            guidata(handles.frmPoseLabeller, handles);   % Update member functions

            displayFrame(handles.m_iFrame, handles);  % Display the coord
      end
   end



%
%--------------------------------------------------
%------------------------------------------------------------
% MENU mnu<XYZ> EVENTS
%------------------------------------------------------------
%--------------------------------------------------
%
% --------------------------------------------------------------------
function mnuOpenAVI_Callback(hObject, eventdata, handles)
% hObject    handle to mnuOpen (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   [sFile, sPath] = uigetfile('*.avi', 'Open AVI file');
   if (sFile ~= 0)
      handles = initAVI(sprintf('%s%s', sPath, sFile), handles);
      displayFrame(handles.m_iFrame, handles);
   end   



% --------------------------------------------------------------------
function mnuExit_Callback(hObject, eventdata, handles)
% hObject    handle to mnuExit (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   close(handles.frmPoseLabeller);

   

% --------------------------------------------------------------------
function mnuLoadCoords_Callback(hObject, eventdata, handles)
% hObject    handle to mnuLoadCoords (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   [sFile, sPath] = uigetfile('*.csv', 'Load coordinate file');
   if (sFile ~= 0)
      readData = csvread(sprintf('%s%s', sPath, sFile));
      handles.m_iCoordBase = csvread(sprintf('%s%s.base', sPath, sFile));
      handles.m_iCoordBase = handles.m_iCoordBase + 1;   % Convert to 1-based

      % Convert 0-based readData to 1-based
      % This is harder than the other way around since (0,0) is special
      for iCol=1:2:size(readData,2)
         % At least one coord must be non-zero, otherwise its the special (0,0)
         adjIdxs = find( (readData(:,iCol) > 0) | (readData(:,iCol+1) > 0) );
         readData(adjIdxs) = readData(adjIdxs) + 1;
      end
      handles.m_iCoordData = readData;
   end
   guidata(handles.frmPoseLabeller, handles);   % Update member functions

   

% --------------------------------------------------------------------
function mnuSaveCoords_Callback(hObject, eventdata, handles)
% hObject    handle to mnuSaveCoords (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   if (isempty(handles.m_iCoordData))
      errordlg('No coordinate data to save');
   else
      [sFile, sPath] = uiputfile('*.csv', 'Save coordinate file');
      if (sFile ~= 0)
         % Take 1 off of m_iCoordData so that image coordinates are
         % 0-based, which is important for C/C++ and Java
         writeData = handles.m_iCoordData;
         adjIdxs = find(writeData > 0);
         writeData(adjIdxs) = writeData(adjIdxs) - 1;

         csvwrite(sprintf('%s%s', sPath, sFile), writeData);
         csvwrite(sprintf('%s%s.base', sPath, sFile), handles.m_iCoordBase - 1);
      end
   end

% --------------------------------------------------------------------
function mnuHead_Callback(hObject, eventdata, handles)
% hObject    handle to mnuHead (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   handles.m_iCoordType = 1;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   set(handles.mnuHead, 'Checked', 'on');

   set(handles.mnuLeftHand, 'Checked', 'off');
   set(handles.mnuRightHand, 'Checked', 'off');
   set(handles.mnuRightFoot, 'Checked', 'off');
   set(handles.mnuLeftFoot, 'Checked', 'off');


% --------------------------------------------------------------------
function mnuRightHand_Callback(hObject, eventdata, handles)
% hObject    handle to mnuRightHand (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   handles.m_iCoordType = 2;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   set(handles.mnuRightHand, 'Checked', 'on');

   set(handles.mnuHead, 'Checked', 'off');
   set(handles.mnuLeftHand, 'Checked', 'off');
   set(handles.mnuRightFoot, 'Checked', 'off');
   set(handles.mnuLeftFoot, 'Checked', 'off');


% --------------------------------------------------------------------
function mnuLeftHand_Callback(hObject, eventdata, handles)
% hObject    handle to mnuLeftHand (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   handles.m_iCoordType = 3;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   set(handles.mnuLeftHand, 'Checked', 'on');

   set(handles.mnuHead, 'Checked', 'off');
   set(handles.mnuRightHand, 'Checked', 'off');
   set(handles.mnuRightFoot, 'Checked', 'off');
   set(handles.mnuLeftFoot, 'Checked', 'off');


% --------------------------------------------------------------------
function mnuRightFoot_Callback(hObject, eventdata, handles)
% hObject    handle to mnuRightFoot (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   handles.m_iCoordType = 4;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   set(handles.mnuRightFoot, 'Checked', 'on');

   set(handles.mnuHead, 'Checked', 'off');
   set(handles.mnuLeftHand, 'Checked', 'off');
   set(handles.mnuRightHand, 'Checked', 'off');
   set(handles.mnuLeftFoot, 'Checked', 'off');

% --------------------------------------------------------------------
function mnuLeftFoot_Callback(hObject, eventdata, handles)
% hObject    handle to mnuLeftFoot (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   handles.m_iCoordType = 5;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   set(handles.mnuLeftFoot, 'Checked', 'on');

   set(handles.mnuHead, 'Checked', 'off');
   set(handles.mnuLeftHand, 'Checked', 'off');
   set(handles.mnuRightHand, 'Checked', 'off');
   set(handles.mnuRightFoot, 'Checked', 'off');
   
%
%--------------------------------------------------
%------------------------------------------------------------
% BUTTON btn<PlayControls> EVENTS
%------------------------------------------------------------
%--------------------------------------------------
%
% --- Executes on button press in btnPlay.
function btnPlay_Callback(hObject, eventdata, handles)
% hObject    handle to btnPlay (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

   if (handles.m_bPlaying == 1)
      % Force stop of play  
      handles.m_bPlaying = 0;
      guidata(handles.frmPoseLabeller, handles);   % Update member functions
      set(handles.btnPlay, 'String', '>');
   else  
      handles.m_bPlaying = 1;
      handles.m_iFrameStep = 1;
      guidata(handles.frmPoseLabeller, handles);   % Update member functions
      set(handles.btnPlay, 'String', '||');  % Change button function to 'pause'

      while (handles.m_bPlaying)
         doPlay(handles);
         drawnow;    % Allow other events
         % Check if m_bPlaying has been updated by another event
         handles = guidata(handles.frmPoseLabeller);
      end
   end




% --- Executes on button press in btnRewind.
function btnRewind_Callback(hObject, eventdata, handles)
% hObject    handle to btnRewind (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% Jump back to first frame
   handles.m_bPlaying = 0;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   handles = initAVI(handles.m_sAVI, handles);
   displayFrame(1, handles);

   
% --- Executes on button press in btnFRev.
function btnFRev_Callback(hObject, eventdata, handles)
% hObject    handle to btnFRev (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   handles.m_bPlaying = 1;
   handles.m_iFrameStep = -10;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   set(handles.btnPlay, 'String', '||');  % Change play button function to 'pause'

   while (handles.m_bPlaying)
      doPlay(handles);
      drawnow;    % Allow other events
      % Check if m_bPlaying has been updated by another event
      handles = guidata(handles.frmPoseLabeller);
   end

   
% --- Executes on button press in btnRevStep.
function btnRevStep_Callback(hObject, eventdata, handles)
% hObject    handle to btnRevStep (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   handles.m_bPlaying = 0;
   handles.m_iFrameStep = -1;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   set(handles.btnPlay, 'String', '>');  % Change play button function to 'play'

   doPlay(handles);


% --- Executes on button press in btnRev.
function btnRev_Callback(hObject, eventdata, handles)
% hObject    handle to btnRev (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   handles.m_bPlaying = 1;
   handles.m_iFrameStep = -1;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   set(handles.btnPlay, 'String', '||');  % Change play button function to 'pause'

   while (handles.m_bPlaying)
      doPlay(handles);
      drawnow;    % Allow other events
      % Check if m_bPlaying has been updated by another event
      handles = guidata(handles.frmPoseLabeller);
   end



% --- Executes on button press in btnStep.
function btnStep_Callback(hObject, eventdata, handles)
% hObject    handle to btnStep (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   handles.m_bPlaying = 0;
   handles.m_iFrameStep = 1;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   set(handles.btnPlay, 'String', '>');  % Change play button function to 'play'

   doPlay(handles);



% --- Executes on button press in btnFFwd.
function btnFFwd_Callback(hObject, eventdata, handles)
% hObject    handle to btnFFwd (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   handles.m_bPlaying = 1;
   handles.m_iFrameStep = 10;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   set(handles.btnPlay, 'String', '||');  % Change play button function to 'pause'

   while (handles.m_bPlaying)
      doPlay(handles);
      drawnow;    % Allow other events
      % Check if m_bPlaying has been updated by another event
      handles = guidata(handles.frmPoseLabeller);
   end



% --- Executes on button press in btnXFFwd.
function btnXFFwd_Callback(hObject, eventdata, handles)
% hObject    handle to btnXFFwd (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
   handles.m_bPlaying = 1;
   handles.m_iFrameStep = 25;
   guidata(handles.frmPoseLabeller, handles);   % Update member functions
   set(handles.btnPlay, 'String', '||');  % Change play button function to 'pause'

   while (handles.m_bPlaying)
      doPlay(handles);
      drawnow;    % Allow other events
      % Check if m_bPlaying has been updated by another event
      handles = guidata(handles.frmPoseLabeller);
   end


%
%--------------------------------------------------
%------------------------------------------------------------
% PRIVATE METHODS
%------------------------------------------------------------
%--------------------------------------------------
%
% --- Open AVI file and cache first 100 frames.
function handles = initAVI(AVIFilename, handles)
% AVIFilename path to the AVI to open and initialise
% handles     structure with handles and user data (see GUIDATA)

   handles.m_sAVI = AVIFilename;
   handles.m_iFrame = 1;
   handles.m_iCacheBase = 1;

   set(handles.lblFrameCounter, 'String', sprintf('Frame: %d', handles.m_iFrame));

   set(handles.frmPoseLabeller, 'Pointer', 'watch');
   drawnow  % Show pointer
   aviData = aviinfo(handles.m_sAVI);
   handles.m_iAVINumFrames = aviData.NumFrames;
   if (handles.m_iAVINumFrames < 99)
      handles.m_imgCachedFrames = aviread(handles.m_sAVI, 1:handles.m_iAVINumFrames);
   else
      handles.m_imgCachedFrames = aviread(handles.m_sAVI, 1:(1+99));
   end
   set(handles.frmPoseLabeller, 'Pointer', 'arrow');

   % Re-initialise coordinate data since it won't be valid for the new AVI
   handles.m_iCoordData = [];
   handles.m_iCoordBase = 0;     % First frame of coord data  
   guidata(handles.frmPoseLabeller, handles);   % Update member functions

   % Resize txtFrame to fit the new AVI
   posnFrm = get(handles.frmPoseLabeller, 'Position');

   % Resize form if the video needs to make it bigger
   if ((8 + aviData.Width*handles.m_iZoomFactor+5 + 8) > posnFrm(3))
      posnFrm(3) = 8 + aviData.Width*handles.m_iZoomFactor+5 + 8;
   end
   if ((8 + aviData.Height*handles.m_iZoomFactor+5 + 8 + 25 + 8) > posnFrm(4))
      posnFrm(2) = posnFrm(2) + (posnFrm(4) - (8 + aviData.Height*handles.m_iZoomFactor+5 + 8 + 25 + 8)); % Grow form downwards, NOT upwards!
      posnFrm(4) = 8 + aviData.Height*handles.m_iZoomFactor+5 + 8 + 25 + 8;
   end
   set(handles.frmPoseLabeller, 'Position', posnFrm);

   % Now resize the window components to match
   frmPoseLabeller_ResizeFcn(handles.frmPoseLabeller, [], handles)



      
% --- Display given frame with ground-truth coords drawn over it
function displayFrame(frame, handles)
% frame      Frame number to display (1..handles.m_iAVINumFrames)
% handles    structure with handles and user data (see GUIDATA)

   if (frame > 0)
      imgFrame = handles.m_imgCachedFrames(frame-handles.m_iCacheBase+1).cdata;
      [iHeight iWidth iDepth] = size(imgFrame);

      % Draw ground-truth coordinates onto the image
      iCoordIdx = handles.m_iFrame - handles.m_iCoordBase + 1;
      if ( (handles.m_iCoordBase > 0) & (iCoordIdx > 0) & (iCoordIdx <= size(handles.m_iCoordData,1)) )
         iRow = handles.m_iFrame - handles.m_iCoordBase + 1;
         for iCol=1:2:10
            coordXY = handles.m_iCoordData(iRow, iCol:iCol+1);

            if ((coordXY(1) > 0) | (coordXY(2) > 0))  % -1 means occluded, 0 means unassigned or occluded
               coordColour = handles.m_iCoordColours(div(iCol,2)+1,:);
             
               keepRGB = imgFrame(coordXY(2), coordXY(1), :);  % Matrices are [y][x], not [x][y]!
               % Clip coord marker to image bounds
               if (coordXY(1) <= 2)
                  rangeX = 1:(coordXY(1)+2);
               elseif (coordXY(1) > (iWidth-2))
                  rangeX = (coordXY(1)-2):iWidth;
               else
                  rangeX = (coordXY(1)-2):(coordXY(1)+2);
               end
               if (coordXY(2) <= 2)
                  rangeY = 1:(coordXY(2)+2);
               elseif (coordXY(2) > (iHeight-2))
                  rangeY = (coordXY(2)-2):iHeight;
               else
                  rangeY = (coordXY(2)-2):(coordXY(2)+2);
               end
               imgFrame(rangeY, rangeX, 1) = coordColour(1);
               imgFrame(rangeY, rangeX, 2) = coordColour(2);
               imgFrame(rangeY, rangeX, 3) = coordColour(3);
               imgFrame(coordXY(2), coordXY(1), :) = keepRGB;
            end
         end
      end

      % Enlage (zoom) the image for display
      % imresize is a bit slow if you just want to double/triple the image size:
      %  dispFrame = imresize(handles.m_imgCachedFrames(frame-handles.m_iCacheBase+1).cdata, 2);
      iZoom = handles.m_iZoomFactor;  % Get into a shorter name for readability!
                                      % Plus, Matlab struct are likely slow
      dispFrame = zeros(size(imgFrame,1)*iZoom, size(imgFrame,2)*iZoom, 3, 'uint8');
      rowsBase = 0:iZoom:(size(dispFrame,1) - 1);   % Every iZoom-th row
      for x=1:size(imgFrame,2)
          xBase = (x-1) * iZoom;
	  % Copy each pixel in imgFrame to an iZoom-sized block
          % I can't think of a way to make this faster - repmat isn't quicker.
          % One way is to hardcode the zoom. eg for iZoom == 2:
          %    dispFrame(rowsBase+1, xBase+1, :) = imgFrame(:,x,:);
          %    dispFrame(rowsBase+1, xBase+2, :) = imgFrame(:,x,:);
          %    dispFrame(rowsBase+2, xBase+1, :) = imgFrame(:,x,:);
          %    dispFrame(rowsBase+2, xBase+2, :) = imgFrame(:,x,:);
          % This would avoid the for loops, but may not be much faster anyways
          for zmRow=1:iZoom
             for zmCol=1:iZoom
                dispFrame(rowsBase+zmRow, xBase+zmCol, :) = imgFrame(:,x,:);
             end
          end
      end

   else
      sizeTxt = get(handles.txtFrame, 'Position');
      dispFrame = ones(sizeTxt(4), sizeTxt(3), 3);
   end

   set(handles.txtFrame, 'CData', dispFrame);

   


% --- Play one frame, loading from the AVI file if necessary
function doPlay(handles)
% handles    structure with handles and user data (see GUIDATA)

   % Save greyscale of last image for later extracting templates of critical body points
   iOffset = handles.m_iFrame - handles.m_iCacheBase + 1;
   if ( (iOffset > 0) & (iOffset <= size(handles.m_imgCachedFrames,2)) )
      imgLastFrame = handles.m_imgCachedFrames(iOffset).cdata;
      imgLastFrame = imgLastFrame(:,:,1) * 0.3 + imgLastFrame(:,:,2) * 0.59 + imgLastFrame(:,:,3) * 0.11;
   end
   
   handles.m_iFrame = handles.m_iFrame + handles.m_iFrameStep;
   set(handles.lblFrameCounter, 'String', sprintf('Frame: %d', handles.m_iFrame));
   guidata(handles.frmPoseLabeller, handles);   % Update member variables

   if (handles.m_iFrame >= (handles.m_iCacheBase + size(handles.m_imgCachedFrames)))
      if (handles.m_iFrame > handles.m_iAVINumFrames)
         drawnow; % Just in case the user hit pause right at this time!
         handles = guidata(handles.frmPoseLabeller);

         handles.m_iFrame = handles.m_iAVINumFrames;
         handles.m_bPlaying = 0;
         set(handles.btnPlay, 'String', '>');
         guidata(handles.frmPoseLabeller, handles); 
         return;   % Nothing more to play
      end

      % Playing forwards - cache the next 100 frames
      set(handles.frmPoseLabeller, 'Pointer', 'watch');
      drawnow   % Show pointer
      if (handles.m_iFrame+99 > handles.m_iAVINumFrames)
         handles.m_imgCachedFrames = aviread(handles.m_sAVI, handles.m_iFrame:handles.m_iAVINumFrames);
      else
         handles.m_imgCachedFrames = aviread(handles.m_sAVI, handles.m_iFrame:(handles.m_iFrame+99));
      end
      set(handles.frmPoseLabeller, 'Pointer', 'arrow');
      handles.m_iCacheBase = handles.m_iFrame;
   elseif (handles.m_iFrame < handles.m_iCacheBase)
      if (handles.m_iFrame < 1)
         drawnow; % Just in case the user hit pause right at this time!
         handles = guidata(handles.frmPoseLabeller);

         handles.m_iFrame = 1;
         handles.m_bPlaying = 0;
         set(handles.btnPlay, 'String', '>');
         guidata(handles.frmPoseLabeller, handles); 
         return;   % Nothing more to play
      end

      % Playing backwards - cache the previous 100 frames
      set(handles.frmPoseLabeller, 'Pointer', 'watch');
      drawnow  % Show pointer
      if (handles.m_iFrame-99 < 1)
         handles.m_imgCachedFrames = aviread(handles.m_sAVI, 1:(1+99));
         handles.m_iCacheBase = 1;
      else
         handles.m_imgCachedFrames = aviread(handles.m_sAVI, (handles.m_iFrame-99):handles.m_iFrame);
         handles.m_iCacheBase = handles.m_iFrame - 99;
      end
      set(handles.frmPoseLabeller, 'Pointer', 'arrow');
   end
   guidata(handles.frmPoseLabeller, handles);   % Update member variables


   % Template-match critical points to ease the ground-truth tracking job
   iRow = handles.m_iFrame - handles.m_iCoordBase;  % NOTE: No +1 - we work with the PREVIOUS frame
   if ( (handles.m_iCoordBase > 0) & (iRow > 0) & (iRow <= (size(handles.m_iCoordData,1))) ) 
      % Update templates of critical body points
      [iHeight iWidth iDepth] = size(imgLastFrame);
     
      for iCol=1:2:10
         iTypeIdx = div(iCol,2) + 1;
         thisXY = handles.m_iCoordData(iRow, iCol:iCol+1);
         if ((thisXY(1) > 0) | (thisXY(2) > 0))
            % Extract template, handling bounds clipping if necessary
            if ((iCol >= 3) & (iCol <= 6))   % Hands are small, so use a smaller template
               x1 = thisXY(1)-3;   x2 = thisXY(1)+3;   y1 = thisXY(2)-3;   y2 = thisXY(2)+3;
            else
               x1 = thisXY(1)-5;   x2 = thisXY(1)+5;   y1 = thisXY(2)-5;   y2 = thisXY(2)+5;
            end
            if (x1 < 1),         x1 = 1;        end
            if (x2 > iWidth),    x2 = iWidth;   end
            if (y1 < 1),         y1 = 1;        end
            if (y2 > iHeight),   y2 = iHeight;  end
            handles.m_tmpltImages{iTypeIdx,1} = imgLastFrame(y1:y2, x1:x2);
            handles.m_tmpltOffsets(iTypeIdx,:) = [thisXY(1)-x1, thisXY(2)-y1];
         else
            handles.m_tmpltImages{iTypeIdx,1} = [];  % No template
            handles.m_tmpltOffsets(iTypeIdx,:) = [0 0];
         end
      end
      guidata(handles.frmPoseLabeller, handles);   % Update member variables

      % Do template match
      handles = trackCriticalPoints(handles);
      guidata(handles.frmPoseLabeller, handles);   % Update member variables
   end

   displayFrame(handles.m_iFrame, handles);     

  
      
      
% --- Estimate next position of each unset critical point using normxcorr2
function handles = trackCriticalPoints(handles)
% handles    structure with handles and user data (see GUIDATA)

   iRow = handles.m_iFrame - handles.m_iCoordBase;
   
   if ( (handles.m_iCoordBase > 0) & (iRow > 0) & (iRow <= (size(handles.m_iCoordData,1))) ) 
      [iHeight iWidth iDepth] = size(handles.m_imgCachedFrames(1).cdata);  % All frames have the same size
      
      if (iRow == size(handles.m_iCoordData,1))
         handles.m_iCoordData(iRow+1,:) = 0;   % Add a new row
      end
     
      for iCol=1:2:10
         iTypeIdx = div(iCol, 2) + 1;
         
         lastXY = handles.m_iCoordData(iRow, iCol:iCol+1);         
         thisXY = handles.m_iCoordData(iRow+1, iCol:iCol+1);         
         if ((thisXY(1) == 0) & (thisXY(2) == 0))  % Only overwrite blank entries
            
            if (isempty(handles.m_tmpltImages{iTypeIdx,1}))
               % Copy the coordinates forward - useful if an
               % occlusion is occurring, so that we carry [-1 -1] forward
               thisXY = handles.m_iCoordData(iRow, iCol:iCol+1);
            else
               % Search for the next position at time t using the last template
               % First clip +/-15 pixel search window to boundary
               dx1 = 15;   dy1 = 15;   % For offset alignment after normxcorr2
               x1 = lastXY(1)-dx1;   x2 = lastXY(1)+dx1;   y1 = lastXY(2)-dy1;   y2 = lastXY(2)+dy1;
               if (x1 < 1),       dx1 = dx1 - (1-x1);      x1 = 1;        end
               if (x2 > iWidth),                           x2 = iWidth;   end
               if (y1 < 1),       dy1 = dy1 - (1-y1);      y1 = 1;        end
               if (y2 > iHeight),                          y2 = iHeight;  end
               imgSearchWindow = handles.m_imgCachedFrames(handles.m_iFrame-handles.m_iCacheBase+1).cdata(y1:y2, x1:x2, :);
               imgSearchWindow = imgSearchWindow(:,:,1) * 0.3 + imgSearchWindow(:,:,2) * 0.59 + imgSearchWindow(:,:,3) * 0.11;   % Convert to greyscale
               
               imgXCorr = normxcorr2(handles.m_tmpltImages{iTypeIdx,1}, imgSearchWindow);              

               % Cut off the bottom-left edges of the correlation - the
               % bottom-left is an area of low overlap which can get
               % erroneously good correlation (oddly, normxcorr2 fully 
               % overlaps on the top/right, but not the bottom/left).
               [iTmpltHeight, iTmpltWidth] = size(handles.m_tmpltImages{iTypeIdx,1});
               
               imgXCorr = imgXCorr(1:(size(imgXCorr,1)-iTmpltHeight+1), iTmpltWidth:end);
               [maxVals maxRowIdx] = max(imgXCorr);
               [maxVals maxColIdx] = max(maxVals);
               maxRowIdx = maxRowIdx(maxColIdx);
               maxColIdx = maxColIdx + iTmpltWidth-1;   % Restore correct index
               
               thisXY = [maxColIdx-iTmpltWidth-dx1, maxRowIdx-iTmpltHeight-dy1];
               thisXY = thisXY + lastXY + handles.m_tmpltOffsets(iTypeIdx,:);
            end
         end
         
         % thisXY is either a) what it was before, b) transferred from t-1
         % or c) a template match estimate of the new position
         handles.m_iCoordData(iRow+1, iCol:iCol+1) = thisXY;
      end

      guidata(handles.frmPoseLabeller, handles);   % Update member variables
   end



% --- Update coordinate data
function handles = updateCoords(mouseXY, handles)
% mouseXY    [x,y] position of mouse click in the image (scaled to img resolution)
%            [-1,-1] implies the critical point is occluded.
% handles    structure with handles and user data (see GUIDATA)

if (isempty(handles.m_iCoordData))
   handles.m_iCoordData = zeros(1,10);    % 5 (x,y) points (head, hands, legs)
   handles.m_iCoordBase = handles.m_iFrame;
end

handles.m_iCoordData(handles.m_iFrame-handles.m_iCoordBase+1, 2*(handles.m_iCoordType-1)+1) = mouseXY(1);
handles.m_iCoordData(handles.m_iFrame-handles.m_iCoordBase+1, 2*(handles.m_iCoordType-1)+2) = mouseXY(2);
guidata(handles.frmPoseLabeller, handles);



