Create Freehand ROI Editing Tool
This example shows how to create a simple tool to edit the shape of a freehand ROI using another ROI object. By default,
ROI objects include waypoints that you can click and drag to adjust the shape of the ROI. You can also add waypoints interactively to any part of the boundary.Freehand
Another way to edit the shape of freehand ROIs, offered by many popular image manipulation programs, is an 'eraser' or 'brush' tool. This example implements one of these tools, using another ROI object to edit the freehand ROI.
Create Freehand ROI
Create a Freehand ROI that follows the shape of a segmentation mask. For more details on this process, see Use Freehand ROIs to Refine Segmentation Masks.
Read MRI data into the workspace.
im = dicomread('knee1.dcm');
Segment the MRI image and select the two largest regions of the mask.
segmentedLabels = imsegkmeans(im,3); boneMask = segmentedLabels==2; boneMask = bwareafilt(boneMask, 1);
Get the coordinates of the boundaries of the two segmented regions.
blocations = bwboundaries(boneMask,'noholes');
Convert the locations returned by bwboundaries
to x,y order.
pos = blocations{1}; pos = fliplr(pos);
Display the image.
figure hImage = imshow(im,[]);
Create a freehand ROI inside the segmented mask.
hf = drawfreehand('Position', pos);
Create the Freehand ROI Editing Tool
Create a Circle ROI that will be used as the eraser or brush ROI editing tool. (You can use any of the images.roi.*
classes by making a small change, mentioned below).
he = images.roi.Circle(... 'Center', [50 50],... 'Radius', 10,... 'Parent', gca,... 'Color','r');
Associate two event listeners with the Circle ROI. One listens for ROI movement and the other listens for when movement stops. The ROI moving callback function , the example makes sure to have its position snap to pixel locations and also to change color (Red/Green) to indicate if the edit operation will remove or add to the target freehand ROI. Once the editor ROI stops moving, we will create corresponding binary masks for the editor ROI and the target freehand ROI and make the required edit. Finally, we'll transform the updated mask back to a freehand ROI object.Wire up a listener to react whenever this editor ROI is moved
addlistener(he,'MovingROI', @(varargin)editorROIMoving(he, hf)); addlistener(he,'ROIMoved', @(varargin)editFreehand(hf, he));
Interactively Edit the Freehand ROI
This animation shows the add and remove edit operation.
This is the ROI moving callback function. This function ensure that the editor ROI snaps to the pixel grid, and changes the color of the editor ROI to indicate if it will add to the freehand ROI or a remove a region from the freehand ROI. If the center of the editor ROI is outside the target freehand ROI, removes operation, otherwise it will 'add'.
function editorROIMoving(he, hf) % Snap editor ROI to grid he.Position = round(he.Position); % Check if the circle ROI's center is inside or outside the freehand ROI. center = he.Center; isAdd = hf.inROI(center(1), center(2)); if isAdd % Green if inside (since we will add to the freehand). he.Color = 'g'; else % Red otherwise. he.Color = 'r'; end end
This is the edit freehand ROI callback that adds or removes the region of the editor ROI that intersects the target freehand ROI.
function editFreehand(hf, he) % Create a mask for the target freehand. tmask = hf.createMask(); [m, n,~] = size(tmask); % Include the boundary pixel locations boundaryInd = sub2ind([m,n], hf.Position(:,2), hf.Position(:,1)); tmask(boundaryInd) = true; % Create a mask from the editor ROI emask = he.createMask(); boundaryInd = sub2ind([m,n], he.Position(:,2), he.Position(:,1)); emask(boundaryInd) = true; % Check if center of the editor ROI is inside the target freehand. If you % use a different editor ROI, ensure to update center computation. center = he.Center; % isAdd = hf.inROI(center(1), center(2)); if isAdd % Add the editor mask to the freehand mask newMask = tmask|emask; else % Delete out the part of the freehand which intersects the editor newMask = tmask&~emask; end % Update the freehand ROI perimPos = bwboundaries(newMask, 'noholes'); hf.Position = [perimPos{1}(:,2), perimPos{1}(:,1)]; end
See Also
dicomread
| imsegkmeans
| bwareafilt
| bwboundaries
| drawfreehand
| Freehand
| Circle
| addlistener
| inROI
| createMask