gusucode.com > vision工具箱matlab源码程序 > vision/+vision/+internal/+calibration/+tool/CameraCalibrationTool.m
% CameraCalibrationTool Main class for the Camera Calibrator App % % This object implements the core routines in Camera Calibrator App. % All the callbacks that you see in the UI are implemented below. % % NOTES % ===== % 1. To invoke the tool, follow these steps: % >> tool = vision.internal.calibration.tool.CameraCalibrationTool; % >> tool.show(); % or simply invoke cameraCalibrator.m % % 2. To manage tool instances and to be able to close all tools, % a persistent variable is used. It is protected by mlock. That means % that clear classes will not unload this class unless all UI % instances are closed. At that point, the tool calls munlock and a % "clear classes" command can actually unload the class from memory. % You can always verify if the class is fully unlocked by calling: % >> mislocked vision.internal.calibration.tool.CameraCalibrationTool % % 3. Naming conventions % a. Main set of callbacks for tool-strip buttons use simple verbs, % e.g. calibrate, export, saveSession, etc. % b. Smaller callback use the verb "do", e.g. doKeyPress, % doEditCallback, etc. % c. Methods are camel-cased. % Copyright 2012-2015 The MathWorks, Inc. classdef CameraCalibrationTool < vision.internal.uitools.ToolStripApp properties(Access=private) % Tool group management CalibrationTab ImageCaptureTab % Stereo or single camera IsStereo = false; % Handles the MainImage figure MainImageDisplay; ReprojectionErrorsDisplay; ExtrinsicsDisplay; % The item below is just a dummy cache for storing Java related % items that would otherwise break by going out of scope Misc % 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 JBoardList MinBoards = 2; % minimum number of boards required for calibration CurrentBoard = []; OpenSessionPath; % A flag indicating that doSelection() is in progress to prevent % doDeleteKey from executing. StillDrawing = false; % In single camera calibrator this is a char array. In stereo camera % calibrator this is a cell array of two char arrays. LastImageDir = {}; end properties (Access=public, Hidden) ImagePreviewDisplay; end %---------------------------------------------------------------------- % Public methods %---------------------------------------------------------------------- methods (Access=public) %------------------------------------------------------------------ function this = CameraCalibrationTool(isStereo) if nargin == 0 isStereo = false; end this.IsStereo = isStereo; import vision.internal.calibration.*; % generate a name for this tool; we need a unique string for % each instance [~, name] = fileparts(tempname); if isStereo title = getString(message('vision:caltool:StereoToolTitle')); else title = getString(message('vision:caltool:ToolTitle')); end this.ToolGroup = toolpack.desktop.ToolGroup(name, title); this.CalibrationTab = tool.CalibrationTab(this, isStereo); add(this.ToolGroup, getToolTab(this.CalibrationTab), 1); this.displayInitialDataBrowserMessage(); this.SessionManager = ... vision.internal.calibration.tool.CalibrationSessionManager; this.SessionManager.AppName = title; this.SessionManager.IsStereo = this.IsStereo; this.Session = tool.Session; % initialize the session object if isStereo this.Session.ExportVariableName = 'stereoParams'; end this.setDefaultCameraModelOptions(); % handle closing of the 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; end %------------------------------------------------------------------ function show(this) this.removeViewTab(); % Remove QuickAccess this.removeQuickAccess(); this.ToolGroup.open(); % create figures and lay them out the way that we want them imageslib.internal.apputil.ScreenUtilities.setInitialToolPosition(this.getGroupName()); % create default window layout this.createDefaultLayout(); % the call below affects data browser's width this.resetDataBrowserLocation(); % update all button states to indicate the tool's state this.updateButtonStates(); drawnow(); end end % public methods %---------------------------------------------------------------------- % Many of the methods below are public because they are used by tests % or by CalibrationTab, but they still should not be used outside of % these two areas. %---------------------------------------------------------------------- methods (Access=public, Hidden) %------------------------------------------------------------------ % New session button callback %------------------------------------------------------------------ function newSession(this) % First check if we need to save anything before we wipe % existing data isCanceled = this.processSessionSaving(); if isCanceled return; end % Wipe the UI clean this.resetAll; % Reset the camera model as well this.setDefaultCameraModelOptions(); this.CalibrationTab.enableNumRadialCoefficients(); % Update the button states this.updateButtonStates(); 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 calFilesString = getString(message('vision:caltool:CalibrationSessionFiles')); allFilesString = getString(message('vision:uitools:AllFiles')); selectFileTitle = getString(message('vision:uitools:SelectFileTitle')); [filename, pathname] = uigetfile( ... {'*.mat', [calFilesString,' (*.mat)']; ... '*.*', [allFilesString, ' (*.*)']}, ... selectFileTitle, this.OpenSessionPath); wasCanceled = isequal(filename,0) || isequal(pathname,0); if wasCanceled return; end % preserve the last path for next time this.OpenSessionPath = pathname; % Indicate that this is going to take some time setWaiting(this.ToolGroup, true); this.processOpenSession(pathname, filename) 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.Session.CameraModel = this.CalibrationTab.CameraModel; this.SessionManager.saveSession(this.Session, fileName); end %------------------------------------------------------------------ function saveSessionAs(this) fileName = vision.internal.uitools.getSessionFilename(... this.SessionManager.DefaultSessionFileName); if ~isempty(fileName) this.saveSession(fileName); end end %------------------------------------------------------------------ % Add images/Add images from file button callback %------------------------------------------------------------------ function addImages(this) if this.Session.hasAnyBoards() [files, isUserCanceled] = getImageFiles(this); if isUserCanceled return; end addImagesToExistingSession(this, files); else [files, squareSize, units, isUserCanceled] = ... getImageFilesAndSquareSize(this); if isUserCanceled return; end addImagesToNewSession(this, files, squareSize, units); end end %------------------------------------------------------------------ % Add images from camera button callback %------------------------------------------------------------------ function addImagesFromCamera(this) existingTabs = this.ToolGroup.TabNames; % If image capture tab is not in the toolgroup, add it and bring % focus to it. if isempty(this.ImageCaptureTab) this.ImageCaptureTab = vision.internal.calibration.tool.ImageCaptureTab(this); addlistener(this.ImageCaptureTab, 'CloseTab', @(~,~)closeImageCaptureTab(this)); end if ~any(strcmp(existingTabs, getName(this.ImageCaptureTab))) add(this.ToolGroup, getToolTab(this.ImageCaptureTab), 2); end % Create the device and launch preview. this.ImageCaptureTab.createDevice; this.ToolGroup.SelectedTab = getName(this.ImageCaptureTab); this.ImagePreviewDisplay.makeFigureVisible(); % Disable buttons in calibration tab. this.CalibrationTab.updateTabStatus(false); % Update camera property states. this.ImageCaptureTab.updatePropertyStates(); drawnow(); % Set preview window to image window md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; loc = md.getClientLocation(this.MainImageDisplay.Title); md.setClientLocation(this.ImagePreviewDisplay.Title,this.getGroupName(), loc); % Set focus to imageCaptureTab this.ToolGroup.SelectedTab = getName(this.ImageCaptureTab); end %------------------------------------------------------------------ % Add images from camera to a session. %------------------------------------------------------------------ function addImagesFromCameraToSession(this, files) if this.Session.hasAnyBoards() addImagesToExistingSession(this, files); else [squareSize, units] = this.getSquareSize(); isUserCanceled = isempty(squareSize); if isUserCanceled this.displayInitialDataBrowserMessage; return; end addImagesToNewSession(this, files, squareSize, units); end end %------------------------------------------------------------------ % Calibrate button callback %------------------------------------------------------------------ function ok = calibrate(this) ok = true; % set the cursor to "busy" to indicate that the % calibration process may take some time setWaiting(this.ToolGroup, true) % get camera model options this.Session.CameraModel = this.CalibrationTab.CameraModel; try imagesUsed = calibrate(this.Session); catch calibEx errordlg(calibEx.message, ... getString(message('vision:caltool:CalibrationFailedTitle')), ... 'modal'); setWaiting(this.ToolGroup, false); % reset the cursor resetCalibration(this.Session); this.updateButtonStates(); ok = false; % indicate failure return; end setWaiting(this.ToolGroup, false); % reset the cursor % Create the tiled layout for reprojection and extrinsics this.createTiledSection(); % check if some images might have been rejected if ~all(imagesUsed) % this code path is not very likely % warn the user about image removal warndlg(getString(message('vision:caltool:badBoards', sum(~imagesUsed)))); removeIndex = find(~imagesUsed); list = this.JBoardList; this.Session.BoardSet.removeBoard(removeIndex); %#ok<FNDSB> list.setListData(this.Session.BoardSet.BoardIcons); % select first image on the list after board removal list.setSelectedIndex(1); end % update session state this.updateButtonStates(); % display calibration results this.drawPlots(); % redisplay the board; this time with the undistort button this.drawBoard(); end %------------------------------------------------------------------ % Export button callback %------------------------------------------------------------------ function export(this) if this.IsStereo paramsPrompt = getString(message(... 'vision:caltool:StereoParamsExportPrompt')); else paramsPrompt = getString(message(... 'vision:caltool:CameraParamsExportPrompt')); end exportDlg = vision.internal.calibration.tool.ExportDlg(... this.getGroupName(), paramsPrompt, ... this.Session.ExportVariableName, ... this.Session.ExportErrorsVariableName, ... this.Session.ShouldExportErrors); wait(exportDlg); if ~exportDlg.IsCanceled assignin('base', exportDlg.ParamsVarName, this.Session.CameraParameters); % display the camera parameters at the command prompt evalin('base', exportDlg.ParamsVarName); if exportDlg.ShouldExportErrors assignin('base', exportDlg.ErrorsVarName, ... this.Session.EstimationErrors); evalin('base', exportDlg.ErrorsVarName); end % remember the current variable name this.Session.ExportVariableName = exportDlg.ParamsVarName; this.Session.ExportErrorsVariableName = exportDlg.ErrorsVarName; this.Session.ShouldExportErrors = exportDlg.ShouldExportErrors; end end %------------------------------------------------------------------ % Layout button callback %------------------------------------------------------------------ function layout(this) % Disable App Interaction setWaiting(this.ToolGroup, true); % record the threshold line level if ~isempty(this.ReprojectionErrorsDisplay) [loc,isLine] = getSliderState(this.ReprojectionErrorsDisplay); end this.closeAllFigures(); this.resetDataBrowserLocation(); this.createDefaultLayout(); % if we have data, restore plots if this.Session.hasAnyBoards if this.Session.isCalibrated() % reset the plots to their original state this.Session.ExtrinsicsView = 'CameraCentric'; this.drawPlots(); % restore the threshold line level if ~isempty(this.ReprojectionErrorsDisplay) restoreSliderState(this.ReprojectionErrorsDisplay,loc,isLine); end end this.drawBoard(); end % Zoom buttons are affected if the main image is restored this.updateButtonStates(); drawnow(); % Re-enable App Interaction setWaiting(this.ToolGroup, false); end %------------------------------------------------------------------ % Help button callback %------------------------------------------------------------------ function help(this) mapfile_location = fullfile(docroot,'toolbox',... 'vision','vision.map'); if this.IsStereo doc_tag = 'visionStereoCalibrator'; else doc_tag = 'visionCameraCalibrator'; end helpview(mapfile_location, doc_tag); end %------------------------------------------------------------------ % Codegen button callback %------------------------------------------------------------------ function generateCode(this) codeString = generateCode(this.Session); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Output the generated code to the MATLAB editor %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% editorDoc = matlab.desktop.editor.newDocument(codeString); editorDoc.smartIndentContents; end %------------------------------------------------------------------ % This method is used for testing %------------------------------------------------------------------ function setClosingApprovalNeeded(this, in) this.ToolGroup.setClosingApprovalNeeded(in); end %------------------------------------------------------------------ % This callback updates session state when the camera model UI % elements are changed %------------------------------------------------------------------ function cameraModelChanged(this) if ~isCalibrated(this.Session) || ... ~isequal(this.Session.CameraModel, this.CalibrationTab.CameraModel) this.Session.CanExport = false; this.Session.IsChanged = true; else this.Session.CanExport = true; this.Session.IsChanged = false; end this.updateButtonStates(); end %------------------------------------------------------------------ function doOptimizationOptions(this) dlg = vision.internal.calibration.tool.OptimizationOptionsDlg(... this.getGroupName(), this.Session.OptimizationOptions); wait(dlg); if isempty(this.Session.OptimizationOptions) this.Session.OptimizationOptions.InitialIntrinsics = []; this.Session.OptimizationOptions.InitialDistortion = []; end if ~isequal(dlg.OptimizationOptions, this.Session.OptimizationOptions) this.Session.OptimizationOptions = dlg.OptimizationOptions; if isempty(this.Session.OptimizationOptions.InitialDistortion) this.CalibrationTab.enableNumRadialCoefficients(); else numCoeffs = numel(this.Session.OptimizationOptions.InitialDistortion); this.Session.CameraModel.NumDistortionCoefficients = numCoeffs; this.CalibrationTab.disableNumRadialCoefficients(numCoeffs); end % This drawnow is necessary. Otherwise there is a timing % issue that prevents the session flags from being set % correctly. drawnow(); this.Session.IsChanged = true; this.Session.CanExport = false; end this.updateButtonStates(); end %------------------------------------------------------------------ function deleteToolInstance(this) this.manageToolInstances('delete', this); end %------------------------------------------------------------------ function processOpenSession(this, pathname, filename) this.resetAll(); % Start fresh session = this.SessionManager.loadSession(pathname, filename); if isempty(session) return; end this.Session = session; % Restore the state of the buttons related to the camera model this.restoreCameraModel(); if ~isempty(this.Session.BoardSet) % Proceed only if the BoardsSet was initialized at least % once; even if it doesn't hold any boards this.updateImageStrip(this.Session.BoardSet.BoardIcons); % Restore image strip % create tiles if needed if this.Session.isCalibrated() this.createTiledSection(); this.drawPlots(); % Restore calibration plots if available end this.drawBoard(); % Display first board on the list % Preserve the CanExport flag before setting the options % which triggers a callback that resets the flag keepExportFlag = this.Session.CanExport; % Restore the CanExport flag this.Session.CanExport = keepExportFlag; % Update main UI buttons this.updateButtonStates(); if isempty(this.Session.OptimizationOptions) || ... isempty(this.Session.OptimizationOptions.InitialDistortion) this.CalibrationTab.enableNumRadialCoefficients(); else this.CalibrationTab.disableNumRadialCoefficients(... numel(this.Session.OptimizationOptions.InitialDistortion)); end end this.Session.IsChanged = false; % we just loaded it now end end %public hidden %---------------------------------------------------------------------- % Private methods %---------------------------------------------------------------------- methods (Access=private) %------------------------------------------------------------------ function setDefaultCameraModelOptions(this) numRadialCoeffs = 2; computeSkew = false; computeTangentialDist = false; this.CalibrationTab.setCameraModelOptions(numRadialCoeffs, ... computeSkew, computeTangentialDist); drawnow; % lets the button callback fire before we change % the session state below this.Session.IsChanged = false; end %------------------------------------------------------------------ function restoreCameraModel(this) % Restore the calibration configuration button states computeSkew = this.Session.CameraModel.ComputeSkew; computeTangentialDist = ... this.Session.CameraModel.ComputeTangentialDistortion; numRadialCoeffs = ... this.Session.CameraModel.NumDistortionCoefficients; this.CalibrationTab.setCameraModelOptions(numRadialCoeffs, ... computeSkew, computeTangentialDist); % Give the cameraModelChanged callback enough time to fire drawnow; end %------------------------------------------------------------------ function doZoom(this, src, ~) % (src, evnt) drawnow(); if ~this.MainImageDisplay.isAxesValid() return; end this.MainImageDisplay.makeHandleVisible(); % remove the listeners while we manipulate button % selections this.removeZoomListeners(); drawnow(); switch (src.Name) case 'btnZoomIn' state = this.CalibrationTab.ZoomPanel.ZoomInButtonState; this.MainImageDisplay.setZoomInState(state); this.CalibrationTab.ZoomPanel.resetButtons(); drawnow(); this.CalibrationTab.ZoomPanel.ZoomInButtonState = state; case 'btnZoomOut' state = this.CalibrationTab.ZoomPanel.ZoomOutButtonState; this.MainImageDisplay.setZoomOutState(state); this.CalibrationTab.ZoomPanel.resetButtons(); drawnow(); this.CalibrationTab.ZoomPanel.ZoomOutButtonState = state; case 'btnPan' state = this.CalibrationTab.ZoomPanel.PanButtonState; this.MainImageDisplay.setPanState(state); this.CalibrationTab.ZoomPanel.resetButtons(); drawnow(); this.CalibrationTab.ZoomPanel.PanButtonState = state; end % let the button selections re-draw drawnow(); % add back the listeners this.addZoomListeners(); this.MainImageDisplay.makeHandleInvisible(); end % doZoom %-------------------------------------------------------------- function addZoomListeners(this) this.CalibrationTab.ZoomPanel.addListeners(@this.doZoom); end %-------------------------------------------------------------- function removeZoomListeners(this) if ~isempty(this.Session.BoardSet) && (this.Session.BoardSet.NumBoards > 0) this.CalibrationTab.ZoomPanel.removeListeners(); drawnow(); end end %------------------------------------------------------------------ function displayInitialDataBrowserMessage(this) if this.IsStereo msg = getString(message(... 'vision:caltool:LoadImagesFirstMsgStereo')); else msg = getString(message(... 'vision:caltool:LoadImagesFirstMsg')); end % Use Java list to display the message label = javaObjectEDT('javax.swing.JLabel', ... {msg}); label.setName('InitialDataBrowser'); % Add JList to a panel container layout = java.awt.BorderLayout; panel = javaObjectEDT('javax.swing.JPanel', layout); % Use nice white background just like the rest of the tool panel.setBackground(java.awt.Color.white); % Add the panel to the tool group panel.add(label, java.awt.BorderLayout.NORTH); this.ToolGroup.setDataBrowser(panel); drawnow(); end %------------------------------------------------------------------ function isCanceled = processSessionSaving(this) isCanceled = false; sessionChanged = this.Session.IsChanged; yes = getString(message('MATLAB:uistring:popupdialogs:Yes')); no = getString(message('MATLAB:uistring:popupdialogs:No')); cancel = getString(message('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 %------------------------------------------------------------------ function [files, isUserCanceled] = getImageFiles(this) if this.IsStereo [files, isUserCanceled] = getImageFilesAndSquareSizeStereo(this); else if isempty(this.LastImageDir) || ~exist(this.LastImageDir, 'dir') this.LastImageDir = pwd(); end [files, isUserCanceled] = imgetfile('MultiSelect', true, ... 'InitialPath', this.LastImageDir); if ~isUserCanceled && ~isempty(files) this.LastImageDir = fileparts(files{1}); end end end %------------------------------------------------------------------ function [files, squareSize, units, isUserCanceled] = ... getImageFilesAndSquareSize(this) persistent imageDir; % only used in single camera calibrator if this.IsStereo [files, isUserCanceled, squareSize, units] = ... getImageFilesAndSquareSizeStereo(this); else if isempty(this.LastImageDir) if isempty(imageDir) || ~exist(imageDir, 'dir') this.LastImageDir = pwd(); else this.LastImageDir = imageDir; end end [files, isUserCanceled] = imgetfile('MultiSelect', true, ... 'InitialPath', this.LastImageDir); if isUserCanceled squareSize = []; units = ''; else [squareSize, units] = this.getSquareSize(); isUserCanceled = isempty(squareSize); if ~isempty(files) this.LastImageDir = fileparts(files{1}); imageDir = this.LastImageDir; end end end end %------------------------------------------------------------------ function [files, isUserCanceled, squareSize, units] = ... getImageFilesAndSquareSizeStereo(this) needSquareSize = (nargout > 2); [squareSize, units] = getInitialSquareSize(this); if isempty(this.LastImageDir) this.LastImageDir{1} = pwd(); this.LastImageDir{2} = pwd(); end loadDlg = ... vision.internal.calibration.tool.LoadStereoImagesDlg(... this.getGroupName(), this.LastImageDir{1},... this.LastImageDir{2}, squareSize, units); if ~needSquareSize disableSquareSize(loadDlg); end wait(loadDlg); files = loadDlg.FileNames; this.LastImageDir{1} = loadDlg.Dir1; this.LastImageDir{2} = loadDlg.Dir2; isUserCanceled = isempty(files); if needSquareSize squareSize = loadDlg.SquareSize; units = loadDlg.Units; end end %------------------------------------------------------------------ function closeImageCaptureTab(this) % Remove the tab. tabName = this.ImageCaptureTab.getName(); removeTab(this.ToolGroup, tabName); % Close the preview. closePreview(this.ImageCaptureTab); % @TODO: Update the controls this.ImagePreviewDisplay.makeFigureInvisible(); % Update button status this.CalibrationTab.updateTabStatus(true); this.updateButtonStates; end %------------------------------------------------------------------ function status = isLiveImageCaptureRunning(this) status = false; % If capture is running return true. if ( this.isLiveImageCaptureOpen && this.ImageCaptureTab.CaptureFlag) status = true; end end %------------------------------------------------------------------ function status = isLiveImageCaptureOpen(this) % Do not honor this if live capture is running. status = false; % If capture tab is open return true. if (~this.IsStereo && isImageCaptureTabInGroup(this)) status = true; end end end %private methods methods(Access=public, Hidden) %------------------------------------------------------------------ function imageStats = addImagesToNewSession(this,... files, squareSize, units) import vision.internal.calibration.*; try setWaiting(this.ToolGroup, true); this.Session.BoardSet = ... tool.BoardSet(files, squareSize, units); setWaiting(this.ToolGroup, false); % reset the cursor drawnow(); % Let the user know how many boards were detected is some % were missed imageStats.numProcessed = size(files, 2); imageStats.numAdded = this.Session.BoardSet.NumBoards; imageStats.numDuplicates = 0; showAddImageStatsDlg(this, imageStats); updateAfterAddingImages(this); 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); % reset the cursor errordlg(loadingEx.message, ... getString(message('vision:caltool:LoadingBoardsFailedTitle')), ... 'modal'); % Manage the image strip if this.Session.hasAnyBoards() this.updateImageStrip(this.Session.BoardSet.BoardIcons); else this.displayInitialDataBrowserMessage; end end end end %public hidden methods(Access = private) %------------------------------------------------------------------ function updateAfterAddingImages(this) % Manage the image strip this.updateImageStrip(this.Session.BoardSet.BoardIcons); % Update session state this.Session.CanExport = false; this.Session.IsChanged = true; if ~this.isLiveImageCaptureOpen this.updateButtonStates(); end % Update displays this.updatePlots(); this.drawBoard(); end %------------------------------------------------------------------ function imageStats = addImagesToExistingSession(this, files) try setWaiting(this.ToolGroup, true); % We are adding to an existing board set previousNumBoards = this.Session.BoardSet.NumBoards; imageStats.numDuplicates = this.Session.BoardSet.addBoards(files); imageStats.numAdded = this.Session.BoardSet.NumBoards - ... previousNumBoards; imageStats.numProcessed = size(files, 2); setWaiting(this.ToolGroup, false); % reset the cursor showAddImageStatsDlg(this, imageStats); updateAfterAddingImages(this); 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); % reset the cursor errordlg(loadingEx.message, ... getString(message('vision:caltool:LoadingBoardsFailedTitle')), ... 'modal'); % Manage the image strip if this.Session.hasAnyBoards() this.updateImageStrip(this.Session.BoardSet.BoardIcons); else this.displayInitialDataBrowserMessage; end end end %------------------------------------------------------------------ function showAddImageStatsDlg(this, imageStats) if imageStats.numAdded == imageStats.numProcessed % nothing to display return; end rejectedFileNames = this.Session.BoardSet.LastNonDetectedPathNames; if this.IsStereo statsDlg = ... vision.internal.calibration.tool.AddImageStatsStereoDlg(... this.getGroupName(), imageStats, rejectedFileNames); else statsDlg = vision.internal.calibration.tool.AddImageStatsDlg(... this.getGroupName(), imageStats, rejectedFileNames); end wait(statsDlg); end %------------------------------------------------------------------ function drawBoard(this) % What if the figure has been closed? if ~ishandle(this.MainImageDisplay.Fig) return; end boardIdx = this.getSelectedBoardIndex(); if boardIdx > 0 board = this.Session.BoardSet.getBoard(boardIdx); this.MainImageDisplay.drawBoard(board, boardIdx, ... this.Session.CameraParameters); end end %------------------------------------------------------------------ % Puts the image strip in focus %------------------------------------------------------------------ function setFocusOnBoards(this) drawnow; this.JBoardList.requestFocus; end %------------------------------------------------------------------ % Returns true if recalibration is a valid option %------------------------------------------------------------------ function ret = canRecalibrate(this) idxMultiselect = this.getSelectedBoardIndices(); ret = (this.Session.BoardSet.NumBoards - ... length(idxMultiselect)) >= this.MinBoards; ret = this.Session.isCalibrated() && ret; end end %---------------------------------------------------------------------- % Smaller Toolstrip Button Callbacks %---------------------------------------------------------------------- methods(Access=public) %------------------------------------------------------------------ % Set up management of the image strip %------------------------------------------------------------------ function updateImageStrip(this, icons, varargin) % Populate the list with board thumbnails and file names this.JBoardList = javaObjectEDT('javax.swing.JList'); this.JBoardList.setName('ImageStrip'); % Manipulate the cell renderer to display both icons and text isIdDisplayed = true; isTextRightOfIcon = ~this.IsStereo; cellRenderer = ... com.mathworks.toolbox.vision.ImageStripCellRenderer(... isIdDisplayed, isTextRightOfIcon); this.JBoardList.setCellRenderer(cellRenderer); this.JBoardList.setListData(icons); % Add the list to a panel container layout = javaObjectEDT('java.awt.BorderLayout'); dataPanel = javaObjectEDT('javax.swing.JPanel', layout); dataScrollPane = javaObjectEDT('javax.swing.JScrollPane', ... this.JBoardList); dataScrollPane.setWheelScrollingEnabled(true); % Add the panel to the tool group dataPanel.add(dataScrollPane, java.awt.BorderLayout.CENTER); this.ToolGroup.setDataBrowser(dataPanel); selectedIndex = 0; if (nargin==3) selectedIndex = varargin{1}; end this.JBoardList.setSelectedIndex(selectedIndex); % select first image this.JBoardList.ensureIndexIsVisible(selectedIndex); % select first image % Add a listener for handling file selections this.addSelectionListener(); popupListener = addlistener(this.JBoardList, 'MousePressed', ... @doPopup); keyListener = addlistener(this.JBoardList, 'KeyPressed', ... @(evt,data)this.doDeleteKey(evt,data)); % Store handles to prevent going out of scope this.Misc.PopupListener = popupListener; this.Misc.KeyListener = keyListener; % Other listeners to consider: ComponentKey, Key, Mouse % Store handles to prevent going out of scope this.Misc.DataPanel = dataPanel; %-------------------------------------------------------------- function doPopup(~, hData) if hData.getButton == 3 % right-click % Do not honor this if live capture is running. if isLiveImageCaptureRunning(this) return; end % 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.getSelectedBoardIndices(); 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 list.setSelectedIndex(jIdx); idxMultiselect = idx; end % Create a popup if this.canRecalibrate() item = getString(message('vision:caltool:RemoveAndRecalibrate')); itemName = 'removeAndRecalibrateItem'; else item = getString(message('vision:uitools:Remove')); itemName = 'removeItem'; end menuItemRemove = javaObjectEDT('javax.swing.JMenuItem',... item); menuItemRemove.setName(itemName); actionListener = addlistener(menuItemRemove,'Action',... @removeAndRecalibrate); % main popup callback % Prevent it from going out of scope this.Misc.PopupActionListener = actionListener; jmenu = javaObjectEDT('javax.swing.JPopupMenu'); jmenu.add(menuItemRemove); % Display the popup jmenu.show(list, point.x, point.y); jmenu.repaint; end %---------------------------------------------------------- % Note: the recalibration is done only if there was a prior % valid calibration done. If the boards were only loaded % without hitting "calibrate" button, the re-calibration is % not invoked. %---------------------------------------------------------- function removeAndRecalibrate(~,~) this.processRemoveAndRecalibrate(idxMultiselect) end %removeAndRecalibrate end % doPopup end % updateImageStrip %-------------------------------------------------------------- function doDeleteKey(this,evt,hData) if ishghandle(evt) isDelete = strcmpi(hData.Key,'delete'); else isDelete = hData.getExtendedKeyCode == 127;% delete code end if isDelete if isLiveImageCaptureRunning(this) return; end % Return if doSelection() is in progress. Otherwise % the session may become inconsistent, causing an error. if this.StillDrawing return; end % If we are in a session with a valid calibration data % ask the user if they want to recalibrate and give them % an option to bail out; otherwise, don't bother and % simply delete the boards if this.canRecalibrate() question = getString(message('vision:caltool:ConfirmRecalibration')); title = getString(message('vision:caltool:RecalibrateTitle')); yes = getString(message('MATLAB:uistring:popupdialogs:Yes')); cancel = getString(message('MATLAB:uistring:popupdialogs:Cancel')); buttonName = questdlg(question, title, yes, cancel, yes); if ~strcmp(buttonName, yes) return; end end % CTRL-DEL will also end up here idxMultiselect = this.getSelectedBoardIndices(); this.processRemoveAndRecalibrate(idxMultiselect); end end % File selection handler %---------------------------------------- function doSelection(this, ~, hData) % ~ was hSrc % Do not honor this if live capture is running. if isLiveImageCaptureRunning(this) return; end if this.Session.BoardSet.NumBoards == 0 return end if hData.getSource.isSelectionEmpty == 1 return end if this.getSelectedBoardIndex() < 1 return; end if ~hData.getValueIsAdjusting % Poor man's lock. Set a flag indicating that doSelection() % is in progress to prevent doDeleteKey() from modifying % the session. this.StillDrawing = true; if ~isempty(this.ReprojectionErrorsDisplay) resetSlider(this.ReprojectionErrorsDisplay); end this.updatePlots(); this.drawBoard(); this.setFocusOnBoards(); this.StillDrawing = false; end end %------------------------------------------------------------------ function makeSelectionVisible(this, index) javaMethodEDT('ensureIndexIsVisible', this.JBoardList, index-1); end %------------------------------------------------------------------ function processRemoveAndRecalibrate(this, idxMultiselect) list = this.JBoardList; this.Session.BoardSet.removeBoard(idxMultiselect); this.Session.IsChanged = true; this.updateButtonStates(); jLowestIdx = idxMultiselect(1)-1; if this.Session.BoardSet.NumBoards ~= 0 list.setListData(this.Session.BoardSet.BoardIcons); if jLowestIdx ~= 0 newIdx = jLowestIdx -1; else newIdx = 0; end list.setSelectedIndex(newIdx); % one before else this.resetAll; end % Update the UI before proceeding further drawnow; % Recalibrate if this.Session.isCalibrated() if this.Session.HasEnoughBoards isOK = this.calibrate(); if ~isOK this.resetCalibrationResults(); end else this.resetCalibrationResults(); end end end %------------------------------------------------------------------ % Updates the current maximum reprojectionError in slider %------------------------------------------------------------------ function val = getMaximumReprojectionError(this) if ~isempty(this.Session.CameraParameters) if ~this.IsStereo val = max(mean(hypot(this.Session.CameraParameters.ReprojectionErrors(:,1,:),... this.Session.CameraParameters.ReprojectionErrors(:,2,:)))); else val1 = max(mean(hypot(this.Session.CameraParameters.CameraParameters1.ReprojectionErrors(:,1,:),... this.Session.CameraParameters.CameraParameters1.ReprojectionErrors(:,2,:)))); val2 = max(mean(hypot(this.Session.CameraParameters.CameraParameters2.ReprojectionErrors(:,1,:),... this.Session.CameraParameters.CameraParameters2.ReprojectionErrors(:,2,:)))); val = max(val1,val2); end end end %------------------------------------------------------------------ % returns index of the selected board %------------------------------------------------------------------ function idx = getSelectedBoardIndex(this) idx = double(this.JBoardList.getSelectedIndex); idx = idx+1; % make it one based end %------------------------------------------------------------------ function [idx, jIdx] = getSelectedBoardIndices(this) idx = double(this.JBoardList.getSelectedIndices); jIdx = idx; % 0-based java index idx = idx+1; % make it one based end %------------------------------------------------------------------ % Gets the UI to the starting point, as if nothing has been loaded %------------------------------------------------------------------ function resetAll(this) % reset the session this.Session.reset(); % reset the message in the data browser this.displayInitialDataBrowserMessage(); % Not calibrated any more. Discard the tiled section if ~isempty(this.ReprojectionErrorsDisplay) this.ReprojectionErrorsDisplay.close(); end if ~isempty(this.ExtrinsicsDisplay) this.ExtrinsicsDisplay.close(); end % wipe the visible figures this.MainImageDisplay.wipeFigure(); % Reset the image capture tab. if ~isempty(this.ImageCaptureTab) this.ImageCaptureTab.resetAll(); end % Reset the layout to one image panel. grpname = this.getGroupName(); md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; drawnow md.setDocumentArrangement(grpname, md.TILED, ... java.awt.Dimension(1,1)); % update buttons this.updateButtonStates(); end %------------------------------------------------------------------ % Unlike resetAll(), this method resets all but the image data. % It will wipe the calibration results. %------------------------------------------------------------------ function resetCalibrationResults(this) % wipe the calibration portion of the Session this.Session.resetCalibration(); % Not calibrated any more. Discard the tiled section if ~isempty(this.ReprojectionErrorsDisplay) this.ReprojectionErrorsDisplay.close(); end if ~isempty(this.ExtrinsicsDisplay) this.ExtrinsicsDisplay.close(); end % Reset the layout to one image panel. grpname = this.getGroupName(); md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; md.setDocumentArrangement(grpname, md.TILED, ... java.awt.Dimension(1,1)); this.updateButtonStates(); % redraw the board since the reprojection data is no longer % available this.drawBoard(); end %------------------------------------------------------------------ % Calibration requires at least two boards. This routine grays out % the calibration button if there are fewer than MinBoards boards. %------------------------------------------------------------------ function updateButtonStates(this) % Calibration tab if ~isempty(this.Session.BoardSet) if (this.Session.BoardSet.NumBoards < this.MinBoards) % gray out the calibration button this.Session.HasEnoughBoards = false; else this.Session.HasEnoughBoards = true; end end this.CalibrationTab.updateButtonStates(this.Session); if ~isempty(this.Session.BoardSet) && (this.Session.BoardSet.NumBoards > 0) this.enableZoomButtons(true); else this.enableZoomButtons(false); end end %------------------------------------------------------------------ function enableZoomButtons(this, enable) if enable if ~this.CalibrationTab.ZoomPanel.IsEnabled this.CalibrationTab.ZoomPanel.enableButtons(); this.addZoomListeners(); end else this.removeZoomListeners(); this.CalibrationTab.ZoomPanel.resetButtons(); this.CalibrationTab.ZoomPanel.disableButtons(); end end %------------------------------------------------------------------ % Implements the dialog which asks for checkerboard square size %------------------------------------------------------------------ function [squareSize, units] = getSquareSize(this) [initSquareSize, initUnits] = getInitialSquareSize(this); squareSizeDlg = vision.internal.calibration.tool.SquareSizeDlg(... this.getGroupName(), initSquareSize, initUnits); wait(squareSizeDlg); if ~squareSizeDlg.IsCanceled squareSize = squareSizeDlg.SquareSize; units = squareSizeDlg.Units; else squareSize = []; % return empty to indicate what happened units = ''; end end %-------------------------------------------------------------- function [initSquareSize, initUnits] = getInitialSquareSize(this) if isempty(this.Session.BoardSet) initSquareSize = 25; initUnits = 'mm'; else initSquareSize = this.Session.BoardSet.SquareSize; initUnits = this.Session.BoardSet.Units; end end %-------------------------------------------------------------- function doOKKeyPress(~, ~, evd) switch(evd.Key) case {'return','space','escape'} uiresume(gcbf); end end %------------------------------------------------------------------ function updatePlots(this) % Unlick drawPlots, updatePlots only update necessary part of plots % without redrawing it for performance improvement. if this.Session.isCalibrated() % has calibration results updateSelection(this.ReprojectionErrorsDisplay,this.getHighlightIndex()); updateSelection(this.ExtrinsicsDisplay,this.getHighlightIndex()); drawnow; else if ~isempty(this.ReprojectionErrorsDisplay) this.ReprojectionErrorsDisplay.lockFigure(); end if ~isempty(this.ExtrinsicsDisplay) this.ExtrinsicsDisplay.lockFigure(); end end end %------------------------------------------------------------------ function drawPlots(this) if this.Session.isCalibrated() % has calibration results this.plotExtrinsics; this.plotErrors; else this.ReprojectionErrorsDisplay.lockFigure(); this.ExtrinsicsDisplay.lockFigure(); end end %------------------------------------------------------------------ function highlightIndex = getHighlightIndex(this) if this.Session.isCalibrated() % This can happen when adding new images to the bottom % of the image stack. In that case, we do not have % anything to highlight for the brand new boards boardIndex = this.getSelectedBoardIndices(); highlightIndex = ... boardIndex(boardIndex <= ... this.Session.CameraParameters.NumPatterns); else highlightIndex = []; end end %------------------------------------------------------------------ function plotExtrinsics(this, varargin) displayFigure = this.ExtrinsicsDisplay; % What if the figure has been closed? if ~ishandle(displayFigure.Fig) return; end if ~isAxesValid(displayFigure) displayFigure.createAxes(... @()onSwitchView(this, displayFigure, 'CameraCentric'), ... @()onSwitchView(this, displayFigure, 'PatternCentric')); end plotGraph(this, displayFigure); drawnow(); end %------------------------------------------------------------------ function plotErrors(this, varargin) displayFigure = this.ReprojectionErrorsDisplay; % What if the figure has been closed? if ~ishandle(displayFigure.Fig) return; end if ~isAxesValid(displayFigure) displayFigure.createAxes(); end plotGraph(this, displayFigure); set(displayFigure.Fig, 'KeyPressFcn',@(evt,data)this.doDeleteKey(evt,data)); drawnow(); end %-------------------------------------------------------------- function plotGraph(this, displayFigure) plot(displayFigure, ... this.Session.CameraParameters, this.getHighlightIndex(), ... @(h, ~)onClickPlot(this, displayFigure, h), ... @(h, ~)onClickPlotSelected(this, displayFigure, h)); end %-------------------------------------------------------------- function onSwitchView(this, displayFigure, newView) displayFigure.switchView(newView); plotGraph(this, displayFigure); end %------------------------------------------------------------------ function onClickPlot(this, displayFigure, h) [clickedIdx, selectionType] = getSelection(displayFigure, h); processClick(this, selectionType, clickedIdx); end %------------------------------------------------------------------ function onClickPlotSelected(this, displayFigure, h) [clickedIdx, selectionType] = getSelection(displayFigure, h); processSelectedClick(this, selectionType, clickedIdx); end %------------------------------------------------------------------ function processClick(this,selectionType,clickedIdx) % Reset the threshold line location % do this first because the cost is cheap resetSlider(this.ReprojectionErrorsDisplay); % Remove selection listener, because we are going to % trigger a selection in the image browser programmatically this.removeSelectionListener(); switch(selectionType) case 'alt' % control-click or right-click prevIdx = this.getSelectedBoardIndices(); this.JBoardList.setSelectedIndices([prevIdx; clickedIdx]-1); case 'normal' % plain click this.JBoardList.setSelectedIndex(clickedIdx - 1); this.makeSelectionVisible(clickedIdx); case 'extend' % shift-click prevIdx = this.getSelectedBoardIndices(); dists = abs(prevIdx - clickedIdx); [~, prevIdxIdx] = min(dists); prevIdx = prevIdx(prevIdxIdx); this.JBoardList.setSelectedIndices((min(prevIdx,clickedIdx):max(prevIdx,clickedIdx))-1); end this.updatePlots(); this.drawBoard(); this.setFocusOnBoards(); % Add the selection listener back this.addSelectionListener(); drawnow; end %------------------------------------------------------------------ function processSelectedClick(this,selectionType,clickedIdx) switch(selectionType) case 'alt' % control-click or right-click on a selected bar should % deselect it selectedIdx = this.getSelectedBoardIndices(); if numel(selectedIdx) > 1 selectedIdx(selectedIdx == clickedIdx) = []; this.JBoardList.setSelectedIndices(selectedIdx - 1); end case 'normal' this.JBoardList.setSelectedIndex(clickedIdx - 1); this.makeSelectionVisible(clickedIdx); case 'extend' prevIdx = this.getSelectedBoardIndices(); dists = abs(prevIdx - clickedIdx); dists(dists == 0) = inf; [~, prevIdxIdx] = min(dists); prevIdx = prevIdx(prevIdxIdx); this.JBoardList.setSelectedIndices((min(prevIdx,clickedIdx):max(prevIdx,clickedIdx))-1); end end %------------------------------------------------------------------ % Remove selection listener from the image browser % Needed for enabling click-ability on the reprojection errors bar % graph. function removeSelectionListener(this) if ishandle(this.Misc.SelectionListener) delete(this.Misc.SelectionListener); end end %------------------------------------------------------------------ % Add selection listener to the image browser handle the update of % the image display and the graphs. function addSelectionListener(this) this.Misc.SelectionListener = addlistener(this.JBoardList, ... 'ValueChanged', @this.doSelection); end %------------------------------------------------------------------ function outSession = getSession(this) outSession = this.Session; 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 %------------------------------------------------------------------ function addToolInstance(this) this.manageToolInstances('add', this); 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 = getString(message('MATLAB:uistring:popupdialogs:Yes')); no = getString(message('MATLAB:uistring:popupdialogs:No')); cancel = getString(message('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) % clean up the preview figure if ~isempty(this.ImagePreviewDisplay) % If image capture tab is not in the toolgroup, add it and bring % focus to it. if isImageCaptureTabInGroup(this) this.ImageCaptureTab.closePreview(); end this.ImagePreviewDisplay.close(); end % clean up the figures this.MainImageDisplay.close(); if ~isempty(this.ReprojectionErrorsDisplay) this.ReprojectionErrorsDisplay.close(); end if ~isempty(this.ExtrinsicsDisplay) this.ExtrinsicsDisplay.close(); end end %------------------------------------------------------------------ function createDefaultLayout(this) % create all the required figures if this.IsStereo this.MainImageDisplay = ... vision.internal.calibration.tool.StereoCalibrationImageDisplay(this.getGroupName()); else this.MainImageDisplay = ... vision.internal.calibration.tool.SingleCalibrationImageDisplay; this.ImagePreviewDisplay = ... vision.internal.calibration.tool.ImagePreview; this.addFigure(this.ImagePreviewDisplay.Fig); % Prevent user from deleting preview image; drawnow; md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; md.getClient(getString(message('vision:uitools:MainPreviewFigure')),... this.ToolGroup.Name).putClientProperty(... com.mathworks.widgets.desk.DTClientProperty.PERMIT_USER_CLOSE,... java.lang.Boolean.FALSE); end this.addFigure(this.MainImageDisplay.Fig); % Prevent user from deleting Main image; drawnow; md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; md.getClient(getString(message('vision:uitools:MainImageFigure')),... this.ToolGroup.Name).putClientProperty(... com.mathworks.widgets.desk.DTClientProperty.PERMIT_USER_CLOSE,... java.lang.Boolean.FALSE); % Create 1 tile for Image alone. grpname = this.getGroupName(); md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; md.setDocumentArrangement(grpname, md.TILED, ... java.awt.Dimension(1,1)); % Turn on the visibility this.MainImageDisplay.makeFigureVisible(); % If image capture tab is in the toolgroup, bring % focus to it. if isImageCaptureTabInGroup(this) loc = md.getClientLocation(this.MainImageDisplay.Title); this.ImagePreviewDisplay.makeFigureVisible(); drawnow; md.setClientLocation(this.ImagePreviewDisplay.Title,this.getGroupName(), loc); end if this.Session.isCalibrated() createTiledSection(this); end end % createDefaultLayout %------------------------------------------------------------------ function tf = isImageCaptureTabInGroup(this) if isempty(this.ImageCaptureTab) tf = false; else tabname = getName(this.ImageCaptureTab); existingTabs = this.ToolGroup.TabNames; tf = any(strcmp(existingTabs, tabname)); end end %------------------------------------------------------------------ function createTiledSection(this) % Create the tiled section for reprojection and extrinsics plot if isempty(this.ReprojectionErrorsDisplay) || ~this.ReprojectionErrorsDisplay.isAxesValid this.ReprojectionErrorsDisplay = ... vision.internal.calibration.tool.ReprojectionErrorsDisplay(); this.addFigure(this.ReprojectionErrorsDisplay.Fig); addlistener(this.ReprojectionErrorsDisplay,'ErrorPlotChanged',@(~,~)this.updateSelection); end if isempty(this.ExtrinsicsDisplay) || ~this.ExtrinsicsDisplay.isAxesValid this.ExtrinsicsDisplay = ... vision.internal.calibration.tool.ExtrinsicsDisplay; this.addFigure(this.ExtrinsicsDisplay.Fig); end grpname = this.getGroupName(); md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; % Going first to one windows helps with default layout % restoration % Set desired layout; Main figure fills the entire first column md.setDocumentArrangement(grpname, md.TILED, ... java.awt.Dimension(2,2)); % sets the grid if this.IsStereo md.setDocumentColumnSpan(grpname, 0, 0, 2); % joins grid elements % Make the first row wider md.setDocumentRowHeights(grpname, [0.6, 0.4]); % values must add to 1 else md.setDocumentRowSpan(grpname, 0, 0, 2); % joins grid elements % Make the first column wider md.setDocumentColumnWidths(grpname, [0.7, 0.3]); % values must add to 1 end this.MainImageDisplay.makeFigureVisible(); this.ReprojectionErrorsDisplay.makeFigureVisible(); this.ExtrinsicsDisplay.makeFigureVisible(); drawnow; % set each figure to its own tile md.setClientLocation(this.MainImageDisplay.Title,grpname,... com.mathworks.widgets.desk.DTLocation.create(0)); md.setClientLocation(this.ReprojectionErrorsDisplay.Title,grpname,... com.mathworks.widgets.desk.DTLocation.create(1)); md.setClientLocation(this.ExtrinsicsDisplay.Title,grpname,... com.mathworks.widgets.desk.DTLocation.create(2)); end %------------------------------------------------------------------ function updateSelection(this) % Update selected images based on reprojection error display this.removeSelectionListener(); indx = this.ReprojectionErrorsDisplay.getSelected(); this.JBoardList.setSelectedIndices(indx-1); this.makeSelectionVisible(min(indx)); this.addSelectionListener(); this.updatePlots() this.drawBoard(); end end %Smaller Toolstrip Button Callbacks %---------------------------------------------------------------------- % Static public methods %---------------------------------------------------------------------- methods (Static) %------------------------------------------------------------------ function deleteAllTools vision.internal.calibration.tool.CameraCalibrationTool.manageToolInstances('deleteAll'); end %------------------------------------------------------------------ function deleteAllToolsForce vision.internal.calibration.tool.CameraCalibrationTool.manageToolInstances('deleteAllForce'); end end %---------------------------------------------------------------------- % Static private methods %---------------------------------------------------------------------- methods (Access='private', Static) %------------------------------------------------------------------ % Manages a persistent variable for the purpose of tracking the % tool instances. %------------------------------------------------------------------ function manageToolInstances(action, varargin) mlock(); persistent toolArray; switch action case 'add' if isempty(toolArray) % first time toolArray = varargin{1}; else % add to existing array toolArray(end+1) = varargin{1}; end case 'delete' for i=1:length(toolArray) this = varargin{1}; if strcmp(this.getGroupName(), toolArray(i).getGroupName()) toolArray(i) = []; delete(this); % self-destruct break; end end case 'deleteAll' % wipe backwards since toolArray will be shrinking for i = length(toolArray):-1:1 delete(toolArray(i)); end toolArray = []; case 'deleteAllForce' % wipe backwards since toolArray will be shrinking for i = length(toolArray):-1:1 setClosingApprovalNeeded(toolArray(i), false); delete(toolArray(i)); end toolArray = []; end % if all tools are closed, permit clearing of the class; this % is helpful during development of the tool if isempty(toolArray) munlock(); end end end end