function varargout = main(varargin)
% MAIN Application M-file for main.fig
%    FIG = MAIN launch main GUI.
%    MAIN('callback_name', ...) invoke the named callback.

% $Id: main.m,v 1.6 2002/07/24 20:24:54 tobin Exp tobin $
% 2002-07-22 Tobin Fricke <tobin@splorg.org>

% Last Modified by GUIDE v2.0 23-Jul-2002 02:23:14

if nargin == 0  % LAUNCH GUI

    disp('-- Filament Selection GUI Tool  ($Revision: 1.6 $ $Date: 2002/07/24 20:24:54 $)');
    disp('-- 2002-07-22 Tobin Fricke <tobin@splorg.org> Weizmann Institute of Science');

	fig = openfig(mfilename,'reuse');

	% 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);
	guidata(fig, handles);

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

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

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

end


%| 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 varargout = FileAddButton_Callback(h, eventdata, handles, varargin)
% Stub for Callback of the uicontrol handles.pushbutton2.
[filename, pathname] = uigetfile('*.spi;*.SPI;*.DAT','Choose a SPIDER file to add.');
if filename==0, return, end;  
AddFile([pathname filename]);
FileBox_update;


% --------------------------------------------------------------------
function varargout = ContrastButton_Callback(h, eventdata, handles, varargin)
% Stub for Callback of the uicontrol handles.pushbutton3.
Image = findall(gcbf,'Tag','TheImage');
if length(Image) > 0,
  image_scale_contrast(Image);
end



function ZoomButton_callback(h, eventdata, handles, varargin)
% zoom tool
% zoom(gcbf,'toggle');
set(findall(gcbf,'Tag','FilamentTraceButton'),'Value',0);
TheImage = findall(gcbf,'Tag','TheImage');
TheAxes = get(TheImage,'Parent');
set(TheImage,'ButtonDownFcn','main(''ZoomBtnDown'',gcbo,[],guidata(gcbo))');
set(TheAxes,'ButtonDownFcn','main(''ZoomBtnDown'',gcbo,[],guidata(gcbo))');
SetStatusText('[Left button: Zoom in] [Middle button: Click-and-drag motion] [Right button: Zoom out]');

function ZoomBtnDown(h, eventdata, handles, varargin)
st = get(gcbf,'SelectionType');
TheAxes = findall(gcbf,'Tag','TheAxes');
cp = get(TheAxes,'CurrentPoint');
cp = cp([1 3]);
zoom_in = 0.80;
zoom_out = 1.20;
if strcmp(get(h,'type'),'image'),
    h = get(h,'Parent');
end

if strcmp(st,'open'),
  if isappdata(h,'LastSelectionType')
     st = getappdata(h,'LastSelectionType');
 else
     st = 'normal';
 end
end
setappdata(h,'LastSelectionType',st);
if strcmp(st,'extend'),
 % foo
 setappdata(gcbf,'OldBtnUpFcn',get(gcbf,'WindowButtonUpFcn'));
 setappdata(gcbf,'OldBtnMnFcn',get(gcbf,'WindowButtonMotionFcn'));
 setappdata(TheAxes,'LastPoint',cp);
 set(gcbf,'WindowButtonUpFcn','main(''Drag'',gcbo,[],guidata(gcbo),1)');
 set(gcbf,'WindowButtonMotionFcn','main(''Drag'',gcbo,[],guidata(gcbo),0)');
 SetStatusText('Click-and-drag');
 return;
elseif strcmp(st,'normal'),
  factor = zoom_in;
  disp('Zoom In');
else
  factor = zoom_out;
  disp('Zoom Out');
end
if (isappdata(gcbf,'OldBtnUpFcn')),
  %% There is a bug somewhere -- this doesn't actually work
  set(gcbf,'WindowButtonUpFcn',getappdata(gcbf,'OldBtnUpFcn'));
  set(gcbf,'WindowButtonMotionFcn',getappdata(gcbf,'OldBtnMnFcn'));
  %% HACK
  set(gcbf,'WindowButtonMotionFcn','');
  set(gcbf,'WindowButtonUpFcn','');
end  
set(TheAxes,'XLim',((get(TheAxes,'XLim')-cp)*factor)+cp);
set(TheAxes,'YLim',((get(TheAxes,'YLim')-cp)*factor)+cp);


function Drag(h, eventdata, handles, varargin)
TheAxes = findall(gcbf,'Tag','TheAxes');
cp = get(TheAxes,'CurrentPoint');
cp = cp([1 3]);
delta = getappdata(TheAxes,'LastPoint') - cp;
set(TheAxes,'XLim',(get(TheAxes,'XLim')+delta(1)));
set(TheAxes,'YLim',(get(TheAxes,'YLim')+delta(2)));
if cell2mat(varargin(1)),
 set(gcbf,'WindowButtonUpFcn',getappdata(gcbf,'OldBtnUpFcn'));
 set(gcbf,'WindowButtonMotionFcn',getappdata(gcbf,'OldBtnMnFcn'));
end

% --------------------------------------------------------------------
function varargout = ColormapBox_Callback(h, eventdata, handles, varargin)
% Stub for Callback of the uicontrol handles.ColormapBox.
a = findall(gcbf,'Tag','TheAxes');
Value = get(h,'Value');
Strings = get(h,'String');
%colormap(a,cell2mat(Strings(Value))); % BUG! FIXED! it was because matlab gets the current colormap size if it's not specified explicitly
set(gcbf,'Colormap',eval([cell2mat(Strings(Value)) '(64)'])); % this could be dangerous

function SetStatusText(str)
h = findall(gcbf,'Tag','StatusText');
set(h,'String',str);


% --------------------------------------------------------------------
function varargout = FilamentTraceButton_Callback(h, eventdata, handles, varargin)
% Start the filament-tracing tool
set(findall(gcbf,'Tag','ZoomButton'),'Value',0);
TheImage = findall(gcbf,'Tag','TheImage');
TheAxes = get(TheImage,'Parent');
set(TheImage,'ButtonDownFcn','main(''TraceBtnDown'',gcbo,[],guidata(gcbo))');
set(TheAxes,'ButtonDownFcn','main(''TraceBtnDown'',gcbo,[],guidata(gcbo))');
SetStatusText('[Left button: start filament]');
  

function TraceBtnDown(h, eventdata, handles, varargin)
button = get(gcbf,'SelectionType');
TheAxes = findall(gcbf,'Tag','TheAxes');
cp = get(TheAxes,'CurrentPoint');  cp = cp([1 3]);
if strcmp(get(h,'type'),'image'),
    h = get(h,'Parent');
end

if isappdata(gcbf,'TraceState'),
    state = getappdata(gcbf,'TraceState');
else
    state.mode = 'idle';
end
  
if strcmp(button,'normal') | strcmp(button,'extend'),
    
  if strcmp(state.mode,'drawing'),
    % add a new point to the current line

    XData = get(state.line,'XData');
    YData = get(state.line,'YData');
    TData = getappdata(state.line,'TData');
    
    XData = [XData cp(1,1)];
    YData = [YData cp(1,2)];
    TData = [TData strcmp(button,'normal')];
    
    set(state.line,'XData',XData);
    set(state.line,'YData',YData);
    setappdata(state.line','TData',TData);
    
    l = length(XData);
    disp(sprintf('Filament %d: Added vertex %d: (X=%0.2f,Y=%02.f,T=%d)',get(state.line,'UserData'),l,XData(l),YData(l),TData(l)));
    
  elseif strcmp(state.mode,'idle'),
    % start a new line
    disp('starting a new line');
    state.line = line('Parent',TheAxes,'Tag','FilamentLine','XData',[cp(1,1)],'YData',[cp(1,2)],'UserData',CreateNewFilament); 
    setappdata(state.line,'TData',[1]);
    state.mode = 'drawing';
    set(state.line,'color',[1 0 0]);
    SetStatusText('[Left button: add vertex to filament] [Right button: done with filament]');
  end
  
elseif strcmp(button,'alt'),
  
  %if strcmp(state.mode,'idle'),
  %  moveplot(state.line,'axy');
  %end
  
  if strcmp(state.mode,'drawing'),
    state.mode = 'idle';
    % transfer the XData, YData into the global state object
    TheState = GetState;
    XData = get(state.line,'XData');
    YData = get(state.line,'YData');
    TData = getappdata(state.line,'TData');
    ID = get(state.line,'UserData');
    % Find the group
    thisgroup = 0;
    for n=1:length(TheState.files(TheState.currentfile).groups),
        if  TheState.files(TheState.currentfile).groups(n).ID == ID,
            thisgroup = n;
        end    
    end
    % Add the data
    TheState.files(TheState.currentfile).groups(thisgroup).XData = XData;
    TheState.files(TheState.currentfile).groups(thisgroup).YData = YData;
    TheState.files(TheState.currentfile).groups(thisgroup).TData = TData;
    SaveState(TheState);
    SetStatusText('[Left button: start filament]');  % this should be somewhere else
    FilamentBox_update;
  end

end

setappdata(gcbf,'TraceState',state);




% --------------------------------------------------------------------
function varargout = FilamentBox_Callback(h, eventdata, handles, varargin)
% Deactivate the previously selected filament
if isappdata(gcbo,'LastValue'),
    Value = getappdata(gcbo,'LastValue');
    disp(['Deselecting filament number ' num2str(Value)]); % DEBUG
    h = findall(gcbf,'Tag','FilamentLine','UserData',Value);
    set(h,'Color',[1 0 0]);
end
Value = get(gcbo,'Value');
String = get(gcbo,'String');
Value = str2num(String{Value});
setappdata(gcbo,'LastValue',Value);
disp(['Selecting filament number ' num2str(Value)]); %DEBUG
h = findall(gcbf,'Tag','FilamentLine','UserData',Value);
set(h,'Color',[1 1 0]);

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

% Find the currently selected file (if any)

h = findall(gcbf,'Tag','FileBox');
Strings = get(h,'String');
Value = get(h,'Value');

% Determine whether there are filaments associated with the currently a
% TODO

% Remove the file from the box

RemoveFile(Strings(Value));
FileBox_update;
% Delete the image?



% --------------------------------------------------------------------
function varargout = HistogramButton_Callback(h, eventdata, handles, varargin)
% Stub for Callback of the uicontrol handles.HistogramButton.
TheImage = findall(gcbf,'Tag','TheImage');
CData = get(TheImage,'CData');
hist(reshape(CData,1,prod(size(CData))),50);


% --------------------------------------------------------------------
function varargout = SaveButton_Callback(h, eventdata, handles, varargin)
[filename, pathname] = uiputfile('*.mat','Save application state');
if (filename == 0),
    disp('The operation was cancelled.');
    return;
end
state = GetState;
save([pathname filename],'state');
disp(['The application state was saved to the file "' pathname filename '".']);

% --------------------------------------------------------------------
function varargout = LoadButton_Callback(h, eventdata, handles, varargin)
[filename, pathname] = uigetfile('*.mat','Load application state');
if (filename == 0),
    disp('The operation was cancelled.');
    return;
end
load([pathname filename],'state');
disp(['The application state was loaded from the file "' pathname filename '".']);
SaveState(state);
FileBox_update;
FilamentBox_update;

% --------------------------------------------------------------------
function varargout = FilamentDeleteButton_Callback(h, eventdata, handles, varargin)
FilamentBox = findobj(gcbf,'Tag','FilamentBox');
Value = get(FilamentBox,'Value');
String = get(FilamentBox,'String');
Value = str2num(String{Value});
h = findall(gcbf,'Tag','FilamentLine','UserData',Value);
delete(h);
state = GetState;
indicies = [];
for n=1:length(state.files(state.currentfile).groups),
    if state.files(state.currentfile).groups(n).ID == Value,
      % state.files(state.currentfile).groups(n) = [];
      % ah, we can't delete now, because that shortens the array... and length(-) is only calculated once
      indicies = [indicies n];
      disp(['Deleting group from file "' state.files(state.currentfile).filename '" at index ' num2str(n)]);
  end
end
state.files(state.currentfile).groups(indicies) = [];
SaveState(state);
FilamentBox_update;


%======================================================================
% STATE FUNCTIONS
%----------------------------------------------------------------------


function state = GetState
if isappdata(gcbf,'State'),
    state = getappdata(gcbf,'State');
else
    state.files = [];
end

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


function SaveState(state)
setappdata(gcbf,'State',state);

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

function success = AddFile(filename)
success = 0;  % pessimistic, I know
state = GetState;
% Make sure that the filename isn't added already
M = length(state.files);
if (M>0),
  [filenames{1:M}] = deal(state.files.filename);
  if (max(strcmp(filenames,filename))),
    warning('Trying to add a file that''s already been added.');
    return;
  end
end
% Add the given file
file.filename = filename;
file.groups = [];
% Update the state information
state.files = [ state.files file ];
SaveState(state);
success = 1;

function success = RemoveFile(filename)
success = 0;  % pessimistic, I know
state = GetState;
M = length(state.files);
if (M>0),
  [filenames{1:M}] = deal(state.files.filename);
  state.files(strcmp(filenames,filename)) = [];
  % assume success
  success = 1;
end
SaveState(state);

function success = SelectFile(filename)
% purpose is just to set "currentfile" to something
success = 0;  
state = GetState;
state.currentfile = 0; % maybe we should instead keep the previous currentfile if the new one can't be found?
M = length(state.files);
if (M>0),
  [filenames{1:M}] = deal(state.files.filename);
  location = find(strcmp(filenames,filename));
  if (length(location) > 0),
      state.currentfile = location(1);
      success = 1;
  end
end
SaveState(state);



function n = CreateNewFilament
state = GetState;
% find a new group ID
if isfield(state,'lastgroup'),
  state.lastgroup = state.lastgroup + 1;
else
  state.lastgroup = 1;
end
% make the group structure
newgroup.XData = [];
newgroup.YData = [];
newgroup.TData = [];
newgroup.ID = state.lastgroup;
% append this group into the group list
state.files(state.currentfile).groups = [ state.files(state.currentfile).groups newgroup ];
% n = length(state.files(state.currentfile).groups);
n = newgroup.ID;
SaveState(state);


%======================================================================
% GUI UPDATE FUNCTIONS


function varargout = FileBox_Callback(h, eventdata, handles, varargin)
% Stub for Callback of the uicontrol handles.listbox1.
FileBox = findall(gcbf,'Tag','FileBox');
Value = get(FileBox,'Value');
String = get(FileBox,'String');
filename = cell2mat(String(Value));

% Delete any pre-existing GUI stuff
delete(get(findall(gcbf,'Tag','TheAxes'),'Children'));

% Load load load
disp(['Loading SPIDER data from file "' filename '"']);
data = spi_read(filename);
data_size = size(data);
disp(sprintf('  Loaded %dx%d pixels',data_size(1),data_size(2)));
Axes = findall(gcbf,'Tag','TheAxes');
Image = findall(Axes,'Tag','TheImage');
if (length(Image)>0), delete(Image); end
TheImage = image('Parent',Axes, ...
                 'CData',double(data), ...
                 'Tag','TheImage', ...
                 'ButtonDownFcn',get(Axes,'ButtonDownFcn')); % inherit from container
image_scale_contrast(TheImage);
% daspect(Axes,[1 1 1]);
% daspect(Axes,'auto');
SelectFile(filename);
FilamentBox_update;
DrawFilaments;

function FileBox_update
FileBox = findobj(gcbf,'Tag','FileBox');
state = GetState;
M = length(state.files);
if (M>0),
  [filenames{1:M}] = deal(state.files.filename);
  set(FileBox,'String',filenames);
end
value = get(FileBox,'Value');
if value>M | value<1,
    set(FileBox,'Value',M);
end

function FilamentBox_update
FilamentBox = findobj(gcbf,'Tag','FilamentBox');
state = GetState;
groupnames = {};
file = state.files(state.currentfile);
M = length(file.groups);
if (M>0),
  [groupnames{1:M}] = deal(file.groups.ID);
  % groupnames{1:M} = 1:M;
end
% make sure the 'value' field is still in range
value = get(FilamentBox,'Value');
if value>M | value<1,
    set(FilamentBox,'Value',M);
end
set(FilamentBox,'String',groupnames);

function DrawFilaments
TheImage = findall(gcbf,'Tag','TheImage');
TheAxes = findall(gcbf,'Tag','TheAxes');
state = GetState;
file = state.files(state.currentfile);
for n=1:length(file.groups),
    line('Parent',TheAxes,'Tag','FilamentLine', ...
         'XData',file.groups(n).XData, ...
         'YData',file.groups(n).YData, ...
         'UserData',file.groups(n).ID, ...
         'Color',[1 0 0]);
end;
     
%=======================================================================
% Actual operations

function image_scale_contrast(h)
% h is the handle to an image
a = get(h,'Parent');
data = get(h, 'CData');
datasize = size(data);
xlim = round(get(a, 'Xlim'));
ylim = round(get(a, 'Ylim'));

xlim(xlim > datasize(1)) = datasize(1);
ylim(ylim > datasize(2)) = datasize(2);
xlim(xlim < 1) = 1;
ylim(ylim < 1) = 1;

the_data = data(xlim(1):xlim(2),ylim(1):ylim(2));
the_data = reshape(data,1,prod(size(data)));

the_mean = mean(the_data);
the_std = std(the_data);

set(a,'CLimMode','Manual');
set(a,'CLim',[(the_mean - the_std*4) (the_mean + the_std*4)]);
set(h,'CDataMapping', 'Scaled');
