function o_patience = gi_acontour(task, varargin)
%gi_acontour: graphical interface for ac_segmentation
%   o_p = gi_acontour(t, a) performs a graphical interface task, t, according to
%   some arguments, a, for ac_segmentation. t is a string among
%   'initialization', 'new resolution', 'evolution update', 'movie update',
%   'result', 'patience', and 'close'. Another "special" task is 'dbcont'.
%   Normally, only initialization, result, close, and dbcont should be called
%   explicitly. The other tasks are meant to be called by ac_segmentation.
%
%   gi_acontour('initialization', t, b, a, o_t, o_s) creates a graphical
%   interface window with a title, t, and a background image, b, drawing an
%   initial active contour, a. In the evolution window, only one contour is
%   displayed. The purpose is to check that the segmentation process is running
%   as expected. If the evolution involves several active contours, the one that
%   is displayed is either the first one in the list or the i^th one with i
%   equal to acontour_indices(1) where acontour_indices is a field of the
%   o_inferface parameter of ac_segmentation. In the (optional) movie window,
%   either the first active contour is displayed or the contours with indices
%   acontour_indices. The background of the evolution and movie windows is b. If
%   several active contours segment several images, the segmentation has to be
%   run several times to generate all the movies corresponding to the different
%   images. However, there is automatically one result window per active contour
%   showing the initial and final contours. Therefore, only one execution is
%   required if only still result images are needed.
%   After the initialization, ac_segmentation calls other tasks to display and
%   control (i.e., pause, resume, and stop) the evolution. o_t is the movie
%   title. If isempty(o_t) is true, no movie is generated. Otherwise, an avi
%   file, o_t.avi (see notes below), of the evolution is generated by the result
%   task (see below) with a framesize equal to size(b) times o_s, where o_s is
%   an integer greater or equal to 1. By default, o_s is taken equal to 1.
%   Note 1: if o_t already contains the avi extension, it is not added.
%   Note 2: if o_t is equal to 'undecided', a graphical interface opens (by the
%   result task (see below)) to select the file directory and name.
%   Note 3: If the last character of o_t is equal to '*', then instead of the
%   avi output, still frames are output (see below)
%
%   The last few active contour evolutions are displayed with the following
%   color code: samples evolving inward are red, samples evolving outward are
%   blue, and samples almost not evolving are green. This display involving only
%   the samples of the active contour, it can be very sparse and confusing if
%   the active contour has a low resolution and is composed of many single
%   active contours. At each new resolution/scale, the initial contour is
%   displayed in magenta at the current scale. However, the subsequent evolution
%   is displayed with a rescaling to the original scale.
%
%   The evolution of the energy of the segmentation (i.e., of the set of all of
%   the active contours, not just the one display here) is superimposed on the
%   active contour evolution. The segmentation being expressed as a
%   minimization, the energy should decrease regularly. When changing
%   resolution/scale, it usually jumps down (although it is not impossible that
%   it slightly jumps up). If the energy persists to increase, it might be due
%   to the active contour resolution being too low. Indeed, the error made when
%   discretizing the continuous evolution equation increases if the resolution
%   gets lower, possibly leading to an improper evolution. In spite of that
%   fact, ac_segmentation should detect this irregular behavior and stop the
%   evolution but does not. This should be investigated. For now, increasing the
%   resolution should work.
%
%   It takes some iterations to detect that the energy has stabilized but the
%   evolution process should eventually stop.
%
%   When the evolution process is paused by pressing the associated button, it
%   is not paused immediately. ac_segmentation checks for a pause request at
%   each new iteration but the normal flow of computation is not directly
%   interrupted by pressing pause. In particular, the energy and amplitude
%   functions passed to ac_segmentation complete normally. When done, the pause
%   button becomes a stop button and a resume button appears. No user
%   interaction should occur in the meantime. The current result can be output
%   by pressing the stop button. The evolution process can also be stopped by a
%   ctrl-c in the command window. However, the current result is not available
%   this way.
%   When the energy and velocity amplitude functions passed to ac_segmentation
%   do not require much computation, if a movie output has been requested, and
%   if the window manager is in "focus follows mouse" mode, it can happen that
%   the pause button "misses" some mouse clicks for not getting the focus long
%   enough (or at least, it seems so). It is not known if this can also happen
%   in a "click to focus" mode. Note that this behavior might be due to an
%   incorrect plot command use, e.g. in ac_plot.
%
%   gi_acontour('result', b, i, f, o_d, o_n, o_f) opens one window per frame
%   contained by b. b can be a cell array containing a single frame or n frames
%   where n is the number of active contours. The frames can be in grayscale
%   (size(b{.},3) is equal to 1) or in color (size(b{.},3) is equal to 3). b can
%   also be an array such that size(b,3) is equal to size(b_init,3) times k,
%   where b_init is the background b passed to the initialization task (see
%   above) and k is equal to n.
%   i is the initial active contour or a cell array of initial active contours.
%   f contains the final active contour(s) and length(i) is equal to length(f)
%   if both are cell arrays (otherwise, they are both struct arrays). If there
%   is only one background image, it is used to display all of the active
%   contours. Otherwise, there is a one-to-one correspondence between background
%   images and initial/final active contours.
%   o_d is the directory to write the movie to, if requested by the
%   initialization task (see above). If isempty(o_d) is true, pwd is used. By
%   default, o_d is taken equal to []. In case of still frames, o_n samples of
%   the evolution are output as images of type o_f. By default, o_n and o_f are
%   taken equal to 4 and 'png', respectively.
%
%   Instead of using the close task, the graphical interface window can be
%   closed like any regular window. The dbcont task is related to debugging
%   (including debugging of code called from this toolbox but not being part of
%   it) and is not very convenient.
%
%   Note:
%   The other tasks should be documented in a later release to allow the writing
%   of alternate interfaces for ac_segmentation.
%   The energy can keep on increasing without ac_segmentation stopping the
%   evolution. This should be investigated.
%
%See also ac_segmentation, acontour.
%
%Active Contour Toolbox by Eric Debreuve
%Last update: July 5, 2006

persistent p_title p_background_size ...
   p_initial_acontour p_acontours_handles p_handle_lengths ...
   p_movie_acontour p_energy_handle ...
   p_evolution_fig p_control p_movie_fig ...
   p_movie_title p_making_of_movie p_styles_for_movie ...
   p_iteration_limit p_motion_picture

switch task
   case 'initialization'%, title, a background image, a (cell of) acontour(s)[, movie title, movie scaling]
      p_title = varargin{1};

      %background image
      background = p_scaling(varargin{2});
      p_background_size = size(background);
      if length(p_background_size) < 3
         p_background_size(3) = 1;
      end

      %evolution figure
      p_control = s_layout(p_title, background);
      p_evolution_fig = p_control.parent;
      p_initial_acontour = [];
      p_acontours_handles = [];
      p_handle_lengths = 0;
      p_energy_handle = [];

      %movie figure
      p_making_of_movie = false;
      if nargin > 4
         if ~isempty(varargin{4})
            p_making_of_movie = true;
            p_movie_title  = varargin{4};
            p_styles_for_movie = {'er--1.5' 'eg--1.5' 'ec--1.5' 'er-.1.5' 'eg-.1.5' 'ec-.1.5'};%should be a parameter

            p_movie_fig = figure('Name', 'Motion picture', 'MenuBar', 'none');
            if p_background_size(3) == 1
               colormap('gray')
            end
            image(background)
            p_control.screen = gca;
            if nargin > 5
               truesize(p_movie_fig, varargin{5} * p_background_size([1 2]))
            else
               truesize(p_movie_fig)
            end
            axis off
            hold on
            %axis tight, axis equal, axis manual
            p_movie_acontour = [];
            if iscell(varargin{3})
               feval(mfilename, 'movie update', varargin{3}, 0)
            else
               feval(mfilename, 'movie update', {varargin{3}}, 0)
            end
            movegui(p_movie_fig, 'southwest')
         end
      end

      guidata(p_evolution_fig, p_control)
      drawnow

      disp('close segmentation progress window with: gi_acontour(''close'')')

   case 'new resolution'%, an acontour, resolution, iteration limit
      p_iteration_limit = varargin{3};
      delete(p_initial_acontour)%not an error if empty
      p_initial_acontour = [];
      if ~isempty(p_acontours_handles)
         delete(p_acontours_handles)
         p_acontours_handles = [];
         p_handle_lengths = 0;
      end
      if ~isempty(p_energy_handle)
         delete(p_energy_handle)
         p_energy_handle = [];
      end
      if ~isempty(varargin{1})%an acontour can disappear during evolution
         p_initial_acontour = ac_plot(p_control.axes, varargin{1}, p_background_size(1), 'em:');
      end
      set(p_control.resolution, 'String', ['Resolution ' num2str(varargin{2}) ' (' datestr(clock, 'HH:MM:SS') ')'])
      set(p_control.iteration, 'String', ['0 of max' int2str(p_iteration_limit)])
      drawnow

   case 'evolution update'%, samples, iteration[, amplitudes, energies]
      if length(p_handle_lengths) > 4
         delete(p_acontours_handles(1:p_handle_lengths(1)))
         p_acontours_handles(1:p_handle_lengths(1)) = [];
         p_handle_lengths(1) = [];
      end
      if nargin > 3
         small_amplitude = 0.05 * max(abs(varargin{3}));
         forward  = find(varargin{3}      >=   small_amplitude);
         on_site  = find(abs(varargin{3}) <    small_amplitude);
         backward = find(varargin{3}      <= - small_amplitude);
         hr = plot(p_control.axes, varargin{1}(2,forward),  varargin{1}(1,forward),  'r.');
         hg = plot(p_control.axes, varargin{1}(2,on_site),  varargin{1}(1,on_site),  'g.');
         hb = plot(p_control.axes, varargin{1}(2,backward), varargin{1}(1,backward), 'b.');
         p_acontours_handles = [p_acontours_handles; hr; hg; hb];
         p_handle_lengths(end+1) = length([hr hg hb]);
      else
         hr = plot(p_control.axes, varargin{1}(2,:), varargin{1}(1,:), 'c.');
         p_acontours_handles = [p_acontours_handles; hr];
         p_handle_lengths(end+1) = length(hr);
      end
      if nargin > 4
         energies = varargin{4};
         if length(energies) > 1
            max_length = p_background_size(2) / 25;
            if length(energies) > max_length
               energies = energies([1:round(length(energies) / max_length):(end-1), end]);
            end
            minimum = min(energies);
            maximum = max(energies);
            energies = 1 + ((p_background_size(1) - 1) / (maximum - minimum)) * (energies - minimum);
            abscissa = 1:((p_background_size(2) - 1) / (length(energies) - 1)):p_background_size(2);
            if length(abscissa) ~= length(energies)
               abscissa = [abscissa p_background_size(2)];
            end
            delete(p_energy_handle)%not an error if empty
            p_energy_handle = plot(p_control.axes, abscissa, p_background_size(1) - energies + 1, ':ow', 'MarkerFaceColor', 'b');
         end
      end
      set(p_control.iteration, 'String', [int2str(varargin{2}) ' of max' int2str(p_iteration_limit)])
      drawnow

   case 'movie update'%, a cell of acontours, overall iteration
      if p_making_of_movie
         delete(p_movie_acontour)%not an error if empty
         p_movie_acontour = [];
         for acontour_idx = 1:length(varargin{1})
            if ~isempty(varargin{1}{acontour_idx})%an acontour can disappear during evolution
               p_movie_acontour = [p_movie_acontour; ...
                  ac_plot(p_control.screen, varargin{1}{acontour_idx}, ...
                  p_background_size(1), p_styles_for_movie{1 + mod(acontour_idx-1, length(p_styles_for_movie))})];
            end
         end
         frame = getframe(p_control.screen);
         frame.cdata = frame.cdata(1:end-1,1:end-1,:);
         if varargin{2} > 0
            p_motion_picture(varargin{2}+1) = frame;
         else
            p_motion_picture = struct(frame);
         end
      end

   case 'result'%, background image(s), initial acontour(s), final acontour(s)[, directory of movie, number of stills or list of stills, format of stills]
      if iscell(varargin{2})
         initial_acontours = varargin{2};
         final_acontours   = varargin{3};
      else
         initial_acontours = {varargin{2}};
         final_acontours   = {varargin{3}};
      end

      %background images
      if iscell(varargin{1})
         %this cell should be of length 1 or of the same length as initial_acontours and final_acontours
         backgrounds = varargin{1};
         %in case the segmentation progress background was given in grayscale
         %and the final display is requested in color:
         p_background_size(3) = size(backgrounds{1}, 3);
      else
         %size(varargin{1}, 3) should be equal to p_background_size(3) or
         %p_background_size(3)*length(final_acontours)
         backgrounds = cell(1, size(varargin{1}, 3) / p_background_size(3));
         for frame_idx = 1:length(backgrounds)
            backgrounds{frame_idx} = p_scaling(varargin{1}(:,:,(1:p_background_size(3)) + p_background_size(3)*(frame_idx - 1)));
         end
      end

      %static figures
      geom_of_1st_subplot    = [0.07 0.05 0.42 0.9];
      geom_of_2nd_subplot    = geom_of_1st_subplot;
      geom_of_2nd_subplot(1) = 2 * geom_of_2nd_subplot(1) + geom_of_2nd_subplot(3);

      frame_idx = 1;
      if iscell(final_acontours)
         number_of_acontours = length(final_acontours);
      else
         number_of_acontours = 1;
      end
      for acontour_idx = 1:number_of_acontours
         figure('Name', p_title);

         subplot('Position', geom_of_1st_subplot)
         title('Initial segmentation')
         hold on
         image(backgrounds{frame_idx})
         if p_background_size(3) == 1
            colormap('gray')
         end
         axis image ij
         ac_plot(initial_acontours{acontour_idx}, p_background_size(1), 'er--1.5')

         subplot('Position', geom_of_2nd_subplot)
         title('Final segmentation')
         hold on
         image(backgrounds{frame_idx})
         if p_background_size(3) == 1
            colormap('gray')
         end
         axis image ij
         ac_plot(final_acontours{acontour_idx}, p_background_size(1), 'er--1.5')

         frame_idx = min(frame_idx + 1, length(backgrounds));
      end

      %movie figure
      if p_making_of_movie
         close(p_movie_fig)

         if p_movie_title(end) == '*'
            p_movie_title(end) = [];

            number_or_stills = 4;
            format_of_stills = 'png';
            if nargin > 5
               number_or_stills = varargin{5};
            if nargin > 6
               format_of_stills = varargin{6};
               end
            end

            if nargin > 4
               s_movie2img(p_motion_picture, p_movie_title, number_or_stills, format_of_stills, varargin{4})
            else
               s_movie2img(p_motion_picture, p_movie_title, number_or_stills, format_of_stills)
            end
         else
            if nargin > 4
               s_movie2avi(p_motion_picture, p_movie_title, varargin{4})
            else
               s_movie2avi(p_motion_picture, p_movie_title)
            end
         end
      end

      set(p_control.resolution, 'FontWeight', 'bold')
      set(p_control.resolution, 'String', ['Finished @ ' datestr(clock, 'HH:MM:SS')])
      set([p_control.stop p_control.debug], 'Visible', 'off')

   case 'patience'
      o_patience = get(p_evolution_fig, 'UserData');

   case 'dbcont'
      if get(p_evolution_fig, 'UserData') == -1
         set(p_evolution_fig, 'UserData', 1)
         set([p_control.stop p_control.debug], 'Visible', 'on')
         disp('run: ''return'' to resume normal execution or use the debug menu/toolbar')
      end

   case 'close'
      delete(p_evolution_fig)
      clear(mfilename)

   otherwise
      if strcmp(task([1 2]), 'c_')
         feval(task, varargin{:})
      else
         help(mfilename)
      end
end



function c_stop(hObject, eventdata, handles)

if strcmp(get(handles.resume, 'Visible'), 'off')
   set(hObject, 'String', 'S T O P')
   set(handles.resume, 'Visible', 'on')
   uiwait(handles.parent)
else
   set([hObject handles.resume handles.debug], 'Visible', 'off')
   set(handles.parent, 'UserData', 0)
   uiresume(handles.parent)
   %cannot close here otherwise 'UserData' will not exist anymore
end



function c_debug(hObject, eventdata, handles)

set([hObject handles.stop], 'Visible', 'off')
set(handles.parent, 'UserData', -1)
disp(['run: ' mfilename '(''dbcont'') to resume normal execution'])



function c_resume(hObject, eventdata, handles)

set(hObject, 'Visible', 'off')
set(handles.stop, 'String', 'pause')
uiresume(handles.parent)



function s_movie2avi(motion_picture, movie_title, o_directory)

if isempty(motion_picture) || isequal(motion_picture, struct('cdata', [], 'colormap', []))
   return
end

if (nargin < 3) || isempty(o_directory)
   o_directory = pwd;
end
if o_directory(end) ~= filesep
   o_directory = [o_directory filesep];
end

if strcmp(movie_title, 'undecided')
   drawnow

   [movie_title, path] = uiputfile('*.avi', 'AVI video file', o_directory);
   if movie_title == 0
      movie_title = '';
   else
      movie_title = fullfile(path, movie_title);
   end
end

if length(movie_title) > 4
   if ~strcmpi(movie_title(end-3:end), '.avi')
      movie_title = [movie_title '.avi'];
   end
end

if ~isempty(movie_title)
   movie2avi(motion_picture, movie_title, 'Compression', 'none', 'Fps', 10, ...
      'Videoname', 'Active Contour Toolbox#Eric Debreuve:www.i3s.unice.fr/~debreuve')
end



function s_movie2img(motion_picture, base_of_filenames, stills, format, o_directory)

if isempty(motion_picture) || isequal(motion_picture, struct('cdata', [], 'colormap', []))
   return
end

if (nargin < 5) || isempty(o_directory)
   o_directory = pwd;
end
if o_directory(end) ~= filesep
   o_directory = [o_directory filesep];
end

%this is to let stills be either a number of stills (length(stills) is strictly
%less than 2) or a list of stills
if length(stills) < 2
   if length(motion_picture) < (stills - 1)
      stills = 1:length(motion_picture);
   else
      stills = [1 + ((0:(stills - 2)) * round(length(motion_picture) / (stills - 1))), length(motion_picture)];
   end
end

oom_of_last = floor(log10(stills(end)));%order of magnitude

if size(motion_picture(1).cdata,3) < 3
   for still_index = stills
      padding_of_zeros = repmat('0', 1, oom_of_last - floor(log10(still_index)));
      imwrite(motion_picture(still_index).cdata, motion_picture(still_index).colormap, ...
         [base_of_filenames padding_of_zeros int2str(still_index) '.' format])
   end
else
   for still_index = stills
      padding_of_zeros = repmat('0', 1, oom_of_last - floor(log10(still_index)));
      imwrite(motion_picture(still_index).cdata, ...
         [base_of_filenames padding_of_zeros int2str(still_index) '.' format])
   end
end



function control = s_layout(title, background)

control.parent = figure(...
   'Tag', 'gi_acontour_figure',...
   'Name', [title ': Segmentation progress'],...
   'NumberTitle', 'off',...
   'MenuBar', 'none',...
   'Resize', 'off',...
   'Unit', 'pixels',...
   'UserData', 1,...
   'DoubleBuffer', 'on');
background_color = get(control.parent, 'Color');

image(background)
control.axes = gca;
axis(control.axes, 'image')
if size(background, 3) == 1
   colormap('gray')
end
hold on

figuresize = get(control.parent, 'Position');
set(control.axes, 'Unit', 'pixels')
framesize = get(control.axes, 'Position');
graduation_size = get(control.axes, 'TightInset');

uicontrol(...
   'Tag', 'starting time',...
   'Parent', control.parent,...
   'BackgroundColor', background_color,...
   'FontName', 'helvetica',...
   'FontSize', 9,...
   'FontWeight', 'bold',...
   'HorizontalAlignment', 'left',...
   'Unit', 'pixels', ...
   'Position', [framesize(1) 0.5*(figuresize(4)+framesize(2)+framesize(4)-20) 0.4*framesize(3) 20],...
   'String', ['Started @ ' datestr(clock, 'HH:MM:SS')],...
   'Style', 'text',...
   'Behavior', get(0,'defaultuicontrolBehavior'));

control.resolution = uicontrol(...
   'Tag', 'intermediate time',...
   'Parent', control.parent,...
   'BackgroundColor', background_color,...
   'FontName', 'helvetica',...
   'FontSize', 9,...
   'HorizontalAlignment', 'right',...
   'Unit', 'pixels', ...
   'Position', [(framesize(1)+0.4*framesize(3)) 0.5*(figuresize(4)+framesize(2)+framesize(4)-20) 0.6*framesize(3) 20],...
   'String', 'Resolution ... (...)',...
   'Style', 'text',...
   'Behavior', get(0,'defaultuicontrolBehavior'));

control.iteration = uicontrol(...
   'Tag', 'iteration',...
   'Parent', control.parent,...
   'BackgroundColor', background_color,...
   'FontName', 'helvetica',...
   'FontSize', 10,...
   'HorizontalAlignment', 'center',...
   'Unit', 'pixels', ...
   'Position', [framesize(1) (0.5*(framesize(2)-graduation_size(2)-30)) framesize(3) 20],...
   'String', '... of max...',...
   'Style', 'text',...
   'Behavior', get(0,'defaultuicontrolBehavior'));

control.stop = uicontrol(...
   'Tag', 'stop',...
   'Parent', control.parent,...
   'BackgroundColor', background_color,...
   'Callback', 'gi_acontour(''c_stop'',gcbo,[],guidata(gcbo))',...
   'FontName', 'helvetica',...
   'FontSize', 9,...
   'Unit', 'pixels', ...
   'Position', [(framesize(1)+framesize(3)-95) (0.5*(framesize(2)-graduation_size(2)-30)) 95 30],...
   'String', 'pause',...
   'Behavior', get(0,'defaultuicontrolBehavior'));

control.debug = uicontrol(...
   'Tag', 'debug',...
   'Parent', control.parent,...
   'BackgroundColor', background_color,...
   'Callback', 'gi_acontour(''c_debug'',gcbo,[],guidata(gcbo))',...
   'FontName', 'helvetica',...
   'FontSize', 9,...
   'Unit', 'pixels', ...
   'Position', [framesize(1) (0.5*(framesize(2)-graduation_size(2)-30)) 95 30],...
   'String', 'debug',...
   'Behavior', get(0,'defaultuicontrolBehavior'));

control.resume = uicontrol(...
   'Tag', 'resume',...
   'Parent', control.parent,...
   'BackgroundColor', background_color,...
   'Callback', 'gi_acontour(''c_resume'',gcbo,[],guidata(gcbo))',...
   'FontName', 'helvetica',...
   'FontSize', 9,...
   'Unit', 'pixels', ...
   'Position', [framesize(1) (0.5*(framesize(2)-graduation_size(2)-30)) 95 30],...
   'String', 'R E S U M E',...
   'Behavior', get(0,'defaultuicontrolBehavior'),...
   'Visible', 'off');
