Main Content

Subsample or Simplify a Freehand ROI

This example shows how to subsample or reduce the number of points in a Freehand ROI object.

Introduction

The drawfreehand function creates a smooth-looking, freehand, region-of-interest (ROI). However, the edge of the ROI is actually made of discrete points distributed all along the boundary. Two factors contribute to how smooth a freehand ROI looks: 1) the density of points and 2) the Smoothing property of the freehand ROI object.

When drawing interactively, mouse motion determines the density of points. For large complex ROIs, the number of points used can be quite large.

The Smoothing property controls how the boundary looks. By default, the Freehand object uses a Gaussian smoothing kernel with a sigma value of 1 and a filter size of 5. Changing this value only changes how the boundary looks, it does not change the underlying Position property of the object.

Default Density of Points

Reducing the density of points can help reduce the space required to store the ROI data and may also speed up any computation that depends on the number of these points. One way to reduce the density of points is to subsample the points, for example, pick every other point.

Create a sample freehand ROI by converting a mask to an ROI. The ROI is very dense since every boundary pixel will correspond to a point in the ROI.

im = imread("football.jpg");
bw = im(:,:,1)>200;
bw = bwareafilt(bw, 1);
bloc = bwboundaries(bw,"noholes");
roipos = fliplr(bloc{1});
imshow(im)
hfh = drawfreehand(Position=roipos);

To visualize the density of the points, turn every point in the ROI into a waypoint.

hfh.Waypoints(:) = true;
title("Original Density");
snapnow

Zoom in.

xlim([80 200]);
ylim([70 160]);
snapnow

Subsampling the Position Points

Subsample the points that make up the Position property of the freehand ROI. Since the freehand ROI is very dense. Subsampling can substantially reduce the size without losing fidelity. Query the initial, full, fine-grained position.

fpos = hfh.Position;

Subsample, picking every other point.

cpos = fpos(1:2:end,:);

Update the Position property of the ROI.

hfh.Position = cpos;

To see the density, turn all points into waypoints.

hfh.Waypoints(:) = true;
title("Simple Subsample");
snapnow

Subsampling - Using Rate of Change

A better approach to subsample the points would be to selectively start removing points which have low curvature. It makes more sense to remove a point that is along a relatively straight portion of the ROI rather than one near a curve. One simple approach to define a curvature value is to measure the rate of the change in position locations.

Measure the rate of change. The neighbor of the first point is the last point.

dfpos = diff([fpos(end,:); fpos]);

Define an ad-hoc measure of curvature based on a simple low-pass filter.

cm = sum(abs(conv2(dfpos, ones(3,2),"same")),2);

Sort by curvature.

[~, cmInds] = sort(cm);

Pick 3/4 of the points with lower curvature values to remove from the ROI.

numPointsToCull = round(0.25*size(fpos,1));

Remove those positions.

cpos = fpos;
cpos(cmInds(1:numPointsToCull),:) = [];

Update the ROI, turning on all Waypoints to see the impact.

hfh.Position = cpos;
hfh.Waypoints(:) = true;
title("Curvature Based Subsample (Factor of 4)")
snapnow

Subsampling - Using reduce method on freehand ROI objects

An even better approach to subsample the points would be to use the reduce method on the ROI object. The reduce method operates directly on the Position property of the ROI object. You can affect the number of points removed by specifying a tolerance value between [0 1.0] as an optional input argument. The default value of tolerance is 0.01.

Reset the Position property and call reduce on the ROI object.

hfh.Position = fpos;
reduce(hfh);

View the updated ROI, turning all the points into waypoints to see the impact.

hfh.Waypoints(:) = true;
title("Subsampling Using Reduce Method")
snapnow

Interactive Subsampling

Another way to subsample is to use events to make this process easier. First create a listener to interactively change the number of points that the freehand ROI uses. Use the UserData property of the Freehand object to cache the full resolution Position data, along with the current value of tolerance. Then add a custom context menu to the ROI object by creating a new uimenu and parenting it to the UIContextMenu of the Freehand object. This menu option allows you to finalize the ROI, which deletes the temporary cache.

Restore the original ROI, and cache the original position along with its curvature measure in UserData.

hfh.Waypoints(:) = true;
hfh.UserData.fpos = fpos;
hfh.UserData.tol = 0;

Respond to mouse scroll.

h = gcf;
h.WindowScrollWheelFcn = @(h, evt) changeSampleDensity(hfh, evt);

Add a context menu to finalize the ROI and perform any clean up needed.

uimenu(hfh.UIContextMenu,Text="Finalize", ...
    MenuSelectedFcn=@(varargin)finalize(hfh));

title("Scroll to change density interactively");

Animation of the Interactive Subsampling

Callback Function - Change the Sample Density Based on Mouse Scroll

This function gets called on scroll action. Scrolling up increases the density, and scrolling down decreases it. This allows you to interactively select the number of points to retain.

function changeSampleDensity(hfh, evt)
% Restore Position property of ROI.
hfh.Position = hfh.UserData.fpos;
% Change tolerance by a fixed amount based on the direction of the scroll.
% This code changes the tolerance by 0.01 for every scroll increment.
tol = hfh.UserData.tol + 0.01 * (evt.VerticalScrollCount);
% Restrict the range of tolerance values to be from 0 to 0.15, which is the
% useful range.
tol = max(min(tol, 0.15), 0);
% Call |reduce| with the specified tolerance.
reduce(hfh,tol);
hfh.UserData.tol = tol;
% Update the ROI and turn all the points into waypoints to show the
% density.
hfh.Waypoints(:) = true;
end

Callback Function - Finalize the Freehand ROI

Delete and create a new Freehand ROI with the subsampled points to save on space.

function finalize(hfh)
h = ancestor(hfh,"figure");
% Reset the mouse scroll wheel callback.
h.WindowScrollWheelFcn = [];
% Save finalized set of points.
pos = hfh.Position;
% Delete and create a new Freehand ROI with the new |Position| value.
delete(hfh);
drawfreehand(gca,Position=pos);
end

See Also

| | |

Related Topics