gusucode.com > vision工具箱matlab源码程序 > vision/+vision/+internal/+cascadeTrainer/+tool/ImageWithROIsDisplay.m
% ImageWithROIsDisplay encapsulates an axes containing an image with % overlayed ROIs % Copyright 2016 The MathWorks, Inc. classdef ImageWithROIsDisplay < handle properties ImageAxes = []; Parent = []; ImageMode = 'ROImode'; CurrentROIs = {}; end properties(Dependent) IsValid; end properties(Access=private) AxesTag = 'TrainingImageAxes'; InsideNudge = false; CopiedROIs = []; end events ImageClick ParentMouseClick ParentMouseRelease ImageModeChanged AddFullImageROI NextImage PreviousImage ROIsChanged RotateClockwise RotateCounterClockwise end methods %------------------------------------------------------------------ function this = ImageWithROIsDisplay(parent) this.Parent = parent; set(this.Parent, 'Visible', 'on'); iptaddcallback(this.Parent,'KeyPressFcn',@this.doFigKeyPress); end %------------------------------------------------------------------ function tf = get.IsValid(this) tf = isvalid(this) && ~isempty(this.Parent) && isvalid(this.Parent) && ishandle(this.Parent); end %------------------------------------------------------------------ function delete(this) if ishandle(this.Parent) set(this.Parent,'HandleVisibility','on'); close(this.Parent); end end %------------------------------------------------------------------ function setCategoriesVisible(this, tf) for i = 1:numel(this.CurrentROIs) this.CurrentROIs{i}.setTextLabelVisible(tf); end end %------------------------------------------------------------------ function drawImage(this, imageMatrix, imageLabel) set(this.Parent, 'HandleVisibility', 'on'); if isempty(this.ImageAxes) || ~ishandle(this.ImageAxes) % add an axes if needed hAxes = axes('Parent', this.Parent,... 'Tag', this.AxesTag); this.ImageAxes = hAxes; else hAxes = this.ImageAxes; end if isa(imageMatrix, 'single') imageMatrix = ... vision.internal.cascadeTrainer.tool.adjustAndConvertSingleImage(imageMatrix); end hImage = imshow(imageMatrix,'InitialMagnification', 'fit',... 'Parent', hAxes, 'Border', 'tight'); set(hImage, 'buttondownfcn', @(~,~) notify(this, 'ImageClick')); % Install context menu again. Have to do it twice because the % pointer behavior code prevents the context menus from % showing up immediately after images are loaded. this.installContextMenu('ROI'); % add title title(hAxes, imageLabel, 'Interpreter', 'none'); % set pointer behavior to cross when hovering over the image this.setPointerToCross(); % Disable overwriting to put on the ROIs set(hAxes, 'NextPlot', 'add'); % resets all axes properties to default values set(hAxes, 'NextPlot', 'replace'); set(hAxes, 'Tag', this.AxesTag); % add tag after reset set(this.Parent, 'HandleVisibility', 'callback'); end %------------------------------------------------------------------ % selection can be 'ROI', 'ZoomIn', 'ZoomOut', or 'Pan' %------------------------------------------------------------------ function installContextMenu(this, selection) choices = {'off','on'}; hFig = this.Parent; hCmenu = uicontextmenu('Parent', hFig); % Paste pasteUIMenu = uimenu(hCmenu, 'Label', ... getString(message('vision:trainingtool:PastePopup')),... 'Callback', @this.pasteSelectedROIs, 'Accelerator', 'V'); if isempty(this.CopiedROIs) set(pasteUIMenu,'Enable','off'); end % Add full image ROI uimenu(hCmenu, 'Label', ... getString(message('vision:trainingtool:FullROIPopup')),... 'Callback', @(~, ~)notify(this, 'AddFullImageROI')); % ROI mode uimenu(hCmenu, 'Label', ... getString(message('vision:trainingtool:ROImodePopup')), ... 'Checked', choices{strcmp(selection,'ROI')+1}, ... 'Callback', @(~, ~)setImageMode(this, 'ROImode')); % Zoom in uimenu(hCmenu, 'Label', ... getString(message('vision:trainingtool:ZoomInPopup')),... 'Checked', choices{strcmp(selection,'ZoomIn')+1}, ... 'Callback', @(~, ~)setImageMode(this, 'ZoomInMode')); % Zoom out uimenu(hCmenu, 'Label', ... getString(message('vision:trainingtool:ZoomOutPopup')),... 'Checked', choices{strcmp(selection,'ZoomOut')+1},.... 'Callback', @(~, ~)setImageMode(this, 'ZoomOutMode')); % Pan uimenu(hCmenu, 'Label', ... getString(message('vision:trainingtool:PanPopup')),... 'Checked', choices{strcmp(selection,'Pan')+1}, ... 'Callback', @(~,~)setImageMode(this, 'PanMode')); % Rotate rotateMenu = uimenu(hCmenu, 'Label', ... getString(message('vision:trainingtool:RotateImage'))); uimenu(rotateMenu, 'Label', ... getString(message('vision:trainingtool:RotateClockwise')), ... 'Callback', @(~,~)notify(this, 'RotateClockwise'), ... 'Accelerator', 'R'); uimenu(rotateMenu, 'Label', ... getString(message('vision:trainingtool:RotateCounterClockwise')), ... 'Callback', @(~,~)notify(this, 'RotateCounterClockwise')); hImage = findobj(this.ImageAxes, 'Type', 'image'); set(hImage, 'UIContextMenu', hCmenu); end %------------------------------------------------------------------ % Pressing a key in the main figure, will send you through this % code %------------------------------------------------------------------ function doFigKeyPress(this,~,src) if isempty(this.ImageAxes) || ~ishandle(this.ImageAxes) % Nothing to do if none of the images are loaded. return; end modifierKeys = {'control', 'command'}; if any(strcmp(src.Modifier, modifierKeys{ismac()+1})) switch src.Key case 'a' this.selectAllROIs(); case 'c' this.copySelectedROIs(); case 'v' if ~isempty(this.CopiedROIs) this.pasteSelectedROIs; end case 'x' this.cutSelectedROIs(); case 'r' if any(strcmp(src.Modifier, 'shift')) notify(this, 'RotateCounterClockwise'); else notify(this, 'RotateClockwise'); end otherwise this.directionKeyPress(src.Key, src.Modifier); end else switch src.Key case 'escape' this.setImageMode('ROImode'); case 'delete' this.deleteSelectedROIs(); case 'pagedown' notify(this, 'NextImage'); case 'pageup' notify(this, 'PreviousImage'); otherwise this.directionKeyPress(src.Key, src.Modifier); end end end %------------------------------------------------------------------ function directionKeyPress(this, keyPressed, modifierPressed) directionKeys = {'uparrow', 'downarrow', 'rightarrow',... 'leftarrow'}; keyIndex = find(strcmp(keyPressed, directionKeys), 1); if ~isempty(keyIndex) hAxes = this.ImageAxes; currentROIs = findall(hAxes, 'tag', 'imrect',... 'Selected','on'); if isempty(currentROIs) if keyIndex==1 notify(this, 'PreviousImage'); elseif keyIndex==2 notify(this, 'NextImage'); end else nudgeROI(); end end %-------------------------------------------------------------- function nudgeROI() hImage = findobj(hAxes, 'Type', 'image'); [y_extent, x_extent, ~] = size(get(hImage,'CData')); constraint_fcn = makeConstrainToRectFcn('imrect',... [1 x_extent+1], [1 y_extent+1]); roiObjects = arrayfun(@iptgetapi, currentROIs); roiPositions = cell2mat(arrayfun(@(x) x.getPosition(),... roiObjects,'UniformOutput',false)); % Get number of pixels to nudge (based on whether CTRL is % pressed), and the index of the ROI that is closest to the % image boundary [nudgePixels, index] = nudgeDirection(); % If the ROI closest to the image boundary is within bounds % after nudging, then nudge all ROIs newPos = roiPositions(index,:) + nudgePixels + [0.5 0.5 0 0]; this.InsideNudge = 1; if isequal(newPos, constraint_fcn(newPos)) newRoiPositions = cell2mat(arrayfun(@(x)... roiPositions(x,:) + nudgePixels,... (1:numel(currentROIs))','UniformOutput',false)); arrayfun(@(x,y) x.setPosition(newRoiPositions(y,:)),... roiObjects,(1:numel(currentROIs))'); end this.InsideNudge = 0; notify(this, 'ROIsChanged'); drawnow(); %---------------------------------------------------------- function [nudgePixels, index] = nudgeDirection() switch keyIndex case 1 nudgePixels = [0 -5 0 0]; [~,index] = min(roiPositions(:,2)); case 2 nudgePixels = [0 5 0 0]; [~,index] = max(roiPositions(:,2)+... roiPositions(:,4)); case 3 nudgePixels = [5 0 0 0]; [~,index] = max(roiPositions(:,1)+... roiPositions(:,3)); case 4 nudgePixels = [-5 0 0 0]; [~,index] = min(roiPositions(:,1)); end if ~isempty(modifierPressed) if any(strcmp(modifierPressed, {'control',... 'command'})) nudgePixels = nudgePixels./5; end end end % end of nudgeDirection() end % end of nudgeROI() end %------------------------------------------------------------------ % Select all ROIs function selectAllROIs(this, varargin) for i = 1:numel(this.CurrentROIs) this.CurrentROIs{i}.IsSelected = true; end end %------------------------------------------------------------------ function setImageMode(this, mode) this.ImageMode = mode; if isequal(this.ImageMode, 'ROImode') this.setPointerToCross(); end notify(this, 'ImageModeChanged'); end %------------------------------------------------------------------ function drawBoundingBoxes(this, bboxs, catIDs, colors, catNames, showROILabels) % Restore selection of ROIs across multiple images for i = 1:numel(this.CurrentROIs) delete(this.CurrentROIs{i}); end this.CurrentROIs = {}; for i = 1:size(bboxs, 1) catID = catIDs(i); drawROI(this, bboxs(i,:), catID, colors{i}, catNames{i}, ... showROILabels); end end %------------------------------------------------------------------ function deselectAllROIs(this) for i = 1:numel(this.CurrentROIs) this.CurrentROIs{i}.IsSelected = false; end end %------------------------------------------------------------------ function drawROI(this, bbox, catID, color, catName, showROILabel, ... isSelected) if nargin < 7 isSelected = false; end hImage = findobj(this.ImageAxes, 'Type', 'image'); this.CurrentROIs{end+1} = ... vision.internal.cascadeTrainer.tool.EnhancedROI(... bbox, this.ImageAxes,... hImage, catID, color, catName, showROILabel); addlistener(this.CurrentROIs{end}, 'Delete', ... @(~,~)notify(this, 'ROIsChanged')); addlistener(this.CurrentROIs{end}, 'Move', ... @this.moveIfNotNudging); addlistener(this.CurrentROIs{end}, 'Copy', ... @this.copySelectedROIs); addlistener(this.CurrentROIs{end}, 'Cut', ... @this.cutSelectedROIs); this.CurrentROIs{end}.IsSelected = isSelected; end %-------------------------------------------------------------- function moveIfNotNudging(this, varargin) if ~this.InsideNudge notify(this, 'ROIsChanged'); end end %-------------------------------------------------------------- % Copy selected ROIs function copySelectedROIs(this, varargin) this.CopiedROIs = {}; for i = numel(this.CurrentROIs):-1:1 if this.CurrentROIs{i}.IsValid && this.CurrentROIs{i}.IsSelected this.CopiedROIs{end+1} = this.CurrentROIs{i}.CopiedData; end end if ~isempty(this.CopiedROIs) this.enablePaste(); end end %-------------------------------------------------------------- % Cut selected ROIs function cutSelectedROIs(this, varargin) % Copy selected ROIs this.copySelectedROIs(); % Delete selected ROIs this.deleteSelectedROIs(); end %-------------------------------------------------------------- % Delete selected ROIs function deleteSelectedROIs(this, varargin) numROIs = numel(this.CurrentROIs); isDeleted = false(1, numROIs); for i = 1:numROIs if this.CurrentROIs{i}.IsSelected delete(this.CurrentROIs{i}); isDeleted(i) = true; end end this.CurrentROIs = this.CurrentROIs(~isDeleted); notify(this, 'ROIsChanged'); end %------------------------------------------------------------------ % Paste selected ROI co-ordinates function pasteSelectedROIs(this, varargin) hAxes = this.ImageAxes; hImage = findobj(hAxes, 'Type', 'image'); [y_extent, x_extent, ~] = size(get(hImage,'CData')); constraint_fcn = makeConstrainToRectFcn('imrect',... [1 x_extent+1], [1 y_extent+1]); % Offsets needed for pasting in case of overlap xOffset = round(x_extent/100 + 1); yOffset = round(y_extent/100 + 1); offset(1,:) = [ xOffset, yOffset, 0, 0]; offset(2,:) = [-xOffset, -yOffset, 0, 0]; offset(3,:) = [ xOffset, -yOffset, 0, 0]; offset(4,:) = [-xOffset, yOffset, 0, 0]; numROIs = numel(this.CopiedROIs); currentROIs = zeros(0, 4); if numROIs > 0 for i = 1:numel(this.CurrentROIs) if this.CurrentROIs{i}.IsValid this.CurrentROIs{i}.IsSelected = false; currentROIs(end+1, :) = ... this.CurrentROIs{i}.BBox + [0.5 0.5 0 0]; %#ok<AGROW> end end else return; end numPastedROIs = 0; for i = 1:numROIs boxPoints = this.CopiedROIs{i}.bbox; % Pasting between images of different sizes if (boxPoints(1) > x_extent) || (boxPoints(2) > y_extent) continue; end boxPoints(3) = min(boxPoints(3), x_extent+1-boxPoints(1)); boxPoints(4) = min(boxPoints(4), y_extent+1-boxPoints(2)); % Offset code offsetIndex = 1; if ~isempty(currentROIs) % Check if pasted ROI overlaps with existing ROIs if ~isempty(intersect(currentROIs,boxPoints,'rows')) lastToLastPoints = [NaN NaN NaN NaN]; lastPoints = boxPoints; try % Find a place to paste since there is overlap findPlaceToPaste(); catch % No place found, do not paste ROI boxPoints = []; end end end if ~isempty(boxPoints) catID = this.CopiedROIs{i}.categoryID; color = this.CopiedROIs{i}.color; catName = this.CopiedROIs{i}.categoryName; isSelected = true; this.drawROI(boxPoints, catID, ... color, catName, true, isSelected); numPastedROIs = numPastedROIs + 1; end end if numPastedROIs > 0 % Update the session notify(this, 'ROIsChanged'); end %TODO: set status text. % Notify user if all ROIs are not pasted % if numROIs ~= numPastedROIs % this.setStatusText('paste', [numROIs numPastedROIs]); % end % % Flush the event queue before new paste callback % This is required to ensure ROIs are pasted only as long as % CTRL+V is held down drawnow; %------------------------------------------------------- % Recursive function to look for a place to paste function findPlaceToPaste() % Add offset and check if within bounds newBoxPoints = boxPoints + offset(offsetIndex,:); if ~isequal(constraint_fcn(newBoxPoints), newBoxPoints) % Outside bounds, so change offset direction and keep % looking for a place offsetIndex = offsetIndex + 1; if offsetIndex==5 offsetIndex = 1; end findPlaceToPaste(); else % Check if we are have already been at the current % location before (this check is needed to avoid % getting stuck in an infinite loop) if ~isequal(lastToLastPoints,newBoxPoints) % If there is no overlap, then we have found a % place to paste. This is the only point of return % from this recursive function if ~isempty(intersect(currentROIs,boxPoints,'rows')) % We still have overlap, update previous % locations, and keep looking for a place boxPoints = newBoxPoints; lastToLastPoints = lastPoints; lastPoints = boxPoints; findPlaceToPaste(); end else % We have been here before, so change direction % and keep looking for a place offsetIndex = offsetIndex + 1; if offsetIndex==5 offsetIndex = 1; end findPlaceToPaste(); end end end end %-------------------------------------------------------------- % Function to enable 'Paste' context menu at the first copy function enablePaste(this, varargin) hAxes = this.ImageAxes; hImage = findobj(hAxes, 'Type', 'image'); foundPaste = findall(get(hImage,'UIContextMenu'),... 'Label','Paste'); set(foundPaste, 'Enable','on'); end %------------------------------------------------------------------ function setPointerToCross(this) hFig = this.Parent; hImage = findobj(hFig, 'Type', 'image'); enterFcn = @(figHandle, currentPoint)... set(figHandle, 'Pointer', 'cross'); iptSetPointerBehavior(hImage, enterFcn); iptPointerManager(hFig); end %------------------------------------------------------------------ function resetPointerBehavior(this) hFig = this.Parent; hImage = findobj(hFig, 'Type', 'image'); iptSetPointerBehavior(hImage, []); iptPointerManager(hFig); end %------------------------------------------------------------------ function wipeFigure(this) fig = this.Parent; if ishandle(fig) set(fig,'HandleVisibility','on'); clf(fig); % clean out figure content zoom(fig, 'off'); pan(fig, 'off'); resetCallbacks(); % turn off the visibility set(fig,'HandleVisibility','off'); end %---------------------------------------- function resetCallbacks % remove any hanging callbacks set(fig,'WindowButtonMotionFcn',[]) set(fig,'WindowButtonUpFcn',[]) set(fig,'WindowButtonDownFcn',[]) set(fig,'WindowKeyPressFcn',[]) set(fig,'WindowKeyReleaseFcn',[]) % install listeners that temporarily block use of the % image browser iptaddcallback(fig, 'WindowButtonDownFcn',... @(~,~)notify(this, 'ParentMouseClick')); iptaddcallback(fig, 'WindowButtonUpFcn',... @(~,~)notify(this, 'ParentMouseRelease')); end % end of resetCallbacks end % end of wipeFigure %------------------------------------------------------------------ % Zoom In Button callback %------------------------------------------------------------------ function doZoomIn(this, varargin) hFig = this.Parent; set(hFig,'HandleVisibility','on'); if ~isempty(hFig) % if axes is in place this.resetPointerBehavior(); % Disable zoom if enabled, to set context menus (context % menus cannot be set when zoom is on) if strcmp(this.ImageMode, 'Zoom') zoom(hFig, 'off'); end h = zoom(hFig); set(h, 'Direction', 'in'); % create context menus this.installContextMenu('ZoomIn'); zoom(hFig, 'inmode'); end drawnow(); set(hFig, 'HandleVisibility', 'off'); this.ImageMode = 'Zoom'; end %------------------------------------------------------------------ % Zoom Out buttons callback %------------------------------------------------------------------ function doZoomOut(this, varargin) hFig = this.Parent; set(hFig,'HandleVisibility','on'); if ~isempty(hFig) % if axes is in place this.resetPointerBehavior(); % Disable zoom if enabled, to set context menus (context % menus cannot be set when zoom is on) if strcmp(this.ImageMode, 'Zoom') zoom(hFig, 'off'); end h = zoom(hFig); set(h, 'Direction', 'out'); % create context menus this.installContextMenu('ZoomOut'); zoom(hFig, 'outmode'); end drawnow(); set(hFig,'HandleVisibility','off'); this.ImageMode = 'Zoom'; end %------------------------------------------------------------------ % Pan Button callback %------------------------------------------------------------------ function doPan(this, varargin) hFig = this.Parent; set(hFig,'HandleVisibility','on'); hAxes = this.ImageAxes; if ~isempty(hAxes) % if axes is in place drawnow(); this.resetPointerBehavior(); h = pan(hFig); this.installContextMenu('Pan'); set(h, 'Enable', 'on'); end drawnow(); set(hFig,'HandleVisibility','off'); this.ImageMode = 'Pan'; end %------------------------------------------------------------------ % Add ROIs callback %------------------------------------------------------------------ function doAddROI(this, varargin) hFig = this.Parent; set(hFig,'HandleVisibility','on'); hAxes = this.ImageAxes; if ~isempty(hAxes) % if axes is in place switch this.ImageMode case 'Zoom' zoom(hFig, 'off'); case 'Pan' pan(hFig, 'off'); end this.installContextMenu('ROI'); this.setPointerToCross(); end this.ImageMode = 'ROImode'; drawnow(); set(hFig,'HandleVisibility','off'); end end end