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');