function varargout = head_annotation_interface(varargin)
  
% HEAD_ANNOTATION_INTERFACE Application M-file for head_annotation_interface.fig
%    FIG = HEAD_ANNOTATION_INTERFACE launch head_annotation_interface GUI.
%    HEAD_ANNOTATION_INTERFACE('callback_name', ...) invoke the named callback.
%
% HEAD GT INTERFACE Version 1.2 by Guillaume Lathoud - lathoud@idiap.ch
%
% PPM reading functions courtesy of Jean-Marc Odobez - odobez@idiap.ch
% TimeCode extraction tool courtesy of Olivier Masson - masson@idiap.ch
%
% Last Modified by GUIDE v2.0 25-Jun-2004 14:27:01
  
  if nargin == 0  % LAUNCH GUI
    
    % First quickly check wether or not we'll be able to extract the TimeCode information
    timecode_cmd = [];
    if isunix
      [s,w] = system( 'uname -s' );
      w = deblank( w );
      if ~s
	if strcmp( w, 'SunOS' )
	  d = dir( './TimeCodeSolaris.out' );
	  if ~isempty( d )
	    timecode_cmd = d( 1 ).name;
	  end
	elseif strcmp( w, 'Linux' )
	  d = dir( './TimeCodeLinux.out' );
	  if ~isempty( d )
	    timecode_cmd = d( 1 ).name;
	  end
	end
      end
    end
    
    if isempty( timecode_cmd )
      waitfor( msgbox( 'The TimeCode extraction tool was not found. Timecode information will NOT be provided.','Warning', 'warn' ) );
    end

    % Create the interface window
    
    fig = openfig(mfilename,'reuse');
    drawnow
    
    % Use system color scheme for figure:
    set(fig,'Color',get(0,'defaultUicontrolBackgroundColor'));

    % Generate a structure of handles to pass to callbacks, and store it. 
    handles = guihandles(fig);
    
    
    % Save a pointer to itself!
    handles.self = fig;

    % Move it
    v = get( fig, 'Position' );
    set( fig, 'Position', [ 1 11 v(3:end)] );
    set( fig, 'Position', [ 1 v(4)+1 v(3:end)] );

    % Set to 1 for debugging
    handles.verbose = 0;

    % Store the timecode command (if any)
    handles.timecode_cmd = timecode_cmd;
    
    % Set version number and interface window's title
    handles.version = 1.1;
    set( fig, 'Name', sprintf( 'Head Annotation Interface %g - by lathoud@idiap.ch', handles.version ) );
    
    % Give some instructions to the user
    show_guidelines( handles );
    
    
    % Add a figure for the image;
    handles.image_fig = figure;
    set( handles.image_fig, 'doublebuffer', 'on' );
    v = get( handles.image_fig, 'Position' );
    set( handles.image_fig, 'Position', [v(1) 1 1.5*v(3:4)]);
    
    % Currently no image displayed
    handles.image_handle = [];
    handles.image_xdata = [];
    handles.image_ydata = [];
    
    % While ground-truthing, save to the file every N ground-truth frames
    handles.save_interval = 5;
    
    % Color of load/save messages
    handles.io_color    = [ 0 0 1.0 ];
    
    % Set to 1 to show the red box during ground-truthing
    handles.show_gt_box = 0;

    handles.gt_mode = 0;
    

    
    % Set default value for PPM directory  &  load images (if any)

    handles.the_dir = 'seq6_iain2_cam1';
    set( handles.edit1, 'String', handles.the_dir );
    
    set( handles.edit2, 'String', 'seq6_iain2_cam1_iain.headgt' );

    % Save for future callbacks
    guidata(fig, handles);

    % Initialization done.
    % --------------------------------------------------
    
    % Try to open PPM directory
    edit1_Callback( fig, [], handles );
    
    % Try to read GT file
    refresh_contents( fig, handles );
    

    
    if nargout > 0
      varargout{1} = fig;
    end

  elseif ischar(varargin{1}) % INVOKE NAMED SUBFUNCTION OR CALLBACK

  try
    if (nargout)
      [varargout{1:nargout}] = feval(varargin{:}); % FEVAL switchyard
    else
      feval(varargin{:}); % FEVAL switchyard
    end
  catch
    disp(lasterr);
  end

  end

% ----------------------------------------------------------------------
function show_guidelines( handles )

  if handles.verbose
    disp( '[ head_annotation_interface:show_guidelines ]' )
  end
  
      % Preliminary instructions
    a_string = sprintf( 'by lathoud@idiap.ch\n' );
    
    a_string = sprintf( '%s\nWith this interface you will annotate:', a_string );
    a_string = sprintf( '%s\n1) A rectangle around the head of a person.', a_string );
    a_string = sprintf( '%s\n2) Whether the head is not visible at all (is_visible = 0),', a_string );
    a_string = sprintf( '%s\n   partially visible (is_visible = 0.5 ),', a_string );
    a_string = sprintf( '%s\n   or fully visible (is_visible = 1.0 ).', a_string );
    a_string = sprintf( '%s\n', a_string );
    a_string = sprintf( '%s\nConstraints:', a_string );
    a_string = sprintf( '%s\n- make sure ALL visible pixels of the head are in the rectangle (e.g. head top, ears, chin).', a_string );
    a_string = sprintf( '%s\n- make the rectangle as tight as possible.', a_string );
    a_string = sprintf( '%s\n', a_string );
    a_string = sprintf( '%s\nDo not worry about the colored ball marker, the focus is the head only = you can cut through the ball.', a_string );
    a_string = sprintf( '%s\n', a_string );
    a_string = sprintf( '%s\nThe ground-truth file is a 7-column matrix, stored in ASCII format.', a_string );
    a_string = sprintf( '%s\nLoading and saving are done automatically.', a_string );
    a_string = sprintf( '%s\n', a_string );
    a_string = sprintf( '%s\nColor of the rectangle:', a_string );
    a_string = sprintf( '%s\n- red: manual ground-truth (your input) (is_manual_gt = 1).', a_string );
    a_string = sprintf( '%s\n- blue: interpolated ground-truth, e.g. from a tracker (is_manual_gt = 0).', a_string );
    a_string = sprintf( '%s\n\nIMPORTANT: the various video streams do not necessarily start at the same time (frame #1). It is therefore advisable that you make sure the same time frames are annotated on the various cameras, based on the "TimeCode" field, NOT on the frame "#" field.', a_string );    
    waitfor( msgbox( a_string, sprintf( 'Guidelines for Head Annotation Interface %g', handles.version ) ) ); 
    
% ----------------------------------------------------------------------
  
%| ABOUT CALLBACKS:
%| GUIDE automatically appends subfunction prototypes to this file, and 
%| sets objects' callback properties to call them through the FEVAL 
%| switchyard above. This comment describes that mechanism.
%|
%| Each callback subfunction declaration has the following form:
%| <SUBFUNCTION_NAME>(H, EVENTDATA, HANDLES, VARARGIN)
%|
%| The subfunction name is composed using the object's Tag and the 
%| callback type separated by '_', e.g. 'slider2_Callback',
%| 'figure1_CloseRequestFcn', 'axis1_ButtondownFcn'.
%|
%| H is the callback object's handle (obtained using GCBO).
%|
%| EVENTDATA is empty, but reserved for future use.
%|
%| HANDLES is a structure containing handles of components in GUI using
%| tags as fieldnames, e.g. handles.figure1, handles.slider2. This
%| structure is created at GUI startup using GUIHANDLES and stored in
%| the figure's application data using GUIDATA. A copy of the structure
%| is passed to each callback.  You can store additional information in
%| this structure at GUI startup, and you can change the structure
%| during callbacks.  Call guidata(h, handles) after changing your
%| copy to replace the stored original so that subsequent callbacks see
%| the updates. Type "help guihandles" and "help guidata" for more
%| information.
%|
%| VARARGIN contains any extra arguments you have passed to the
%| callback. Specify the extra arguments by editing the callback
%| property in the inspector. By default, GUIDE sets the property to:
%| <MFILENAME>('<SUBFUNCTION_NAME>', gcbo, [], guidata(gcbo))
%| Add any extra arguments after the last argument, before the final
%| closing parenthesis.



function handles = gtfile_warning( handles )

  if handles.verbose
    disp( '[ head_annotation_interface:gtfile_warning ]' )
  end

  waitfor( msgbox( 'Make sure you update the GT file name as well', 'PPM directory changed' ) );
  
  set( handles.edit2, 'ForegroundColor', 'r' );

  set( handles.text5, 'String', 'Check the GT file name' );
  set( handles.text5, 'ForegroundColor', 'r' );
  
  drawnow;
  


% --------------------------------------------------------------------
function varargout = edit1_Callback(h, eventdata, handles, varargin)

  if handles.verbose
    disp( '[ head_annotation_interface:edit1_Callback ]' );
  end
  
  new_dir = get( handles.edit1, 'String' );
  
  if ~exist( new_dir, 'dir' )

    waitfor( msgbox( sprintf( [ 'Could not find directory "' new_dir '".\n\nYour input was ignored.' ] ), 'Directory nname error' ) );

    set( handles.edit1, 'String', handles.the_dir );
    
  else
      
    d = dir( fullfile( new_dir, '*.ppm' ) );
    
    ppm_found = any( ~[ d.isdir ] );
    
    if ~ppm_found
      
      waitfor( msgbox( sprintf( [ 'Could not find any ".ppm" files in directory "' new_dir '".\n\nYour input was ignored.' ] ), 'Directory content error' ) );
      
    else
      
      % Refresh
      refresh_contents( h, handles );
      
      gtfile_warning( handles );
      
    end
    
  end    
  
  
% --------------------------------------------------------------------
function varargout = slider1_Callback(h, eventdata, handles, varargin)

  if handles.verbose
    disp( '[ head_annotation_interface:slider1_Callback ]' )
  end
  
  if ~handles.gt_mode
    set( handles.text5, 'String', '' );
    set( handles.text5, 'Foregroundcolor', [ 0 0 0 ] );
  end
  
  if ~isempty( handles.the_list )
    update_image( h, handles );
  end


% --------------------------------------------------------------------
% "Browse" button

function varargout = pushbutton1_Callback(h, eventdata, handles, varargin)

  if handles.verbose
    disp( '[ head_annotation_interface:pushbutton1_Callback ]' )
  end
  
  old_pwd = pwd;
  
  if ~isempty( handles.the_dir )
    if exist( handles.the_dir, 'dir' )
      cd( handles.the_dir );
    end
  end
  
  [ fn, pn ]=uigetfile('*','Change directory and pick ANY file');
  
  % Make sure the current working directory is unmodified  
  cd( old_pwd );

  if ischar( pn )

    if ~exist( pn, 'dir' )
      waitfor( msgbox( sprintf( [ 'Could not find directory "' pn '".\n\nYour input was ignored.' ] ), 'Directory nname error' ) );
    
    else
    
      d = dir( fullfile( pn, '*.ppm' ) );
      
      ppm_found = any( ~[ d.isdir ] );
      
      if ~ppm_found
	
	waitfor( msgbox( sprintf( [ 'Could not find any ".ppm" files in directory "' pn '".\n\nYour input was ignored.' ] ), 'Directory content error' ) );
	
      else
	
	% Store the new image directory
	set( handles.edit1, 'String', pn );
	
	% Refresh
	refresh_contents( h, handles );
	
	gtfile_warning( handles );
	
      end
      
    end
    
  end


% ---------------------------------------------------------------------
% "Refresh" implementation

function refresh_contents( h, handles )

  if handles.verbose
    disp( '[ head_annotation_interface:refresh_contents ]' )
  end

  the_dir = get( handles.edit1, 'String' );
  d = dir( fullfile( the_dir, '*.ppm' ) );
  
  if ~any( ~[ d.isdir ] )
    
    handles.the_list = {};

    % ----------
    % Save this copy of the "handles" structure
    guidata( handles.figure1, handles );
  
  else

    % Build a list of PPM FILES
    the_list = { d.name }; 
    the_list = the_list( find( ~[ d.isdir ] ) );
    
    % Save new list of files
    handles.the_dir = the_dir;
    handles.the_list = the_list;

    % Check the GT file
    gt_file = get( handles.edit2, 'String' );
    if ~exist( gt_file, 'file' )
      handles.gt_m = [];
      set( handles.text5, 'String', '[ New GT file - memory cleared ]' );
      set( handles.text5, 'Foregroundcolor', handles.io_color );
    else
      handles.gt_m = load( gt_file, '-ASCII' );
      set( handles.text5, 'String', '[ GT file loaded into memory ]' );
      set( handles.text5, 'Foregroundcolor', handles.io_color  );
    end
    
    % ----------
    
    % Update slider
    current_value = get( handles.slider1, 'Value' );

    n = length( handles.the_list );

    if n < 1
      set( handles.slider1, 'Visible', 'off' );
      
    elseif n < 2
      set( handles.slider1, 'Visible', 'off' );
      
    else
      set( handles.slider1, 'Visible', 'on' );
      
    end

    set( handles.slider1, 'SliderStep', [ 1/max(1,n), 1/max(1,n/10)] );
    
    
    % ----------
    % Save this copy of the "handles" structure
    guidata( handles.figure1, handles );


    
    % ----------
    % Remaining code: update image name and display

    update_image( handles.figure1, handles );
  
  end

% ----------------------------------------------------------------------
function image_ind = get_image_ind( handles )

  if handles.verbose
    disp( '[ head_annotation_interface:get_image_ind ]' )
  end

  n = length( handles.the_list );
  
  image_ind = round( 1 + get( handles.slider1, 'Value' ) * (n-1) );
  image_ind = max( 1, image_ind );
  image_ind = min( length( handles.the_list ), image_ind );

% ----------------------------------------------------------------------
function set_image_ind( handles, new_image_ind )

  if handles.verbose
    disp( '[ head_annotation_interface:set_image_ind ]' )
  end

  n = length( handles.the_list );
  if isempty( new_image_ind )
    new_image_ind = get_image_ind( handles );
  else
    new_image_ind = max( 1, min( n, new_image_ind( 1 ) ) );
  end
  
  set( handles.edit4, 'String', num2str( new_image_ind ) );
  
  if new_image_ind ~= get_image_ind( handles );
    set( handles.slider1, 'Value', (new_image_ind-1)/ (n-1) );
    update_image( gcbo, handles );
  end
  
% ----------------------------------------------------------------------
function update_image( hObject, handles )
 
  if handles.verbose
    disp( '[ head_annotation_interface:update_image ]' )
  end
  
  image_ind = get_image_ind( handles );

  set( handles.edit4, 'String', num2str( image_ind ) );
  
  if image_ind < 1

    % Update image
    figure( handles.image_fig );
    clf;
    
    % Update GT display
    set( handles.text6, 'String', '[ No GT for this frame. ]' );
    
  else
    
    
    % Update image
    
    a_name = handles.the_list{ image_ind };
    image_fullname = fullfile( handles.the_dir, a_name );
    figure( handles.image_fig );
    hold off;
    handles.image_handle = image( uint8( Lecture( image_fullname ) ) );
    handles.image_timecode = [];
    if ~isempty( handles.timecode_cmd )
      [s,w] = system( [ handles.timecode_cmd ' ' image_fullname ] );
      handles.image_timecode = deblank( w );
    end
    handles.image_xdata = get( handles.image_handle, 'XData' );
    handles.image_ydata = get( handles.image_handle, 'YData' );
    a_title = a_name;
    if ~isempty( handles.image_timecode )
      a_title = [ a_title ' ( TimeCode ' handles.image_timecode ' )' ];
    end
    title( a_title );
    
    % Add a box 
    
    has_gt =  ~isempty( handles.gt_m );
    if has_gt; ind = find( handles.gt_m(:,1) == image_ind ); has_gt = ~isempty( ind ); end 
    
    if ~has_gt
      % Update GT display
      set( handles.text6, 'String', '[ No GT for this frame. ]' );
    else
      is_manual_gt = handles.gt_m( ind, 2 );
      is_visible = handles.gt_m( ind, 3 );
      
      if is_visible
	hold on
	xybox = handles.gt_m( ind, 4:7 );
	
	h = line( xybox( [1 1 3 3 1] ), xybox( [2 4 4 2 2] ) );
	if is_visible < 0.99
	  set( h, 'LineStyle', '--' );
	end
	
	if is_manual_gt
	  set( h, 'Color', 'r' );
	else
	  set( h, 'Color', 'c' );
	end
      end
      
      % Update GT display
      new_string =  [ 'GT: [' num2str( handles.gt_m(ind,:)) ']' ];
      new_string = [ new_string sprintf( '\n\nimage_ind:     #%d\nis_manual_gt: %d\nis_visible:       %3.1f', image_ind, is_manual_gt, is_visible ) ];
      if is_visible
	new_string = [ new_string sprintf( '\nupper-left:   ( %5.2f, %5.2f )\nlower-right: ( %5.2f, %5.2f )', xybox ) ];
      end
      
      set( handles.text6, 'String', new_string);
    end
  end
  
  % Update "previous manual gt" and "next manual gt" buttons
  
  update_gt_buttons( handles );

  % Update bottom text
  if ~handles.gt_mode & all( get( handles.text5, 'Foregroundcolor' )== [0 0 0] )
    set( handles.text5, 'String', '' );
  end

  guidata( hObject, handles );
  


% --------------------------------------------------------------------
function varargout = edit2_Callback(h, eventdata, handles, varargin)

  if handles.verbose
    disp( '[ head_annotation_interface:edit2_Callback ]' )
  end

  a_string = get( handles.edit2, 'String' );
  
  [p,n,e] = fileparts( a_string );
  if ~exist( p )
    waitfor( msgbox( sprintf( [ 'Directory "' p '" does not exist!\n\nYour input was ignored.' ] ), 'GT file selection error' ) );
    set( handles.edit2, 'String', handles.the_dir );
    
  else
    
    refresh_contents( h, handles );
    set( handles.edit2, 'ForegroundColor', 'k' );
    
  end
  

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

function headgt_interval = get_headgt_interval( handles )
  
  if handles.verbose
    disp( '[ head_annotation_interface:get_headgt_interval ]' )
  end

  headgt_interval = str2num( get( handles.edit3, 'String' ) );
  if isempty( headgt_interval )
    headgt_interval = 12;
  end

  
function varargout = edit3_Callback(h, eventdata, handles, varargin)
  
  if handles.verbose
    disp( '[ head_annotation_interface:edit3_Callback ]' )
  end

  headgt_interval = get_headgt_interval( handles );
  
  set( h, 'String', num2str( headgt_interval ) );
  
  % Save it
  
  guidata( h, handles );
  
  
% --------------------------------------------------------------------
function show_gtmanual( handles )
  
  if handles.verbose
    disp( '[ head_annotation_interface:show_gtmanual ]' )
  end

  set( handles.text5, 'String', sprintf( 'GROUND-TRUTHING:\n[Leftbutton] then [Leftbutton] to ground-truth a fully visible head,\n[Leftbutton] then [Rightbutton] to ground-truth a partially visible head,\n[Rightbutton] for a completely occluded/invisible head,\n[Space] to skip frame,\n[Return] to exit the GT mode.' ));
  set( handles.text5, 'Foregroundcolor', 'k' );
  

function varargout = pushbutton3_Callback(h, eventdata, handles, varargin)

  if handles.verbose
    disp( '[ head_annotation_interface:pushbutton3_Callback ]' )
  end

  if ~isfield( handles, 'gt_mode' ) | handles.gt_mode == 0
    
    try
    
    handles.gt_mode = 1;
    
    handles.gt_count = 0;  % To save regularly
    
    show_gtmanual( handles );
    
    while handles.gt_mode
      
      figure( handles.image_fig );
      set( handles.image_fig, 'Selected', 'on' );
      set( handles.image_fig, 'SelectionHighlight', 'on' );

      
      %%%%%%%%%%%%%%%%%%%%
      % Button 1
      
      % Upper-left corner
      
      [x1,y1,b1] = ginput( 1 );
      
      do_stop = isempty( b1 );
      
      if do_stop
	handles.gt_mode = 0; 
	break;
      end
      
      % Does the user want to skip this frame?
      do_skip = (b1 == 32);   % "Space" key
      
      % Does the user want to end the process
      do_stop = (b1 > 32 );
      
      if do_stop
	handles.gt_mode = 0;
	break;
      end

      if ~do_skip
	
	% Does the user want to mark this frame as invisible?
	if b1 ~= 1
	  % yes
	  x1 = 0;
	  y1 = 0;
	  x2 = 0;
	  y2 = 0;
	  b2 = b1;
	  
	else
	  %no

	  %%%%%%%%%%%%%%%%%%%%
	  % Button 2
	  % Lower-right corner
	  
	  [x2,y2,b2] = ginput( 1 );
	  
	  do_stop = isempty( b2 );
	  
	  if do_stop
	    handles.gt_mode = 0;
	    break;
	  end
	  
	  % Does the user want to skip this frame?
	  do_skip = (b2 == 32);   % "Space" key
	  
	  % Does the user want to end the process
	  do_stop = (b2 > 32 );
	  
	  if do_stop
	    handles.gt_mode = 0;
	    break;
	  end
	end
	
	if ~do_skip
	  image_ind = get_image_ind( handles );

	  x = [ min( x1, x2) max( x1, x2 )];
	  y = [ min( y1, y2) max( y1, y2 )];
	  b = [ b1 b2];

	  % Clip it within the image boundaries

	  if ~isempty( handles.image_xdata )
	    x = max( handles.image_xdata( 1 ) - 0.5, x );
	    x = min( handles.image_xdata( 2 ) + 0.5, x );
	  elseif handles.verbose
	    disp( '[ pushbutton3_Callback: for some reason "handles.image_xdata" is empty. ]' );
	  end
	  
	  if ~isempty( handles.image_ydata )
	    y = max( handles.image_ydata( 1 ) - 0.5, y );
	    y = min( handles.image_ydata( 2 ) + 0.5, y );
	  elseif handles.verbose
	    disp( '[ pushbutton3_Callback: for some reason "handles.image_ydata" is empty. ]' );
	  end
	    

	  % Mark this GT was given manually
	  
	  is_manual_gt = 1;

	  % is_visible:
	  % value of 0.0 means head is invisible or completely occluded
	  % value of 0.5 means head is partially occluded
	  % value of 1.0 means head is fully visible 
	  is_visible = mean( b==1 );
	  
	  if handles.verbose
	    disp( sprintf( 'GT is_visible: %5.2f', is_visible ) );
	  end
	  
	  xybox = [ x(1) y(1) x(2) y(2) ];
	  
	  
	  
	  
	  % New line to add to the ground-truth matrix
	  
	  new_line = [ image_ind is_manual_gt is_visible xybox ];

	  if handles.show_gt_box
	    % Display it so that the user can see right away the result of his marking
	    
	    figure( handles.image_fig ); hold on;
	    hh = line( [ x(1) x(2) x(2) x(1) x(1) ], [y(1) y(1) y(2) y(2) y(1)]);
	    set( hh, 'Color', 'r' );
	  end
	  
	  % Store it, replacing old data if needed
	  
	  if ~isempty( handles.gt_m )
	    ind = find( handles.gt_m(:,1) == image_ind );
	    if isempty( ind )
	      handles.gt_m(end+1,:) = new_line;
	    else
	      handles.gt_m( ind, : ) = new_line;
	    end
	  else
	    handles.gt_m = new_line;
	  end

	  handles.gt_count = handles.gt_count+1;
	  
	  if handles.gt_count >= handles.save_interval
	    
	    % Save it to the file
	    save_file( handles );
	    
	    handles.gt_count = 0;
	    	      
	  else
	    show_gtmanual( handles );
	  end
	  
	end

      end
      
      % Move on to the next ground-truth frame
      slider_step = get( handles.slider1, 'SliderStep' );
      slider_max = get( handles.slider1, 'Max' );
      old_slider_value = get( handles.slider1, 'Value' );

      new_slider_value = old_slider_value + get_headgt_interval( handles ) *  slider_step( 1 );
      new_slider_value = min( slider_max, new_slider_value );
      set( handles.slider1, 'Value', new_slider_value );

      slider1_Callback( h, eventdata, handles, varargin );

      % Last frame ?
      if abs( new_slider_value -old_slider_value ) < eps
	handles.gt_mode = 0;
      end
      
    end

    set( handles.text5, 'String', '' );
	    
    % Save it to the file
    save_file( handles );

    handles.gt_count = 0;
    
    catch
      
      handles.gt_mode = 0;

      set( handles.text5, 'String', '' );
      
      disp( [ '[ pushbutton3_Callback: caught error "' lasterr  '" ]' ] );
      keyboard
      
    end    
  end

  % Update the memory
  guidata( h, handles );
  
  if handles.verbose
    disp( 'Exited GT mode' );
  end
  

% --------------------------------------------------
% To save the file in ASCII format, without the "e" notation

function save_file( handles )

  if handles.verbose
    disp( '[ head_annotation_interface:save_file ]' )
  end

  gt_m = sortrows( handles.gt_m );
  gt_file = get( handles.edit2, 'String' );

  fid = fopen( gt_file, 'wt' );
  if fid < 0
    waitfor( msgbox( [ 'Could not write-open "' gt_file '"' ], 'IO error' ) );
  else
    fprintf( fid,'%4d %4d %4.1f %10.4f %10.4f %10.4f %10.4f\n',gt_m.');
    fclose( fid );
  end

  
  set( handles.text5, 'String', '[ GT file saved ]' );
  set( handles.text5, 'Foregroundcolor', handles.io_color );
  
  
% --- Executes during object creation, after setting all properties.
function edit4_CreateFcn(hObject, eventdata, handles)
% hObject    handle to edit4 (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 isstruct( handles )
    if handles.verbose
      disp( '[ head_annotation_interface:edit4_CreateFcn ]' )
    end
  end

  
  if ispc
    set(hObject,'BackgroundColor','white');
  else
    set(hObject,'BackgroundColor',get(0,'defaultUicontrolBackgroundColor'));
  end


function edit4_Callback(hObject, eventdata, handles)
% hObject    handle to edit4 (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 edit4 as text
%        str2double(get(hObject,'String')) returns contents of edit4 as a double


  if handles.verbose
    disp( '[ head_annotation_interface:edit4_Callback ]' )
  end

  new_image_ind = str2num( get( handles.edit4, 'String' ) );
  n = length( handles.the_list );
  if isempty( new_image_ind )
    new_image_ind = get_image_ind( handles );
  else
    new_image_ind = max( 1, min( n, new_image_ind( 1 ) ) );
  end
  
  set( handles.edit4, 'String', num2str( new_image_ind ) );
  
  if new_image_ind ~= get_image_ind( handles );
    set( handles.slider1, 'Value', (new_image_ind-1)/ (n-1) );
    update_image( hObject, handles );
  end

    
  if ~handles.gt_mode
    set( handles.text5, 'String', '' );
    set( handles.text5, 'Foregroundcolor', [ 0 0 0 ] );
  end

  
% --------------------------------------------------------------------
function varargout = pushbutton5_Callback(h, eventdata, handles, varargin)

  if handles.verbose
    disp( '[ head_annotation_interface:pushbutton5_Callback ]' )
  end

  old_pwd = pwd;
  
  pp = fileparts( get( handles.edit2, 'String' ) );
  
  if ~isempty( pp )
    if exist( pp, 'dir' )
      cd( pp );
    end
  end
  
  [ fn, pn ]=uiputfile('*.headgt','Choose GT file');
  
  if ischar( pn )
    
    fulln = fullfile( pn, fn );
    
    if exist( fulln, 'dir' )
      
      waitfor( msgbox( 'Please pick or create a FILE, not a directory!', 'GT file selection error' ) );
      
    elseif ~exist( pn, 'dir' )
      
      waitfor( msgbox( sprintf( [ 'Directory "' pn '" does not exist!\n\nYour input was ignored.' ] ), 'GT file selection error' ) );
      
    else
      
      % Store the new gt file name
      set( handles.edit2, 'String', fulln );
      
      % Refresh
      refresh_contents( h, handles );
      
    end
    
  end
  
  % Make sure the current working directory is unmodified  
  cd( old_pwd );
  
  
% --------------------------------------------------------------------
function pmgt_ind = get_prev_manual_gt( handles )
  
  if handles.verbose
    disp( '[ head_annotation_interface:get_prev_manual_gt ]' )
  end

  pmgt_ind = [];

  if isfield( handles, 'gt_m' )
    if ~isempty( handles.gt_m )
      
      image_ind = get_image_ind( handles );
      ind = find( ( handles.gt_m( :,1 ) < image_ind ) & handles.gt_m(:,2) );
      
      if ~isempty( ind )
	pmgt_ind = max( handles.gt_m( ind, 1 ) );
      end
      
    end
  end  
  
% --------------------------------------------------------------------
function nmgt_ind = get_next_manual_gt( handles )
  
  if handles.verbose
    disp( '[ head_annotation_interface:get_next_manual_gt ]' )
  end

  nmgt_ind = [];

  if isfield( handles, 'gt_m' )
    if ~isempty( handles.gt_m )
      
      image_ind = get_image_ind( handles );
      ind = find( ( handles.gt_m( :,1 ) > image_ind ) & handles.gt_m(:,2) );
      
      if ~isempty( ind )
	nmgt_ind = min( handles.gt_m( ind, 1 ) );
      end
      
    end
  end
  
% --------------------------------------------------------------------
function varargout = pushbutton_prevmgt_Callback(h, eventdata, handles, varargin)

  if handles.verbose
    disp( '[ head_annotation_interface:pushbutton_prevmgt_Callback ]' )
  end

  pmgt_ind = get_prev_manual_gt( handles );
  
  if ~isempty( pmgt_ind )
    
    set_image_ind( handles, pmgt_ind );
  if ~handles.gt_mode
    set( handles.text5, 'String', '' );
    set( handles.text5, 'Foregroundcolor', [ 0 0 0 ] );
  end
    
  end
  
% --------------------------------------------------------------------
function varargout = pushbutton_nextmgt_Callback(h, eventdata, handles, varargin)

  if handles.verbose
    disp( '[ head_annotation_interface:pushbutton_nextmgt_Callback ]' )
  end

  nmgt_ind = get_next_manual_gt( handles );
  
  if ~isempty( nmgt_ind )
    
    set_image_ind( handles, nmgt_ind );
  if ~handles.gt_mode
    set( handles.text5, 'String', '' );
    set( handles.text5, 'Foregroundcolor', [ 0 0 0 ] );
  end
    
  end
  
  % ----------------------------------------------------------------------
function update_gt_buttons( handles )

  if handles.verbose
    disp( '[ head_annotation_interface:update_gt_buttons ]' )
  end

% Update GT buttons
    pmgt_ind = get_prev_manual_gt( handles );
    
    if ~isempty( pmgt_ind )
      set( handles.pushbutton_prevmgt, 'String', sprintf( 'Go to previous manual GT (#%d)', pmgt_ind ) );
      set( handles.pushbutton_prevmgt, 'Visible', 'on' );
    else
      set( handles.pushbutton_prevmgt, 'Visible', 'off' );
    end
    
    nmgt_ind = get_next_manual_gt( handles );
    if ~isempty( nmgt_ind )
      set( handles.pushbutton_nextmgt, 'String', sprintf( 'Go to next manual GT (#%d)', nmgt_ind ) );
      set( handles.pushbutton_nextmgt, 'Visible', 'on' );
    else
      set( handles.pushbutton_nextmgt, 'Visible', 'off' );
    end
    
    % ----------------------------------------------------------------------
    function  Image=Lecture(nom)
%  Image=Lecture(nom)  : Lecture d'une image 
%  format supporté : pgm et ceux de imread (jpeg, tiff, ...)
%   exemple : Im1=Lecture('lezard.pgm');
%
%  Pour une image brute, voir LectureRaw

  [s,r]=strtok(nom,'.');
  while ~size(r,2)==0
    [s,r]=strtok(r,'.');
  end

  if strcmp(s,'pgm')==1
    Image=double(LecturePgm(nom));
  elseif strcmp(s,'ppm')==1
      Image=double(LecturePnm(nom));
  elseif strcmp(s,'pnm')==1
      Image=double(LecturePnm(nom));
  else
    Image=imread(nom);
    Image=double(Image);
  end

  % ----------------------------------------------------------------------
  function  Image=LecturePgm(nom)
%  Image=LecturePgm(nom)  : Lecture d'une image au format pgm
%    exemple : Im1=LecturePgm('lezard.pgm');
%
s=size(nom);
s=s(2);
while nom(s)==' '
    s=s-1;
end

fid=fopen(nom(1:s),'r');

nl=abs(10);
Ent=fscanf(fid,'%c',2);
if Ent=='P5'

  c=fscanf(fid,'%c',1);
  while  c~=nl,
     c=fscanf(fid,'%c',1);
  end
  c=fscanf(fid,'%c',1);
  while c=='#',
      while  c~=nl,
         c=fscanf(fid,'%c',1);
      end
      c=fscanf(fid,'%c',1);
  end  
else
  'Ce n''est pas un fichier pgm'
end

%Traitement des nombres de colonnes et de lignes
dim=c;
c=fscanf(fid,'%c',1);
while  c~=nl,
    dim=[dim,c];
    c=fscanf(fid,'%c',1);
end
ndim=length(dim);
nb=strtok(dim,' ');
dim=dim(length(nb)+1:ndim);
Nbco=str2num(nb);
Nbli=str2num(dim);

%On trouve 255 puis, ligne suivante, les donnees
s=fscanf(fid,'%c',3);
if s~='255'
  'Attention, erreur de format'
end
  
c=fscanf(fid,'%c',1);
while  c~=nl,
   c=fscanf(fid,'%c',1);
end


%%% Recuperation des donnees dans une matrice

Image=fread(fid,[Nbco,Nbli]);
Image=Image';

fclose(fid);

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

function  Image=LecturePnm(nom)
%  Image=LecturePnm(nom)  : Lecture d'une image au format pnm
%    exemple : Im1=LecturePnm('lezard.pnm');
%
s=size(nom);
s=s(2);
while nom(s)==' '
    s=s-1;
end
sname=s;

%stupid but works


for p=0:2
fid=fopen(nom(1:sname),'r');

nl=abs(10);
Ent=fscanf(fid,'%c',2);
if Ent=='P6'

  c=fscanf(fid,'%c',1);
  while  c~=nl,
     c=fscanf(fid,'%c',1);
  end
  c=fscanf(fid,'%c',1);
  while c=='#',
      while  c~=nl,
         c=fscanf(fid,'%c',1);
      end
      c=fscanf(fid,'%c',1);
  end  
else
  'Ce n''est pas un fichier ppm'
end

%Traitement des nombres de colonnes et de lignes
dim=c;
c=fscanf(fid,'%c',1);
while  c~=nl,
    dim=[dim,c];
    c=fscanf(fid,'%c',1);
end
ndim=length(dim);
nb=strtok(dim,' ');
dim=dim(length(nb)+1:ndim);
Nbco=str2num(nb);
Nbli=str2num(dim);

%On trouve 255 puis, ligne suivante, les donnees
s=fscanf(fid,'%c',3);
if s~='255'
  'Attention, erreur de format'
end
  
c=fscanf(fid,'%c',1);
while  c~=nl,
   c=fscanf(fid,'%c',1);
end


%%% Recuperation des donnees dans une matrice

if p==0
  Image=zeros(Nbli,Nbco,3);
end
if p==1
   sss=fread(fid,1,'uchar');
end
if p==2
   sss=fread(fid,2,'uchar');
end

ImageR=fread(fid,[Nbco,Nbli],'uchar',2);
Image(:,:,p+1)=ImageR';

fclose(fid);

end % end loop on plans


% --------------------------------------------------------------------
function varargout = pushbutton8_Callback(h, eventdata, handles, varargin)
 
  if handles.verbose
    disp( '[ head_annotation_interface:pushbutton8_Callback ]' )
  end

  show_guidelines( handles );
  


% --------------------------------------------------------------------
function varargout = pushbutton9_Callback(h, eventdata, handles, varargin)

  if handles.verbose
    disp( '[ head_annotation_interface:pushbutton9_Callback ]' )
  end

  close( handles.image_fig );
  close( handles.self );
  