changeset 75:13fee9301f12

Added qtdecomp: quadtree decomposition
author jmones
date Mon, 09 Aug 2004 01:48:54 +0000
parents 3fd199825b13
children 5d684cbaf092
files INDEX qtdecomp.m
diffstat 2 files changed, 309 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/INDEX
+++ b/INDEX
@@ -18,11 +18,12 @@
  impad
  padarray 
  rotate_scale
-Statistics
+Analysis and Statistics
  corr2 
  imhist 
  mean2 
  std2 
+ qtdecomp
 Filtering
  colfilt
  histeq 
new file mode 100644
--- /dev/null
+++ b/qtdecomp.m
@@ -0,0 +1,307 @@
+## Copyright (C) 2004 Josep Mones i Teixidor
+##
+## 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 Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{S} = } qtdecomp (@var{I})
+## @deftypefnx {Function File} {@var{S} = } qtdecomp (@var{I},@var{threshold})
+## @deftypefnx {Function File} {@var{S} = } qtdecomp (@var{I},@var{threshold},@var{mindim})
+## @deftypefnx {Function File} {@var{S} = } qtdecomp (@var{I},@var{threshold},@var{[mindim maxdim]})
+## @deftypefnx {Function File} {@var{S} = } qtdecomp (@var{I},@var{fun})
+## @deftypefnx {Function File} {@var{S} = } qtdecomp (@var{I},@var{fun},@var{P1},@var{P2},...)
+## Performs quadtree decomposition
+##
+## qtdecomp decomposes a square image @var{I} into four equal-sized
+## blocks. Then it performs some kind of test on each block to decide if
+## it should decompose them further. This process is repeated
+## iteratively until there's no block left to be decomposed.
+##
+## Note that blocks are not decomposed if their dimensions are not even.
+##
+## The output is a sparse matrix whose non-zero elements determine the
+## position of the block (the element is at top-left position in the
+## block) and size of each block (the value of the element determines
+## length of a side of the square-shaped block).
+##
+## S = qtdecomp(I) decomposes an intensity image @var{I} as described
+## above. By default it doesn't split a block if all elements are equal.
+##
+## S = qtdecomp(I, threshold) decomposes an image as decribed, but only
+## splits a block if the maximum value in the block minus the minimum
+## value is greater than @var{threshold}, which is a value between 0 and
+## 1. If @var{I} is of class uint8, @var{threshold} is multiplied by 255
+## before use. Also, if@var{I} is of class uint16, @var{threshold} is 
+## multiplied by 65535.
+##
+## S = qtdecomp(I, threshold, mindim) decomposes an image using the
+## @var{threshold} as just described, but doesn't produce blocks smaller
+## than mindim.
+##
+## S = qtdecomp(I, threshold, [mindim maxdim]) decomposes an image as
+## described, but produces blocks that can't be bigger than maxdim. It
+## decomposes to maxdim even if it isn't needed if only @var{threshold}
+## was considered.
+##
+## S = qtdecomp(I, fun) decomposes an image @var{I} and uses function
+## @var{fun} to decide if a block should be splitted or not. @var{fun}
+## is called with a m-by-m-by-k  array of m-by-m blocks to be
+## considered, and should return a vector of size k, whose elements
+## represent each block in the stacked array. @var{fun} sets the
+## corresponding value to 1 if the block should be split, and 0
+## otherwise.
+##
+## S = qtdecomp(I, fun, ...) behaves as qtdecomp(I, fun) but passes
+## extra parameters to @var{fun}.
+##
+## @end deftypefn
+
+## Author:  Josep Mones i Teixidor <jmones@puntbarra.com>
+
+function S = qtdecomp(I, p1, varargin)
+  if (nargin<1)
+    usage("S=qtdecomp(I)");
+  endif
+  
+  if (!ismatrix(I) || size(I,1)!=size(I,2))
+    error("qtdecomp: I should be a square matrix.");
+  endif
+
+  ## current size (assumed to be square)
+  curr_size=size(I,1);
+
+  ## initial mindim to a sensible value
+  mindim=1;
+ 
+  ## sensible default maxdim value
+  maxdim=curr_size;
+
+  if (nargin<2)
+    ## Initialize decision method variable
+    ## We could have implemented threshold as a function and use an
+    ## uniform interface (function handle) to decide whether to split or
+    ## not blocks. We have decided not to do so because block
+    ## rearrangement that is needed as a parameter to functions is
+    ## expensive and because it needs NDArray, which is only available
+    ## in Octave CVS.
+    decision_method=0;
+  elseif (isreal(p1))
+    ## p1 is threshold
+    threshold=p1;
+    decision_method=1;
+
+    if(strcmp(typeinfo(I), 'uint8 matrix'))
+      threshold*=255;
+    elseif(strcmp(typeinfo(I), 'uint16 matrix'))
+      threshold*=65535;
+    endif
+
+    if (nargin>3)
+      usage("S=qtdecomp(I,threshold,mindim),        \
+	  S=qtdecomp(I,threshold,[mindim maxdim])");
+    elseif (nargin==3)
+      dims=varargin{1};
+      if (isvector(dims)&&length(dims)==2)
+	mindim=dims(1);
+	maxdim=dims(2);
+      elseif (isreal(dims))
+	mindim=dims;
+      else
+	error("qtdecomp: third parameter must be 'mindim' or '[mindim maxdim]'");
+      endif
+      ## we won't check if mindim or maxdim are powers of 2. It's too
+      ## restrictive and don't need it at all.
+    endif
+    
+  elseif (strcmp(typeinfo(p1),"function handle") || \
+	  (isstr(p1) && strncmp(p1, "inline_func", 11)))
+    ## function handles seem to return true to isscalar
+    fun=p1;
+    decision_method=2;
+  else
+    error("qtdecomp: second parameter must be a integer (threshold) or a function handle (fun).");
+  endif
+  
+  ## initialize results matrices
+  res=[];
+
+  ## bool to flag end
+  finished=false;
+
+  ## array of offsets to blocks to evaluate
+  offsets=[1,1];
+
+  if (maxdim<mindim)
+    error("qtdecomp: mindim must be smaller than maxdim.");
+  endif
+
+  ## See if we have to split a minimum regarless other considerations.
+  if (maxdim<curr_size)
+    initial_splits=ceil(log2(curr_size/maxdim));
+    if(initial_splits>0)
+      divs=2^initial_splits;
+      if (rem(curr_size,divs)!=0)
+	error("qtdecomp: Can't decompose I enough times to fulfill maxdim requirement.");
+      endif
+      ## update curr_size
+      curr_size/=divs;
+      if(curr_size<mindim)
+	error("qtdecomp: maxdim restriction collides with mindim restriction.");
+      endif
+      els=transpose([0:divs-1]*curr_size+1);
+      offsets=[kron(els,ones(divs,1)), kron(ones(divs,1),els)];
+    endif
+  endif
+
+  while(!finished && rows(offsets)>0)
+    ## check other ending conditions:
+    ## is size is odd?
+    ## is splitted size < than mindim?
+    if ((rem(curr_size,2)!=0)||((curr_size/2)<mindim))
+      ## can't continue, lets add current evaluation blocks to results
+      res=[res; offsets, ones(size(offsets,1),1)*curr_size];
+      finished = true;
+    else
+      if (decision_method<2)
+	db=logical(ones(rows(offsets),1));
+	for r=1:rows(offsets)
+	  o=offsets(r,:);
+	  fo=offsets(r,:)+curr_size-1;
+
+	  if(decision_method==0)
+	    ## is everything equal?
+	    if (all(I(o(1),o(2))==I(o(1):fo(1),o(2):fo(2))))
+	      db(r)=0;
+	    endif
+	  else
+	    ## check threshold
+	    t=I(o(1):fo(1),o(2):fo(2));
+	    t=t(:);
+	    if ((max(t)-min(t))<=threshold)
+	      db(r)=0;
+	    endif
+	  endif
+	endfor
+      elseif(decision_method==2)
+	## function handle decision method
+	## build blocks
+	b=zeros(curr_size,curr_size,rows(offsets));
+	rbc=offsets(:,1:2)+curr_size-1;
+	for r=1:rows(offsets)
+	  b(:,:,r)=I(offsets(r,1):rbc(r,1),offsets(r,2):rbc(r,2));
+	endfor
+
+	db=feval(fun, b, all_va_args);
+      else
+	error("qtdecomp: execution shouldn't reach here. Please report this as a bug.");
+      endif
+
+      ## Add blocks that won't divide to results
+      nd=offsets(find(!db),:);
+      res=[res; nd, ones(size(nd,1),1)*curr_size];
+      
+      ## Update curr_size for next iteration
+      curr_size/=2;
+      
+      ## Prepare offsets for next iteration
+      otemp=offsets(find(db),:);
+      hs=ones(rows(otemp),1)*curr_size;
+      zs=zeros(size(hs));
+      offsets=[otemp;otemp+[hs,zs];otemp+[zs,hs];otemp+[hs,hs]];
+    endif
+  endwhile
+
+  S=sparse(res(:,1),res(:,2),res(:,3),size(I,1),size(I,2));
+endfunction
+
+
+%!demo
+%! full(qtdecomp(eye(8)))
+%! %It finds 2 big blocks of 0 and it decomposes further where 0 and 1 are mixed.
+
+
+%!# Test if odd-sized limits split
+%!assert(full(qtdecomp(eye(5))), reshape([5,zeros(1,24)],5,5));
+%!assert(full(qtdecomp(eye(6))), repmat(reshape([3,zeros(1,8)],3,3),2,2));
+
+%!shared A, B1, B2, B4, O2, O4, Req, R1, R10, R10m2
+%! A=[ 1, 4, 2, 5,54,55,61,62;
+%!     3, 6, 3, 1,58,53,67,65;
+%!     3, 6, 3, 1,58,53,67,65;
+%!     3, 6, 3, 1,58,53,67,65;
+%!    23,42,42,42,99,99,99,99;    
+%!    27,42,42,42,99,99,99,99;    
+%!    23,22,26,25,99,99,99,99;    
+%!    22,22,24,22,99,99,99,99];
+%! B1=[1];
+%! B2=[2,0;0,0];
+%! B4=reshape([4,zeros(1,15)],4,4);
+%! O2=ones(2,2);
+%! O4=ones(4,4);
+%! Req=[O4,O4; 
+%!      [O2,B2;O2,O2],B4];
+%! R1=[O4,O4;
+%!    [O2,B2;B2,O2],B4];
+%! R10=[[B4,[B2,B2;B2,B2]];
+%!      [[O2,B2;B2,B2],B4]];
+%! R10m2=[[B4,[B2,B2;B2,B2]];
+%!        [[B2,B2;B2,B2],B4]];
+
+%!# Test 'equal' method
+%!test
+%! a=ones(2,2);
+%! b=[2,0;0,0];
+%! assert(full(qtdecomp(eye(4))), [a,b;b,a]);
+
+%!assert(full(qtdecomp(A)), Req);
+
+%!# Test 'threshold' method
+%!assert(full(qtdecomp(A,0)), Req);
+%!assert(full(qtdecomp(A,1)), R1);
+%!assert(full(qtdecomp(A,10)), R10);
+%!assert(full(qtdecomp(A,10,2)), R10m2);
+%!assert(full(qtdecomp(A,100,[2, 4])), [B4,B4;B4,B4]);
+
+%!# Test 'fun' method
+%!# Can't define a function inside tests because Octave
+%!# complains about nested functions. If you know how to
+%!# do it please add function handle tests.
+
+%!# inline version
+%!# We use [size(A),1](3) as a trick since size(A,3) fails if A is 2D.
+
+%!# no params
+%!test
+%! first_eq=inline("(A(1,1,:)!=(54*ones(1,1,[size(A),1](3))))(:)","A");
+%! assert(full(qtdecomp(A,first_eq)),[O4,B4;O4,O4]); 
+
+%!# 1 param
+%!test
+%! first_eq=inline("(A(1,1,:)!=(c*ones(1,1,[size(A),1](3))))(:)","A","c");
+%! assert(full(qtdecomp(A,first_eq,54)),[O4,B4;O4,O4]); 
+
+%!# 3 params
+%!test
+%! first_eq=inline("(A(1,1,:)!=((c1+c2+c3)*ones(1,1,[size(A),1](3))))(:)","A","c1","c2","c3");
+%! assert(full(qtdecomp(A,first_eq,4,40,10)),[O4,B4;O4,O4]); 
+
+
+
+%
+% $Log$
+% Revision 1.1  2004/08/09 01:48:54  jmones
+% Added qtdecomp: quadtree decomposition
+%
+%
+