function digitize07(varargin)
%DIGITIZE07 Digitze points on an image using the mouse
% DIGITIZE07(filename) displays an image and allows the user to
% digitize points using the mouse, similar to MATLAB's built-in GINPUT
% and similar to other digitizers available at the MATLAB Central File
% Exchange: 'digitize','digitize2.m', etc. The main new feature of
% this version is that points are draggable; this permits fine tuning
% of already digitized points using the zoom feature.
%
% Other features adopted from previous versions include:
% - Import previously digitized points
% - Export digitized points to the workspace or file
% - Interactively change the marker color, size, and shape
% - Pin digitized points (i.e. toggle draggable mode)
% - Delete unwanted points by right-clicking on the point
% - Fully interactive GUI: Errors are reported to dialog boxes
% rather than to the Workspace
%
% DIGITIZE07(filename) opens an interactive GUI and allows the user to
% digitize an unlimited number of points. The file must be an image that
% is recognized by IMREAD
%
% DIGITIZE07 by itself opens the digitizer and prompts the use to load an image file
%
%
%USING THE GUI:
% (a) Digitizing points. Point-and-click (left or right button to create a new point).
% (b) Drag a new point. Hold the button you used to create the point and
% drag it to a new location. A point may be dragged anywhere within
% the axes boundaries.
% (c) Drag an existing point. Left-click and hold to drag.
% (d) Disable/enable drag. Use the pin toggle button on the left of the
% figure's toolbar. This will pin all existing points. New points are
% still draggable.
% (e) Other features. Other menu features and the zoom tool are self-explanatory.
%
%
% See also GINPUT
%
%
% Acknowledgements:
% This was developed based on the functions "draggable" and "digitize2"
% which are both available from the MATLAB Central File Exchange.
%
% Author:
% Todd C Pataky (0todd0@gmail.com) ['zero' todd 'zero'@gmail.com]
% 18-April-2007
%IMPLEMENTATION NOTES:
% 1. The handles of digitized points are stored as application data in
% the image's axes. These handles are passed into different callback
% functions which allows for easy implementation of dragging using the
% figure properties: WindowButtonMotionFcn and WindowButtonUpFcn
% 2. The digitizer uses Figure 1 to open the image. If Figure 1 already
% existis it will be cleared.
% 3. Features for a future version:
% - Display point labels
% - Reorder points
% - Connect points
% - Create reference axes
% - Calibration (scale image coordinates to mm, m, etc.)
%
%% PRELIMINARY DATA CHECKS
%%%%%%%%%%%%%%%%%%%%%
%(1) Ensure proper argument specification:
%%%%%%%%%%%%%%%%%%%%%
switch nargin
case 0
initializeFigure %see INITIALIZATION FUNCTIONS below
initializeAxes([])
case 1
try %Attempt to initiate the GUI:
imfinfo(varargin{1}); %This will generate an error if not recognized by IMREAD
initializeFigure
initializeAxes(varargin{1})
catch
fprintf('\n\n\nError opening file.\n')
fprintf(' Please ensure that the file exists\n')
fprintf(' and that its format is recognized by ''imread.m''\n');
error(lasterror)
end
otherwise
error('Maximum of one input argument.')
end
%%%%%%%%%%%%%%%%%%%%%
%(2) Check Workspace for existence of 'XY':
%%%%%%%%%%%%%%%%%%%%%
if evalin('base','exist(''XY'');')==1
msgbox(['The variable XY exists in the Workspace. ',...
'Selecting ''Export XY''...''To Workspace'' will overwrite the current XY data.'],...
'Warning!','warn')
end
%% INITIALIZATION FUNCTIONS
function initializeFigure
figure(1)
clf
set(gcf,'numberTitle','off','name','Digitize07')
set(gcf,'menubar','none','closeRequestFcn',@closeFigure)
%%%%%%%%%%%%%%%%%%%%%%%%%
%CREATE MENU
%%%%%%%%%%%%%%%%%%%%%%%%%
%(a) Load functions
%%%%%%%%%%%%%%%%%%%%%%%%%
mh = uimenu(gcf,'Label','Load','separator','on');
uimenu(mh,'Label','Image...','callback',@callback_loadImage);
uimenu(mh,'Label','Points...','callback',@callback_loadPoints);
%%%%%%%%%%%%%%%%%%%%%%%%%
%(b) Export functions
%%%%%%%%%%%%%%%%%%%%%%%%%
mh = uimenu(gcf,'Label','Export XY');
uimenu(mh,'label','To Workspace','callback',@callback_export2Base)
uimenu(mh,'label','To .mat File...','callback',{@callback_export2File,'.mat'},'separator','on')
uimenu(mh,'label','To .dat File...','callback',{@callback_export2File,'.dat'})
%%%%%%%%%%%%%%%%%%%%%%%%%
%(c) Marker style functions
%%%%%%%%%%%%%%%%%%%%%%%%%
mh = uimenu(gcf,'Label','Marker Style');
mh1 = uimenu(mh,'Label','Color');
uimenu(mh1,'Label','Static Color...','callback',@callback_changeStaticColor);
uimenu(mh1,'Label','Dragging Color...','callback',@callback_changeDragColor);
uimenu(mh,'Label','Size...','callback',@callback_changeMarkerSize);
mh2 = uimenu(mh,'Label','Symbol');
uimenu(mh2,'Label','+ Plus sign','callback',{@callback_changeSymbol,'+'})
uimenu(mh2,'Label','o Circle','callback',{@callback_changeSymbol,'o'})
uimenu(mh2,'Label','* Asterisk','callback',{@callback_changeSymbol,'*'})
uimenu(mh2,'Label','. Point','callback',{@callback_changeSymbol,'.'})
uimenu(mh2,'Label','x Cross','callback',{@callback_changeSymbol,'x'})
uimenu(mh2,'Label','s Square','callback',{@callback_changeSymbol,'s'})
uimenu(mh2,'Label','d Diamond','callback',{@callback_changeSymbol,'d'})
uimenu(mh2,'Label','^ Triangle (up)','callback',{@callback_changeSymbol,'^'},'separator','on')
uimenu(mh2,'Label','v Triangle (down)','callback',{@callback_changeSymbol,'v'})
uimenu(mh2,'Label','> Triangle (right)','callback',{@callback_changeSymbol,'>'})
uimenu(mh2,'Label','< Triangle (left)','callback',{@callback_changeSymbol,'<'})
uimenu(mh2,'Label','p Pentagram','callback',{@callback_changeSymbol,'p'},'separator','on')
uimenu(mh2,'Label','h Hexagram','callback',{@callback_changeSymbol,'h'})
%%%%%%%%%%%%%%%%%%%%%%%%%
%CREATE TOGGLE ICONS:
%%%%%%%%%%%%%%%%%%%%%%%%%
ht = uitoolbar;
%%%%%%%%%%%%%%%%%%%%%%%%%
%(a) Pin points
%%%%%%%%%%%%%%%%%%%%%%%%%
[x,map] = imread([matlabroot,'\toolbox\matlab\icons\pin_icon.gif']);
cdata = ind2rgb(x,map);
cdata(cdata==1)=NaN;
uitoggletool(ht,'cdata',cdata,'TooltipString','Pin Points',...
'onCallback', 'setappdata(gca,''pinPoints'',1)',...
'offCallback','setappdata(gca,''pinPoints'',0)');
%%%%%%%%%%%%%%%%%%%%%%%%%
%(b) Zoom tool
%%%%%%%%%%%%%%%%%%%%%%%%%
fname = [matlabroot,'\toolbox\matlab\icons\zoomplus.mat'];
load(fname) %var name: 'cdata' (stored in zoomplus.mat)
uitoggletool(ht,'cdata',cdata,'TooltipString','Zoom','clickedCallback','zoom')
function initializeAxes(fname,varargin)
axes('position',[0.05 0.05 0.9 0.9])
%%%%%%%%%%%%%%%%%%%%%%%%%
%Initialize application data:
%%%%%%%%%%%%%%%%%%%%%%%%%
setappdata(gca,'axlim',[]) %required for dragging points
setappdata(gca,'H',[])
setappdata(gca,'pinPoints',0)
if nargin==1
markerSpecs = struct('statColor',[0 0 1],'dragColor',[1 0 0],'size',6,'style','o');
setappdata(gca,'markerSpecs',markerSpecs)
else setappdata(gca,'markerSpecs',varargin{1})
end
%%%%%%%%%%%%%%%%%%%%%%%%%
%Create message or display image
%%%%%%%%%%%%%%%%%%%%%%%%%
if isempty(fname)
th = text(0.5,0.5,'Please load an image to begin.');
set(th,'fontsize',14,'horizontalalignment','center','verticalalignment','middle',...
'backgroundcolor',0.9*[1 1 1],'edgecolor','k')
axis off
else displayImage(fname)
end
function displayImage(fname)
X = imread(fname);
ih = imagesc(X);
set(ih,'buttonDownFcn',@d07_createPoint)
axis equal tight
hold on
axlim = [get(gca,'xlim') get(gca,'ylim')];
setappdata(gca,'axlim',axlim)
%% MAIN DIGITIZING CODE
function d07_createPoint(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
xy = get(gca,'CurrentPoint'); %CurrentPoint yields two output rows
xy = xy(1,1:2);
h = d07_plotPoints(xy);
setappdata(gca,'H',[getappdata(gca,'H'); h])
%Enable dragging until button is released:
set(gcf,'WindowButtonMotionFcn',{@d07_dragPoint,h},'WindowButtonUpFcn',{@d07_buttonUp,h})
function [H] = d07_plotPoints(XY)
%%%%%%%%%%%%%%%%%%%%%%%%%
H = zeros(size(XY,1),1);
for k=1:size(XY,1)
H(k) = plot(XY(k,1),XY(k,2),'.');
%Create context menu for deleting points:
cmenu = uicontextmenu;
uimenu(cmenu, 'Label', 'Delete this point','Callback', {@deletePoint,H(k)});
uimenu(cmenu, 'Label', 'Delete all points...','Callback', @deleteAllPoints,'separator','on');
set(H(k),'UIContextMenu', cmenu)
end
markerSpecs = getappdata(gca,'markerSpecs');
set(H,'Color',markerSpecs.statColor,...
'MarkerSize',markerSpecs.size,...
'Marker',markerSpecs.style,...
'ButtonDownFcn',@d07_clickPoint);
function d07_clickPoint(h,varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
if ~getappdata(gca,'pinPoints')
markerSpecs = getappdata(gca,'markerSpecs');
set(h,'color',markerSpecs.dragColor)
set(gcf,'WindowButtonMotionFcn',{@d07_dragPoint,h},'WindowButtonUpFcn',{@d07_buttonUp,h})
end
function d07_dragPoint(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
h = varargin{3};
markerSpecs = getappdata(gca,'markerSpecs');
set(h,'color',markerSpecs.dragColor)
%%%%%%%%%%%%%%%%%%%%%%%%%
% Ensure that the dragged point lies within the axis bounds:
%%%%%%%%%%%%%%%%%%%%%%%%%
axlim = getappdata(gca,'axlim');
X = get(gca,'currentpoint');
[x,y] = deal(X(1,1),X(1,2));
if x<axlim(1)
x=axlim(1);
elseif x>axlim(2)
x=axlim(2);
end
if y<axlim(3)
y=axlim(3);
elseif y>axlim(4)
y=axlim(4);
end
%%%%%%%%%%%%%%%%%%%%%%%%%
% Update marker position
%%%%%%%%%%%%%%%%%%%%%%%%%
set(h,'xdata',x,'ydata',y)
function d07_buttonUp(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
h = varargin{3};
markerSpecs = getappdata(gca,'markerSpecs');
set(h,'color',markerSpecs.statColor)
set(gcf,'WindowButtonMotionFcn',[],'WindowButtonUpFcn',[])
%% CALLBACK FUNCTIONS
function callback_loadImage(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
% Check if there are existing points
if ~isempty(getappdata(gca,'H'))
button = questdlg(['Loading a new image will clear current points. ',...
'OK to continue?'],'Warning!!','OK','Cancel','OK');
if isequal(button,'Cancel')
return
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%
% Get an image file
[fname,pathName] = uigetfile('*.*');
if isequal(fname,0)
return
end
markerSpecs = getappdata(gca,'markerSpecs');
delete(gca)
initializeAxes([pathName,fname],markerSpecs)
function callback_loadPoints(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
% Check if an image has been loaded
if ~isempty(findobj(gca,'type','text'))
errordlg('Please load an image before loading points.','Error')
return
end
%%%%%%%%%%%%%%%%%%%%%%%%%
% Check if there are existing points
if ~isempty(getappdata(gca,'H'))
button = questdlg(['Loading new points will clear current points. ',...
'OK to continue?'],'Warning!!','OK','Cancel','OK');
if isequal(button,'Cancel')
return
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%
% Get a points file
[fname,pathName] = uigetfile({'*.dat','Data files (*.dat)'; '*.mat','MAT files (*.mat)'});
if isequal(fname,0)
return
end
%%%%%%%%%%%%%%%%%%%%%%%%%
% Check that the data are in the correct format
ext = fname(end-3:end);
switch ext
case '.dat'
XY = load([pathName,fname]);
if size(XY,2)~=2
errordlg('Data must be an m-by-2 matrix.','Error')
return
end
case '.mat'
w = whos('-file',[pathName fname]);
if length({w(:).name})>1
errordlg('.mat file must contain only one variable.','Error')
return
elseif w.size(2)~=2 || length(w.size)~=2
errordlg('Data must be an m-by-2 matrix','Error')
return
else load([pathName,fname])
eval(['XY = ',w.name,';'])
end
otherwise
errordlg('Must only load .dat or .mat files.','Error')
return
end
%%%%%%%%%%%%%%%%%%%%%%%%%
%Check that the data fall within the axes boundaries
axlim = getappdata(gca,'axlim');
if min(XY(:,1)) < axlim(1) ||...
max(XY(:,1)) > axlim(2) ||...
min(XY(:,2)) < axlim(3) ||...
max(XY(:,2)) > axlim(4)
errordlg('Loaded points must be inside axes boundaries.','Error')
return
end
%%%%%%%%%%%%%%%%%%%%%%%%%
%Delete old data:
delete(getappdata(gca,'H'))
%%%%%%%%%%%%%%%%%%%%%%%%%
%Plot new data
H = d07_plotPoints(XY);
setappdata(gca,'H',H)
function callback_export2Base(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
[XY] = getXY;
if isempty(XY)
msgbox('No existing points. Please digitize at least one point before exporting.',...
'Warning!','warn')
else
assignin('base','XY',XY)
msgbox('Data exported to Workspace. Variable name: ''XY''')
end
function callback_export2File(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
% Check for existing points
[XY] = getXY;
if isempty(XY)
msgbox('No existing points. Please digitize at least one point before exporting.',...
'Warning!','warn')
return
end
%%%%%%%%%%%%%%%%%%%%%%%%%
% Get a file name for writing
ext = varargin{3};
[fname,pathname] = uiputfile({['*',ext],[upper(ext(2:4)),' Files (',ext,')']});
if isequal(fname,0)
return
end
%%%%%%%%%%%%%%%%%%%%%%%%%
% Check the file extension of the chosen file
if ~isempty(findstr('.',fname))
ext = fname(end-3:end);
if ~ismember(ext,{'.dat','.mat'})
%the user has chosen a different file extension
errordlg('Please use the default extension.','Error')
return
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%
% Write data
switch ext
case '.mat'
save([pathname,fname],'XY')
msgbox('Data exported. Variable name: ''XY''')
case '.dat'
if isempty(findstr('.',fname))
fname = [fname,'.dat'];
end
save([pathname,fname],'XY','-double','-ascii','-tabs')
msgbox('Data exported.')
otherwise
end
function callback_changeStaticColor(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
markerSpecs = getappdata(gca,'markerSpecs');
c = uisetcolor(markerSpecs.statColor,'Choose Marker Color');
if ~isequal(c,0)
markerSpecs.statColor = c;
set(getappdata(gca,'H'),'color',c)
setappdata(gca,'markerSpecs',markerSpecs)
end
function callback_changeDragColor(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
markerSpecs = getappdata(gca,'markerSpecs');
c = uisetcolor(markerSpecs.dragColor,'Choose Marker Color');
if ~isequal(c,0)
markerSpecs.dragColor = c;
setappdata(gca,'markerSpecs',markerSpecs)
end
function callback_changeMarkerSize(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
markerSpecs = getappdata(gca,'markerSpecs');
a = inputdlg({'Enter marker size:'},'',1,{num2str(markerSpecs.size)});
if ~isempty(a)
a = str2double(a{1});
try
H = getappdata(gca,'H');
set(H,'markersize',a)
markerSpecs.size = a;
setappdata(gca,'markerSpecs',markerSpecs)
catch
err = lasterror;
errordlg(err.message,'Marker Size Error')
return
end
end
function callback_changeSymbol(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
symbol = varargin{3};
H = getappdata(gca,'H');
set(H,'marker',symbol)
markerSpecs = getappdata(gca,'markerSpecs');
markerSpecs.style = symbol;
setappdata(gca,'markerSpecs',markerSpecs)
function deletePoint(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
h = varargin{3};
H = getappdata(gca,'H');
H(H==h)=[];
setappdata(gca,'H',H)
delete(h)
function deleteAllPoints(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
button = questdlg('OK to delete all points?','Warning!!','OK','Cancel','OK');
if isequal(button,'Cancel')
return
end
delete(getappdata(gca,'H'))
setappdata(gca,'H',[])
function closeFigure(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%
button = questdlg('Export data to Workspace before closing?',...
'','Export&Close','Close','Cancel','Export&Close');
switch button
case 'Export&Close'
[XY] = getXY;
assignin('base','XY',XY)
fprintf('\n\nData imported from Digitize2D:\n')
fprintf(' Name: ''XY''\n')
fprintf(' Size: [%.0f %.0f]\n\n',size(XY))
delete(gcf)
case 'Close'
delete(gcf)
case 'Cancel'
return
end
%% UTILITY FUNCTIONS
function [XY] = getXY
H = getappdata(gca,'H');
XY = [get(H,'xdata') get(H,'ydata')];
if length(H)>1
XY = cell2mat(XY);
end