Search code examples
matlabplotgroupingvisualizationmatlab-figure

Drawing rectangles around vertical groups of pixels having the same value


Consider the following visualization of a 7x5 matrix consisting of 3 distinct regions/values:

bL = toeplitz( [zeros(1,5) -2*ones(1,2)], [0 -ones(1,4)] ); 
hF = figure(); hA = axes(hF);    
imagesc(hA,bL); axis(hA,'image'); set(hA,'XTick',[],'YTick',[]);
N = 4; cmap = parula(N); colormap(cmap(1:end-1,:));

Unselected

Now let's say I "select" 0 or more pixels in each column such that:

  • Selected pixels can only be chosen in the green region.
  • Selected pixels are always contiguous.
  • Selection is performed by assigning a constant new value, which is different from the 3 initial regions.

Several examples of selection (using the value 1):

%Example 1:
cSF = toeplitz([ones(1,1) zeros(1,4) -2*ones(1,2)],[1 -ones(1,4)]);
%Example 2:
oSF = toeplitz( [zeros(1,5) -2*ones(1,2)], [0 -ones(1,4)] );
oSF(end-2:end,find(any(oSF==-2,1),1,'last')+1:end) = 1; 
%Example 3:
iSF = toeplitz([ones(1,3) zeros(1,2) -2*ones(1,2)],[1 -ones(1,4)]);
% Plot:
hF = figure();
hP(1) = subplot(1,3,1); imagesc(cSF);
hP(2) = subplot(1,3,2); imagesc(oSF);
hP(3) = subplot(1,3,3); imagesc(iSF);
axis(hP,'image'); set(hP,'XTick',[],'YTick',[]);

Possible selections

My objective is to draw a set of rectangles encompassing "selected" (yellow) pixels belonging to the same column. For the examples above, the results should look like this (respectively):

Desired result

The way I see it, for the code to be general it should accept: (1) an axes handle where the imagesc should be plotted; (2) a data array; (3) a value found in the data array, representing "chosen" pixels; and optionally the color of the enclosed pixels.

I found some ways of doing this using patch and rectangle (see own answer), but I'm wondering if this can be achieved with fewer function calls or in other ways I hadn't thought of.


Solution

  • Loopless solution using patch:

    Here's a solution that generates coordinates for patch without needing a loop:

    function column_highlight(hA, data, selectionVal)
    
      assert(nargin >= 2);
      if (nargin < 3) || isempty(selectionVal)
        selectionVal = 1;
      end
    
      nCol = size(data, 2);
      data = diff([false(1, nCol); (data == selectionVal); false(1, nCol)]);
      [r, c] = find(data);
      r = reshape(r-0.5, 2, []);
      c = c(1:2:end);
      X = [c-0.5 c+0.5 c+0.5 c-0.5].';
      Y = r([1 1 2 2], :);
      patch(hA, 'XData', X, 'YData', Y, 'FaceColor', 'none');
    
    end
    


    Solution using regionprops:

    If you have the Image Processing Toolbox, you can solve this by labeling each masked column section and getting the 'BoundingBox' shape measure using regionprops:

    function column_highlight(hA, data, selectionVal)
    
      assert(nargin >= 2);
      if (nargin < 3) || isempty(selectionVal)
        selectionVal = 1;
      end
    
      labelMat = bsxfun(@times, (data == selectionVal), 1:size(data, 2));
      coords = regionprops(labelMat, 'BoundingBox');
      coords = vertcat(coords.BoundingBox);
      coords(:, 3:4) = coords(:, 1:2)+coords(:, 3:4);
      X = coords(:, [1 3 3 1]).';
      Y = coords(:, [4 4 2 2]).';
      patch(hA, 'XData', X, 'YData', Y, 'FaceColor', 'none');
    
    end