function varargout = img2mesh(varargin)
% 
%  Format: 
%      newworkspace = img2mesh or imgmesh(workspace)
%
%  A GUI for Iso2Mesh for streamlined mesh data processing
%  
%  Author: Qianqian Fang <q.fang at neu.edu>
%  
%  Input:
%        workspace (optional): a struct containing the below fields
%           .graph: a digraph object containing the i2m workspace data
%  Output:
%        newworkspace (optional): the updated workspace, with the same
%        subfields as the input.
%
%   If a user supplys an output variable, the GUI will not return until 
%   the user closes the window; if a user does not provide any output,
%   the call will return immediately.
%
%   Please find more information at http://iso2mesh.sf.net/
%  
% -- this function is part of iso2mesh toolbox (http://iso2mesh.sf.net)
%

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @i2m_OpeningFcn, ...
                   'gui_OutputFcn',  @i2m_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{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT


% --- Executes just before i2m is made visible.
function i2m_OpeningFcn(hObject, eventdata, handles, varargin)

cm = uicontextmenu;
uimenu(cm,'Label','Plot','CallBack',{@processdata,handles});
uimenu(cm,'Label','Rename','CallBack',{@processdata,handles});
uimenu(cm,'Label','Delete','CallBack',{@processdata,handles});

mimeshing = uimenu(cm,'Label','Meshing');
miv2s = uimenu(mimeshing,'Label','Volume to surface','CallBack',{@processdata,handles});
uimenu(mimeshing,'Label','Volume to mesh','CallBack',{@processdata,handles});
uimenu(mimeshing,'Label','Surface to mesh','CallBack',{@processdata,handles});
uimenu(mimeshing,'Label','Surface to volume','CallBack',{@processdata,handles});
uimenu(mimeshing,'Label','Close and fill volume','CallBack',{@processdata,handles});
uimenu(mimeshing,'Label','Extract surface','CallBack',{@processdata,handles});
uimenu(mimeshing,'Label','Tessellate surface','CallBack',{@processdata,handles});

mirepair = uimenu(cm,'Label','Surface repair');
uimenu(mirepair,'Label','Clean surface','CallBack',{@processdata,handles});
uimenu(mirepair,'Label','Repair surface','CallBack',{@processdata,handles});
uimenu(mirepair,'Label','Smooth surface','CallBack',{@processdata,handles});
uimenu(mirepair,'Label','Simplify surface','CallBack',{@processdata,handles});
uimenu(mirepair,'Label','Remesh surface','CallBack',{@processdata,handles});
uimenu(mirepair,'Label','Simplify surface','CallBack',{@processdata,handles});
uimenu(mirepair,'Label','Reorient mesh elements','CallBack',{@processdata,handles});

mibool = uimenu(cm,'Label','Surface boolean');
uimenu(mibool,'Label','Or','CallBack',{@processdata,handles});
uimenu(mibool,'Label','And','CallBack',{@processdata,handles});
uimenu(mibool,'Label','All','CallBack',{@processdata,handles});
uimenu(mibool,'Label','Diff','CallBack',{@processdata,handles});
uimenu(mibool,'Label','First','CallBack',{@processdata,handles});
uimenu(mibool,'Label','Second','CallBack',{@processdata,handles});

mireport = uimenu(cm,'Label','Report');
uimenu(mireport,'Label','Containing data','CallBack',{@processdata,handles});
uimenu(mireport,'Label','Mesh quality histogram','CallBack',{@processdata,handles});
uimenu(mireport,'Label','Element volume histogram','CallBack',{@processdata,handles});
uimenu(mireport,'Label','Total volume','CallBack',{@processdata,handles});

mirefresh=uimenu(cm,'Label','Refresh','CallBack',{@miRefresh_Callback,handles});
uimenu(cm,'Label','Save as','CallBack',{@processdata,handles});

miv2s.Separator='on';
mimeshing.Separator='on';
mirefresh.Separator='on';

root=get(handles.fgI2M,'userdata');

if(isempty(root))
    root=struct('graph',digraph,'menu',cm);
end
set(handles.fgI2M,'userdata',root);
set(handles.axFlow,'position',[0 0 1 1]);
set(handles.axPreview,'position',[0 0 1 1]);

set(handles.fgI2M,'UIContextMenu',handles.meCreate);


axis(handles.axFlow,'off');
axis(handles.axPreview,'off');

% Choose default command line output for i2m
handles.output = hObject;

% Update handles structure
guidata(hObject, handles);

% UIWAIT makes i2m wait for user response (see UIRESUME)
% uiwait(handles.fgI2M);

function processdata(source,callbackdata,handles)

try
obj=get(handles.fgI2M,'currentobject');
if(strcmp(class(obj),'matlab.graphics.chart.primitive.GraphPlot')==0)
    if(~isempty(obj.UserData) && strcmp(class(obj.UserData),'matlab.graphics.chart.primitive.GraphPlot'))
        obj=obj.UserData;
    else
        return
    end
end
root=get(handles.fgI2M,'userdata');
pos=get(handles.axFlow,'currentpoint');
[nodedata,nodetype,nodeid]=getnodeat(root,obj,pos);

newtype=dummytype;
prefix='x';
switch source.Label
    case 'Volume to surface'
        if(isstruct(nodetype) && isfield(nodetype,'hasvol') && nodetype.hasvol)
            [newdata,newtype]=v2sgui(nodedata);
            prefix='Vol2Surf';
        else
            error('no volume data found');
        end
    case 'Volume to mesh'
        if(nodetype.hasvol)
            [newdata,newtype]=v2mgui(nodedata);
            prefix='Vol2Mesh';
        else
            error('no volume data found');
        end
    case 'Surface to mesh'
        if(nodetype.hasnode && nodetype.hasface)
            [newdata,newtype]=s2mgui(nodedata);
            prefix='Surf2Mesh';
        else
            error('no surface data found');
        end
    case 'Surface to volume'
        if(nodetype.hasnode && nodetype.hasface)
            ndiv = inputdlg('Division number along the shortest dimension:',...
                'surf2vol - rasterizing a surface mesh',1,{'50'});
            if(isempty(ndiv))
                return;
            end
            newdata.vol=s2v(nodedata.node,nodedata.face,str2num(ndiv{1}));
            newtype.hasvol=1;
            prefix='Surf2Vol';
        else
            error('no surface data found');
        end
    case 'Close and fill volume'
        if(nodetype.hasvol)
            rad=inputdlg('maximum gap length in voxel (scalar)','Close and fill a volume',1,{'1'});
            newdata.vol=fillholes3d(nodedata.vol,str2num(rad{1}));
            newtype=nodetype;
            prefix='FillVol';
        else
            error('no volume data found');
        end
    case 'Extract surface'
        if(isstruct(nodetype) && isfield(nodetype,'hasvol') && nodetype.hasvol)
            [newdata.node,newdata.face]=binsurface(nodedata.vol);
            prefix='BinSurf';
        elseif(nodetype.hasnode && nodetype.haselem)
            newdata.node=nodedata.node;
            newdata.face=volface(nodedata.elem(:,1:min(4,size(nodedata.elem,2))));
            [newdata.node,newdata.face]=removeisolatednode(newdata.node,newdata.face);
            prefix='VolSurf';
        elseif(nodetype.hasnode && nodetype.hasface)
            [newdata.node,newdata.face]=deal(nodedata.node,nodedata.face);
            prefix='CopySurf';
        end
        newtype.hasnode=1;
        newtype.hasface=1;
    case 'Clean surface'
        if(nodetype.hasnode && nodetype.hasface)
            [newdata.node,newdata.face]=meshcheckrepair(nodedata.node,nodedata.face,'deep');
            newtype=nodetype;
            prefix='CleanSurf';
        else
            error('no surface data found');
        end
    case 'Repair surface'
        if(nodetype.hasnode && nodetype.hasface)
            [newdata.node,newdata.face]=meshcheckrepair(nodedata.node,nodedata.face,'meshfix');
            newtype=nodetype;
            prefix='RepairSurf';
        else
            error('no surface data found');
        end
    case 'Smooth surface'
        if(nodetype.hasnode && nodetype.hasface)
            res = inputdlg({'Method (laplacian,laplacianhc,lowpass):','Iteration (integer):','Alpha (scalar):'},...
                'sms - smoothing a surface mesh',[1,1,1],{'lowpass','20','0.5'});
            if(isempty(res))
                return;
            end
            newdata=nodedata;
            newtype=nodetype;
            newdata.node=sms(nodedata.node,nodedata.face,str2num(res{2}),str2num(res{3}),res{1});
            prefix='SmoothSurf';
        else
            error('no surface data found');
        end
    case 'Simplify surface'
        if(nodetype.hasnode && nodetype.hasface)
            res = inputdlg('Percentage of edges to keep (0-1):',...
                'Simplify mesh',[1],{'1'});
            if(isempty(res))
                return;
            end
            [newdata.node,newdata.face]=meshresample(nodedata.node,nodedata.face,str2num(res{1}));
            newtype=nodetype;
            prefix='SimplifySurf';
        else
            error('no surface data found');
        end
    case 'Remesh surface'
        if(nodetype.hasnode && nodetype.hasface)
            res = inputdlg({'Rasterization voxel size (scalar):','Max gap to close (scalar):','Max surface element radis (scalar):'},...
                'Remesh surface',[1,1,1],{'1','20','3'});
            if(isempty(res))
                return;
            end
            opt.gridsize=str2num(res{1});
            opt.closesize=str2num(res{2});
            opt.elemsize=str2num(res{3});
            [newdata.node, newdata.face]=remeshsurf(nodedata.node,nodedata.face,opt);
            newtype.hasnode=1;
            newtype.hasface=1;
            prefix='RemeshSurf';
        else
            error('no surface data found');
        end
    case 'Tessellate surface'
        if(nodetype.hasnode && nodetype.hasface)
            [newdata.node,newdata.elem]=fillsurf(nodedata.node,nodedata.face);
            newdata.face=volface(newdata.elem(:,1:min(4,size(newdata.elem,2))));
            newtype=nodetype;
            newtype.haselem=1;
            prefix='TessMesh';
        else
            error('no surface data found');
        end
    case 'Reorient mesh elements'
        if(nodetype.hasnode && nodetype.haselem)
            [newdata.node,newdata.elem]=meshreorient(nodedata.node,nodedata.elem);
            newtype=nodetype;
            prefix='ReorientMesh';
        elseif(nodetype.hasnode && nodetype.hasface)
            [newdata.node,newdata.face]=surfreorient(nodedata.node,nodedata.face);
            newtype=nodetype;
            prefix='ReorientSurf';
        else
            error('no surface or tetrahedral mesh data found');
        end
    case 'Mesh quality histogram'
        if(nodetype.hasnode && nodetype.haselem)
            quality=meshquality(nodedata.node,nodedata.elem);
        elseif(nodetype.hasnode && nodetype.hasface)
            quality=meshquality(nodedata.node,nodedata.face);
        end
        if(exist('quality','var'))
            figure;
            hist(quality,50);
        end
        return;
    case 'Element volume histogram'
        if(nodetype.hasnode && nodetype.haselem)
            evol=elemvolume(nodedata.node,nodedata.elem);
        elseif(nodetype.hasnode && nodetype.hasface)
            evol=elemvolume(nodedata.node,nodedata.face);
        end
        if(exist('evol','var'))
            figure;
            hist(evol,50);
        end
        return;
    case 'Total volume'
        if(nodetype.hasnode && nodetype.haselem)
            evol=elemvolume(nodedata.node,nodedata.elem);
        elseif(nodetype.hasnode && nodetype.hasface)
            [no,el]=fillsurf(nodedata.node,nodedata.face);
            evol=elemvolume(no,el);
        end
        if(exist('evol','var'))
            msgbox(sprintf('Total volume is %f cubic voxel',sum(evol)),'Total volume');
        end
        return;
    case 'Containing data'
        msg='';
        if(nodetype.hasnode)
            msg=[msg sprintf('\nContaining %d nodes (%d columns)',size(nodedata.node,1),size(nodedata.node,2))];
        end
        if(nodetype.hasface)
            msg=[msg sprintf('\nContaining %d triangles (%d columns)',size(nodedata.face,1),size(nodedata.face,2))];
        end
        if(nodetype.haselem)
            msg=[msg sprintf('\nContaining %d tetrehedra (%d columns)',size(nodedata.elem,1),size(nodedata.elem,2))];
        end
        if(nodetype.hasvol)
            msg=[msg sprintf('\nContaining [%d x %d x %d ] volume',size(nodedata.vol,1),size(nodedata.vol,2),size(nodedata.vol,3))];
        end
        msgbox(msg,'Mesh data report');
        return;
    case 'Plot'
        if(isstruct(nodetype) && isfield(nodetype,'hasnode') && nodetype.hasnode)
            if(isfield(nodetype,'haselem') && nodetype.haselem)
                figure('keypressfcn',@plotfigevent,'userdata',struct('node',nodedata.node,'face',[],'elem',nodedata.elem));
                plotmesh(nodedata.node,[],nodedata.elem);
            else
                figure('keypressfcn',@plotfigevent,'userdata',struct('node',nodedata.node,'elem',[],'face',nodedata.face));
                plotmesh(nodedata.node,nodedata.face);
            end
        else
            figure('keypressfcn',@plotfigevent,'userdata',struct('vol',nodedata.vol));
            hs=slice(double(nodedata.vol),[],[ceil(size(nodedata.vol,2)*0.5)],ceil(size(nodedata.vol,3)*0.5));
            set(hs,'linestyle','none');
        end
    case {'Or','And','Diff','All','First','Second'}
        if(isstruct(nodetype) && isfield(nodetype,'hasnode'))
            if((nodetype.hasface || nodetype.haselem) && nodetype.hasnode)
                pt=ginput(1);
                [nodedata2,nodetype2,nodeid2]=getnodeat(root,obj,pt);
                if(~nodetype2.hasnode || ~nodetype2.hasface)
                    if(nodetype2.hasnode && nodetype2.haselem)
                        nodedata2.face=volface(nodedata2.elem);
                    else
                        error('Second operand does not contain a surface');
                    end
                end
                op=source.Label;
                if(strcmp(op,'Intersect'))
                    op='inter';
                end
                if(~nodetype.hasface)
                    nodedata.face=volface(nodedata.elem);
                end
                [newdata.node,newdata.face]=surfboolean(nodedata.node,nodedata.face,lower(op),nodedata2.node,nodedata2.face(:,[1 3 2]));
                newtype.hasnode=1;
                newtype.hasface=1;
                prefix=source.Label;
            else
                warndlg('Selected node does not contain a surface mesh');
            end
        end
    case 'Delete'
        button = questdlg('Are you sure to delete the selected node?', 'Confirm','No');
        if strcmpi(button, 'No') ||  strcmpi(button, 'Cancel')
	        return;
        end
        root.graph=rmnode(root.graph,root.graph.Nodes.Name{nodeid});
        updategraph(root,handles);
    case 'Rename'
        newname = inputdlg('Define a new name:',...
                'Rename',1,{root.graph.Nodes.Name{nodeid}});
        if(isempty(newname))
            return;
        end
        if(isempty(newname{1}) || ~isempty(cell2mat(regexp( root.graph.Nodes.Name, ['^' newname{1} '$']))))
            error('empty or duplicated node name');
        end
        root.graph.Nodes.Name{nodeid}=newname{1};
        updategraph(root,handles);
    case 'Save as'
        if(~nodetype.haselem && ~nodetype.hasnode)
            error('selected data does not have a mesh');
            return;
        end
        filter = {'*.jmesh';'*.*'};
        [file, path] = uiputfile(filter,'Export mesh');
        if ~isequal(file,0) && ~isequal(path,0)
            if(nodetype.haselem)
                savejmesh(nodedata.node,nodedata.face,nodedata.elem,fullfile(path,file));
            else
                savejmesh(nodedata.node,nodedata.face,fullfile(path,file));
            end
        end
end

if(exist('newdata','var') && exist('newtype','var'))
    cla(handles.axPreview);
    cla(handles.axFlow);
    newdata.preview=getpreview(newdata,newtype,[400,400]);
    [newkey,root.graph]=addnodewithdata(handles,newdata,newtype,prefix);
    root.graph=addedge(root.graph,{root.graph.Nodes.Name{nodeid}},{newkey});
    if(strcmp(source.Parent.Type, 'uimenu') && strcmp(source.Parent.Label,'Surface boolean'))
        root.graph=addedge(root.graph,{root.graph.Nodes.Name{nodeid2}},{newkey});
    end
    updategraph(root,handles);
end

catch ME
    msg=sprintf('Error: \n%s\n',ME.message);
    for e=1:length(ME.stack)
       msg=sprintf('%s\nFile: %s\nFunction: %s\nLine: %d\n\n',msg,ME.stack(e).file,ME.stack(e).name,ME.stack(e).line);
    end
	uiwait(warndlg(msg,'I2M ERROR'));
    updategraph(root,handles);
end

function plotfigevent(hobject,event)
data=get(hobject,'userdata');
plotpos=[];
if(isfield(data,'plotpos'))
    plotpos=data.plotpos;
end
if(isempty(plotpos))
    switch event.Key
        case {'rightarrow','uparrow'}
            plotpos=0;
        case {'leftarrow','downarrow'}
            plotpos=9;
        otherwise  
    end
end
if(isfield(data,'node'))
    pmax=max(data.node);
    pmin=min(data.node);
    cla;
    switch event.Key
        case 'rightarrow'
            plotpos=min(plotpos+1,9);
            plotmesh(data.node,data.face,data.elem,sprintf('x>%f',(pmax(1)-pmin(1))*plotpos*0.1+pmin(1)))
        case 'leftarrow'
            plotpos=max(plotpos-1,0);
            plotmesh(data.node,data.face,data.elem,sprintf('x>%f',(pmax(1)-pmin(1))*plotpos*0.1+pmin(1)))
        case 'uparrow'
            plotpos=min(plotpos+1,9);
            plotmesh(data.node,data.face,data.elem,sprintf('y>%f',(pmax(2)-pmin(2))*plotpos*0.1+pmin(2)))
        case 'downarrow'
            plotpos=max(plotpos-1,0);
            plotmesh(data.node,data.face,data.elem,sprintf('y>%f',(pmax(2)-pmin(2))*plotpos*0.1+pmin(2)))
        otherwise  
    end
end
data.plotpos=plotpos;
set(hobject,'userdata',data);
%----------------------------------------------------------------
function [nodedata,nodetype,nodeid]=getnodeat(root,obj,pos)
nodedist=[obj.XData(:)-pos(1,1) obj.YData(:)-pos(1,2)];
nodedist=sum(nodedist.*nodedist,2);
[mindist, nodeid]=min(nodedist);
nodedata=root.graph.Nodes.Data{nodeid};
nodetype=root.graph.Nodes.Type{nodeid};

%----------------------------------------------------------------
function mytype=dummytype
mytype.hasnode=0;
mytype.hasface=0;
mytype.haselem=0;
mytype.hasvol=0;

%----------------------------------------------------------------
function [newdata, newtype]=v2sgui(data)
prompt = {'Threshold (scalar or array):',...
    'Surface element radius bound (scalar):',...
    'Surface element distance bound (scalar)','Method: (cgalsurf,cgalmesh,simplify)'};
title = 'vol2surf - extracting surface mesh from volume';
dims = [1 1 1 1];
definput = {'0.5','5','1','cgalsurf'};
res = inputdlg(prompt,title,dims,definput);
newdata=[]; 
newtype=dummytype;
if(isempty(res))
    return;
end

opt=struct('radbound',str2num(res{2}),'distbound',str2num(res{3}));
[newdata.node,newdata.face]=v2s(data.vol,eval(res{1}),opt, res{4});
newtype.hasnode=1;
newtype.hasface=1;
%----------------------------------------------------------------
function [newdata, newtype]=v2mgui(data)
prompt = {'Threshold (scalar or []):',...
    'Surface element radius bound (scalar):',...
    'Surface element distance bound (scalar)',...
    'Max element volume (scalar):',...
    'Method (cgalsurf,cgalmesh,simplify):'};
title = 'vol2mesh - extracting tet mesh from volume';
dims = [1 1 1 1 1];
definput = {'[]','5','1','30','cgalmesh'};
res = inputdlg(prompt,title,dims,definput);
newdata=[]; 
newtype=dummytype;
if(isempty(res))
    return;
end

opt=struct('radbound',str2num(res{2}),'distbound',str2num(res{3}));
[newdata.node,newdata.elem,newdata.face]=v2m(data.vol,eval(res{1}),...
   opt, res{4});
newtype.hasnode=1;
newtype.hasface=1;
newtype.haselem=1;

%----------------------------------------------------------------
function img=getpreview(nodedata,nodetype,imsize)
hfpreview=figure('visible', 'off');
ax=axes('parent',hfpreview,'Units','pixels','position',[1, 1, imsize(1), imsize(2)]);
if(isfield(nodetype,'haselem') && nodetype.haselem)
    plotmesh(nodedata.node,[],nodedata.elem,'linestyle',':','edgealpha',0.3,'parent',ax);
elseif(isfield(nodetype,'hasface') && nodetype.hasface)
    plotmesh(nodedata.node,nodedata.face,'linestyle','-','parent',ax);
elseif(isfield(nodetype,'hasvol') && nodetype.hasvol)
    hs=slice(double(nodedata.vol),[],[ceil(size(nodedata.vol,2)*0.5)],ceil(size(nodedata.vol,3)*0.5),'parent',ax);
    set(hs,'linestyle','none');
elseif(isfield(nodetype,'hasnode') && nodetype.hasnode)
    plotmesh(nodedata.node,'.','parent',ax);
end
set(ax,'color','none')
axis(ax,'equal');
axis(ax,'off');
img=getframe(gca);
img=flipud(img.cdata);
delete(ax);
close(hfpreview);

%----------------------------------------------------------------
function [newdata, newtype]=s2mgui(data)
prompt = {'Simplification ratio (%edges to keep, 0-1):',...
    'Max element volume (scalar):',...
    'Method (tetgen,tetgen1.5,cgalpoly):',...
    'Region seeds (N x 3 array):',...
    'Hole seeds (N x 3 array):'};
title = 'surf2mesh - creating tet mesh from surfaces';
dims = [1 1 1 1 1];
definput = {'1','30','tetgen','[]','[]'};
res = inputdlg(prompt,title,dims,definput);
newdata=[]; 
newtype=dummytype;
if(isempty(res))
    return;
end

[newdata.node,newdata.elem,newdata.face]=...
   s2m(data.node,data.face,str2num(res{1}),...
     str2num(res{2}),res{3},eval(res{4}),eval(res{5}));
newtype.hasnode=1;
newtype.hasface=1;
newtype.haselem=1;

% --- Outputs from this function are returned to the command line.
function varargout = i2m_OutputFcn(hObject, eventdata, handles) 
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
handles.output=get(handles.fgI2M,'userdata');
varargout{1} = handles.output;

% --------------------------------------------------------------------
function miWeb_Callback(hObject, eventdata, handles)
web('http://iso2mesh.sourceforge.net');

% --------------------------------------------------------------------
function miDoc_Callback(hObject, eventdata, handles)
web('http://iso2mesh.sourceforge.net/cgi-bin/index.cgi?Doc');


% --------------------------------------------------------------------
function miAbout_Callback(hObject, eventdata, handles)
helpmsg={
'\bf\fontsize{12}I2M: An Integrated GUI for Iso2Mesh Meshing Toolbox\rm\fontsize{10}',
'',
'Copyright (c) 2018 Qianqian Fang <q.fang at neu.edu>',
''
'Computational Optics&Translational Imaging Lab (http://fanglab.org)',
'Department of Bioengineering',
'Northeastern University',
'360 Huntington Ave, Boston, MA 02115, USA',
'',
'URL:    http://iso2mesh.sourceforge.net',
''};

opt.Interpreter = 'tex';
opt.WindowStyle = 'modal';

msgbox(helpmsg,'About','help',opt);

% --------------------------------------------------------------------
function miSphere_Callback(hObject, eventdata, handles)
prompt = {'Center:','Radius (scalar):',...
    'Surface element radius bound (scalar)',...
    'Max element volume (scalar, 0 - only create surface):'};
title = 'Create Mesh';
dims = [1 1 1 1];
definput = {'[0 0 0]','50','6','20'};
res = inputdlg(prompt,title,dims,definput);
if(isempty(res))
    return;
end
newtype=dummytype;
opt=str2num(res{3});
if(str2num(res{4})==0)
   [newdata.node,newdata.face]=meshasphere(eval(res{1}),...
     str2num(res{2}),opt);
else
   [newdata.node,newdata.face,newdata.elem]=meshasphere(eval(res{1}),...
     str2num(res{2}),opt, str2num(res{4}));
   newtype.haselem=1;
end
newtype.hasnode=1;
newtype.hasface=1;

if(exist('newdata','var') && exist('newtype','var'))
    newkey=addnodewithdata(handles,newdata,newtype,'Sphere');
end

% --------------------------------------------------------------------
function miBox_Callback(hObject, eventdata, handles)
prompt = {'Diagonal end point 1 (1x3 vector):','Diagonal end point 2 (1x3 vector):',...
    'Surface element radius bound (scalar)',...
    'Max element volume (scalar, 0 - only create surface):'};
title = 'Create Mesh';
dims = [1 1 1 1];
definput = {'[0 0 0]','[100 60 30]','6','30'};
res = inputdlg(prompt,title,dims,definput);
if(isempty(res))
    return;
end
newtype=dummytype;
opt=str2num(res{3});
if(str2num(res{4})==0)
   [newdata.node,newdata.face]=meshabox(eval(res{1}),eval(res{2}),opt);
else
   [newdata.node,newdata.face,newdata.elem]=meshabox(eval(res{1}),...
     eval(res{2}),opt, str2num(res{4}));
   newtype.haselem=1;
end
newtype.hasnode=1;
newtype.hasface=1;

if(exist('newdata','var') && exist('newtype','var'))
    newkey=addnodewithdata(handles,newdata,newtype,'Box');
end

% --------------------------------------------------------------------
function miCylinder_Callback(hObject, eventdata, handles)
prompt = {'Axis end-point 1','Axis end-point 2','Radius (scalar):',...
    'Surface element radius bound (scalar)',...
    'Max element volume (scalar, 0 - only create surface):',...
    'Circle division:'};
title = 'Create Mesh';
dims = [1 1 1 1 1 1];
definput = {'[0 0 0]','[0 0 50]','10','3','20','20'};
res = inputdlg(prompt,title,dims,definput);
if(isempty(res))
    return;
end
newtype=dummytype;
opt=str2num(res{4});
maxvol=str2num(res{5});

if(maxvol==0)
   [newdata.node,newdata.face]=meshacylinder(eval(res{1}),eval(res{2}),...
     str2num(res{3}),opt);
else
   [newdata.node,newdata.face,newdata.elem]=meshacylinder(eval(res{1}),eval(res{2}),...
     str2num(res{3}),opt,maxvol);
   newtype.haselem=1;
end
newtype.hasnode=1;
newtype.hasface=1;

if(exist('newdata','var') && exist('newtype','var'))
    newkey=addnodewithdata(handles,newdata,newtype,'Cyl');
end

% --------------------------------------------------------------------
function miLoadVol_Callback(hObject, eventdata, handles)

nodedata=struct;
nodetype=dummytype;
filters={'*.nii;*.hdr;*.img;*.tif;*.tiff;*.inr;*.bin;*.ubj','3D volume file (*.nii;*.hdr;*.img;*.tif;*.tiff;*.inr;*.bin;*.ubj)';...
    '*.nii','Nifti file (*.nii)';...
    '*.hdr;*.img','Analyze 7.5 file (*.hdr;*.img)';...
    '*.tif;*.tiff','Multipage TIFF file (*.tif)';...
    '*.inr','INR image (*.inr)';...
    '*.bin','Binary file (*.bin)';...
    '*.ubj','Universal JSON (*.ubj)';...
    '*.*','All (*.*)'};
[file,path,idx] = uigetfile(filters);
if isequal(file,0)
   return;
else
   if(regexp(file,'\.[Nn][Ii][Ii]$'))
       im=readnifti(fullfile(path,file));
       nodedata.vol=im.img;
       nodetype.hasvol=1;
   elseif(regexp(file,'(\.[Hh][Dd][Rr]$|\.[Ii][Mm][Gg]$)'))
       im=readnifti(fullfile(path,file));
       nodedata.vol=im.img;
       nodetype.hasvol=1;
   elseif(regexp(file,'\.[Tt][Ii][Ff][Ff]*$'))
       nodedata.vol=readmptiff(fullfile(path,file));
       nodetype.hasvol=1;
   elseif(regexp(file,'\.[Ii][Nn][Rr]$'))
       nodedata.vol=readinr(fullfile(path,file));
       nodetype.hasvol=1;
   elseif(regexp(file,'\.[Bb][Ii][Nn]$'))
       prompt = {'Dimension (1x3 vector):',...
            'Datatype (short,float,double,integer,...):'};
       title = 'Load generic binary file';
       dims = [1 1];
       definput = {'[]','short'};
       [res,isok] = inputdlg(prompt,title,dims,definput);
       if(isok==0)
           return;
       end
       nodedata.vol=loadmc2(fullfile(path,file),eval(res{1}),res{2});
       nodetype.hasvol=1;
   elseif(regexp(file,'\.[Uu][Bb][Jj]$'))
       nodedata=loadbj(fullfile(path,file));
       if(isstruct(nodedata) && isfield(nodedata,'vol'))
           nodetype.hasvol=1;
       end
   end
end

if(exist('nodedata','var'))
    nodetype=getnodetype(nodedata);
    if(nodetype.hasvol)
        addnodewithdata(handles,nodedata,nodetype,'Vol');
    end
else
    warndlg('no valid mesh data found','Warning');
end

% --------------------------------------------------------------------
function miLoadMesh_Callback(hObject, eventdata, handles)

nodedata=struct;
nodetype=dummytype;
filters={'*.jmesh;*.off;*.medit;*.smf;*.json','3D Mesh files (*.jmesh;*.off;*.medit;*.smf;*.json)';...
    '*.jmesh','JSON mesh (*.jmesh)';...
    '*.off','OFF file (*.off)';...
    '*.medit','Medit file (*.medit)';...
    '*.ele','Tetgen element mesh file (*.ele)';...
    '*.json','JSON file (*.json)';'*.*','All (*.*)'};
[file,path,idx] = uigetfile(filters);
if isequal(file,0)
   return;
else
   if(regexp(file,'\.[Oo][Ff][Ff]$'))
       [nodedata.node, nodedata.face]=readoff(fullfile(path,file));
   elseif(regexp(file,'\.[Mm][Ee][Dd][Ii][Tt]$'))
       [nodedata.node, nodedata.elem]=readmedit(fullfile(path,file));
   elseif(regexp(file,'\.[Ee][Ll][Ee]$'))
       [pathstr,name,ext] = fileparts(fullfile(path,file));
       [nodedata.node, nodedata.elem]=readtetgen(fullfile(pathstr,name));
   elseif(regexp(file,'\.[Jj][Mm][Ee][Ss][Hh]$'))
       nodedata=importjmesh(fullfile(path,file));
   elseif(regexp(file,'\.[Jj][Ss][Oo][Nn]$'))
       nodedata=loadjson(fullfile(path,file));
   end
end
if(exist('nodedata','var'))
    adddatatograph(handles,nodedata);
else
    warndlg('no valid mesh data found','Warning');
end

% --------------------------------------------------------------------
function miLoadSurf_Callback(hObject, eventdata, handles)
nodedata=struct;
nodetype=dummytype;
filters={'*.jmesh;*.off;*.asc;*.smf;*.smf;*.json','3D Mesh files (*.jmesh;*.off;*.asc;*.smf;*.smf;*.json)';...
    '*.jmesh','JSON mesh (*.jmesh)';...
    '*.off','OFF file (*.off)';...
    '*.asc','ASC file (*.asc)';...
    '*.gts','GNU Trangulated Surface file (*.gts)';...
    '*.smf','Simple Model Format (*.smf)';...
    '*.json','JSON file (*.json)';'*.*','All (*.*)'};
[file,path,idx] = uigetfile(filters);
if isequal(file,0)
   return;
else
   if(regexp(file,'\.[Oo][Ff][Ff]$'))
       [nodedata.node, nodedata.face]=readoff(fullfile(path,file));
   elseif(regexp(file,'\.[Aa][Ss][Cc]$'))
       [nodedata.node, nodedata.face]=readasc(fullfile(path,file));
   elseif(regexp(file,'\.[Gg][Tt][Ss]$'))
       [nodedata.node, nodedata.face]=readgts(fullfile(path,file));
   elseif(regexp(file,'\.[Ss][Mm][Ff]$'))
       [nodedata.node, nodedata.face]=readsmf(fullfile(path,file));
   elseif(regexp(file,'\.[Jj][Mm][Ee][Ss][Hh]$'))
       nodedata=importjmesh(fullfile(path,file));
   elseif(regexp(file,'\.[Jj][Ss][Oo][Nn]$'))
       nodedata=loadjson(fullfile(path,file));
   end
end
if(exist('nodedata','var'))
    adddatatograph(handles,nodedata);
else
    warndlg('no valid mesh data found','Warning');
end

% --------------------------------------------------------------------
function nodedata=importjmesh(filename)
data=loadjson(filename);
nodedata=struct;
if(isfield(data,'MeshNode'))
   nodedata.node=data.MeshNode;
end
if(isfield(data,'MeshElem'))
   nodedata.elem=data.MeshElem;
end
if(isfield(data,'MeshSurf'))
   nodedata.face=data.MeshSurf;
end
if(isfield(data,'MeshNodeVal'))
   nodedata.node(:,end+1:end+size(data.MeshNodeVal,2))=data.MeshNodeVal;
end
if(isfield(data,'MeshTetraVal'))
   nodedata.elem(:,end+1:end+size(data.MeshTetraVal,2))=data.MeshTetraVal;
end

% --------------------------------------------------------------------
function adddatatograph(handles,nodedata)
nodetype=getnodetype(nodedata);
if(nodetype.haselem)
    addnodewithdata(handles,nodedata,nodetype,'Tet');
elseif(nodetype.hasface)
    addnodewithdata(handles,nodedata,nodetype,'Surf');
elseif(nodetype.hasnode)
    addnodewithdata(handles,nodedata,nodetype,'Point');
elseif(nodetype.hasvol)
    addnodewithdata(handles,nodedata,nodetype,'Vol');
end

% --- Executes during object creation, after setting all properties.
function axFlow_CreateFcn(hObject, eventdata, handles)
% hObject    handle to axFlow (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: place code in OpeningFcn to populate axFlow


% --- Executes during object creation, after setting all properties.
function fgI2M_CreateFcn(hObject, eventdata, handles)
% hObject    handle to fgI2M (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

%--------------------------------------------------------------------
function nodetype=getnodetype(nodedata)
nodetype=dummytype;
if(~isstruct(nodedata))
    return;
end
names=fieldnames(nodedata);
for i=1:length(names)
      switch names{i}
           case 'node'
                nodetype.hasnode=1;
           case 'face'
                nodetype.hasface=1;
           case 'elem'
                nodetype.haselem=1;
           case 'vol'
                nodetype.hasvol=1;
      end
      dat=nodedata.(names{i});
      if((isnumeric(dat) || islogical(dat)) && ndims(dat)==3)
           nodetype.hasvol=1;
      end
end
% --------------------------------------------------------------------

function [key,newgraph]=addnodewithdata(handles,nodedata,nodetype,name)

root=get(handles.fgI2M,'userdata');

if(isempty(root))
    root=struct('graph',digraph,'menu',uicontextmenu);
end
if(nargin<4)
    name='x';
end

id=1;
if(~isempty(root.graph.Nodes))
  while(find(strcmp(root.graph.Nodes.Name,sprintf('%s%d',name,id))))
    id=id+1;
  end
end
key=sprintf('%s%d',name,id);

cla(handles.axPreview);
cla(handles.axFlow);
nodedata.preview=getpreview(nodedata,nodetype,[400,400]);

nodeprop=table({key},{nodedata},{nodetype},'VariableNames',{'Name','Data','Type'});
root.graph=addnode(root.graph,nodeprop);
if(nargout>1)
    newgraph=root.graph;
end
updategraph(root, handles);

% --------------------------------------------------------------------
function hobj=updatepreview(root,obj,handles)
cla(handles.axPreview);
view(handles.axPreview,2)
nx=obj.XData(:);
ny=obj.YData(:);
nn=length(nx);
hold(handles.axPreview,'on');
dx=get(handles.axFlow,'xlim');
dy=get(handles.axFlow,'ylim');
wd=min([diff(dx) diff(dy)])/15;
hobj=zeros(1,nn);
set(handles.axPreview,'xlim',dx);
set(handles.axPreview,'ylim',dy);
dim=get(handles.axPreview,'dataaspectratio');
[wfig, hfig]=getwindowsize(handles.fgI2M);
wfig=wfig*dim(2);
hfig=hfig*dim(1);

for i=1:nn
    if(isfield(root.graph.Nodes.Data{i},'preview'))
        hobj(i)=imagesc(nx(i)+[-2*wd 0], ny(i)+[-wd wd]*(wfig/hfig), imresize(root.graph.Nodes.Data{i}.preview,0.25), ...
            'parent',handles.axPreview);
    end
end
set(hobj,'userdata',obj);
set(hobj,'UIContextMenu',root.menu);
set(handles.axPreview,'xlim',dx);
set(handles.axPreview,'ylim',dy);
axis(handles.axPreview,'off');
hold(handles.axPreview,'off');
% --------------------------------------------------------------------

function [width, height]=getwindowsize(fig)
oldunits = get(fig, 'Units');
set(fig, 'Units', 'pixels');
figpos = get(fig, 'Position');
set(fig, 'Units', oldunits);
width=figpos(3);
height=figpos(4);

function [hg,hobj]=updategraph(root, handles)
set(handles.fgI2M,'userdata',root);
hg=plot(root.graph,'parent',handles.axFlow,'ArrowSize',15);
hobj=updatepreview(root,hg,handles);
% set(hg,'Selected','on');
axis(handles.axFlow,'off');
set(handles.axFlow,'XColor','none')
set(hg,'UIContextMenu',root.menu);

% --------------------------------------------------------------------
function miEllipsoid_Callback(hObject, eventdata, handles)
prompt = {'Center (1x3 vector):',...
    'Radii (a scalar, or 1x3 or 1x5 vector):',...
    'Max element volume (scalar, 0 - only create surface):',...
    'Circle division:'};
title = 'Create an Ellipsoid Mesh';
dims = [1 1 1 1];
definput = {'[0 0 0]','[50 30 20]','3','30'};
res = inputdlg(prompt,title,dims,definput);
if(isempty(res))
    return;
end
newtype=dummytype;
opt=str2num(res{3});
maxvol=str2num(res{4});

if(maxvol==0)
   [newdata.node,newdata.face]=meshanellip(eval(res{1}),eval(res{2}),opt);
else
   [newdata.node,newdata.face,newdata.elem]=meshanellip(eval(res{1}),...
     eval(res{2}),opt, maxvol);
   newtype.haselem=1;
end
newtype.hasnode=1;
newtype.hasface=1;

if(exist('newdata','var') && exist('newtype','var'))
    newkey=addnodewithdata(handles,newdata,newtype,'Cyl');
end

% --------------------------------------------------------------------
function miLattice_Callback(hObject, eventdata, handles)
prompt = {'X-lattice range (a vector):',...
    'Y-lattice range (a vector):',...
    'Z-lattice range (a vector):',...
    'Max element volume (scalar, 0 - only create surface):'};
title = 'Create Lattice Grid Mesh';
dims = [1 1 1 1];
definput = {'[1 100]','[1 50]','[1 10 30]','30'};
res = inputdlg(prompt,title,dims,definput);
if(isempty(res))
    return;
end
newtype=dummytype;
maxvol=str2num(res{4});
if(maxvol==0)
   [newdata.node,newdata.face]=latticegrid(eval(res{1}),eval(res{2}),eval(res{3}));
else
   [no,fc,c0]=latticegrid(eval(res{1}),eval(res{2}),eval(res{3}));
   [newdata.node,newdata.elem,newdata.face]=surf2mesh(no,fc,[],[],1,maxvol,c0);
   newtype.haselem=1;
end
newtype.hasnode=1;
newtype.hasface=1;

if(exist('newdata','var') && exist('newtype','var'))
    newkey=addnodewithdata(handles,newdata,newtype,'Lattice');
end

% --------------------------------------------------------------------
function miMeshgrid5_Callback(hObject, eventdata, handles)
prompt = {'X-lattice range (a vector):',...
    'Y-lattice range (a vector):',...
    'Z-lattice range (a vector):'};
title = 'Create Meshgrid (5 tet/cell) Mesh';
dims = [1 1 1];
definput = {'1:10','1:8','1:5'};
res = inputdlg(prompt,title,dims,definput);
if(isempty(res))
    return;
end
newtype=dummytype;

[newdata.node,newdata.elem]=meshgrid5(eval(res{1}),eval(res{2}),eval(res{3}));
newdata.face=volface(newdata.elem);
newtype.hasnode=1;
newtype.hasface=1;
newtype.haselem=1;

if(exist('newdata','var') && exist('newtype','var'))
    newkey=addnodewithdata(handles,newdata,newtype,'Meshgrid5_');
end

% --------------------------------------------------------------------
function miMeshgrid6_Callback(hObject, eventdata, handles)
prompt = {'X-lattice range (a vector):',...
    'Y-lattice range (a vector):',...
    'Z-lattice range (a vector):'};
title = 'Create Meshgrid (6 tet/cell) Mesh';
dims = [1 1 1];
definput = {'1:10','1:8','1:5'};
res = inputdlg(prompt,title,dims,definput);
if(isempty(res))
    return;
end
newtype=dummytype;

[newdata.node,newdata.elem]=meshgrid5(eval(res{1}),eval(res{2}),eval(res{3}));
newdata.face=volface(newdata.elem);
newtype.hasnode=1;
newtype.hasface=1;
newtype.haselem=1;

if(exist('newdata','var') && exist('newtype','var'))
    newkey=addnodewithdata(handles,newdata,newtype,'Meshgrid6_');
end

% --------------------------------------------------------------------
function miOpen_Callback(hObject, eventdata, handles)
filter = {'*.mat';'*.*'};
root=get(handles.fgI2M,'userdata');

[file, path] = uigetfile(filter,'Load workspace');
if isequal(file,0)
   return;
else
   data=load(fullfile(path,file));
end
if(isfield(data,'i2mworkspace'))
    root.graph=data.i2mworkspace;
    updategraph(root,handles);
else
    warndlg('no saved workspace found','Warning');
end

% --------------------------------------------------------------------
function miSaveAll_Callback(hObject, eventdata, handles)
root=get(handles.fgI2M,'userdata');
i2mworkspace=root.graph;
filter = {'*.mat';'*.*'};
[file, path] = uiputfile(filter,'Save workspace');
if ~isequal(file,0) && ~isequal(path,0)
   save(fullfile(path,file),'i2mworkspace');
end

% --------------------------------------------------------------------
function miLoadVar_Callback(hObject, eventdata, handles)

nodedata=struct;
[file,path] = uigetfile({'*.mat','MATLAB data (*.mat)'});
if isequal(file,0)
   return;
else
   data=load(fullfile(path,file));
   vars=fieldnames(data);
   [idx, isok]=listdlg('ListString',vars,...
       'PromptString','Select a 3D array:');
   if(~isok)
       return;
   end
   for i=1:length(idx)
       dat=data.(vars{idx(i)});
       if(ndims(dat)==3 && ~isfield(nodedata,'vol'))
           nodedata.vol=dat;
       else
           nodedata.(vars{idx(i)})=dat;
       end
   end
end
adddatatograph(handles,nodedata);

% --------------------------------------------------------------------
function miExit_Callback(hObject, eventdata, handles)
close(handles.fgI2M);


% --------------------------------------------------------------------
function miRefresh_Callback(hObject, eventdata, handles)
root=get(handles.fgI2M,'userdata');
updategraph(root, handles);
