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, Freehand ROI objects, images.roi.Freehand, already include affordances, called waypoints, for interactively editing their shape. By clicking and dragging any of these waypoints on the ROI edge, you can adjust the edge of the ROI. You can also add waypoints interactively to any part of the boundary.

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 a 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 image.

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');

Display the image.

figure
hImage = imshow(im, []);

Convert the locations returned by bwboundaries to x,y order.

pos = blocations{1};
pos = fliplr(pos);

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

| | | | | | | | |

Related Topics