function varargout = metchgui(varargin)
%    alldata = metchgui(node,elem,points,pface) or metchgui(volume,points,pface)
%
%    A GUI to register a point cloud to a mesh or volumetric image
%  
%    author: Qianqian Fang <q.fang at neu.edu>
%    date: 12/16/2008
%  
%   parameters: 
%        node: node coordinate of the surface mesh (nn x 3)
%        elem: element list of the surface mesh (3 columns for 
%              triangular mesh, 4 columns for cubic surface mesh)
%        points: the coordinates (3 columns for x/y/z) of the 
%              point cloud which you want to register
%        pface:trianglular surface defined on the point cloud.
%              pface is optional; if presents, metch will display 
%              a surface object instead of a point cloud.
%
%   the input can also be two parameters in form of metchgui(volume,points), 
%    where volume is a 3D image (array).
%
%   outputs:
%        alldata: a structrure containing all processing outputs
%        the fields include: 
%         .node: the input node 
%         .elem: the input surface mesh elements
%         .volume: if the input volumetric image
%         .A0: the affine rotation for selected point pairs (after Initialize)
%         .b0: the affine translation for selected point pairs (after Initialize)
%         .A: the affine rotation for the point cloud (after Optimize)
%         .b: the affine translation for the point cloud (after Optimize)
%         .points: the input point cloud
%         .pointsinit: the point cloud after initialization
%         .pointsopt: the point cloud after optimization
%         .pointsproj: the point cloud after projecting to the surface
%         .initplot: the handle to the point cloud plot after init
%         .optplot: the handle to the point cloud plot after optimization
%         .projplot: the handle to the point cloud plot after projection
%
%   If user supplys an output variable, the GUI will not return until the
%   user hits the "close" button or close the window; if user does not
%   supply any output, the call will return immediately; any data user
%   intends to save, he has to click on "Save Session" button and provides
%   a mat-file file name. A single structure named "metchsession" will be
%   stored in this file.
%
%   example: (meshasphere/meshunitsphere are defined in iso2mesh http://iso2mesh.sf.net)
%
%       [noderef,faceref,elemref]=meshunitsphere(0.08,10);
%       [no,fc]=removeisolatednode(noderef(:,1:3),faceref(:,1:3));
%       [node,face,elem]=meshasphere([10 20 15],3,0.5,10);
%       [no2,fc2]=removeisolatednode(node(:,1:3),face(:,1:3));
%       alldata = metchgui(no,fc,no2);
%       % or alldata = metchgui(no,fc,no2,fc2);
%
%   Please find more information at http://iso2mesh.sf.net/cgi-bin/index.cgi?metch
%  
%   this function is part of "metch" toobox, see COPYING for license

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @metchgui_OpeningFcn, ...
                   'gui_OutputFcn',  @metchgui_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}, 'hasoutput');
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT


% --- Executes just before metchgui is made visible.
function metchgui_OpeningFcn(hObject, eventdata, handles, varargin)
handles.hasoutput=0;
if(isempty(varargin))
    fprintf(1,'Metch GUI must be called with parameters:\nFormat: alldata = metchgui(node,elem,points,pface);\n');
    close(handles.MetchGUI);
    return;
end
if(ischar(varargin{end}) && strcmp(varargin{end},'hasoutput'))
        handles.hasoutput=1;
        varargin(end)=[];
end
handles.output = hObject;

set(handles.btAddMeshPt,'userdata',[handles.axMesh,handles.btAddMeshPt,handles.btAddCloudPt]);
set(handles.btAddCloudPt,'userdata',[handles.axMesh,handles.btAddCloudPt,handles.btAddMeshPt]);

% if uses supplied 2 input variables, assume a volume image and a point cloud
if(isnumeric(varargin{1}) && length(size(varargin{1}))==3)
       vol=varargin{1};
       pt=varargin{2};
       
       dat.volume=vol;
       dat.points=pt;

       slice=round(size(vol,3)/2);
       hs=imagesc(vol(:,:,slice),'parent',handles.axMesh);
       set(handles.slPos,'max',size(vol,3),'min',1,'value',slice);

       if(length(varargin)>=3)
       	 pface=varargin{3};
       	 dat.pface=pface;
	 trisurf(pface(:,1:3),pt(:,1),pt(:,2),pt(:,3),'parent',handles.axPoints);
       else
         %plot3(pt(:,1),pt(:,2),pt(:,3),'.','parent',handles.axPoints);
         ptcolor=(pt-repmat(min(pt),size(pt,1),1))./repmat(max(pt)-min(pt),size(pt,1),1);
         drawnow;
         scatter3(pt(:,1),pt(:,2),pt(:,3),3,ptcolor,'filled');	
       end
       set(handles.MetchGUI,'userdata',dat);

       axis(handles.axMesh,'equal');
       axis(handles.axPoints,'equal');
       axis(handles.axMesh,'off');
       grid(handles.axPoints,'on');
       %axis(handles.axPoints,'off');
       
       set(handles.axMesh,'tag','axMesh');
       set(handles.axPoints,'tag','axPoints');
       set(handles.slPos,'visible','on');
       set(handles.lbZPos,'visible','on');
       rotate3d(handles.axPoints,'on');
       rotate3d(gcf,'on');
end

% if uses supplied 3 input variables, assume a surface mesh and a point cloud/surface
if(length(varargin)>=3 && length(size(varargin{1}))==2)
       node=varargin{1};
       elem=varargin{2};
       pt=varargin{3};
       
       dat.node=node;
       dat.elem=elem;
       dat.points=pt;
       if(length(varargin)>=4)
       	 pface=varargin{4};
       	 dat.pface=pface;
       end
       set(handles.MetchGUI,'userdata',dat);

       drawinit(handles,dat)
end
guidata(hObject, handles);

if(handles.hasoutput)
        uiwait(handles.MetchGUI);
end

%---------------------------------------------------------------------------
function drawinit(handles,dat)

node=dat.node;
elem=dat.elem;
pt=dat.points;

hs=trisurf(elem,node(:,1),node(:,2),node(:,3),'parent',handles.axMesh);
%set(hs,'linestyle','none');
%set(hs,'facecolor','b','facealpha',0.8);

%plot3(pt(:,1),pt(:,2),pt(:,3),'.','parent',handles.axPoints);
if(~isfield(dat,'pface'))
	ptcolor=(pt-repmat(min(pt),size(pt,1),1))./repmat(max(pt)-min(pt),size(pt,1),1);
	drawnow;
	scatter3(pt(:,1),pt(:,2),pt(:,3),3,ptcolor,'filled');
else
	trisurf(dat.pface,pt(:,1),pt(:,2),pt(:,3),'parent',handles.axPoints);
end
%hold(handles.axPoints,'on');
%plot3(pt(5:7,1),pt(5:7,2),pt(5:7,3),'ro','parent',handles.axPoints);

axis(handles.axMesh,'equal');
axis(handles.axPoints,'equal');
axis(handles.axMesh,'off');
%axis(handles.axPoints,'off');

set(handles.axMesh,'tag','axMesh');
set(handles.axPoints,'tag','axPoints');

rotate3d(handles.axPoints,'on');
rotate3d(handles.axMesh,'on');
rotate3d(gcf,'on');

%---------------------------------------------------------------------------
function varargout = metchgui_OutputFcn(hObject, eventdata, handles) 
if(length(handles) && isfield(handles,'output') && isfield(handles,'hasoutput') && handles.hasoutput)
        handles.output=get(handles.MetchGUI,'userdata');
        varargout{1} =handles.output;
        close(handles.MetchGUI);
end
%---------------------------------------------------------------------------
function lbMesh_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

%---------------------------------------------------------------------------
function lbPoints_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

%---------------------------------------------------------------------------
function edit1_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

%---------------------------------------------------------------------------
function edit2_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

%---------------------------------------------------------------------------
function edit3_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

%---------------------------------------------------------------------------
function isSelect_Callback(hObject, eventdata, handles)
if(get(hObject,'Value'))
        datacursormode(gcf,'on');
        set(datacursormode(gcf),'UpdateFcn',@myupdatefcn)
else
        datacursormode(gcf,'off');
        rotate3d(gcf,'on');
end

%---------------------------------------------------------------------------
% the respond function when there is a data-tip to popup
%---------------------------------------------------------------------------
function txt=myupdatefcn(empt,event_obj)
pos = get(event_obj,'Position');
idx=  get(event_obj,'DataIndex');

if(length(pos)==3)
  txt = {['x: ',num2str(pos(1))],...
         ['y: ',num2str(pos(2))],['z: ',num2str(pos(3))],['index:', num2str(idx)]};
elseif(length(pos)==2)
  txt = {['x: ',num2str(pos(1))],['y: ',num2str(pos(2))]};
end
targetup=get(get(event_obj,'Target'),'parent');
set(targetup,'userdata',struct('pos',pos,'idx',idx));
if(targetup==findobj('tag','axMesh'))
         set(findobj('tag','btAddMeshPt'),'enable','on');
         set(findobj('tag','btAddCloudPt'),'enable','off');
elseif(targetup==findobj('tag','axPoints'))
         set(findobj('tag','btAddMeshPt'),'enable','off');
         set(findobj('tag','btAddCloudPt'),'enable','on');     
end

%---------------------------------------------------------------------------
function bInit_Callback(hObject, eventdata, handles)
mapto=get(handles.lbMesh,'userdata');
mapfrom=get(handles.lbPoints,'userdata');

maptoidx=get(handles.txMapTo,'userdata');
mapfromidx=get(handles.txMapFrom,'userdata');

if(length(mapto)<4 | length(mapfrom)<4)
        msgbox('You have to select >3 points from the point cloud plot and corresponding points from the mesh','Error','error');
        return;
end
[A,b]=affinemap(mapfrom,mapto)
dat=get(handles.MetchGUI,'userdata');
dat.A0=A;
dat.b0=b;

newpt=(A*dat.points'+repmat(b(:),1,size(dat.points,1)))';
dat.pointsinit=newpt;

hold(handles.axMesh,'on');
if(isfield(dat,'initplot'))
        delete dat.initplot;
        dat.initplot=0;
end
dat.initplot=plot3(newpt(:,1),newpt(:,2),newpt(:,3),'r.','parent',handles.axMesh);

dat.fromidx=mapfromidx;
dat.toidx=maptoidx;
set(handles.MetchGUI,'userdata',dat);

%---------------------------------------------------------------------------
function axPoints_ButtonDownFcn(hObject, eventdata, handles)
if(get(handles.btSelect,'value')==1)
        pp=getCursorInfo(datacursormode(gcf));
        str=[get(handles.lbPoints,'string');mat2str(pp.Position)];
        set(handles.lbPoints,'string',str);
end

%---------------------------------------------------------------------------
function addselectedpt(pos,idx,lb)
if(isempty(get(lb,'value')))
    set(lb,'value',1);
end
listpt=get(lb,'string');
listpt{end+1}=[num2str(idx) ':' mat2str(pos)];
set(lb,'string',listpt);
set(lb,'userdata',[get(lb,'userdata');pos]);


%---------------------------------------------------------------------------
function btAddMeshPt_Callback(hObject, eventdata, handles)
dat=get(handles.axMesh,'userdata');

dat0=get(handles.MetchGUI,'userdata');
if(isfield(dat0,'volume'))
    dat.pos(:,3)=get(handles.slPos,'value');
end

if(isfield(dat,'pos'))
    addselectedpt(dat.pos,dat.idx,handles.lbMesh);
    set(handles.txMapTo,'userdata',[get(handles.txMapTo,'userdata');dat.idx]);
	dat0.mapto=get(handles.lbMesh,'userdata');
    dat0.maptoidx=get(handles.txMapTo,'userdata');
    set(handles.MetchGUI,'userdata',dat0);
else
        msgbox('No point was selected. Please click on "Select" and select a point on the mesh or point cloud','Error','error');
        return;
end

%---------------------------------------------------------------------------
function btAddCloudPt_Callback(hObject, eventdata, handles)
dat=get(handles.axPoints,'userdata');
dat0=get(handles.MetchGUI,'userdata');
if(isfield(dat,'pos'))
    addselectedpt(dat.pos,dat.idx,handles.lbPoints);
    set(handles.txMapFrom,'userdata',[get(handles.txMapFrom,'userdata');dat.idx]);
	dat0.mapfrom=get(handles.lbPoints,'userdata');
    dat0.mapfromidx=get(handles.txMapFrom,'userdata');
    set(handles.MetchGUI,'userdata',dat0);
else
        msgbox('No point was selected. Please click on "Select" and select a point on the mesh or point cloud','Error','error');
        return;
end

%---------------------------------------------------------------------------
function btOptimize_Callback(hObject, eventdata, handles)
dat=get(handles.MetchGUI,'userdata');
if(isfield(dat,'A0') && isfield(dat,'b0')& isfield(dat,'node')& isfield(dat,'elem')& ...
   isfield(dat,'pointsinit')& isfield(dat,'toidx')& isfield(dat,'fromidx'))
        pmask=-1*ones(size(dat.pointsinit,1),1);
        pmask(dat.fromidx)=dat.toidx;
	if(isfield(dat,'A') && isfield(dat,'b'))
	        [Anew,bnew,posnew]=regpt2surf(dat.node,dat.elem,dat.points,pmask,dat.A,dat.b,ones(12,1),20);
	else
                [Anew,bnew,posnew]=regpt2surf(dat.node,dat.elem,dat.points,pmask,dat.A0,dat.b0,ones(12,1),20);
	end
        dat.A=Anew;
        dat.b=bnew;
        dat.pointsopt=posnew;
        if(isfield(dat,'optplot') && dat.optplot)
                delete dat.optplot;
                dat.optplot=0;
        end
        dat.optplot=plot3(posnew(:,1),posnew(:,2),posnew(:,3),'g+','parent',handles.axMesh);
else
        msgbox('You have to select 4 points and click "Initialize" button first','Error','error');
        return;
end
set(handles.MetchGUI,'userdata',dat);


%---------------------------------------------------------------------------
function slPos_Callback(hObject, eventdata, handles)
dat=get(handles.MetchGUI,'userdata');
if(isfield(dat,'volume'))
        hold(handles.axMesh,'off');
        imagesc(dat.volume(:,:,round(get(hObject,'value'))),'parent',handles.axMesh);
        set(handles.axMesh,'tag','axMesh');
        set(handles.lbZPos,'string',num2str(round(get(hObject,'value'))));
end
set(handles.MetchGUI,'userdata',dat);

%---------------------------------------------------------------------------
function slPos_CreateFcn(hObject, eventdata, handles)
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end

%--------------------------------------------------------------------------
function btProj_Callback(hObject, eventdata, handles)
dat=get(handles.MetchGUI,'userdata');
if(isfield(dat,'pointsopt'))
        if(isfield(dat,'projplot') && dat.projplot)
                delete dat.projplot;
                dat.projplot=0;
        end
        nv=nodesurfnorm(dat.node,dat.elem);
        [d2surf,cn]=dist2surf(dat.node,nv,dat.pointsopt);
        [dat.pointsproj dat.elemid dat.weight]=proj2mesh(dat.node,dat.elem,dat.pointsopt,nv,cn);
        hold(handles.axMesh,'on');
        dat.projplot=plot3(dat.pointsproj(:,1),dat.pointsproj(:,2),dat.pointsproj(:,3),'c.','parent',handles.axMesh);
else
        msgbox('You have to first select 4 points, then click "Initialize" and "Optimize" button','Error','error');
        return;
end
set(handles.MetchGUI,'userdata',dat);

%--------------------------------------------------------------------------
function btSaveRes_Callback(hObject, eventdata, handles)
[filename, pathname] = uiputfile('*.mat', 'Save Metch Workspace as');
mapto=get(handles.lbMesh,'userdata');
mapfrom=get(handles.lbPoints,'userdata');

maptoidx=get(handles.txMapTo,'userdata');
mapfromidx=get(handles.txMapFrom,'userdata');

metchsession=get(handles.MetchGUI,'userdata');
if(~isempty(mapto))    metchsession.mapto=mapto; end
if(~isempty(mapfrom))    metchsession.mapfrom=mapfrom; end
if(~isempty(maptoidx))    metchsession.maptoidx=maptoidx; end
if(~isempty(mapfromidx))    metchsession.mapfromidx=mapfromidx; end
fname=[pathname filename];
save(fname,'metchsession');

%--------------------------------------------------------------------------
function btLoadSession_Callback(hObject, eventdata, handles)
[filename, pathname] = uigetfile('*.mat', 'Load Metch Workspace from');
fname=[pathname filename];
load(fname);
handle.output=metchsession;
cla(handles.axMesh);
cla(handles.axPoints);
drawinit(handles,metchsession.node,metchsession.elem,metchsession.points);
%--------------------------------------------------------------------------
function btPlotResults_Callback(hObject, eventdata, handles)

function btClose_Callback(hObject, eventdata, handles)
guidata(hObject,handles);
uiresume;

function btHelp_Callback(hObject, eventdata, handles)
helpmsg={
'Metch GUI: A mesh/volume registration toolbox',
'',
'Author: Qianqian Fang <q.fang at neu.edu>',
'        Martinos Center for Biomedical Imaging',
'        Charlestown, MA 02129, USA',
'',
'== Description of the workflow ==',
'',
' 1. when the GUI pops up, it will display the mesh and the points,',
'    you can rotate both plots so that you can identify the matching ',
'    features',
' 2. switch on "Select" mode, then, click on a land-mark point on the point',
'    plot, when a data-tip shows up, click "Add Selected" button',
' 3. click on the corresponding position on the mesh, and click',
'    "Add Selected"      ',
' 4. repeat the above for at least 4 point pairs (you can select more);',
'    if you want to change views, switch off "Select" box and rotate;',
'    after rotation, switch on "Select" box again',
' 5. click "Initialize": this will create the initial mapping using the',
'    selected point pairs',
' 6. click "Optimize": this will fit the surface with the whole point cloud',
' 7. click "Proj2Mesh": this will project the fitted point clouds onto the',
'    mesh',
' 8. you can quit the GUI by hit "Close", your results will be saved to reg',
' 9. close the window '};

helpdlg(helpmsg);
