view src/bwlabeln.cc @ 906:475a5a2a08cb

Fix make rules and handling of "shared" libraries. * Makefile: we wanted to have shared libraries for strel.so and connectivity.so but that's not really possible (Octave's pkg does not have a system to handle this). So we create object files and statically link them. This the closest we can do at the moment. Also change setting CXXFLAGS to only add '-std=c++0x' rather than defining all the other options again. * conndef.h, connectivity.h: renamed the first as the later. * conndef.cc: moved the definition of the connectivity class into its own connectivity.cc file and include that. The idea is to then create a shared library without Fconndef and Fiptcheckconn but that did not reallt happen yet. * connectivity.cc: definition of the connectivity class, from conndef.cc. * bwlabeln.cc: change the include for the name connectivity header file. * COPYING: update license for the new files.
author Carnë Draug <carandraug@octave.org>
date Thu, 30 Oct 2014 21:34:55 +0000
parents 0219144c2c21
children 8c8ed7c4ab83
line wrap: on
line source

// Copyright (C) 2002 Jeffrey E. Boyd <boyd@cpsc.ucalgary.ca>
// Copyright (C) 2011-2012 Jordi Gutiérrez Hermoso <jordigh@octave.org>
// Copyright (C) 2013 Carnë Draug <carandraug@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 Foundation; either version 3 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, see
// <http://www.gnu.org/licenses/>.

// Copyright
// Jeffrey E. Boyd and Carnë Draug for bwlabel_2d
// Jordi Gutiérrez Hermoso for bwlabel_nd

#include <octave/oct.h>
#include <vector>
#include <set>
#include <algorithm>
#include "union-find.h++"

#include "connectivity.h"
using namespace octave::image;

#include "config.h"

#if defined (HAVE_UNORDERED_MAP)
#include <unordered_map>
#elif defined (HAVE_TR1_UNORDERED_MAP)
#include <tr1/unordered_map>
#else
#error Must have the TR1 or C++11 unordered_map header
#endif

typedef Array<octave_idx_type> coord;

bool operator== (const coord& a, const coord& b)
{
  if (a.nelem () != b.nelem())
    return false;
  for (octave_idx_type i = 0; i < a.nelem (); i++)
    if (  a(i) !=  b(i) )
      return false;

  return true;
}

//Lexicographic order for coords
bool operator< (const coord& a, const coord& b)
{
  octave_idx_type na = a.nelem (), nb = b.nelem ();
  if (na < nb)
    return true;
  if (na > nb)
    return false;
  octave_idx_type i = 0;
  while (a(i) == b(i) && i < na)
    {
      i++;
    }

  if (i == na          //They're equal, but this is strict order
      || a(i) > b(i) )
    return false;

  return true;
}

// A few basic utility functions
//{
inline
coord
to_coord (const dim_vector& dv,
          octave_idx_type k)
{
  octave_idx_type n = dv.length ();
  coord retval ( dim_vector (n, 1));
  for (octave_idx_type j = 0; j < n; j++)
    {
      retval(j) = k % dv(j);
      k /= dv(j);
    }
  return retval;
}

inline
octave_idx_type
coord_to_pad_idx (const dim_vector& dv,
                  const coord& c)
{
  octave_idx_type idx = 0;
  octave_idx_type mul = 1;
  for (octave_idx_type j = 0; j < dv.length (); j++)
    {
      idx += mul*c(j);
      mul *= dv(j) + 2;
    }
  return idx;
}

inline
coord
operator- (const coord& a, const coord& b)
{
  octave_idx_type na = a.nelem ();
  coord retval( dim_vector(na,1) );
  for (octave_idx_type i = 0; i < na; i++)
    {
      retval(i) = a(i) - b(i);
    }
  return retval;
}


inline
coord
operator- (const coord& a)
{
  octave_idx_type na = a.nelem ();
  coord retval (dim_vector(na,1) );
  for (octave_idx_type i = 0; i < na; i++)
    {
      retval(i) = -a(i);
    }
  return retval;
}
//}

std::set<octave_idx_type>
populate_neighbours (const connectivity& conn, const dim_vector& padded_size)
{
  const Array<octave_idx_type> offsets = conn.offsets (padded_size);

  //The zero coordinates are the centre, and the negative ones
  //are the ones reflected about the centre, and we don't need
  //to consider those.
  std::set<octave_idx_type> neighbours_idx;
  for (octave_idx_type i = 0; i < offsets.numel (); i++)
    if (offsets(i) < 0)
      neighbours_idx.insert (offsets(i));

  return neighbours_idx;
}


octave_idx_type
get_padded_index (octave_idx_type r,
                  const dim_vector& dv)
{
  // This function converts a linear index from the unpadded array
  // into a linear index of the array with zero padding around it. I
  // worked it out on paper, but if you want me to explain this, I'd
  // have to work it out again. ;-) --jgh

  octave_idx_type mult = 1;
  octave_idx_type padded = 0;
  for (octave_idx_type j = 0; j < dv.length (); j++)
    {
      padded += mult*(r % dv(j) + 1);
      mult *= dv(j) + 2;
      r /= dv(j);
    }
  return padded;
}

static octave_value_list
bwlabel_nd (const boolNDArray& BW, const connectivity& conn)
{
  octave_value_list rval;
  boolNDArray conn_mask = conn.mask;

  const dim_vector size_vec = BW.dims ();

  // Use temporary array with borders padded with zeros. Labels will
  // also go in here eventually.
  dim_vector padded_size = size_vec;
  for (octave_idx_type j = 0; j < size_vec.length (); j++)
    padded_size(j) += 2;

  auto neighbours = populate_neighbours (conn, padded_size);

  NDArray L (padded_size, 0);

  // L(2:end-1, 2:end, ..., 2:end-1) = BW
  L.insert(BW, coord (dim_vector (size_vec.length (), 1), 1));

  double* L_vec = L.fortran_vec ();
  union_find u_f (L.nelem ());

  for (octave_idx_type BWidx = 0; BWidx < BW.nelem (); BWidx++)
    {
      octave_idx_type Lidx = get_padded_index (BWidx, size_vec);

      if (L_vec[Lidx])
        {
          //Insert this one into its group
          u_f.find (Lidx);

          //Replace this with C++0x range-based for loop later
          //(implemented in gcc 4.6)
          for (auto nbr = neighbours.begin (); nbr != neighbours.end (); nbr++)
            {
              octave_idx_type n = *nbr + Lidx;
              if (L_vec[n] )
                u_f.unite (n, Lidx);
            }
        }
    }

#ifdef USE_UNORDERED_MAP_WITH_TR1
  using std::tr1::unordered_map;
#else
  using std::unordered_map;
#endif

  unordered_map<octave_idx_type, octave_idx_type> ids_to_label;
  octave_idx_type next_label = 1;

  auto idxs  = u_f.get_ids ();

  //C++0x foreach later
  for (auto idx = idxs.begin (); idx != idxs.end (); idx++)
    {
      octave_idx_type label;
      octave_idx_type id = u_f.find (*idx);
      auto try_label = ids_to_label.find (id);
      if( try_label == ids_to_label.end ())
        {
          label = next_label++;
          ids_to_label[id] = label;
        }
      else
          label = try_label -> second;

      L_vec[*idx] = label;
    }

  // Remove the zero padding...
  Array<idx_vector> inner_slice (dim_vector (size_vec.length (), 1));
  for (octave_idx_type i = 0; i < padded_size.length (); i++)
    inner_slice(i) = idx_vector (1, padded_size(i) - 1);

  rval(0) = L.index (inner_slice);
  rval(1) = ids_to_label.size ();
  return rval;
}

static octave_idx_type
find (std::vector<octave_idx_type>& lset, octave_idx_type x)
{
  // Follow lset until we find a value that points to itself
  while (lset[x] != x)
    x = lset[x];
  return x;
}

static octave_value_list
bwlabel_2d (const boolMatrix& BW, const octave_idx_type& n)
{
  // This algorithm was derived from  BKP Horn, Robot Vision, MIT Press,
  // 1986, p 65 - 89 by Jeffrey E. Boyd in 2002. Some smaller changes
  // were then introduced by Carnë Draug in 2013 to speed up by iterating
  // down a column, and what values to use when connecting two labels
  // to increase chance of getting them in the right order in the end.

  const octave_idx_type nr = BW.rows ();
  const octave_idx_type nc = BW.columns ();

  // The labelled image
  Matrix L (nr, nc);

  std::vector<octave_idx_type> lset (nc*nr);    // label table/tree

  octave_idx_type ntable = 0; // number of elements in the component table/tree
  octave_idx_type ind    = 0; // linear index

  bool n4, n6, n8;
  n4 = n6 = n8 = false;
  if (n == 4)
    n4 = true;
  else if (n == 6)
    n6 = true;
  else if (n == 8)
    n8 = true;

  const bool* BW_vec = BW.data ();
  double* L_vec = L.fortran_vec ();

  for (octave_idx_type c = 0; c < nc; c++)
    {
      for (octave_idx_type r = 0; r < nr; r++, ind++)
        {
          if (BW_vec[ind]) // if A is an object
            {
              octave_idx_type stride = ind - nr;
              // Get the neighboring pixels B, C, D, and E
              //
              //  D  B
              //  C  A  <-- ind is linear index to A
              //  E
              //
              // C and B will always be needed so we get them here, but
              // D is only needed when n is 6 or 8, and E when n is 8.

              octave_idx_type B, C;
              if (c == 0)
                C = 0;
              else
                C = find (lset, L_vec[stride]);

              if (r == 0)
                B = 0;
              else
                B = find (lset, L_vec[ind -1]);

              if (n4)
                {
                  // apply 4 connectedness
                  if (B && C) // B and C are labeled
                    {
                      if (B != C)
                        lset[B] = C;

                      L_vec[ind] = C;
                    }
                  else if (B) // B is object but C is not
                    L_vec[ind] = B;
                  else if (C) // C is object but B is not
                    L_vec[ind] = C;
                  else // B, C not object - new object
                    {
                      // label and put into table
                      ntable++;
                      L_vec[ind] = lset[ntable] = ntable;
                    }
                }
              else if (n6)
                {
                  // Apply 6 connectedness. Seem there's more than one
                  // possible way to do this for 2D images but for some
                  // reason, the most common seems to be the top left pixel
                  // and the bottom right
                  // See http://en.wikipedia.org/wiki/Pixel_connectivity

                  octave_idx_type D;
                  // D is only required for n6 and n8
                  if (r == 0 || c == 0)
                    D = 0;
                  else
                    D = find (lset, L_vec[stride -1]);

                  if (D) // D object, copy label and move on
                    L_vec[ind] = D;
                  else if (B && C) // B and C are labeled
                    {
                      if (B == C)
                        L_vec[ind] = B;
                      else
                        {
                          octave_idx_type tlabel = std::min (B, C);
                          lset[B] = tlabel;
                          lset[C] = tlabel;
                          L_vec[ind] = tlabel;
                        }
                    }
                  else if (B) // B is object but C is not
                    L_vec[ind] = B;
                  else if (C) // C is object but B is not
                    L_vec[ind] = C;
                  else // B, C, D not object - new object
                    {
                      // label and put into table
                      ntable++;
                      L_vec[ind] = lset[ntable] = ntable;
                    }
                }
              else if (n8)
                {
                  octave_idx_type D, E;
                  // D is only required for n6 and n8
                  if (r == 0 || c == 0)
                    D = 0;
                  else
                    D = find (lset, L_vec[stride -1]);

                  // E is only required for n8
                  if (c == 0 || r == nr -1)
                    E = 0;
                  else
                    E = find (lset, L_vec[stride +1]);

                  // apply 8 connectedness
                  if (B || C || D || E)
                    {
                      octave_idx_type tlabel = D;
                      if (D)
                        ; // do nothing (tlabel is already D)
                      else if (C)
                        tlabel = C;
                      else if (E)
                        tlabel = E;
                      else if (B)
                        tlabel = B;

                      L_vec[ind] = tlabel;

                      if (B && B != tlabel)
                        lset[B] = tlabel;
                      if (C && C != tlabel)
                        lset[C] = tlabel;
                      if (D)
                        // we don't check if B != tlabel since if B
                        // is true, tlabel == B
                        lset[D] = tlabel;
                      if (E && E != tlabel)
                        lset[E] = tlabel;
                    }
                  else
                    {
                      // label and put into table
                      ntable++;  // run image through the look-up table
                      L_vec[ind] = lset[ntable] = ntable;
                    }
                }
            }
          else
            L_vec[ind] = 0; // A is not an object so leave it
        }
    }

  const octave_idx_type numel = BW.numel ();

  // consolidate component table
  for (octave_idx_type i = 0; i <= ntable; i++)
    lset[i] = find (lset, i);

  // run image through the look-up table
  for (octave_idx_type ind = 0; ind < numel; ind++)
    L_vec[ind] = lset[L_vec[ind]];

  // count up the objects in the image
  for (octave_idx_type i = 0; i <= ntable; i++)
    lset[i] = 0;

  for (octave_idx_type ind = 0; ind < numel; ind++)
    lset[L_vec[ind]]++;

  // number the objects from 1 through n objects
  octave_idx_type nobj = 0;
  lset[0] = 0;
  for (octave_idx_type i = 1; i <= ntable; i++)
    if (lset[i] > 0)
      lset[i] = ++nobj;

  // Run through the look-up table again, so that their numbers
  // match the number of labels
  for (octave_idx_type ind = 0; ind < numel; ind++)
    L_vec[ind] = lset[L_vec[ind]];

  octave_value_list rval;
  rval(0) = L;
  rval(1) = double (nobj);
  return rval;
}

DEFUN_DLD(bwlabeln, args, , "\
-*- texinfo -*-\n\
@deftypefn  {Loadable Function} {[@var{l}, @var{num}] =} bwlabeln (@var{bw})\n\
@deftypefnx {Loadable Function} {[@var{l}, @var{num}] =} bwlabeln (@var{bw}, @var{n})\n\
Label foreground objects in the n-dimensional binary image @var{bw}.\n\
\n\
The optional argument @var{n} sets the connectivity and defaults 26,\n\
for 26-connectivity in 3-D images. Other possible values are 18 and 6\n\
for 3-D images, 4 and 8 for 2-D images, or an arbitrary N-dimensional\n\
binary connectivity mask where each dimension is of size 3.\n\
\n\
The output @var{l} is an Nd-array where 0 indicates a background\n\
pixel, 1 indicates that the pixel belong to object number 1, 2 that\n\
the pixel belong to object number 2, etc. The total number of objects\n\
is @var{num}.\n\
\n\
The algorithm used is a disjoint-set data structure, a.k.a. union-find.\n\
See, for example, http://en.wikipedia.org/wiki/Union-find\n\
\n\
@seealso{bwconncomp, bwlabel, regionprops}\n\
@end deftypefn\n\
")
{
  octave_value_list rval;

  const octave_idx_type nargin = args.length ();
  if (nargin < 1 || nargin > 2)
    {
      print_usage ();
      return rval;
    }

  if (!args(0).is_numeric_type () && !args(0).is_bool_type ())
    {
      error ("bwlabeln: BW must be a numeric or logical matrix");
      return rval;
    }
  boolNDArray BW = args(0).bool_array_value ();
  dim_vector size_vec = BW.dims ();

  connectivity conn;
  try
    {
      conn = (nargin == 2) ? connectivity (args(1)) :
                             connectivity (BW.ndims (), "maximal");
    }
  catch (invalid_connectivity& e)
    {
      error ("bwlabeln: MASK %s", e.what ());
      return octave_value ();
    }

  // The implementation in bwlabel_2d is faster so use it if we can
  const octave_idx_type ndims = BW.ndims ();
  if (ndims == 2 && boolMatrix (conn.mask) == connectivity (4).mask)
    rval = bwlabel_2d (BW, 4);
  else if (ndims == 2 && boolMatrix (conn.mask) == connectivity (8).mask)
    rval = bwlabel_2d (BW, 8);
  else
    rval = bwlabel_nd (BW, conn);

  return rval;
}

// PKG_ADD: autoload ("bwlabel", which ("bwlabeln"));
// PKG_DEL: autoload ("bwlabel", which ("bwlabeln"), "remove");
DEFUN_DLD(bwlabel, args, , "\
-*- texinfo -*-\n\
@deftypefn  {Loadable Function} {[@var{l}, @var{num}] =} bwlabel(@var{BW})\n\
@deftypefnx {Loadable Function} {[@var{l}, @var{num}] =} bwlabel(@var{BW}, @var{n})\n\
Label binary 2 dimensional image.\n\
\n\
Labels foreground objects in the binary image @var{bw}.\n\
The output @var{l} is a matrix where 0 indicates a background pixel,\n\
1 indicates that the pixel belong to object number 1, 2 that the pixel\n\
belong to object number 2, etc.\n\
The total number of objects is @var{num}.\n\
\n\
To pixels belong to the same object if the are neighbors. By default\n\
the algorithm uses 8-connectivity to define a neighborhood, but this\n\
can be changed through the argument @var{n} that can be either 4, 6, or 8.\n\
\n\
@seealso{bwconncomp, bwlabeln, regionprops}\n\
@end deftypefn\n\
")
{
  octave_value_list rval;

  const octave_idx_type nargin = args.length ();
  if (nargin < 1 || nargin > 2)
    {
      print_usage ();
      return rval;
    }

  // We do not check error state after conversion to boolMatrix
  // because what we want is to actually get a boolean matrix
  // with all non-zero elements as true (Matlab compatibility).
  if ((! args(0).is_numeric_type () && ! args(0).is_bool_type ()) ||
      args(0).ndims () != 2)
    {
      error ("bwlabel: BW must be a 2D matrix");
      return rval;
    }
  // For some reason, we can't use bool_matrix_value() to get a
  // a boolMatrix since it will error if there's values other
  // than 0 and 1 (whatever bool_array_value() does, bool_matrix_value()
  // does not).
  const boolMatrix BW = args(0).bool_array_value ();

  // N-hood connectivity
  const octave_idx_type n = nargin < 2 ? 8 : args(1).idx_type_value ();
  if (error_state || (n != 4 && n!= 6 && n != 8))
    {
      error ("bwlabel: BW must be a 2 dimensional matrix");
      return rval;
    }
  return bwlabel_2d (BW, n);
}

/*
%!shared in
%! in = rand (10) > 0.8;
%!assert (bwlabel (in, 4), bwlabeln (in, 4));
%!assert (bwlabel (in, 4), bwlabeln (in, [0 1 0; 1 0 1; 0 1 0]));
%!assert (bwlabel (in, 8), bwlabeln (in, 8));
%!assert (bwlabel (in, 8), bwlabeln (in, [1 1 1; 1 0 1; 1 1 1]));

%!assert (bwlabel (logical ([0 1 0; 0 0 0; 1 0 1])), [0 2 0; 0 0 0; 1 0 3]);
%!assert (bwlabel ([0 1 0; 0 0 0; 1 0 1]), [0 2 0; 0 0 0; 1 0 3]);

## Support any type of real non-zero value
%!assert (bwlabel ([0 -1 0; 0 0 0; 5 0 0.2]), [0 2 0; 0 0 0; 1 0 3]);

%!shared in, out
%!
%! in = [  0   1   1   0   0   1   0   0   0   0
%!         0   0   0   1   0   0   0   0   0   1
%!         0   1   1   0   0   0   0   0   1   1
%!         1   0   0   0   0   0   0   1   0   0
%!         0   0   0   0   0   1   1   0   0   0
%!         0   0   0   0   0   0   0   0   0   0
%!         0   0   0   1   0   0   0   0   0   0
%!         0   0   0   0   1   1   0   1   0   0
%!         0   0   0   1   0   1   0   1   0   1
%!         1   1   0   0   0   0   0   1   1   0];
%!
%! out = [ 0   3   3   0   0   9   0   0   0   0
%!         0   0   0   5   0   0   0   0   0  13
%!         0   4   4   0   0   0   0   0  13  13
%!         1   0   0   0   0   0   0  11   0   0
%!         0   0   0   0   0  10  10   0   0   0
%!         0   0   0   0   0   0   0   0   0   0
%!         0   0   0   6   0   0   0   0   0   0
%!         0   0   0   0   8   8   0  12   0   0
%!         0   0   0   7   0   8   0  12   0  14
%!         2   2   0   0   0   0   0  12  12   0];
%!assert (nthargout ([1 2], @bwlabel, in, 4), {out, 14});
%!assert (nthargout ([1 2], @bwlabel, logical (in), 4), {out, 14});
%!
%! out = [ 0   3   3   0   0   7   0   0   0   0
%!         0   0   0   3   0   0   0   0   0  11
%!         0   4   4   0   0   0   0   0  11  11
%!         1   0   0   0   0   0   0   9   0   0
%!         0   0   0   0   0   8   8   0   0   0
%!         0   0   0   0   0   0   0   0   0   0
%!         0   0   0   5   0   0   0   0   0   0
%!         0   0   0   0   5   5   0  10   0   0
%!         0   0   0   6   0   5   0  10   0  12
%!         2   2   0   0   0   0   0  10  10   0];
%!assert (nthargout ([1 2], @bwlabel, in, 6), {out, 12});
%!assert (nthargout ([1 2], @bwlabel, logical (in), 6), {out, 12});
%!
%! ## The labeled image is not the same as Matlab, but they are
%! ## labeled correctly. Do we really need to get them properly
%! ## ordered? (the algorithm in bwlabeln does it)
%! mout = [0   1   1   0   0   4   0   0   0   0
%!         0   0   0   1   0   0   0   0   0   5
%!         0   1   1   0   0   0   0   0   5   5
%!         1   0   0   0   0   0   0   5   0   0
%!         0   0   0   0   0   5   5   0   0   0
%!         0   0   0   0   0   0   0   0   0   0
%!         0   0   0   3   0   0   0   0   0   0
%!         0   0   0   0   3   3   0   6   0   0
%!         0   0   0   3   0   3   0   6   0   6
%!         2   2   0   0   0   0   0   6   6   0];
%!
%! out = [ 0   2   2   0   0   4   0   0   0   0
%!         0   0   0   2   0   0   0   0   0   5
%!         0   2   2   0   0   0   0   0   5   5
%!         2   0   0   0   0   0   0   5   0   0
%!         0   0   0   0   0   5   5   0   0   0
%!         0   0   0   0   0   0   0   0   0   0
%!         0   0   0   3   0   0   0   0   0   0
%!         0   0   0   0   3   3   0   6   0   0
%!         0   0   0   3   0   3   0   6   0   6
%!         1   1   0   0   0   0   0   6   6   0];
%!assert (nthargout ([1 2], @bwlabel, in, 8), {out, 6});
%!assert (nthargout ([1 2], @bwlabel, logical (in), 8), {out, 6});
%!
%!error bwlabel (rand (10, 10, 10) > 0.8, 4)
%!error bwlabel (rand (10) > 0.8, "text")
%!error bwlabel ("text", 6)
*/