function EditOtl(action, arg)
% EditOtl(action, arg)
%
% mrcat callback functions for creating and editing outlines.
% This is the callback function for creating and editing 
% threshold objects and outline objects.
% 
% The actions understood by this function are:
%
% 'choose'
%     choose a type of editing or zooming to be associated with future mouse clicks
%     arg is one of: 'zoom', 'originalsize', 'AddPoints', 'AddLine', 'delete', 
%     'MgWand', or 'Pixel'.
% 'save'
%     save outline in the filename specified on the MRIOtl GUI
% 'load'
%     load outline from the filename specified on the MRIOtl GUI
% 'help'
%     open helpwin with the contents of the file specified in arg
%
% 'Add_Points'
%     add individual points to the outline (completed by 'do_add')
% 'Add_Line'
%     add a line to the outline (completed by 'motion_addline' and 'do_addline')
% 'Delete_Points'
%     delete points from the outline (completed by 'motion_delete' and 'do_delete')
% 'Change_Color'
%	change color of the selected points to match the currently selected OtlColor
% 'Magic_Wand'
%     draw outline around dark region containing current point (completed by 'do_MgWand')
% 'White_Pixel'
%     turn the pixel under the cursor white (completed by 'do_Pixel')
% 'Clear_Data'
%     clear all points in the object whose tag is arg.
% 'Adjust_Threshold'
%     adjust the threshold by str2num(arg); create or adjust the ThreshPoints object
% 'ROI'
%     show or hide the ROI
% 'Plot3D'
%     Open a three-dimensional plotting window, and plot the outlines in the current directory
%
% Written by Mark Hasegawa-Johnson and Jul Setsu Cha
% Revision 1.26, March 30, 1999
%

% First, define a few constants which are useful later in the program
LINE_DENSITY = 2;    % Density of points added by the AddLine function (pts/pel)
    
% Second, get the current point in axes, and round off to nearest integer
AxHndl = findobj(gcf,'Tag','ImageAxes');
axes(AxHndl);
currentPoint = get(AxHndl,'CurrentPoint');
newX = currentPoint(1);
newY = currentPoint(3);

% Finally, the only way we might be called with no argument is on the down-click of some tools,
% so we specify 'down' as the arg
if nargin<2, 
  arg='down'; 
end;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Now, figure out what action to perform on xdata and ydata, and do it
switch action
  case 'choose',
    EditOtl_choose(arg, AxHndl);
  case 'load',
    EditOtl_load(arg, AxHndl);
  case 'save',
    EditOtl_save(arg, AxHndl);
  case 'Add_Points',
    EditOtl_Add_Points(arg, newX, newY);
  case 'Add_Line',
    EditOtl_Add_Line(arg, newX, newY, LINE_DENSITY);
  case 'Delete_Points',
    EditOtl_Delete_Points(arg, newX, newY);
  case 'Clear_Data',
    EditOtl_Clear_Data(arg);
  case 'Change_Color',
    EditOtl_Change_Color(arg, newX, newY);
  case 'Magic_Wand',
    EditOtl_Magic_Wand(arg, newX, newY);
  case 'White_Pixel',
    EditOtl_White_Pixel(arg, AxHndl, newX, newY);
  case 'Adjust_Threshold',
    EditOtl_Adjust_Threshold(arg, AxHndl);
  case 'ROI',
    EditOtl_ROI(arg);
  case 'Plot3D',
    EditOtl_Plot3D(arg);
  case 'Undo',
    EditOtl_Undo(arg);
  case 'help',
    EditOtl_Help(arg);
  otherwise,
    disp(sprintf('EditOtl: unknown action requested: %s', action));
end;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Change the function of the mouse on the image between the following options:
%
% 1 - zoom in or out
% 2 - Put the image back to the original size
% Other - call EditOtl with the specified argument
%
function option = EditOtl_choose(arg, AxHndl)

% First thing is to delete the EditMarker object --
% if we're changing tools, these are no longer needed
h = findobj(gcf,'Tag','EditMarker');
if h, delete(h); end
set(gcbf,'windowbuttondownfcn','');
    
% List the actions that may be associated with a mouse button press
options = get(findobj('Tag','ZoomOrEdit'),'String');
[Noptions, Woptions] = size(options);

% Figure out what value of ZorE to use
% If the specified option doesn't match any known option, read option from ZoomOrEdit object
ZoomOrEdit = findobj(gcbf,'Tag','ZoomOrEdit');
ZorE = strmatch(arg,options);
if isempty(ZorE), ZorE = get(ZoomOrEdit,'Value'); end;
option = deblank(options(ZorE(1),:));

% If option is 'Original Size', set the menus so that option is 'Zoom On'
if strcmp(option, 'Original_Size'), ZorE = strmatch('Zoom_On',options); end
set(ZoomOrEdit,'Value',ZorE(1));

% Uncheck all of the entries in ToolsMenus except the one which has been chosen
ToolsMenus = get(get(findobj(gcbf,'Tag','ToolsMenu'),'Children'), 'Tag');
radiotgl(['ToolsMenu_', option], ToolsMenus{:});
    
% Get handles to all children of AxHndl, and to AxHndl itself
C = [get(AxHndl, 'Children'); AxHndl];

% Set the callbacks for all children of AxHndl, and for AxHndl itself
for ic=1:length(C),
    if strcmp(option,'Zoom_On') | strcmp(option,'Original_Size'),
	set(C(ic), 'ButtonDownFcn', 'LodImage zoomdown Image ;');
  else,
    % If option is anything else, use the option as input to EditOtl in order to activate image editing
	set(C(ic), 'ButtonDownFcn', sprintf('EditOtl %s', option));
  end;
end
if strcmp(option,'Original_Size'),
	LodImage zoomout Image;
end;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Load the outline file:
function EditOtl_load(arg, AxHndl)

% First, back up the current outline
backup_current_outline('EditUndoMenu');

% Construct the complete otl filename
FileName = otlfile_from_gui;

% Read the OTL file
X = otlread(strcat(FileName, '.otl'));      
[NDIM, NPTS] = size(X);
if NPTS==0, X = zeros(3,0); end;
if NDIM<3, X = [X; repmat(double('g'), [1 NPTS])]; end
x = X(1,:);
y = X(2,:);
colors = char(round(X(3,:)-0.01));

% First, clear all of the previous outlines, so that we don't wind up mixing outlines
color_list = get(findobj(gcbf,'Tag','OtlColor'),'String');
for i=1:length(color_list),
  Hdl = findobj(gcbf,'Tag', [color_list{i}(1) 'OtlPoints']);
  if length(Hdl)>0,
    set(Hdl, 'XData',[],'YData',[]);
  end
end

% For each separate color listed in 'colors', create a new OtlPoints object
while length(x)>0,
  % Find an object with the desired tag, if it exists; if not, create it
  tag = [ colors(1) 'OtlPoints' ]; 
  LnHndl = findobj(gcbf,'Tag',tag);
  plot_xydata(LnHndl, x(colors==colors(1)), y(colors==colors(1)), [ colors(1) 'OtlPoints' ], [colors(1) '+']);
  
  % Remove all of the points we just plotted
  x = x(colors~=colors(1));
  y = y(colors~=colors(1));
  colors = colors(colors~=colors(1));
end
return;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Save the outline file:
function EditOtl_save(fileformat, AxHndl)
% 
% Construct the complete otl filename
FileName = otlfile_from_gui;

% Get a list of colors
color_list = get(findobj(gcbf, 'Tag', 'OtlColor'),'String');

% Initialize x, y, and colors
x = [];
y = [];
colors = '';

% Get XData and YData from each handle, if available
for i=1:length(color_list),
  Hndl = findobj(gcbf,'Tag',[ color_list{i}(1) 'OtlPoints']);
  if ~isempty(Hndl),
    XData{i} = get(Hndl,'XData');
    YData{i} = get(Hndl,'YData');
    x = [ x, XData{i} ];
    y = [ y, YData{i} ];
    colors = [ colors, repmat(color_list{i}(1), [1 length(XData{i})]) ];
  end
end

% Set default values of npixels and corners
npixels = [ ceil(max(x)), ceil(max(y)) ];
corners = zeros(4,3);

% Now try to find out how big the image is
ImageDisplay = findobj(gcbf,'Tag','ImageDisplay');
if length(ImageDisplay)>0, 
  UserData = get(ImageDisplay, 'UserData');
  if isfield(UserData,'unclipped'), npixels = size(UserData.unclipped); end;
  if isfield(UserData,'corners'), corners = UserData.corners; end
  if isfield(UserData,'slice_thickness'), slice_thickness = UserData.slice_thickness; end
end
    
% Now switch, depending on the specified output format
switch fileformat,
  case 'otl',
    % In the case of 'otl' format, save as an outline file
    H = struct('offset', [0.5 0.5 0], 'npixels', [npixels 128], ...
	'scaling_factor', [ 2^14 ./ npixels, 1 ], ...
	'sample_n_bytes', 2, ...
	'slice_thickness', slice_thickness, ...
	'top_left_hand_corner', corners(1,:), ...
	'top_right_hand_corner', corners(2,:), ...
	'bottom_right_hand_corner', corners(4,:));
    
    otlwrite([ FileName '.otl'], [x; y; double(colors)], H);
    
  case { 'csv' 'txt' },
    % In the case of 'csv' or 'txt' format, save as text
    % With 'csv', comma is the separator; with 'txt', tab is the separator
    separator=sprintf('\t');
    if strcmp(fileformat, 'csv'), separator=','; end
    
    % Open the file
    fid=fopen([ FileName '.' fileformat ], 'w');
    if fid<0,
      disp(sprintf('Unable to open file %s.txt for writing', FileName));
      return;
    end
    for i=1:length(x),
      fprintf(fid, '%g%c%g%c%c\n', x(i), separator, y(i), separator, colors(i));
    end
    fclose(fid);
    
    % Send a message to the screen to tell the user where the file went
    disp(sprintf('Saved outline data in the file %s.%s', FileName, fileformat));

end
  
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Action callbacks for adding outline points: 'AddPoints', 'AddLine'
% 'Add_Points' -- add single points to the outline object
%
function EditOtl_Add_Points(arg, newX, newY)
switch arg
  case 'down',
    set(gcbf,'WindowButtonUpFcn','EditOtl Add_Points up');
  case 'up',
    % First, back up the current outline
    backup_current_outline('EditUndoMenu');
    
    color = get_menu_selection('OtlColor');
    [LnHdle, xdata, ydata] = get_xydata([ color(1) 'OtlPoints' ]);
    plot_xydata(LnHdle, [xdata newX], [ydata newY], [color(1) 'OtlPoints'], ...
	[color(1) '+']);
    
    % Finally, if ROI exists, update it
    hROI = findobj(gcbf,'Tag','ROI');
    if hROI, EditOtl('ROI show'); end
end    

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Add_Line
% On button release, check conditions:
%   - if EditMarker doesn't exist, create it, starting at current point
%   - if selection type is normal, then extend the EditMarker to current point
%   - if selection type is right-button or double-click, then create outline points along the EditMarker
%
function EditOtl_Add_Line(arg, newX, newY, LINE_DENSITY)
switch arg
  case 'down',
    set(gcbf, 'WindowButtonUpFcn', 'EditOtl Add_Line up');
  case 'motion',
    EditMarker = findobj(gcbf,'Tag','EditMarker');
    if ~isempty(EditMarker),
      lines = get(EditMarker,{'XData' 'YData'});
      nx = length(lines{1});
      lines{1}(nx) = newX;
      lines{2}(nx) = newY;
      set(EditMarker,{'XData' 'YData'},lines);
    end;
 
  case 'up',
    EditMarker = findobj(gcbf,'Tag','EditMarker');
    if isempty(EditMarker),
      % If EditMarker doesn't exist, create it
      hold on;
      
      % The color of the EditMarker should match the currently selected color
      color = get_menu_selection('OtlColor');
      EditMarker = plot([newX newX], [newY newY], [color(1) '-'],'Tag','EditMarker');
      AxHndl = get(EditMarker,'Parent');
      set(AxHndl,'Tag','ImageAxes');
      
      % Show you the line as you move the mouse around -- this can be kind of slow....
      set(gcbf,'WindowButtonMotionFcn','EditOtl Add_Line motion');
      
    else,
      % If EditMarker already exists, get the existing XData and YData, then modify it
      lineX = get(EditMarker,'XData');
      lineY = get(EditMarker,'YData');
      nx = length(lineX);
      selection_type = get(gcbf,'SelectionType');
      
      if (strcmp(selection_type, 'normal')),
	% If this is a normal left-button click, add a new segment to the EditMarker
	lineX = [lineX(1:(nx-1)) newX newX];
	lineY = [lineY(1:(nx-1)) newY newY];
	set(EditMarker,'XData',lineX,'YData',lineY);
	
      else,
	% If this is a right-button or double-click, create outline points, and delete the EditMarker

	% First, back up the current outline
	backup_current_outline('EditUndoMenu');

	% Initialize the list of points to be added, and get X and Y distances between each point pair
	addpoints = [lineX(1); lineY(1)];
	deltaX = diff(lineX);
	deltaY = diff(lineY);
	
	% For each line segment: Find the length of the line in pels, and round it to an integer
	for i=2:(nx-1),
	  linelength = sqrt(deltaX(i-1)^2 + deltaY(i-1)^2);
	  Npts = LINE_DENSITY*round(linelength);
	  
	  % Add a point LINE_DENSITY times per pel, more or less, rounded to make an integer number of points
	  fineline = [ ...
		lineX(i-1) + [1:Npts] * (lineX(i)-lineX(i-1)) / Npts; ...
	    lineY(i-1) + [1:Npts] * (lineY(i)-lineY(i-1)) / Npts ];
	  addpoints = [addpoints fineline];
	end;
	
	% Add the new points to the outline, and delete the EditMarker object, and
	% reset WindowButtonUpFcn and WindowButtonMotionFcn
	color = get_menu_selection('OtlColor');
	[LnHdle, xdata, ydata] = get_xydata([ color(1) 'OtlPoints']);
	plot_xydata(LnHdle, [xdata addpoints(1,:)], [ydata addpoints(2,:)], [color(1) 'OtlPoints'], ...
	    [color(1) '+']);
	delete(EditMarker);
	set(gcbf,'WindowButtonUpFcn','');
	set(gcbf,'WindowButtonMotionFcn','');
	
	% Finally, if ROI exists, update it
	hROI = findobj(gcbf,'Tag','ROI');
	if hROI, EditOtl('ROI show'); end
      end;
    end;
end    
    
    
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%   
% 'Delete Points' --- Delete a rectangle of pixels:
% On button press, start the rectangle
% On button motion, change the rectangle
% On button up, delete all points in the rectangle, and remove the rectangle object
%
function EditOtl_Delete_Points(arg, newX, newY)
switch arg
  case 'down'
    rectX = repmat(newX,[1 5]);
    rectY = repmat(newY,[1 5]);
    EditMarker = plot(rectX,rectY,'m--');
    set(EditMarker,'Tag','EditMarker');
    
    set(gcbf,'WindowButtonUpFcn','EditOtl Delete_Points up');
    set(gcbf,'WindowButtonMotionFcn','EditOtl Delete_Points motion');
  case 'motion'
    EditMarker = findobj(gcbf,'Tag','EditMarker');
    rectX = get(EditMarker,'XData');
    rectY = get(EditMarker,'YData');
    rectX(2:3) = newX;
    rectY(3:4) = newY;
    set(EditMarker,'XData',rectX,'YData',rectY);
    
  case 'up'
    % First, back up the current outline
    backup_current_outline('EditUndoMenu');
    
    % Find the outline of the rectangle
    EditMarker = findobj(gcbf,'Tag','EditMarker');
    rectX = get(EditMarker,'XData');
    rectY = get(EditMarker,'YData');
    
    % Get the list of colors
    color_list = cellstr(get(findobj(gcbf,'Tag','OtlColor'),'String'));
    for i=1:length(color_list),
      [LnHdle, xdata, ydata] = get_xydata([ color_list{i}(1) 'OtlPoints' ]);
      
      % Figure out which points are outside the rectangle
      outside = find((xdata-rectX(1)).*(xdata-rectX(2)) > 0 | ...
	  (ydata-rectY(1)).*(ydata-rectY(3)) > 0);
      
      % Keep only the points outside the rectangle; throw away the rest
      xdata = xdata(outside);
      ydata = ydata(outside);
      
      % Re-plot the points we kept
      plot_xydata(LnHdle, xdata, ydata, [color_list{i}(1) 'OtlPoints'], ...
	  [color_list{i}(1) '+'] );
    end
    
    % Remove the deletion rectangle
    delete(EditMarker);
    
    % Unset the WindowButtonMotionFcn and WindowButtonUpFcn
    set(gcbf,'WindowButtonMotionFcn','');   
    set(gcbf,'WindowButtonUpFcn','');   
    
    % Finally, if ROI exists, update it
    hROI = findobj(gcbf,'Tag','ROI');
    if hROI, EditOtl('ROI show'); end
end 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Clear all points in the outline object
% This is the callback for the 'X' buttons on the screen
%
function EditOtl_Clear_Data(arg)
if nargin < 1,
  disp('Warning: EditOtl ClearData was called without specifying an object to clear');
  return;
end;  

% Get the handle, if there is only one object
Hdl = findobj(gcf,'Tag',arg);

% If arg is 'OtlPoints', get a list of all of the OtlPoints objects on screen
if strcmp(arg, 'OtlPoints'),
  % First, back up the current outline
  backup_current_outline('EditUndoMenu');

  color_list = get(findobj(gcbf,'Tag','OtlColor'),'String');
  for i=1:length(color_list),
    Hdl = [ Hdl; findobj(gcbf,'Tag', [color_list{i}(1) 'OtlPoints']) ];
  end
end

% Clear XData and YData from each of the specified objects
for i=1:length(Hdl),
  set(Hdl(i),'XData',[],'YData',[]);
end
    
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%   
% Change the color of selected pixels to match the current OtlColor
% 'Change_Color' --- Change a rectangle of pixels:
% On button press, start the rectangle
% On button motion, change the rectangle
% On button up, change all points in the rectangle, and remove the rectangle object
%
function EditOtl_Change_Color(arg, newX, newY)
switch arg
  case 'down',
    % Create the initial "rectangle" as four corners which are all at one point
    rectX = repmat(newX,[1 5]);
    rectY = repmat(newY,[1 5]);
 
    % Get the color of the current outline, and set rectangle to that color
    color = get_menu_selection('OtlColor');
    EditMarker = plot(rectX,rectY,[color(1) '--']);
    set(EditMarker,'Tag','EditMarker');
    
    % Set callback functions
    set(gcbf,'WindowButtonUpFcn','EditOtl Change_Color up');
    set(gcbf,'WindowButtonMotionFcn','EditOtl Change_Color motion');
  case 'motion',
    % Update the EditMarker
    EditMarker = findobj(gcbf,'Tag','EditMarker');
    rectX = get(EditMarker,'XData');
    rectY = get(EditMarker,'YData');
    rectX(2:3) = newX;
    rectY(3:4) = newY;
    set(EditMarker,'XData',rectX,'YData',rectY);
    
  case 'up',
    % First, back up the current outline
    backup_current_outline('EditUndoMenu');
    
    % Find points inside the rectangle
    EditMarker = findobj(gcbf,'Tag','EditMarker');
    rectX = get(EditMarker,'XData');
    rectY = get(EditMarker,'YData');

    % Get the current outline color, and its points
    otlcolor = get_menu_selection('OtlColor');
    [hOtl, xOtl, yOtl] = get_xydata([ otlcolor(1) 'OtlPoints' ]);
	
    % Get the list of colors
    color_list = cellstr(get(findobj(gcbf,'Tag','OtlColor'),'String'));
    for i=1:length(color_list),
      % Only run this loop if color_list{i} ne otlcolor
      if strcmp(otlcolor, color_list{i})==0,
	[LnHdle, xdata, ydata] = get_xydata([ color_list{i}(1) 'OtlPoints' ]);
	
	if length(xdata)>0,
	  % Find out which points are outside the EditMarker
	  outside = ((xdata-rectX(1)).*(xdata-rectX(2)) > 0 | ...
	      (ydata-rectY(1)).*(ydata-rectY(3)) > 0);
	
	  % Points inside the rectangle switch to otlcolor
	  xOtl = [xOtl, xdata(outside==0)];
	  yOtl = [yOtl, ydata(outside==0)];
	
	  % Points outside the rectangle are still in color_list{i}
	  xdata = xdata(outside);
	  ydata = ydata(outside);
	
	  % Re-Plot the points which are still in color_list{i}
	  plot_xydata(LnHdle, xdata, ydata, [color_list{i}(1) 'OtlPoints'], ...
	      [color_list{i}(1) '+'] );
	end
	
      end  
    end
    
    % Re-Plot points in otlcolor
    plot_xydata(hOtl, xOtl, yOtl, [otlcolor(1) 'OtlPoints'], [otlcolor(1) '+'] );
    
    % Remove the deletion rectangle
    delete(EditMarker);
    
    % Unset the WindowButtonMotionFcn and WindowButtonUpFcn
    set(gcbf,'WindowButtonMotionFcn','');   
    set(gcbf,'WindowButtonUpFcn','');   
    
    % Finally, if ROI exists, update it
    hROI = findobj(gcbf,'Tag','ROI');
    if hROI, EditOtl('ROI show'); end
end    

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Selection of dark/light regions: Magic Wand
%
% 'Magic Wand' -- Magic Wand:
% On button release, call MkOtl to look for the best outline of the selected ROI
%
function EditOtl_Magic_Wand(arg, newX, newY)
switch arg,
  case 'down',
    % 'down' just sets the callback so that we'll do something useful on 'up'
    set(gcbf,'WindowButtonUpFcn','EditOtl Magic_Wand up');
    
  case 'up',
    % First, back up the current outline
    backup_current_outline('EditUndoMenu');
    
    % Load the currently selected outline color, and its current points
    otlcolor = get_menu_selection('OtlColor');
    [LnHdle, xdata, ydata] = get_xydata([otlcolor(1) 'OtlPoints']);
    
    % Get rounded version of newX and newY
    intX = round(newX);
    intY = round(newY);
    
    % Get threshold and image
    threshold = get(findobj(gcbf,'Tag','Thresh'), 'Value');
    UserData = get(findobj(gcbf, 'Tag', 'ImageDisplay'), 'UserData');
    Im = UserData.brightness * UserData.unclipped;

    % Call MkOtl to create the outline
    [J,I] = MkOtl(intX, intY, threshold, Im(:,:,1));   
    
    % attach the new outline to xdata and ydata
    xdata = [ xdata J' ];
    ydata = [ ydata I' ];
	
    % re-plot the current otl line
    plot_xydata(LnHdle, xdata, ydata, [otlcolor(1) 'OtlPoints'], [otlcolor(1) '+']);
end
	
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% White_Pixel:
% On button release, whiten the selected pixel in the image.
%
function EditOtl_White_Pixel(arg, AxesHdl, newX, newY)
switch arg,
  case 'down'
    set(gcbf,'WindowButtonUpFcn','EditOtl White_Pixel up');
    
  case 'up',
    % Get rounded version of newX and newY
    intX = round(newX);
    intY = round(newY);
    
    % Get current axis limits
    XLim = get(AxesHdl, 'XLim');
    YLim = get(AxesHdl, 'YLim');

    % Load the image data
    UserData = get(findobj(gcbf,'Tag','ImageDisplay'), 'UserData');

    % If newY, newX is inside the image, 
    % set that matrix element to 1.5 times the current value of white.
    [maxY, maxX, NC] = size(UserData.unclipped);
    if 0 < intX & intX < maxX & 0 < intY & intY < maxY,
      UserData.unclipped(intY,intX,:) = 1.5 / UserData.brightness;
    end;
    ImageDisplay = imagesm(UserData.brightness*UserData.unclipped, XLim, YLim, [], '', AxesHdl);
    set(ImageDisplay, 'UserData', UserData, 'Tag', 'ImageDisplay');
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Adjust_Threshold
% Change the threshold, and show the border points in red.
% This is the callback for the threshold slider on the screen
%
function EditOtl_Adjust_Threshold(arg, AxesHdl)

% Set amount of adjustment to be str2num(arg), if that exists
amount = str2num(arg);
if length(amount)==0, amount=0; end

% Get threshold
% If an amount is given as argument, adjust threshold by that amount
% (useful for button callbacks etc)
ThHdle = findobj(gcf,'Tag','Thresh'); 
th = get(ThHdle,'Value') + amount;
set(ThHdle,'Value',th);

% Write the updated threshold value back to the screen
TtHdle = findobj(gcf,'Tag','ThreshText');
set(TtHdle,'String',sprintf('%4.2g',th));

% Get the image data from UserData; we can't use CData, since it was interpolated to screen-size
ImageDisplay = findobj(gcbf,'Tag','ImageDisplay');
UserData = get(ImageDisplay,'UserData');
Im = UserData.brightness * UserData.unclipped;

% Get image size, and throw away color
if length(size(Im))==3, Im = Im(:,:,1); end;
[maxY,maxX] = size(Im);

% Use find_boundary_points to get a list of points on the boundary
% Input to find_boundary_points is a binary map:
%            pixels above threshold are +1, pixels below threshold are 0.
[X, Y] = find_boundary_points( logical(double(Im)>th), 0, 1);

% Plot the data 'ThreshPoints'
BHdl = findobj(gcf,'Tag','ThreshPoints');
plot_xydata(BHdl, X, Y, 'ThreshPoints', 'rx');

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% ROI
% 'ROI show': Parse CTMRedit to find the region of interest, and show it on the main image axes
% 'ROI hide': Delete the ROI object, and change the ShowRegion button
%
function EditOtl_ROI(arg)
switch arg,
  case 'hide'
    hROI = findobj(gcbf, 'Tag', 'ROI');
    if hROI, delete(hROI); end;
    ShowRegion = findobj(gcbf, 'Tag', 'ShowRegion');
    set(ShowRegion,'String','Show Region','Callback','EditOtl ROI show');
    
  case 'show'
    % Get a list of the OtlPoints objects
    HList = list_objects('OtlColor', 'OtlPoints');

    % Collect together all of the xdata and ydata from all OtlPoints objects
    xdata = [];
    ydata = [];
    for i=1:length(HList),
      xdata = [xdata, get(HList(i), 'XData') ];
      ydata = [ydata, get(HList(i), 'YData') ];
    end
    if length(xdata)==0, return; end;
    
    % Find the max and minimum points
    maxY = ceil(max(ydata))+1;
    maxX = ceil(max(xdata))+1;
    minY = floor(min(ydata))-1;
    minX = floor(min(xdata))-1;

    % Check the ROI_UPSAMPLE menu to find out which value of ROI_UPSAMPLE to use
    ROI_UPSAMPLE=2;
    for c = get(findobj(gcbf, 'Tag', 'ROI_UPSAMPLEMenu'), 'Children')',
      if strcmp(get(c, 'Checked'), 'on'),
	ROI_UPSAMPLE = str2num(get(c, 'Label'));
      end
    end;
    
    % Get a MAP of type uint8, which is 0 outside the outline
    MAP = otl2map(ROI_UPSAMPLE* (xdata-minX), ROI_UPSAMPLE* (ydata-minY), ...
	[ROI_UPSAMPLE*(maxY-minY) ROI_UPSAMPLE*(maxX-minX)], 2*ROI_UPSAMPLE);
    [yROI, xROI] = find(MAP);
    
    % Plot ROI points in the object 'ROI'
    hROI = findobj(gcbf, 'Tag', 'ROI');
    plot_xydata(hROI, xROI/ROI_UPSAMPLE + minX, yROI/ROI_UPSAMPLE + minY, 'ROI', 'b.');
    
    % Change the string and callback of the ShowRegion button
    ShowRegion = findobj(gcbf, 'Tag', 'ShowRegion');
    set(ShowRegion,'String','Hide Region','Callback','EditOtl ROI hide');
end
    
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% EditOtl_Plot3D:
% Open a 3D plot window, and plot all of the outlines which exist in the current directory.
%
function EditOtl_Plot3D(arg)

% Change the pointer to a watch, so user knows the zoom is being computed
set(gcbf,'Pointer','watch');
drawnow;

% First, find out whether or not to interpolate the data before plotting
h = findobj(gcbf, 'Tag', 'Plot3DInterpolateRadio');
if strcmp(get(h, 'Checked'), 'on'),
  interpolate_points = 1
else
  interpolate_points = 0
end

% Check the INTERPOLATION_STYLES menu to find out which value of INTERPOLATION_STYLES to use, if needed
if interpolate_points,
  INTERPOLATION_STYLE='Linear';
  for c = get(findobj(gcbf, 'Tag', 'INTERPOLATION_STYLEMenu'), 'Children')',
    if strcmp(get(c, 'Checked'), 'on'),
      INTERPOLATION_STYLE=sscanf(get(c,'Tag'), 'Radio%s')
    end
  end;
end

% Check the ROI_UPSAMPLE menu to find out which value of ROI_UPSAMPLE to use, if needed
if interpolate_points,
  ROI_UPSAMPLE=2;
  for c = get(findobj(gcbf, 'Tag', 'ROI_UPSAMPLEMenu'), 'Children')',
    if strcmp(get(c, 'Checked'), 'on'),
      ROI_UPSAMPLE = str2num(get(c, 'Label'))
    end
  end;
end

% Use filemenu to get a sorted list of the otl files in the current directory
[ OTLFILE, OTLDIR ] = otlfile_from_gui;
[ AllStrings, Files_cell ] = filemenu(OTLDIR, 'otl');
Files = [ repmat([ OTLDIR filesep ], length(Files_cell), 1), char(Files_cell) ];

% Load all of the otl files in the current directory, and convert to RAS coordinates
[ Xc, Hc ] = otlread(Files);
disp(sprintf('Loaded %d files', length(Xc)));
[ RAS, COLORS ] = otl2ras(Xc, Hc);
disp(sprintf('Loaded %d non-interpolated outline points', length([RAS{:}])));

% If we are asked to plot only the selected color, then pick out those points from RAS
h = findobj(gcbf, 'Tag', 'Plot3DSelectRadio');
if strcmp(get(h, 'Checked'), 'on'),
  color = get_menu_selection('OtlColor');
  color = color(1);
  for ifile=1:length(RAS), if length(COLORS{ifile})>0,
    which = find(COLORS{ifile}==color);
    RAS{ifile} = RAS{ifile}(:,which);
    COLORS{ifile} = COLORS{ifile}(:,which);
  end; end
else
  color = 'g';
end

% Gather otl points from the current directory
if interpolate_points,
  if strcmp(INTERPOLATION_STYLE, 'Tubular'),
    % Use ras2sld to interpolate closed contours.
    POINTS = ras2sld(RAS, ROI_UPSAMPLE);
    disp(sprintf('3D tubular surface is represented using %d points.', size(POINTS,2)));    
  
  elseif strcmp(INTERPOLATION_STYLE, 'Filled'),
    % Use ras2sld to create a filled solid object
    POINTS = ras2sld(RAS, ROI_UPSAMPLE, 1);
    disp(sprintf('3D filled solid is represented using %d points.', size(POINTS,2)));    
  
  elseif strcmp(INTERPOLATION_STYLE, 'Spline'),
    % Use ras2surf to interpolate open curves with splines between slices.
    [ SURF, POINTS ] = ras2surf(RAS, ROI_UPSAMPLE, 'spline', 2);
    disp(sprintf('3D open surface is represented using %d points.', size(POINTS,2)));    
  
  else,
    % Use ras2surf to interpolate open curves with linear interpolation between slices.
    [ SURF, POINTS ] = ras2surf(RAS, ROI_UPSAMPLE, 'linear', 2);
    disp(sprintf('3D open surface is represented using %d points.', size(POINTS,2)));    
  end
  COLOR = repmat(color(1),1,size(POINTS,2));
else,
  % Plot the 3D outline without interpolation, with colors intact
  POINTS = [ RAS{:} ];
  COLOR = [ COLORS{:} ];
end
plot3d(POINTS(1,:), POINTS(2,:), POINTS(3,:), COLOR);

% Finally, end by changing the pointer back to previous pointer, so user knows the zoom is done
set(gcbf,'Pointer','crosshair');




%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Undo:
% Load the most recent outline from UserData of the object specified in Tag
%
function EditOtl_Undo(Tag) 

% Get and update UndoData object in the EditUndoMenu object
EditUndoMenu = findobj(gcbf, 'Tag', Tag);
if isempty(EditUndoMenu), return; end
UndoData = get(EditUndoMenu, 'UserData');
set(EditUndoMenu, 'UserData', { UndoData{2:length(UndoData)} });

% Return if there is nothing to undo
if length(UndoData) < 1,
  if findstr(Tag, 'Redo'),
    disp('Nothing left to Redo!');
  else,
    disp('Nothing left to Undo!');
  end
  return;
end

% Pop the most recent backed up outline
BACKUPDAT = UndoData{1};
if prod(size(BACKUPDAT)) > 0,
  x = BACKUPDAT(1,:);
  y = BACKUPDAT(2,:);
  colors = char(max('a',min('z',BACKUPDAT(3,:))));
else
  x = [];
  y = [];
  colors = '';
end

% If Tag is 'EditUndoMenu', back up current in 'EditRedoMenu', otherwise back up in 'EditUndoMenu'
if strcmp(Tag, 'EditUndoMenu'),
  backup_current_outline('EditRedoMenu');
else
  backup_current_outline('EditUndoMenu');
end

% Clear all of the existing outlines, so that we don't wind up mixing outlines
color_list = get(findobj(gcbf,'Tag','OtlColor'),'String');
for i=1:length(color_list),
  Hdl = findobj(gcbf,'Tag', [color_list{i}(1) 'OtlPoints']);
  if length(Hdl)>0,
    set(Hdl, 'XData',[],'YData',[]);
  end
end

% For each separate color listed in 'colors', create a new OtlPoints object
while length(x)>0,
  % Find an object with the desired tag, if it exists; if not, create it
  tag = [ colors(1) 'OtlPoints' ]; 
  LnHndl = findobj(gcbf,'Tag',tag);
  plot_xydata(LnHndl, x(colors==colors(1)), y(colors==colors(1)), [ colors(1) 'OtlPoints' ], [colors(1) '+']);
  
  % Remove all of the points we just plotted
  x = x(colors~=colors(1));
  y = y(colors~=colors(1));
  colors = colors(colors~=colors(1));
end
return;





%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Help:
% Open helpwin with the contents of the file specified in arg.
%
function EditOtl_Help(arg)
fid = fopen(arg);
if fid>0,
  str = fscanf(fid,'%c');
  fclose(fid);
  helpwin(str, arg);
else,
  str = sprintf('Sorry; unable to find help file %s', arg);
  helpwin(str, arg);
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% End of the main functions; beginning of auxiliary functions.
% These functions are only visible from within EditOtl.m.
% 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Function MkOtl
% [X,Y]=MkOtl(intX,intY,th,Im)
% 
% If the current point Im(intY, intX) is below or equal to the threshold (th), this routine returns X and Y
% values surrounding a contiguous region of below-threshold pixels.
%
% If Im(intY, intX) is above threshold, MkOtl returns a contiguous above-threshold region.
%
function [X,Y]=MkOtl(intX,intY,th,Im)

% INCODE and OUTCODE specify pixels inside and definitely outside the region.
% Both INCODE and OUTCODE must be different from 0.
INCODE = -1;
OUTCODE = 1;

% Initialize the MAP matrix:
%   If Im(intY, intX) > th, then set all points below threshold to OUTCODE.
%   If Im(intY, intX) < th, then set all points above threshold to OUTCODE.
if double(Im(intY, intX) > th),
  MAP = OUTCODE * (double(Im) < th);
else
  MAP = OUTCODE * (double(Im) > th);
end

% Find the size of the MAP
[maxY, maxX] = size(MAP);

% Initialize the MAP by setting current point to INCODE
MAP(intY, intX) = INCODE;
Extend = 1;

% Get all points on straight lines through the center point
I = find(MAP(intY,(intX+1):maxX)==OUTCODE);
if length(I)>0, if I(1)>1, MAP(intY, intX+[1:(I(1)-1)]) = INCODE; end; end;
I = find(MAP(intY,1:(intX-1))==OUTCODE);
if length(I)>0, if I(1)>1, MAP(intY, (I(length(I))+1):intX) = INCODE; end; end;
I = find(MAP((intY+1):maxY,intX)==OUTCODE);
if length(I)>0, if I(1)>1, MAP(intY+[1:(I(1)-1)],intX) = INCODE; end; end;
I = find(MAP(1:(intY-1),intX)==OUTCODE);
if length(I)>0, if I(1)>1, MAP((I(length(I))+1):intY,intX) = INCODE; end; end;

% Iteratively collect all touching points below threshold, and mark them INCODE
while length(Extend)>0,
  O1 = (MAP==INCODE);
  Extend = find(MAP==0 & ( ...
		[O1(:, 2:maxX) zeros(maxY,1)] | ...
		[zeros(maxY,1) O1(:, 1:(maxX-1))] | ...
		[O1(2:maxY, :); zeros(1,maxX)] | ...
		[zeros(1,maxX); O1(1:(maxY-1), :)] ));
  if length(Extend)>0, MAP(Extend) = INCODE; end;
end;

[X,Y] = find_boundary_points(MAP, INCODE, OUTCODE);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% function [X,Y] = find_boundary_points(MAP, INCODE, OUTCODE)
%
% Find the (X,Y) coordinates of every edge which separates pixels of value INCODE
% from pixels of value OUTCODE in the image called MAP.
%
function [X,Y] = find_boundary_points(MAP, INCODE, OUTCODE)

% Get the size of MAP
[maxY, maxX] = size(MAP);

% Get a logical map of the pixels which are inside the region
O1 = logical(MAP==INCODE);

% Find all pixels left of a boundary; adjust X1 so that it is coordinate of the boundary
[Y1,X1] = find( MAP==OUTCODE & [O1(:, 2:maxX) zeros(maxY,1)] );
X1 = X1+0.5;

% Find all pixels right of a boundary; adjust X2 so that is is coordinate of the boundary
[Y2,X2] = find( MAP==OUTCODE & [zeros(maxY,1) O1(:, 1:(maxX-1))] );
X2 = X2-0.5;

% Find all pixels above a boundary; adjust Y3 so that it is coordinate of the boundary
[Y3,X3] = find( MAP==OUTCODE & [O1(2:maxY, :); zeros(1,maxX)] );
Y3 = Y3+0.5;

% Find all pixels below a boundary; adjust Y4 so that it is coordinate of the boundary
[Y4,X4] = find( MAP==OUTCODE & [zeros(1,maxX); O1(1:(maxY-1), :)] );
Y4 = Y4-0.5;

% Sort together all of these points, and remove duplications
data = unique([ X1 Y1; X2 Y2; X3 Y3; X4 Y4 ], 'rows' );
X = data(:,1);
Y = data(:,2);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%5
% get_menu_selection: 
% a convenience routine for finding the currently selected string from a tagged menu
function selection = get_menu_selection(tag)

Menu = findobj(gcbf,'Tag',tag);
String = get(Menu,'String');
Value = get(Menu,'Value');
selection = String{Value};


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%5
% otlfile_from_gui:
% a convenience routine for finding the current OTL filename and directory
% from the GUI window
function [ Path, Directory, FName ] = otlfile_from_gui()
% 
% Construct the complete otl filename
FName = get(findobj(gcbf,'Tag','SvEdOtl'),'String');
TName = get(findobj(gcbf,'Tag','OtlDir'),'String');
PName = get_menu_selection('ImagePlane');
SName = get_menu_selection('ImageSeries');
global ROOTDIR;
Directory = fullfile(ROOTDIR, TName, SName, PName);
Path = fullfile(Directory, FName);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Function plot_xydata
function plot_xydata(LnHdle, xdata, ydata, Tag, fmt)

% Finally, plot the data: 
% If OtlPoints exists, change it; if not, create it
if isempty(LnHdle),
  hold on
  LnHdle = plot(xdata,ydata,fmt);
  set(LnHdle,'Tag',Tag);  
else
  set(LnHdle,'XData',xdata,'YData',ydata);
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%5
% Function get_xydata
function [LnHdle, xdata, ydata] = get_xydata(Tag)

% Get xdata and ydata from the OtlPoints object (create if necessary)
LnHdle = findobj(gcbf,'Tag',Tag);
if isempty(LnHdle),
  xdata = [];
  ydata = [];
else
  xdata = get(LnHdle,'XData');
  ydata = get(LnHdle,'YData');
end;
 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Function list_otl_objects
% Get a vector of handles of all of the objects called sprintf('%c%s', c(1), TagEnd)
% where c is one of the strings from the String property of the object tagged StringsObj.
%
function HList = list_objects(StringsObj, TagEnd)

% Get a list of the possible strings
HStrings = findobj(gcbf, 'Tag', StringsObj);
if ~isempty(HStrings), 
  color_list = get(HStrings,'String');

  % Initialize HList
  HList = [];

  % Find handles of each color, if they exist
  for i=1:length(color_list),
    Hndl = findobj(gcbf,'Tag',[ color_list{i}(1) 'OtlPoints']);
    if ~isempty(Hndl), HList = [ HList; Hndl ]; end
  end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Function backup_current_outline
% Back up the current outline by storing it in the UserData of the specified object
% We keep a list of up to 40 backup outlines, so that users can step back a long way.
%
% Since this gives us a copy of the current outline, we'll return it, in case it's useful.
%
function [x, y, colors] = backup_current_outline(Tag)
NUM_UNDOS = 40;

% Get a list of the possible colors
h = findobj(gcbf,'Tag','OtlColor');
if isempty(h),
  color_list = {'g'};
else
  color_list = get(h,'String');
end

% Initialize x, y, and colors
x = [];
y = [];
colors = '';

% Get XData and YData from each of the outline handles, if available
for i=1:length(color_list),
  Hndl = findobj(gcbf,'Tag',[ color_list{i}(1) 'OtlPoints']);
  if ~isempty(Hndl),
    XData{i} = get(Hndl,'XData');
    YData{i} = get(Hndl,'YData');
    x = [ x, XData{i} ];
    y = [ y, YData{i} ];
    colors = [ colors, repmat(color_list{i}(1), [1 length(XData{i})]) ];
  end
end

% Store all together in a matrix
BACKUPDAT = [ x; y; double(colors) ];

% Get the EditUndoMenu object
EditUndoMenu = findobj(gcbf, 'Tag', Tag);
if ~isempty(EditUndoMenu),
  UserData = get(EditUndoMenu, 'UserData');
else,
  return
end

% Add BACKUPDAT to UserData, and store it
if length(UserData) < NUM_UNDOS,
  UserData = { BACKUPDAT, UserData{:} };
else
  UserData = { BACKUPDAT, UserData{1:(NUM_UNDOS-1)} };
end

set(EditUndoMenu, 'UserData', UserData);

