gusucode.com > vision工具箱matlab源码程序 > vision/+vision/+internal/+cascadeTrainer/+tool/TrainingDataLabelerTool.m
% TrainingDataLabelerTool Main class for the trainingImageLabeler App % % This object implements the core routines in trainingImageLabeler App. % All the callbacks that you see in the UI are implemented below. classdef TrainingDataLabelerTool < vision.internal.uitools.ToolStripApp properties ShowROILabels = true; end properties(Access = 'private') SplitPanel; % Tool group management LabelingTab % UI state ImageDisplay = []; MaxNumIcons % Takes a maximum number of image icons that can be shown in the full sized UI % The item below is just a dummy cache for storing Java related % items that would otherwise break by going out of scope Misc DataBrowser % Handle to the Java's JList which holds all the boards. It must % be available throughout the class so that one can obtain the % currently selected board JImageList ImageStrip MCategoryStrip OpenSessionPath; % variables needed to control HG/Java synchronization IsInteractionDisabledByScrollCallback = false; IsBrowserInteractionEnabled = true; end %====================================================================== %---------------------------------------------------------------------- % Public methods %---------------------------------------------------------------------- methods (Access = 'public') %------------------------------------------------------------------ function this = TrainingDataLabelerTool() import vision.internal.cascadeTrainer.*; % generate a name for this tool; we need a unique string for % each instance [~, name] = fileparts(tempname); title = vision.getMessage('vision:trainingtool:ToolTitle'); this.ToolGroup = toolpack.desktop.ToolGroup(name, title); this.LabelingTab = tool.LabelingTab(this); add(this.ToolGroup, getToolTab(this.LabelingTab), 1); this.SessionManager = ... vision.internal.cascadeTrainer.tool.TrainingImageLabelerSessionManager; this.Session = vision.internal.cascadeTrainer.tool.Session; % initialize the session object this.MaxNumIcons = 13; this.setupDataBrowser(); % handle closing of the tool group this.setClosingApprovalNeeded(true); addlistener(this.ToolGroup, 'GroupAction',... @(es,ed)doClosingSession(this, es, ed)); % manageToolInstances this.addToolInstance(); % set the path for opening sessions to the current directory this.OpenSessionPath = pwd; this.updateCategoryStrip(); this.setSelectedCategoryIndex(1); this.ToolGroup.Title = getString(message(... 'vision:trainingtool:ToolTitleWithSession', ... this.SessionManager.DefaultSessionFileName)); end %------------------------------------------------------------------ function show(this) this.removeViewTab(); this.removeDocumentTabs(); % open the tool this.ToolGroup.open(); % create figures and lay them out the way we want them imageslib.internal.apputil.ScreenUtilities.setInitialToolPosition(this.getGroupName()); this.setupImageDisplay(); % update button states to indicate the tool's current state this.updateButtonStates(); drawnow(); end end % public methods %====================================================================== methods (Access = 'public', Hidden) function conditionallyEnableBrowserIneraction(this, tf) if ~this.IsInteractionDisabledByScrollCallback this.setBrowserInteractionEnabled(tf); end end %------------------------------------------------------------------ % New session button callback %------------------------------------------------------------------ function newSession(this) % First check if we need to save anything before wiping the % existing data isCanceled = this.processSessionSaving(); if isCanceled return; end % Wipe the UI clean this.resetAll(); % Update Button states this.updateButtonStates(); % Update Status text this.setStatusText(); this.ToolGroup.Title = getString(message(... 'vision:trainingtool:ToolTitleWithSession', ... this.SessionManager.DefaultSessionFileName)); end %------------------------------------------------------------------ % Open session button callback %------------------------------------------------------------------ function openSession(this) % First check if we need to save anything before we wipe % existing data isCanceled = this.processSessionSaving(); if isCanceled return; end trainingFilesString = vision.getMessage... ('vision:trainingtool:LabelingSessionFiles'); allFilesString = vision.getMessage('vision:uitools:AllFiles'); selectFileTitle = vision.getMessage('vision:uitools:SelectFileTitle'); [filename, pathname] = uigetfile( ... {'*.mat', [trainingFilesString,' (*.mat)']; ... '*.*', [allFilesString, ' (*.*)']}, ... selectFileTitle, this.OpenSessionPath); wasCanceled = isequal(filename,0) || isequal(pathname,0); if wasCanceled return; else % preserve the last path for next time this.OpenSessionPath = pathname; end % Indicate that this is going to take some time setWaiting(this.ToolGroup, true); preserveExistingSession = false; this.processOpenSession(pathname, filename, preserveExistingSession); this.setStatusText(); setWaiting(this.ToolGroup, false); this.ToolGroup.Title = getString(message(... 'vision:trainingtool:ToolTitleWithSession', this.Session.FileName)); end %------------------------------------------------------------------ function importROIsFromWorkspace(this) % First check if we need to save anything before we wipe % existing data isCanceled = this.processSessionSaving(); if isCanceled return; end [ROIs, varName, isCanceled] = vision.internal.cascadeTrainer.tool.roigetvar(); if isCanceled return; end try session = vision.internal.cascadeTrainer.tool.Session(... ROIs, '', ''); catch errordlg(... getString(message('vision:trainingtool:UnableLoadROIMsg', varName)),... getString(message('vision:trainingtool:UnableLoadROITitle')),... 'modal'); return; end session.ExportVariableName = varName; preserveExistingSession = false; this.loadSession(session, preserveExistingSession); this.ToolGroup.Title = getString(message(... 'vision:trainingtool:ToolTitle')); end %------------------------------------------------------------------ % Add multiple sessions to current session button callback % Not yet implemented fully (wait to see if this is a needed % feature %------------------------------------------------------------------ function addToCurrentSession(this) trainingFilesString = vision.getMessage... ('vision:trainingtool:LabelingSessionFiles'); allFilesString = vision.getMessage('vision:uitools:AllFiles'); selectFileTitle = vision.getMessage('vision:uitools:SelectFileTitle'); [filename, pathname] = uigetfile( ... {'*.mat', [trainingFilesString,' (*.mat)']; ... '*.*', [allFilesString, ' (*.*)']}, ... selectFileTitle, this.OpenSessionPath); wasCanceled = isequal(filename,0) || isequal(pathname,0); if wasCanceled return; else % preserve the last path for next time this.OpenSessionPath = pathname; end setWaiting(this.ToolGroup, true); preserveExistingSession = true; this.processOpenSession(pathname, filename, preserveExistingSession); this.setStatusText(); setWaiting(this.ToolGroup, false); end %------------------------------------------------------------------ % Save session button callback %------------------------------------------------------------------ function saveSession(this, fileName) % If we didn't save the session before, ask for the filename if nargin < 2 if isempty(this.Session.FileName) fileName = vision.internal.uitools.getSessionFilename(... this.SessionManager.DefaultSessionFileName); if isempty(fileName) return; end else fileName = this.Session.FileName; end end this.SessionManager.saveSession(this.Session, fileName); this.ToolGroup.Title = getString(message(... 'vision:trainingtool:ToolTitleWithSession', this.Session.FileName)); end %------------------------------------------------------------------ function saveSessionAs(this) fileName = vision.internal.uitools.getSessionFilename(... this.SessionManager.DefaultSessionFileName); if ~isempty(fileName) this.saveSession(fileName); end this.ToolGroup.Title = getString(message(... 'vision:trainingtool:ToolTitleWithSession', this.Session.FileName)); end %------------------------------------------------------------------ % Add images button callback %------------------------------------------------------------------ function addImages(this) persistent imageDir; if isempty(imageDir) || ~exist(imageDir, 'dir') imageDir = pwd(); end % Get image file names [files, isUserCanceled] = imgetfile('MultiSelect', true, ... 'InitialPath', imageDir); if isUserCanceled || isempty(files) return; end imageDir = fileparts(files{1}); addImagesToSession(this, files); end %------------------------------------------------------------------ % Add Category button callback %------------------------------------------------------------------ function addCategory(this) categoryDlg = vision.internal.cascadeTrainer.tool.CategoryDlg(... this.getGroupName(), 'add'); wait(categoryDlg); % Close up the modal dialog if ~categoryDlg.IsCanceled varName = categoryDlg.VarName; startingIndex = this.Session.CategorySet.addCategoryToSession(varName); this.Session.IsChanged = true; this.updateCategoryStrip(); this.setSelectedCategoryIndex(startingIndex); this.makeCategorySelectionVisible(startingIndex); end drawnow; end %------------------------------------------------------------------ % Add images to a session %------------------------------------------------------------------ function addImagesToSession(this, files) % If a single file is selected convert the string into a cell % array before passing it to the Session object if ~isa(files, 'cell') files = {files}; end try setWaiting(this.ToolGroup, true); % Add images to the session object startingIndex = this.Session.ImageSet.addImagesToSession(files); if isempty(startingIndex) setWaiting(this.ToolGroup, false); dlg = warndlg(... getString(message('vision:uitools:NoImagesAddedMessage')),... getString(message('vision:uitools:NoImagesAddedTitle')),... 'modal'); uiwait(dlg); return; % This would indicate presence of duplicates end this.Session.IsChanged = true; if ~this.Session.hasAnyImages() setWaiting(this.ToolGroup, false); errordlg(... getString(message('vision:trainingtool:NoValidImagesFoundMsg')),... getString(message('vision:uitools:LoadingImagesFailedTitle')),... 'modal'); drawnow; return; end this.updateImageStrip(); % Update selection in the list this.setSelectedImageIndex(startingIndex); this.makeSelectionVisible(startingIndex); % Manage the image strip this.updateImageStrip(); drawnow(); % Update session state this.Session.IsChanged = true; this.Session.CanExport = this.getExportStatus(); this.updateButtonStates(); % Update displays this.drawImages(); this.setStatusText(); setWaiting(this.ToolGroup, false); catch loadingEx if ~isvalid(this) % we already went through delete sequence; this can % happen if the images did not yet load and someone % already closed the tool return; end setWaiting(this.ToolGroup, false); % if it errors out set the toolgroup busy to false errordlg(loadingEx.message,... vision.getMessage('vision:uitools:LoadingImagesFailedTitle'),... 'modal'); return; end end %------------------------------------------------------------------ % Zoom In Button callback %------------------------------------------------------------------ function doZoomIn(this, varargin) this.ImageDisplay.doZoomIn(varargin{:}); this.setFocusOnImages(); end %------------------------------------------------------------------ % Zoom Out buttons callback %------------------------------------------------------------------ function doZoomOut(this, varargin) this.ImageDisplay.doZoomOut(varargin{:}); this.setFocusOnImages(); end %------------------------------------------------------------------ % Pan Button callback %------------------------------------------------------------------ function doPan(this, varargin) this.ImageDisplay.doPan(varargin{:}); this.setFocusOnImages(); end %------------------------------------------------------------------ % Add ROIs callback %------------------------------------------------------------------ function doAddROI(this, varargin) this.ImageDisplay.doAddROI(varargin{:}); this.setFocusOnImages(); end %------------------------------------------------------------------ % Export button callback %------------------------------------------------------------------ function export(this) this.updateSessionObject(); if ~this.Session.ImageSet.areAllImagesLabeled() choice = questdlg(vision.getMessage('vision:trainingtool:UnlabeledImagesPrompt'),... vision.getMessage('vision:trainingtool:UnlabeledImagesTitle'),... vision.getMessage('vision:trainingtool:ExportROIs'),... vision.getMessage('vision:trainingtool:ContinueLabeling'),... vision.getMessage('vision:trainingtool:ContinueLabeling')); % Handle of the dialog is destroyed by the user % closing the dialog or the user pressed cancel if isempty(choice) || ... strcmp(choice, vision.getMessage('vision:trainingtool:ContinueLabeling')) return; end end varName = this.Session.ExportVariableName; exportDlg = vision.internal.cascadeTrainer.tool.ExportDlg(... this.getGroupName, varName); if this.Session.NumCategories > 1 exportDlg.disableFormat(); end wait(exportDlg); if ~exportDlg.IsCanceled varName = exportDlg.VarName; format = exportDlg.VarFormat; if isequal(format, getString(message('vision:trainingtool:StructFormat'))) catID = this.Session.CategorySet.CategoryStruct(1).categoryID; this.Session.ImageSet.setROIBoundingBoxesForCategory(catID); outputVar = this.Session.ImageSet.ROIBoundingBoxes; for i = 1:numel(outputVar) outputVar(i).objectBoundingBoxes = round(outputVar(i).objectBoundingBoxes); end assignin('base', varName, outputVar); else labelTable = this.Session.getLabelTable(); assignin('base', varName, labelTable); % display the parameters at the command prompt end evalin('base', varName); end drawnow; end %------------------------------------------------------------------ % Help button callback %------------------------------------------------------------------ function help(~) mapfile_location = fullfile(docroot,'toolbox',... 'vision','vision.map'); doc_tag = 'visionTrainingImageLabeler'; helpview(mapfile_location, doc_tag); end %------------------------------------------------------------------ function deleteToolInstance(this) imageslib.internal.apputil.manageToolInstances('remove',... 'trainingImageLabeler', this); delete(this); end %------------------------------------------------------------------ function addToolInstance(this) imageslib.internal.apputil.manageToolInstances('add',... 'trainingImageLabeler', this); end %------------------------------------------------------------------ % This method is used for testing %------------------------------------------------------------------ function setClosingApprovalNeeded(this, in) this.ToolGroup.setClosingApprovalNeeded(in); end %------------------------------------------------------------------ function processOpenSession(this, pathname, filename,... preserveExistingSession) session = this.SessionManager.loadSession(pathname, filename); if isempty(session) return; end loadSession(this, session, preserveExistingSession); end %------------------------------------------------------------------ function loadSession(this, session, preserveExistingSession) isNewSession = false; if ~preserveExistingSession this.resetAll(); % Start fresh this.Session.FileName = session.FileName; isNewSession = true; end % use the loaded session if ~this.Session.ImageSet.hasAnyImages() this.Session = session; selectedIndex = 1; else % index of the first element of the added session selectedIndex = numel(this.Session.ImageSet.ImageStruct)+1; % matlab based index categoryStruct = session.CategorySet.CategoryStruct; count = this.Session.CategorySet.compareCategoryStruct(categoryStruct); % count = index means the new category gets mapped to the % existing category, count = 0 means there is a conflict in % the color and the color gets changed for the new session % category (warn), count = -1, add this category to the new % list if any(count==0) dlg = warndlg(... getString(message('vision:trainingtool:DuplicateColorPromptMessage')),... getString(message('vision:trainingtool:DuplicateColorPromptTitle')),... 'modal'); uiwait(dlg); end imagesCatID = {session.ImageSet.ImageStruct.catID}; imagesCatID = cellfun(@(x) zeros(size(x)), imagesCatID, 'UniformOutput', false); for i = 1:numel(count) origIndex = categoryStruct(i).categoryID; varName = categoryStruct(i).categoryName; if(count(i)==0) [~,newIndex] = this.Session.CategorySet.addCategoryToSession(varName); imagesCatID = session.replaceCategoryIndex(origIndex,... newIndex, imagesCatID); continue; end if(count(i)==-1) color = categoryStruct(i).categoryColor; [~,newIndex] = this.Session.CategorySet.addCategoryToSession(varName,color); else newIndex = count(i); end imagesCatID = session.replaceCategoryIndex(origIndex, ... newIndex, imagesCatID); end s = session.ImageSet; [s.ImageStruct.catID] = imagesCatID{:}; session.ImageSet = s; addedImages = this.Session.ImageSet.addImageStructToCurrentSession(... session.ImageSet.ImageStruct, ... session.ImageSet.AreThumbnailsGenerated); % Return if no new images are added if ~addedImages return; end this.Session.IsChanged = true; end if ~isempty(this.Session.ImageSet.ImageStruct) this.updateImageStrip(); % Restore image strip this.updateCategoryStrip(); idx = 1; this.MCategoryStrip.setSelectedCategoryIndex(idx); this.setSelectedImageIndex(selectedIndex); this.makeSelectionVisible(selectedIndex); this.drawImages(); % Display the first image on the list this.updateImageStrip(); % Restore image stripclear classes end this.Session.CanExport = this.getExportStatus(); this.updateButtonStates(); if isNewSession this.Session.IsChanged = false; end end %------------------------------------------------------------------ function doClosingSession(this, group, event) if strcmp(event.EventData.EventType, 'CLOSING') && ... group.isClosingApprovalNeeded this.closingSession(group) end end %------------------------------------------------------------------ function closingSession(this, group) sessionChanged = this.Session.IsChanged; yes = vision.getMessage('MATLAB:uistring:popupdialogs:Yes'); no = vision.getMessage('MATLAB:uistring:popupdialogs:No'); cancel = vision.getMessage('MATLAB:uistring:popupdialogs:Cancel'); if sessionChanged selection = this.askForSavingOfSession(); else selection = no; end switch selection case yes this.saveSession(); group.approveClose this.deleteToolInstance(); case no group.approveClose this.deleteToolInstance(); case cancel group.vetoClose otherwise group.vetoClose end end %------------------------------------------------------------------ function closeAllFigures(this) delete(this.ImageDisplay); end %------------------------------------------------------------------ function setupImageDisplay(this) % create all the required figures this.ImageDisplay = ... vision.internal.cascadeTrainer.tool.ImageWithROIsDisplay(... makeFig()); this.addFigure(this.ImageDisplay.Parent); drawnow(); % Prevent the user from being able to close the main image figure % using a keyboard shortcut. md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; client = md.getClient('MainImageFigure', this.ToolGroup.Name); client.putClientProperty(... com.mathworks.widgets.desk.DTClientProperty.PERMIT_USER_CLOSE,... java.lang.Boolean.FALSE); % Add listeners. addlistener(this.ImageDisplay, 'ImageClick', ... @this.imageClick); addlistener(this.ImageDisplay, 'ParentMouseClick', ... @(~,~)this.conditionallyEnableBrowserIneraction(false)); addlistener(this.ImageDisplay, 'ParentMouseRelease', ... @(~,~)this.conditionallyEnableBrowserIneraction(true)); addlistener(this.ImageDisplay, 'ImageModeChanged', ... @this.respondToImageModeChange); addlistener(this.ImageDisplay, 'AddFullImageROI', ... @this.addFullImageROI); addlistener(this.ImageDisplay, 'NextImage', ... @(~, ~)this.changeImage(1)); addlistener(this.ImageDisplay, 'PreviousImage', ... @(~, ~)this.changeImage(-1)); addlistener(this.ImageDisplay, 'ROIsChanged', ... @this.updateInternalROIs); addlistener(this.ImageDisplay, 'RotateClockwise', ... @(~, ~)rotateClockwise(this, this.getSelectedImageIndices())); addlistener(this.ImageDisplay, 'RotateCounterClockwise', ... @(~, ~)rotateCounterClockwise(this, this.getSelectedImageIndices())); end %------------------------------------------------------------------ function resetDataBrowserLocation(this) % restore data browser to its original location md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; md.setClientLocation('DataBrowserContainer', this.getGroupName(), ... com.mathworks.widgets.desk.DTLocation.create('W')) end end %======================================================================= %---------------------------------------------------------------------- % Static public methods %---------------------------------------------------------------------- methods (Static) %------------------------------------------------------------------ function deleteAllTools imageslib.internal.apputil.manageToolInstances('deleteAll',... 'trainingImageLabeler'); end end %====================================================================== %---------------------------------------------------------------------- % Private methods %---------------------------------------------------------------------- methods (Access = 'private') %------------------------------------------------------------------ function ret = getExportStatus(this) ret = this.Session.getExportStatus(); end %------------------------------------------------------------------ % Gets the UI to the starting point, as if nothing has been loaded %------------------------------------------------------------------ function resetAll(this) % reset the message in the data browser this.setupDataBrowser(); % wipe the visible figures wipeFigure(this.ImageDisplay); % reset the session this.Session.reset(); this.updateButtonStates(); this.updateCategoryStrip(); end %------------------------------------------------------------------ function setupDataBrowser(this) msg = getString(message('vision:trainingtool:LoadImagesFirstMsg')); this.DataBrowser = javaObject('com.mathworks.toolbox.vision.TCDataBrowser'); % Use Java list to display the message label = javaObjectEDT('javax.swing.JLabel', {msg}); label.setName('InitialDataBrowser'); this.ImageStrip = javaObject('com.mathworks.toolbox.vision.ImageStrip'); this.JImageList = javaMethod('getImageList', this.ImageStrip); add(this.ImageStrip.getImagePanel(),label,java.awt.BorderLayout.NORTH); this.DataBrowser.addPanel('Images', ... getString(message('vision:trainingtool:DataBrowserTitleImages')), this.ImageStrip.getImagePanel()); this.MCategoryStrip = vision.internal.cascadeTrainer.tool.MCategoryStrip; % initialize the category strip object; this.DataBrowser.addPanel('Categories', ... getString(message('vision:trainingtool:DataBrowserTitleCategories')),this.MCategoryStrip.getPanel()); this.ToolGroup.setDataBrowser(this.DataBrowser.getPanel()); drawnow(); end %------------------------------------------------------------------ % Training Data Labeler app requires at least one image with % an ROI. This routine grays out the export button and all the % buttons in the mode section if there are no images / images % without any ROIs. %------------------------------------------------------------------ function updateButtonStates(this) this.Session.CanExport = this.getExportStatus(); this.LabelingTab.updateButtonStates(this.Session); end %------------------------------------------------------------------ function isCanceled = processSessionSaving(this) isCanceled = false; sessionChanged = this.Session.IsChanged; yes = vision.getMessage('MATLAB:uistring:popupdialogs:Yes'); no = vision.getMessage('MATLAB:uistring:popupdialogs:No'); cancel = vision.getMessage('MATLAB:uistring:popupdialogs:Cancel'); if sessionChanged selection = this.askForSavingOfSession(); else selection = no; end switch selection case yes this.saveSession(); case no case cancel isCanceled = true; end end %------------------------------------------------------------------ % This function can suspend and resume interaction with the % image browser. This is particularly useful for synchronizing % Java UI with MATLAB's functions. %------------------------------------------------------------------ function setBrowserInteractionEnabled(this, isEnabled) if isempty(this.Session.ImageSet.ImageStruct) return; end scrollPane = this.ImageStrip.getImageScrollPane(); this.IsBrowserInteractionEnabled = isEnabled; if isEnabled javaMethod('enableImageScrolling', this.ImageStrip); else javaMethod('disableImageScrolling', this.ImageStrip); end javaMethodEDT('setEnabled', scrollPane, isEnabled); javaMethodEDT('setWheelScrollingEnabled', scrollPane, isEnabled); end %------------------------------------------------------------------ function [menuItemRemove, removeActionListener] = ... createImageStripRemovePopupMenu(this, idxMultiselect) item = vision.getMessage('vision:uitools:Remove'); itemName = 'removeItem'; menuItemRemove = javaObjectEDT('javax.swing.JMenuItem',... item); menuItemRemove.setName(itemName); % Added Accelerators jRemoveKeyStroke = javaMethodEDT('getKeyStroke', 'javax.swing.KeyStroke', 'DELETE'); menuItemRemove.setAccelerator(jRemoveKeyStroke); removeActionListener = addlistener(menuItemRemove,'Action',... @remove); % main popup callback %---------------------------------------------------------- function remove(~,~) this.processRemove(idxMultiselect) end %remove end %------------------------------------------------------------------ function [menuRotate, rotateClockwiseListener,... rotateCounterClockwiseListener] = ... createImageStripRotatePopupMenu(this, idxMultiselect) % Main rotate menu item item1 = vision.getMessage('vision:trainingtool:RotateImage'); itemName1 = 'rotateImage'; menuRotate = javaObjectEDT('javax.swing.JMenu', ... item1); menuRotate.setName(itemName1); item2 = vision.getMessage('vision:trainingtool:RotateClockwise'); itemName2 = 'rotateClockWise'; % Clockwise sub-menu subMenuRotateClockwise = javaObjectEDT('javax.swing.JMenuItem', ... item2); subMenuRotateClockwise.setName(itemName2); jRotateClockwiseKeyStroke = javaMethodEDT(... 'getKeyStroke', 'javax.swing.KeyStroke', 'control R'); subMenuRotateClockwise.setAccelerator(jRotateClockwiseKeyStroke); menuRotate.add(subMenuRotateClockwise); % Counter-clockwise sub-menu item3 = vision.getMessage('vision:trainingtool:RotateCounterClockwise'); itemName3 = 'rotateCounterClockWise'; subMenuRotateCounterClockwise = javaObjectEDT('javax.swing.JMenuItem', ... item3); subMenuRotateCounterClockwise.setName(itemName3); jRotateCounterClockwiseKeyStroke = javaMethodEDT(... 'getKeyStroke', 'javax.swing.KeyStroke', 'control shift R'); subMenuRotateCounterClockwise.setAccelerator(jRotateCounterClockwiseKeyStroke); menuRotate.add(subMenuRotateCounterClockwise); % Add listeners rotateClockwiseListener = addlistener(subMenuRotateClockwise, ... 'Action', @rotClockwise); rotateCounterClockwiseListener = addlistener(... subMenuRotateCounterClockwise, 'Action', @rotCounterClockwise); %---------------------------------------------------------- function rotClockwise(~,~) this.rotateClockwise(idxMultiselect); end %---------------------------------------------------------- function rotCounterClockwise(~,~) this.rotateCounterClockwise(idxMultiselect); end end %------------------------------------------------------------------ function [menuSortByROIs, sortByROIsActionListener] = ... createImageStripSortByROIsPopupMenu(this) item = vision.getMessage('vision:trainingtool:SortListByNumROIs'); itemName = 'SortByNumROIs'; menuSortByROIs = javaObjectEDT('javax.swing.JMenuItem',... item); menuSortByROIs.setName(itemName); sortByROIsActionListener = addlistener(menuSortByROIs, 'Action', ... @sortByNumROIs); %---------------------------------------------------------- function sortByNumROIs(~,~) boundingBoxes = {this.Session.ImageSet.ImageStruct.objectBoundingBoxes}; [rows, ~] = cellfun(@size, boundingBoxes); [~, indices] = sortrows(rows'); this.Session.ImageSet.AreThumbnailsGenerated = ... this.Session.ImageSet.AreThumbnailsGenerated(indices); this.Session.ImageSet.ImageStruct = ... this.Session.ImageSet.ImageStruct(indices); % edit: The following lines are being done in many % places. Consider refactoring it into a function. jfirstVisibleIndex = this.JImageList.getFirstVisibleIndex(); jlastVisibleIndex = this.JImageList.getLastVisibleIndex(); for index = jfirstVisibleIndex:jlastVisibleIndex this.Session.ImageSet.updateImageListEntry(index); javaMethodEDT('setListData', this.JImageList, ... {this.Session.ImageSet.ImageStruct.ImageIcon}); end % Scroll to the first unlabeled image after sorting and % select it. this.setSelectedImageIndex(1); this.makeSelectionVisible(1); drawnow; end end %------------------------------------------------------------------ % Set up management of the image strip %------------------------------------------------------------------ function updateImageStrip(this) if(numel(this.Session.ImageSet.ImageStruct)==0) return; end jfirstVisibleIndex = this.JImageList.getFirstVisibleIndex(); jlastVisibleIndex = this.JImageList.getLastVisibleIndex(); if jfirstVisibleIndex == -1 jfirstVisibleIndex = 0; end if jlastVisibleIndex == -1 % By default, a full sized viewport can hold approx 13 % icons, if the number of images selected is lesser than % that set it as the last visible index. jlastVisibleIndex = min(numel(this.Session.ImageSet.ImageStruct)-1,... this.MaxNumIcons); end for ind = jfirstVisibleIndex:jlastVisibleIndex this.Session.ImageSet.updateImageListEntry(ind); end selectedIdx = this.getSelectedImageIndex(); javaMethodEDT('setListData', this.JImageList, ... {this.Session.ImageSet.ImageStruct(:).ImageIcon}); this.setSelectedImageIndex(selectedIdx); % Add a listener for handling file selections this.addSelectionListener(); popupListener = addlistener(this.JImageList, 'MousePressed', ... @doPopup); keyListener = addlistener(this.JImageList, 'KeyPressed', ... @doInterceptKeyPress); % Use the handle command to convert the Java object to a % handle object. scrollCallback = handle(this.ImageStrip.getScrollCallback); % Connect the callback to a nested function. The callback % class requires 'delayed' as the listener type. scrollListener = handle.listener(scrollCallback, 'delayed', @doScroll); mouseMotionListener = addlistener(this.JImageList, 'MouseMoved', ... @this.setStatusText); % Store handles to prevent going out of scope this.Misc.PopupListener = popupListener; this.Misc.KeyListener = keyListener; this.Misc.ScrollListener = scrollListener; this.Misc.MouseMotionListener = mouseMotionListener; this.setStatusText(); %-------------------------------------------------------------- function doScroll(~, ~) if this.Session.ImageSet.areAllIconsGenerated() return; end drawnow(); % update Java UI components before proceeding this.setBrowserInteractionEnabled(false); this.IsInteractionDisabledByScrollCallback = true; jfirstVisibleIndex = this.JImageList.getFirstVisibleIndex(); jlastVisibleIndex = this.JImageList.getLastVisibleIndex(); for index = jfirstVisibleIndex:jlastVisibleIndex doUpdate = this.Session.ImageSet.updateImageListEntry(index); if doUpdate % Do not move the "setWaiting" outside of the for loop! % It will cause the down/up arrows on the scrollbar % to misbehave setWaiting(this.ToolGroup, true); % turn on waiting pointer selectedIndex = this.getSelectedImageIndex(); javaMethodEDT('setListData', this.JImageList, ... {this.Session.ImageSet.ImageStruct.ImageIcon}); this.setSelectedImageIndex(selectedIndex); drawnow(); end end this.setBrowserInteractionEnabled(true); this.IsInteractionDisabledByScrollCallback = false; setWaiting(this.ToolGroup, false); % turn off waiting pointer drawnow(); end %-------------------------------------------------------------- function doInterceptKeyPress(~, hData) if this.IsBrowserInteractionEnabled if hData.getKeyCode == hData.VK_DELETE doDeleteKey(); elseif hData.isControlDown() && hData.isShiftDown() && hData.getKeyCode() == hData.VK_R doCounterClockwiseRotationKey(); elseif hData.isControlDown() && hData.getKeyCode() == hData.VK_R doClockwiseRotationKey(); elseif hData.getKeyCode == hData.VK_ESCAPE % Hitting escape takes you back to ROI drawing mode this.selectROIMode(); end end end %-------------------------------------------------------------- function doDeleteKey() % CTRL-DEL will also end up here idxMultiselect = this.getSelectedImageIndices(); this.processRemove(idxMultiselect); end %-------------------------------------------------------------- function doClockwiseRotationKey() idxMultiselect = this.getSelectedImageIndices(); this.rotateClockwise(idxMultiselect); end %-------------------------------------------------------------- function doCounterClockwiseRotationKey() idxMultiselect = this.getSelectedImageIndices(); this.rotateCounterClockwise(idxMultiselect); end %-------------------------------------------------------------- function doPopup(~, hData) if hData.getButton == 3 % right-click % Get the list widget list = hData.getSource; % Get current mouse location point = hData.getPoint(); % Figure out the index of the board immediately under % the mouse button jIdx = list.locationToIndex(point); % 0-based java idx idx = jIdx + 1; % Figure out the index list in the case of multi-select idxMultiselect = this.getSelectedImageIndices(); if ~any(idx == idxMultiselect) % If the mouse is not over the selected area; % select whatever is under the mouse and override % the multi-selection index this.setSelectedImageIndex(idx); idxMultiselect = idx; end % Create a popup % Removing Images [menuItemRemove, removeActionListener] = ... this.createImageStripRemovePopupMenu(idxMultiselect); % Rotating an Image [menuRotate, rotateClockwiseListener, ... rotateCounterClockwiseListener] = ... this.createImageStripRotatePopupMenu(idxMultiselect); % Sorting by Number of ROIs [menuSortByROIs, sortByROIsActionListener] = ... this.createImageStripSortByROIsPopupMenu(); % Prevent listeners from going out of scope this.Misc.PopupActionListener = [removeActionListener sortByROIsActionListener ... rotateClockwiseListener rotateCounterClockwiseListener]; jmenu = javaObjectEDT('javax.swing.JPopupMenu'); jmenu.add(menuItemRemove); jmenu.add(menuRotate); jmenu.addSeparator(); jmenu.add(menuSortByROIs); % Display the popup jmenu.show(list, point.x, point.y); jmenu.repaint; end end % doPopup end % updateImageStrip %------------------------------------------------------------------ % Set up management of the category strip %------------------------------------------------------------------ function updateCategoryStrip(this) this.updateCategoryPanel(); this.MCategoryStrip.addPopupListener(@doPopup); this.MCategoryStrip.addKeyListener(@doInterceptCategoryStripKeyPress); %-------------------------------------------------------------- function doInterceptCategoryStripKeyPress(~, hData) if this.IsBrowserInteractionEnabled if hData.getKeyCode == hData.VK_DELETE % Figure out the index list in the case of multi-select idxMultiselect = this.MCategoryStrip.getSelectedCategoryIndices(); this.processCategoryRemove(idxMultiselect); elseif hData.getKeyCode == hData.VK_ESCAPE % Hitting escape takes you back to ROI drawing mode this.selectROIMode(); end end end %-------------------------------------------------------------- function doPopup(~, hData) if hData.getButton == 3 % right-click % Get the list widget list = hData.getSource; % Get current mouse location point = hData.getPoint(); % Figure out the index of the board immediately under % the mouse button jIdx = list.locationToIndex(point); % 0-based java idx idx = jIdx + 1; % Figure out the index list in the case of multi-select idxMultiselect = this.MCategoryStrip.getSelectedCategoryIndices(); if ~any(idx == idxMultiselect) % If the mouse is not over the selected area; % select whatever is under the mouse and override % the multi-selection index this.MCategoryStrip.setSelectedCategoryIndex(idx); idxMultiselect = idx; end % Create a popup item = vision.getMessage('vision:trainingtool:Color'); itemName = 'colorItem'; menuItemColor = javaObjectEDT('javax.swing.JMenuItem',... item); menuItemColor.setName(itemName); % Rename Category item = vision.getMessage('vision:trainingtool:Rename'); itemName = 'renameItem'; menuItemRename = javaObjectEDT('javax.swing.JMenuItem',... item); menuItemRename.setName(itemName); % Removing Category item = vision.getMessage('vision:uitools:Remove'); itemName = 'removeItem'; menuItemRemove = javaObjectEDT('javax.swing.JMenuItem',... item); menuItemRemove.setName(itemName); % Added Accelerators jRemoveKeyStroke = javaMethodEDT('getKeyStroke', ... 'javax.swing.KeyStroke', 'DELETE'); menuItemRemove.setAccelerator(jRemoveKeyStroke); this.MCategoryStrip.removeActionListener = addlistener(... menuItemRemove,'Action', @removeCategory); this.MCategoryStrip.changeColorListener = addlistener(menuItemColor, ... 'Action', @changeColor); this.MCategoryStrip.renameCategoryListener = addlistener(menuItemRename, ... 'Action', @renameCategory); jmenu = javaObjectEDT('javax.swing.JPopupMenu'); jmenu.add(menuItemColor); jmenu.add(menuItemRename); if(list.getModel().getSize() > 1) jmenu.add(menuItemRemove); end % Display the popup jmenu.show(list, point.x, point.y); jmenu.repaint; end %---------------------------------------------------------- % Nested Functions %---------------------------------------------------------- function changeColor(~,~) idx = this.MCategoryStrip.getSelectedCategoryIndex(); c = uisetcolor; if isscalar(c) return; end this.Session.changeCategoryColor(c,idx); this.updateCategoryPanel(); this.MCategoryStrip.setSelectedCategoryIndex(idx); this.redrawBoundingBoxes(); end %---------------------------------------------------------- function removeCategory(~,~) this.processCategoryRemove(idxMultiselect); end %---------------------------------------------------------- function renameCategory(~,~) idx = this.MCategoryStrip.getSelectedCategoryIndex(); categoryDlg = vision.internal.cascadeTrainer.tool.CategoryDlg(... this.getGroupName(), 'rename'); wait(categoryDlg); % Close up the modal dialog if ~categoryDlg.IsCanceled varName = categoryDlg.VarName; this.Session.renameCategory(varName,idx); this.updateCategoryPanel(); this.MCategoryStrip.setSelectedCategoryIndex(idx); this.redrawBoundingBoxes(); end end %---------------------------------------------------------- end % doPopup end % updateCategoryStrip %------------------------------------------------------------------ function processCategoryRemove(this, idxMultiselect) if this.Session.NumCategories==1 return; end if this.Session.NumCategories == numel(idxMultiselect) errordlg(vision.getMessage('vision:trainingtool:RemoveAllCategories')); return; end if numel(idxMultiselect) > 1 choice = questdlg(vision.getMessage('vision:trainingtool:RemoveCategoriesPrompt'),... vision.getMessage('vision:trainingtool:RemoveCategoriesTitle'),... vision.getMessage('vision:uitools:Remove'),... vision.getMessage('vision:uitools:Cancel'),... vision.getMessage('vision:uitools:Cancel')); elseif (numel(idxMultiselect) == 1) choice = questdlg(vision.getMessage('vision:trainingtool:RemoveCategoryPrompt'),... vision.getMessage('vision:trainingtool:RemoveCategoryTitle'),... vision.getMessage('vision:uitools:Remove'),... vision.getMessage('vision:uitools:Cancel'),... vision.getMessage('vision:uitools:Cancel')); end % Handle of the dialog is destroyed by the user % closing the dialog or the user pressed cancel if isempty(choice) || ... strcmp(choice, 'Cancel') return; end this.Session.removeCategory(idxMultiselect); this.updateCategoryPanel(); idx = 1; this.MCategoryStrip.setSelectedCategoryIndex(idx); this.redrawBoundingBoxes(); this.updateImageStrip(); end %------------------------------------------------------------------ function updateCategoryPanel(this) num = numel(this.Session.CategorySet.CategoryStruct)-1; [jfirstVisibleIndex, jlastVisibleIndex] = ... this.MCategoryStrip.getFirstLastIndex(num); for ind = jfirstVisibleIndex:jlastVisibleIndex this.Session.CategorySet.updateCategoryListEntry(ind); icon = {this.Session.CategorySet.CategoryStruct.categoryIcon}; this.MCategoryStrip.updateCategoryStrip(icon); end end %------------------------------------------------------------------ % Add selection listener to the image browser to handle the update of % the image display. %------------------------------------------------------------------ function addSelectionListener(this) selectionCallback = handle(this.ImageStrip.getSelectionCallback); % Connect the callback to a class function. The callback % class requires 'delayed' as the listener type. selectionListener = handle.listener(selectionCallback, ... 'delayed', @this.doSelection); this.Misc.SelectionListener = selectionListener; end % File selection handler %---------------------------------------- function doSelection(this, ~, ~) if ~this.Session.hasAnyImages() return else if ~isempty(this.ImageDisplay) && this.ImageDisplay.IsValid %ishandle(this.FigureHandles.MainImage) if ~this.IsInteractionDisabledByScrollCallback drawnow(); setWaiting(this.ToolGroup, true); this.setBrowserInteractionEnabled(false); end this.selectROIMode(); this.ImageDisplay.wipeFigure(); this.drawImages(); this.setStatusText(); if ~this.IsInteractionDisabledByScrollCallback drawnow(); this.setBrowserInteractionEnabled(true); setWaiting(this.ToolGroup, false); end end end end end %====================================================================== methods (Access = 'public', Hidden) %------------------------------------------------------------------ function drawImages(this) if ~this.ImageDisplay.IsValid return; % figure was destroyed end % Handle the case of wiping the data out if ~hasAnyImages(this.Session) return; % this can happen in rapid testing end currentIdx = this.getSelectedImageIndex(); try % image can disappear from the disk [imageMatrix, imageLabel] = this.Session.getImages(currentIdx); catch missingFileEx errordlg(missingFileEx.message,... vision.getMessage... ('vision:uitools:LoadingImagesFailedTitle'), 'modal'); return; end this.ImageDisplay.drawImage(imageMatrix, imageLabel); this.redrawBoundingBoxes(); end % drawImages %------------------------------------------------------------------ function redrawBoundingBoxes(this) index = this.getSelectedImageIndex(); if ~hasROIs(this.Session, index) return; end boundingBoxes = this.Session.ImageSet.ImageStruct... (index).objectBoundingBoxes; categoryID = this.Session.ImageSet.ImageStruct(index).catID; in = arrayfun(@(x) find(x == [this.Session.CategorySet.CategoryStruct.categoryID]),... categoryID,'UniformOutput', false); colors = {this.Session.CategorySet.CategoryStruct([in{:}]).categoryColor}; catNames = cell(1, numel(categoryID)); for i = 1:numel(categoryID) catNames{i} = this.Session.getCategoryName(categoryID(i)); end this.ImageDisplay.drawBoundingBoxes(boundingBoxes, categoryID, ... colors, catNames, this.ShowROILabels); end %------------------------------------------------------------------ function imageClick(this, varargin) mouseClickType = get(this.ImageDisplay.Parent, 'SelectionType'); this.ImageDisplay.deselectAllROIs(); switch mouseClickType case 'normal' % Draw ROI interactively this.drawROI(); case 'open' % triple-click this.addFullImageROI(); end end %--------------------------------------------------------------- function addFullImageROI(this, ~, ~) % Create an ROI to fill the entire image imageIdx = getSelectedImageIndex(this); catIdx = getSelectedCategoryIndex(this); hAxes = this.ImageDisplay.ImageAxes; hImage = findobj(hAxes, 'Type', 'image'); imSize = size(hImage.CData); roi = [1, 1, imSize([2, 1])]; addROI(this.Session, imageIdx, catIdx, roi); this.drawImages(); this.updateImageStrip(); end %--------------------------------------------------------------- function drawROI(this, varargin) hAxes = this.ImageDisplay.ImageAxes; hImage = findobj(hAxes, 'Type', 'image'); roi = vision.internal.uitools.imrectButtonDown.drawROI(hImage); if isempty(roi) % no-op elseif ~vision.internal.uitools.imrectButtonDown.isValidROI(roi) this.updateSessionObject(); else this.setROI(roi); end drawnow(); % Finish all the drawing before moving on end %------------------------------------------------------------------ function setROI(this, roi) currentIndex = this.getSelectedCategoryIndex(); if currentIndex < 1 currentIndex = 1; end catID = this.Session.CategorySet.CategoryStruct(currentIndex).categoryID; color = this.Session.CategorySet.CategoryStruct(currentIndex).categoryColor; catName = this.Session.getCategoryName(catID); this.ImageDisplay.deselectAllROIs(); this.ImageDisplay.drawROI(roi, catID, color, catName, this.ShowROILabels); this.updateInternalROIs(); end %------------------------------------------------------------------ function setCategoriesVisible(this) if ~isempty(this.ImageDisplay) this.ImageDisplay.setCategoriesVisible(this.ShowROILabels); end end %------------------------------------------------------------------ function updateInternalROIs(this, varargin) this.updateSessionObject(); this.Session.CanExport = this.getExportStatus(); this.updateButtonStates(); end end %======================================================================= methods (Access = 'private') %------------------------------------------------------------------- function respondToImageModeChange(this, ~, ~) switch this.ImageDisplay.ImageMode case 'ROImode' selectROIMode(this); case 'ZoomInMode' selectZoomInMode(this); case 'ZoomOutMode' selectZoomOutMode(this); case 'PanMode' selectPanMode(this); end end %------------------------------------------------------------------- function selectROIMode(this, varargin) this.LabelingTab.selectROIMode(); drawnow(); end %------------------------------------------------------------------- function selectZoomInMode(this, varargin) this.LabelingTab.selectZoomInMode(); end %------------------------------------------------------------------- function selectZoomOutMode(this, varargin) this.LabelingTab.selectZoomOutMode(); end %------------------------------------------------------------------- function selectPanMode(this, varargin) this.LabelingTab.selectPanMode(); end %------------------------------------------------------------------- function setStatusText(this, varargin) % Set the status bar to indicate the number of images labeled % by the user. md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; f = md.getFrameContainingGroup(this.getGroupName()); if isempty(f) % Bail out if we can't grab the frame. Apparently, this % can happen when testing on the MAC g1130360. return; end if this.Session.hasAnyImages() totalNumImages = getNumImages(this.Session); numImagesLabeled = getNumLabeledImages(this.Session); totalNumROIs = getNumROIs(this.Session); statusText{1} = vision.getMessage... ('vision:trainingtool:NumImagesLabeled',... numImagesLabeled, totalNumImages); statusText{2} = vision.getMessage... ('vision:trainingtool:NumTotalROIs',totalNumROIs); % setStatusText called by paste operation if ~isempty(varargin) && strcmp(varargin{1},'paste') pasteRoiNums = varargin{2}; statusText{3} = vision.getMessage... ('vision:trainingtool:LastPasteOp',... pasteRoiNums(2), pasteRoiNums(1)); end statusText = strjoin(statusText, ' '); javaMethodEDT('setStatusText', f, statusText); else % clear the status text javaMethodEDT('setStatusText', f, ''); end end %------------------------------------------------------------------ function updateSessionObject(this) currentIndex = this.getSelectedImageIndex; hAxes = this.ImageDisplay.ImageAxes; if isempty(hAxes) return; end currentROIs = findall(hAxes, 'tag', 'imrect'); % Return if no ROIs if isempty(currentROIs) this.Session.CanExport = false; end boundingBoxes = zeros(numel(currentROIs),4); colors = zeros(numel(currentROIs),3); categoryID = zeros(numel(currentROIs),1); for i = 1:numel(currentROIs) boundingBoxes(i,:) = round(getPos(currentROIs(i))); userData = get(currentROIs(i),'UserData'); colors(i,:) = this.Session.getCategoryColor(userData.catID); categoryID(i,1) = userData.catID; end needsUpdate = ... this.Session.ImageSet.updateBoundingBoxes(currentIndex,... boundingBoxes, categoryID); this.Session.IsChanged = needsUpdate; % Increment the ROI count on the icon description this.Session.ImageSet.updateIconDescription(currentIndex); % update the status bar to refresh the progress in labeling this.setStatusText(); javaMethodEDT('updateUI', this.JImageList); % Force an update of the list this.Session.CanExport = true; %-------------------------------------------------------------- function bbox = getPos(ROI) bbox = iptgetapi(ROI); bbox = round(feval(bbox.getPosition)); end end %------------------------------------------------------------------ % returns index of the selected Image %------------------------------------------------------------------ function idx = getSelectedImageIndex(this) idx = double(javaMethodEDT('getSelectedIndex', this.JImageList)); idx = idx+1; % make it one based if idx < 1 idx = 1; end end %------------------------------------------------------------------ function setSelectedImageIndex(this, index) % assumes 1-based index javaMethodEDT('setSelectedIndex', this.JImageList, index-1); end %------------------------------------------------------------------ function makeSelectionVisible(this, index) javaMethodEDT('ensureIndexIsVisible', this.JImageList, index-1); end %------------------------------------------------------------------ function [idx, jIdx] = getSelectedImageIndices(this) idx = double(this.JImageList.getSelectedIndices); jIdx = idx; % 0-based java index idx = idx+1; % make it one based end %------------------------------------------------------------------ % returns index of the selected Category %------------------------------------------------------------------ function idx = getSelectedCategoryIndex(this) idx = this.MCategoryStrip.getSelectedCategoryIndex; end %------------------------------------------------------------------ function setSelectedCategoryIndex(this, index) % assumes 1-based index this.MCategoryStrip.setSelectedCategoryIndex(index); end %------------------------------------------------------------------ function makeCategorySelectionVisible(this, index) this.MCategoryStrip.makeCategorySelectionVisible(index); end %------------------------------------------------------------------ function [idx, jIdx] = getSelectedCategoryIndices(this) [idx, jIdx] = this.MCategoryStrip.getSelectedCategoryIndices; end %------------------------------------------------------------------ function processRemove(this, idxMultiselect) % create a warning that asks if you're sure to remove % Display different warnings based on whether multiple images % are selected or just a single image is selected. cancel = getString(message('MATLAB:uistring:popupdialogs:Cancel')); if numel(idxMultiselect) > 1 choice = questdlg(vision.getMessage('vision:trainingtool:RemoveImagesPrompt'),... vision.getMessage('vision:trainingtool:RemoveImagesTitle'),... vision.getMessage('vision:uitools:Remove'), cancel, cancel); elseif (numel(idxMultiselect) == 1) choice = questdlg(vision.getMessage('vision:trainingtool:RemoveImagePrompt'),... vision.getMessage('vision:trainingtool:RemoveImageTitle'),... vision.getMessage('vision:uitools:Remove'), cancel, cancel); end % Handle of the dialog is destroyed by the user % closing the dialog or the user pressed cancel if isempty(choice) || ... strcmp(choice, cancel) return; end this.Session.ImageSet.removeImage(idxMultiselect); this.Session.IsChanged = true; jLowestIdx = idxMultiselect(1)-1; if this.Session.hasAnyImages() javaMethodEDT('setListData', this.JImageList, {this.Session.ImageSet.ImageStruct.ImageIcon}); if jLowestIdx ~= 0 newIdx = jLowestIdx; else newIdx = 1; end this.setSelectedImageIndex(newIdx); else % Remove all items from the image list if ishandle(this.ImageStrip) javaMethodEDT('resetImageList', this.ImageStrip); end this.Session.resetImages(); wipeFigure(this.ImageDisplay); this.updateImageStrip(); end % Update the UI before proceeding further drawnow; end %------------------------------------------------------------------ function rotateClockwise(this, idxMultiselect) rotationType = 'Clockwise'; rotateImage(this, idxMultiselect, rotationType); end %------------------------------------------------------------------ function rotateCounterClockwise(this, idxMultiselect) rotationType = 'CounterClockwise'; rotateImage(this, idxMultiselect, rotationType); end %------------------------------------------------------------------ function rotateImage(this, idxMultiselect, rotationType) % create a warning that asks if you're sure to overwrite % rotated image % Display different warnings based on whether multiple images % are selected or just a single image is selected. cancel = getString(message('MATLAB:uistring:popupdialogs:Cancel')); if numel(idxMultiselect) > 1 choice = questdlg(vision.getMessage('vision:trainingtool:RotateImagesPrompt'),... vision.getMessage('vision:trainingtool:RotateImagesTitle'),... vision.getMessage('vision:trainingtool:Continue'), cancel, cancel); elseif (numel(idxMultiselect) == 1) choice = questdlg(vision.getMessage('vision:trainingtool:RotateImagePrompt'),... vision.getMessage('vision:trainingtool:RotateImageTitle'),... vision.getMessage('vision:trainingtool:Continue'), cancel, cancel); end % Handle of the dialog is destroyed by the user % closing the dialog or the user pressed cancel if isempty(choice) || ... strcmp(choice, cancel) return; end this.Session.ImageSet.rotateImages(idxMultiselect, rotationType); this.Session.IsChanged = true; javaMethodEDT('setListData', this.JImageList, ... {this.Session.ImageSet.ImageStruct.ImageIcon}); this.setFocusOnImages(); this.setSelectedImageIndex(idxMultiselect(1)); % pick the first index drawnow; this.drawImages(); end %------------------------------------------------------------------ % Puts the image strip in focus %------------------------------------------------------------------ function setFocusOnImages(this) %drawnow; if ishandle(this.JImageList) javaMethodEDT('requestFocus', this.JImageList); end end %-------------------------------------------------------------- function changeImage(this, direction) currentIndex = this.getSelectedImageIndex(); this.setSelectedImageIndex(currentIndex+direction); this.makeSelectionVisible(currentIndex+direction); end end end %------------------------------------------------------------------ % Creates a figure with properties desired by the Training Data labeler % tool UI %------------------------------------------------------------------ function fig = makeFig() % suppress docked-window warning warning('off', 'MATLAB:figure:SetResize'); c = onCleanup(@()warning('on', 'MATLAB:figure:SetResize')); fig = figure('Resize', 'off', 'Visible','off', ... 'NumberTitle', 'off', 'Name', 'MainImageFigure', 'HandleVisibility',... 'callback', 'Color','white','IntegerHandle','off',... 'BusyAction', 'cancel', 'Interruptible', 'off'); end