changeset 585:67d43bf75000

Overhaul regionprops for Nd images
author jordigh
date Fri, 07 Sep 2012 14:52:03 +0000
parents 01e312295406
children 6e8d5281c9a6
files inst/regionprops.m
diffstat 1 files changed, 94 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- a/inst/regionprops.m
+++ b/inst/regionprops.m
@@ -1,4 +1,5 @@
 ## Copyright (C) 2010 Soren Hauberg <soren@hauberg.org>
+## Copyright (C) 2012 Jordi GutiƩrrez Hermoso <jordigh@octave.org>
 ##
 ## This program is free software; you can redistribute it and/or modify it under
 ## the terms of the GNU General Public License as published by the Free Software
@@ -95,45 +96,72 @@
   if (nargin < 1)
     error ("regionprops: not enough input arguments");
   endif
-  
-  if (!ismatrix (bw) || ndims (bw) != 2)
-    error ("regionprops: first input argument must be a NxM matrix");
-  endif
 
   if (numel (varargin) == 0)
-    properties = "basic";
-  elseif (numel (varargin) == 1 && iscellstr (varargin {1}))
-    properties = varargin {1};
+    properties = {"basic"};
+  elseif (numel (varargin) == 1 && iscellstr (varargin{1}))
+      properties = varargin{1};
   elseif (iscellstr (varargin))
     properties = varargin;
   else
     error ("regionprops: properties must be a cell array of strings");
   endif
+
+  properties = lower (properties);
+
+  all_props = {"Area", "EulerNumber", "BoundingBox", "Extent", "Perimeter",\
+               "Centroid", "PixelIdxList", "FilledArea", "PixelList",\
+               "FilledImage", "Image", "MaxIntensity", "MinIntensity",\
+               "WeightedCentroid", "MeanIntensity", "PixelValues",\
+               "Orientation"};
     
-  if (ischar (properties) && strcmpi (properties, "basic"))
-    properties = {"Area", "Centroid", "BoundingBox"};
-  elseif (ischar (properties) && strcmpi (properties, "all"))
-    properties = {"area", "eulernumber", "boundingbox", "extent", "perimeter", ...
-                  "centroid", "pixelidxlist", "filledarea", "pixellist",       ...
-                  "filledimage", "image", "maxintensity", "minintensity",      ...
-                  "weightedcentroid", "meanintensity", "pixelvalues"};
-  elseif (!iscellstr (properties))
+  if (ismember ("basic", properties))
+    properties = union (properties, {"Area", "Centroid", "BoundingBox"});
+    properties = setdiff (properties, "basic");
+  endif
+
+  if (ismember ("all", properties))
+    properties = all_props;
+  endif
+  
+  if (!iscellstr (properties))
     error ("%s %s", "regionprops: properties must be specified as a list of",
            "strings or a cell array of strings");
   endif
 
+  ## Fix capitalisation, underscores of user-supplied properties...
+  for k = 1:numel (properties)
+    property = lower (strrep(properties{k}, "_", ""));
+    [~, idx] = ismember (property, lower (all_props));
+    if (!idx)
+      error ("regionprops: unsupported property: %s", property);
+    endif
+    properties(k) = all_props{idx};
+  endfor
+    
+  N = ndims (bw);
+
   ## Get a labelled image
   if (!islogical (bw) && all (bw >= 0) && all (bw == round (bw)))
     L = bw; # the image was already labelled
     num_labels = max (L (:));
+  elseif (N > 2)
+    [L, num_labels] = bwlabeln (bw);
   else
     [L, num_labels] = bwlabel (bw);
   endif
   
+  ## Return an empty struct with specified properties if there are no labels
+  if num_labels == 0
+    retval = struct ([properties; repmat({{}}, size(properties))]{:});
+    return;
+  endif
+
   ## Compute the properties
   retval = struct ();
-  for k = 1:numel (properties)
-    switch (lower (properties {k}))
+  for property = lower(properties)
+    property = property{:};
+    switch (property)
       case "area"
         for k = 1:num_labels
           retval (k).Area = local_area (L == k);
@@ -153,18 +181,23 @@
         for k = 1:num_labels
           bb = local_boundingbox (L == k);
           area = local_area (L == k);
-          retval (k).Extent = area / (bb (3) * bb (4));
+          idx = length (bb)/2 + 1;
+          retval (k).Extent = area / prod (bb(idx:end));
         endfor
 
       case "perimeter"
-        for k = 1:num_labels
-          retval (k).Perimeter = sum (bwperim (L == k) (:));
-        endfor
+        if (N > 2)
+          warning ("regionprops: skipping perimeter for Nd image");
+        else
+          for k = 1:num_labels
+            retval (k).Perimeter = sum (bwperim (L == k) (:));
+          endfor
+        endif
 
       case "centroid"
         for k = 1:num_labels
-          [Y, X] = find (L == k);
-          retval (k).Centroid = [mean(X), mean(Y)];
+          C = all_coords (L == k);
+          retval (k).Centroid = [mean(C)];
         endfor
 
       case {"pixelidxlist", "pixel_idx_list"}
@@ -179,8 +212,8 @@
 
       case {"pixellist", "pixel_list"}
         for k = 1:num_labels
-          [Y, X] = find (L == k);
-          retval (k).PixelList = [X, Y];
+          C = all_coords (L == k, true, true);
+          retval (k).PixelList = C;
         endfor
 
       case {"filledimage", "filled_image"}
@@ -191,8 +224,10 @@
       case "image"
         for k = 1:num_labels
           tmp = (L == k);
-          [R, C] = find (tmp);
-          retval (k).Image = tmp (min (R):max (R), min (C):max (C));
+          C = all_coords (tmp, false);
+          idx = arrayfun (@(x,y) x:y, min (C), max (C), "unif", 0);
+          idx = substruct ("()", idx);
+          retval (k).Image = subsref (tmp, idx);
         endfor
 
       case {"maxintensity", "max_intensity"}
@@ -202,20 +237,20 @@
     
       case {"minintensity", "min_intensity"}
         for k = 1:num_labels
-          retval (k).MaxIntensity = min (bw (L == k) (:));
+          retval (k).MinIntensity = min (bw (L == k) (:));
         endfor
     
       case {"weightedcentroid", "weighted_centroid"}
         for k = 1:num_labels
-          [Y, X] = find (L == k);
+          C = all_coords (L == k, true, true);
           vals = bw (L == k) (:);
           vals /= sum (vals);
-          retval (k).WeightedCentroid = [dot(X, vals), dot(Y, vals)];
+          retval (k).WeightedCentroid = [dot(C, repmat(vals, 1, columns(C)))];
         endfor
 
       case {"meanintensity", "mean_intensity"}
         for k = 1:num_labels
-          retval (k).MaxIntensity = mean (bw (L == k) (:));
+          retval (k).MeanIntensity = mean (bw (L == k) (:));
         endfor
         
       case {"pixelvalues", "pixel_values"}
@@ -224,6 +259,11 @@
         endfor
     
       case "orientation"
+        if (N > 2)
+          warning ("regionprops: skipping orientation for Nd image");
+          break
+        endif
+
         for k = 1:num_labels
           [Y, X] = find (L == k);
           if (numel (Y) > 1)
@@ -273,7 +313,7 @@
       #case "equivdiameter"
 
       otherwise
-        error ("regionprops: unsupported property '%s'", properties {k});
+        error ("regionprops: unsupported property '%s'", property);
     endswitch
   endfor
 endfunction
@@ -283,6 +323,26 @@
 endfunction
 
 function retval = local_boundingbox (bw)
-  [Y, X] = find (bw);
-  retval = [min(X)-0.5, min(Y)-0.5, max(X)-min(X)+1, max(Y)-min(Y)+1];
+  C = all_coords (bw);
+  retval = [min(C) - 0.5, max(C) - min(C) + 1];
 endfunction
+
+function C = all_coords (bw, flip = true, singleton = false)
+  N = ndims (bw);
+  idx = find (bw);
+  C = cell2mat (nthargout (1:N, @ind2sub, size(bw), idx));
+
+  ## Coordinate convention for 2d images is to flip the X and Y axes
+  ## relative to matrix indexing. Nd images inherit this for the first
+  ## two dimensions.
+  if (flip)
+    [C(2), C(1)] = deal (C(1), C(2));
+  endif
+
+  ## Some functions above expect to work columnwise, so don't return a
+  ## vector
+  if (rows (C) == 1 && !singleton)
+    C = [C; C];
+  endif
+endfunction
+  
\ No newline at end of file