This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Olena, a generic and efficient image processing platform".
The branch skeletons has been deleted
was a087bc4d5c23b64db8ca8a3fe40c73387419ec70
-----------------------------------------------------------------------
a087bc4d5c23b64db8ca8a3fe40c73387419ec70 Catch up with the renaming to detach_cell.
-----------------------------------------------------------------------
hooks/post-receive
--
Olena, a generic and efficient image processing platform
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Olena, a generic and efficient image processing platform".
The branch more-generic-skel has been deleted
was 8cbc0864835adb146da03fde34e7f3328bc580a8
-----------------------------------------------------------------------
8cbc0864835adb146da03fde34e7f3328bc580a8 Use mln::data::paste to simplify mesh skeleton applications.
-----------------------------------------------------------------------
hooks/post-receive
--
Olena, a generic and efficient image processing platform
* mln/topo/is_simple_cell.hh: Add static preconditions.
Reduce the number of pointer manipulations.
Aesthetic changes.
* mln/topo/detach_cell.hh: Move a static precondition.
Aesthetic changes.
* mln/io/vtk/save.hh: Cosmetic changes.
---
milena/ChangeLog | 11 +++++++++++
milena/mln/io/vtk/save.hh | 8 ++++++--
milena/mln/topo/detach_cell.hh | 22 ++++++++++++----------
milena/mln/topo/is_simple_cell.hh | 24 ++++++++++++++----------
4 files changed, 43 insertions(+), 22 deletions(-)
diff --git a/milena/ChangeLog b/milena/ChangeLog
index aa71fb7..63b6054 100644
--- a/milena/ChangeLog
+++ b/milena/ChangeLog
@@ -1,5 +1,16 @@
2011-03-14 Roland Levillain <roland(a)lrde.epita.fr>
+ Miscellaneous changes in mesh-related operations.
+
+ * mln/topo/is_simple_cell.hh: Add static preconditions.
+ Reduce the number of pointer manipulations.
+ Aesthetic changes.
+ * mln/topo/detach_cell.hh: Move a static precondition.
+ Aesthetic changes.
+ * mln/io/vtk/save.hh: Cosmetic changes.
+
+2011-03-14 Roland Levillain <roland(a)lrde.epita.fr>
+
New apps: max curvature-based thinnings using 2- and 1-collapses.
* apps/mesh-segm-skel/mesh-complex-max-curv-2-collapse.cc,
diff --git a/milena/mln/io/vtk/save.hh b/milena/mln/io/vtk/save.hh
index fbe5a85..142608a 100644
--- a/milena/mln/io/vtk/save.hh
+++ b/milena/mln/io/vtk/save.hh
@@ -1,4 +1,4 @@
-// Copyright (C) 2008, 2009, 2010 EPITA Research and Development
+// Copyright (C) 2008, 2009, 2010, 2011 EPITA Research and Development
// Laboratory (LRDE)
//
// This file is part of Olena.
@@ -269,7 +269,7 @@ namespace mln
/* ``1. The first part is the file version and
identifier. This part contains the single line:
- # vtk DataFile Version x.x.
+ # vtk DataFile Version x.x.
This line must be exactly as shown with the
exception of the version number x.x, which will vary
@@ -492,6 +492,10 @@ namespace mln
ostr << std::endl;
}
+ // -------------------- //
+ // Dataset attributes. //
+ // -------------------- //
+
/* ``5. The final part describes the dataset attributes.
This part begins with the keywords POINT_DATA or
CELL_DATA,followed by an integer number specifying
diff --git a/milena/mln/topo/detach_cell.hh b/milena/mln/topo/detach_cell.hh
index 761142e..0bf442c 100644
--- a/milena/mln/topo/detach_cell.hh
+++ b/milena/mln/topo/detach_cell.hh
@@ -30,10 +30,15 @@
/// \file
/// \brief Detaching a cell from a binary (probably complex-based) image.
+# include <mln/core/concept/function.hh>
+# include <mln/core/concept/image.hh>
+# include <mln/core/concept/neighborhood.hh>
+
# include <mln/core/site_set/p_set.hh>
-# include <mln/core/image/complex_image.hh>
+
# include <mln/make/detachment.hh>
+
namespace mln
{
@@ -66,7 +71,7 @@ namespace mln
\pre \a ima is an image of Boolean values.
\param ima The input image from which the cell is to be
- detached.
+ detached.
\param nbh An adjacency relationship between faces
(should return the set of (n-1)- and (n+1)-faces
adjacent to an n-face). */
@@ -80,14 +85,10 @@ namespace mln
\pre \a ima is an image of Boolean values. */
void set_image(mln::Image<I>& ima);
- /** Detach the cell corresponding to \a f from \a ima.
+ /** Detach the cell corresponding to \a f from \a ima_.
- \param f The psite corresponding to the cell to detach.
- \param nbh An adjacency relationship between faces
- (should return the set of (n-1)- and (n+1)-faces
- adjacent to an n-face). */
- void
- operator()(const mln_psite(I)& f);
+ \param f The psite corresponding to the cell to detach. */
+ void operator()(const mln_psite(I)& f);
private:
I* ima_;
@@ -95,6 +96,7 @@ namespace mln
};
+
# ifndef MLN_INCLUDE_ONLY
template <typename I, typename N>
@@ -102,6 +104,7 @@ namespace mln
detach_cell<I, N>::detach_cell(const Neighborhood<N>& nbh)
: ima_(0), nbh_(exact(nbh))
{
+ mlc_equal(mln_value(I), bool)::check();
}
template <typename I, typename N>
@@ -118,7 +121,6 @@ namespace mln
void
detach_cell<I, N>::set_image(mln::Image<I>& ima)
{
- mlc_equal(mln_value(I), bool)::check();
ima_ = exact(&ima);
}
diff --git a/milena/mln/topo/is_simple_cell.hh b/milena/mln/topo/is_simple_cell.hh
index 8796c16..9f1491a 100644
--- a/milena/mln/topo/is_simple_cell.hh
+++ b/milena/mln/topo/is_simple_cell.hh
@@ -34,15 +34,15 @@
# include <mln/core/concept/image.hh>
# include <mln/core/site_set/p_set.hh>
-# include <mln/core/site_set/complex_psite.hh>
-# include <mln/core/site_set/p_complex_piter.hh>
-# include <mln/core/image/complex_neighborhoods.hh>
-# include <mln/core/image/complex_neighborhood_piter.hh>
# include <mln/topo/is_facet.hh>
# include <mln/make/attachment.hh>
+// FIXME: Have the functor take N, NL and NH neighborhood objects at
+// the construction (instead of building them in operator()), as in
+// does is_simple_pair.
+
namespace mln
{
@@ -69,7 +69,8 @@ namespace mln
\tparam NH The neighborhood type returning the set of
(n+1)-faces adjacent to a an n-face. */
template <typename I, typename N, typename NL, typename NH>
- class is_simple_cell : public mln::Function_v2b< is_simple_cell<I, N, NL, NH> >
+ class is_simple_cell
+ : public mln::Function_v2b< is_simple_cell<I, N, NL, NH> >
{
public:
/// Result type of the functor.
@@ -108,6 +109,7 @@ namespace mln
is_simple_cell<I, N, NL, NH>::is_simple_cell()
: ima_(0)
{
+ mlc_equal(mln_value(I), bool)::check();
}
template <typename I, typename N, typename NL, typename NH>
@@ -115,6 +117,7 @@ namespace mln
is_simple_cell<I, N, NL, NH>::is_simple_cell(const mln::Image<I>& ima)
: ima_(exact(&ima))
{
+ mlc_equal(mln_value(I), bool)::check();
}
template <typename I, typename N, typename NL, typename NH>
@@ -131,23 +134,24 @@ namespace mln
is_simple_cell<I, N, NL, NH>::operator()(const mln_psite(I)& p) const
{
mln_precondition(ima_);
- // FIXME: Introduce `const I& ima = *ima_;' and use it instead of
- // `ima_'. Or introduce an `ima()' accessor?
+ // Shortcut.
+ // FIXME: Introduce an `ima()' accessor instead?
+ const I& ima = *ima_;
// The cell corresponding to P cannot be simple unless P is
// facet.
{
NH higher_adj_nbh;
- if (!is_facet(*ima_, p, higher_adj_nbh))
+ if (!is_facet(ima, p, higher_adj_nbh))
return false;
}
typedef p_set<mln_psite(I)> faces_t;
// Compute the attachment of the cell corresponding to P to the
- // domain of *IMA_.
+ // domain of IMA.
N adj_nbh;
- faces_t att = make::attachment(*ima_, p, adj_nbh);
+ faces_t att = make::attachment(ima, p, adj_nbh);
// A cell with an empty attachment is not simple.
/* FIXME: Why p_set does not provide an empty() predicate? */
--
1.5.6.5
* apps/mesh-segm-skel/mesh-complex-max-curv-2-collapse.cc,
* apps/mesh-segm-skel/mesh-complex-max-curv-1-collapse.cc:
Here.
---
milena/ChangeLog | 8 +
.../mesh-complex-max-curv-1-collapse.cc | 196 +++++++++-----------
.../mesh-complex-max-curv-2-collapse.cc | 193 +++++++++----------
3 files changed, 188 insertions(+), 209 deletions(-)
diff --git a/milena/ChangeLog b/milena/ChangeLog
index 63b6054..8c32c8b 100644
--- a/milena/ChangeLog
+++ b/milena/ChangeLog
@@ -1,3 +1,11 @@
+2011-03-20 Roland Levillain <roland(a)lrde.epita.fr>
+
+ Simplify curvature-based thinnings using 2- and 1-collapses apps.
+
+ * apps/mesh-segm-skel/mesh-complex-max-curv-2-collapse.cc,
+ * apps/mesh-segm-skel/mesh-complex-max-curv-1-collapse.cc:
+ Here.
+
2011-03-14 Roland Levillain <roland(a)lrde.epita.fr>
Miscellaneous changes in mesh-related operations.
diff --git a/milena/apps/mesh-segm-skel/mesh-complex-max-curv-1-collapse.cc b/milena/apps/mesh-segm-skel/mesh-complex-max-curv-1-collapse.cc
index 0cb29dd..8b12030 100644
--- a/milena/apps/mesh-segm-skel/mesh-complex-max-curv-1-collapse.cc
+++ b/milena/apps/mesh-segm-skel/mesh-complex-max-curv-1-collapse.cc
@@ -41,7 +41,7 @@
#include <mln/core/image/dmorph/image_if.hh>
#include <mln/core/image/dmorph/sub_image.hh>
-#include <mln/core/image/dmorph/mutable_extension_ima.hh>
+#include <mln/core/routine/extend.hh>
#include <mln/core/routine/mutable_extend.hh>
#include <mln/data/paste.hh>
@@ -49,6 +49,7 @@
#include <mln/labeling/regional_minima.hh>
#include <mln/morpho/closing/area.hh>
+#include <mln/morpho/dilation.hh>
#include <mln/topo/is_n_face.hh>
#include <mln/topo/is_simple_pair.hh>
@@ -101,31 +102,57 @@ main(int argc, char* argv[])
mln::math::sqr(curv.second(v)));
}
+ // Neighborhood type returning the set of (n-1)-faces adjacent to a
+ // an n-face.
+ typedef mln::complex_lower_neighborhood<D, G> lower_adj_nbh_t;
+ lower_adj_nbh_t lower_adj_nbh;
+
+ // Values on edges.
+ /* FIXME: We could probably simplify this by using a
+ convolution-like operator and morphers (see
+ apps/graph-morpho). */
+ mln::p_n_faces_fwd_piter<D, G> e(float_ima.domain(), 1);
+ // For each edge (1-face) E, iterate on the the set of vertices
+ // (0-faces) adjacent to E.
+ mln_niter_(lower_adj_nbh_t) adj_v(lower_adj_nbh, e);
+ // Iterate on edges (1-faces).
+ for_all(e)
+ {
+ float s = 0.0f;
+ unsigned n = 0;
+ // Iterate on vertices (0-faces).
+ for_all(adj_v)
+ {
+ s += float_ima(adj_v);
+ ++n;
+ }
+ float_ima(e) = s / n;
+ // An edge should be adjacent to exactly two vertices.
+ mln_invariant(n == 2);
+ }
+
// Values on triangles.
+ /* FIXME: We could probably simplify this by using a
+ convolution-like operator and morphers (see
+ apps/graph-morpho). */
mln::p_n_faces_fwd_piter<D, G> t(float_ima.domain(), 2);
- // For each triangle (2-face) T, iterate on the the set of vertices
- // (0-faces) transitively adjacent to T.
- typedef mln::complex_m_face_neighborhood<D, G> adj_vertices_nbh_t;
- adj_vertices_nbh_t adj_vertices_nbh;
- mln_niter_(adj_vertices_nbh_t) adj_v(adj_vertices_nbh, t);
- /* FIXME: We should be able to pass this value (m) either at the
- construction of the neighborhood or at the construction of the
- iterator. */
- adj_v.iter().set_m(0);
+ // For each triangle (2-face) T, iterate on the the set of edges
+ // (1-faces) adjacent to T.
+ mln_niter_(lower_adj_nbh_t) adj_e(lower_adj_nbh, t);
// Iterate on triangles (2-faces).
for_all(t)
{
float s = 0.0f;
unsigned n = 0;
- // Iterate on vertices (0-faces).
- for_all(adj_v)
+ // Iterate on edges (1-faces).
+ for_all(adj_e)
{
- s += float_ima(adj_v);
+ s += float_ima(adj_e);
++n;
}
float_ima(t) = s / n;
- // A triangle should be adjacent to exactly two vertices.
- mln_invariant(n <= 3);
+ // A triangle should be adjacent to exactly three edges.
+ mln_invariant(n == 3);
}
// Convert the float image into an unsigned image because some
@@ -141,14 +168,6 @@ main(int argc, char* argv[])
for_all(t)
ima(t) = 1000 * float_ima(t);
- /* FIXME: Workaround: Set maximal values on vertices and edges to
- exclude them from the set of minimal values. */
- for_all(v)
- ima(v) = mln_max(mln_value_(ima_t));
- mln::p_n_faces_fwd_piter<D, G> e(float_ima.domain(), 1);
- for_all(e)
- ima(e) = mln_max(mln_value_(ima_t));
-
/*-----------------.
| Simplification. |
`-----------------*/
@@ -157,7 +176,15 @@ main(int argc, char* argv[])
typedef mln::complex_lower_dim_connected_n_face_neighborhood<D, G> nbh_t;
nbh_t nbh;
- ima_t closed_ima = mln::morpho::closing::area(ima, nbh, lambda);
+ // Predicate type: is a face a triangle (2-face)?
+ typedef mln::topo::is_n_face<mln_psite_(ima_t), 2> is_a_triangle_t;
+ is_a_triangle_t is_a_triangle;
+
+ // Consider only triangles.
+ ima_t closed_ima = mln::duplicate(ima);
+ mln::data::paste(mln::morpho::closing::area(ima | is_a_triangle,
+ nbh, lambda),
+ closed_ima);
/*---------------.
| Local minima. |
@@ -166,68 +193,13 @@ main(int argc, char* argv[])
typedef mln::value::label_16 label_t;
label_t nminima;
- /* FIXME: We should use something like `ima_t | p_n_faces(2)' instead
- of `ima_t' here. Or better: `input' should only associate data
- to 2-faces. */
+ // Consider only triangles.
typedef mln_ch_value_(ima_t, label_t) label_ima_t;
- label_ima_t minima =
- mln::labeling::regional_minima(closed_ima, nbh, nminima);
-
- typedef mln::complex_higher_neighborhood<D, G> higher_nbh_t;
- higher_nbh_t higher_nbh;
-
- // Propagate minima values from triangles to edges.
- // FIXME: Factor this inside a function.
- mln_niter_(higher_nbh_t) adj_t(higher_nbh, e);
- for_all(e)
- {
- label_t ref_adj_minimum = mln::literal::zero;
- for_all(adj_t)
- if (minima(adj_t) == mln::literal::zero)
- {
- // If E is adjacent to a non-minimal triangle, then it must
- // not belong to a minima.
- ref_adj_minimum = mln::literal::zero;
- break;
- }
- else
- {
- if (ref_adj_minimum == mln::literal::zero)
- // If this is the first minimum seen, use it as a reference.
- ref_adj_minimum = minima(adj_t);
- else
- // If this is not the first time a minimum is encountered,
- // ensure it is REF_ADJ_MINIMUM.
- mln_assertion(minima(adj_t) == ref_adj_minimum);
- }
- minima(e) = ref_adj_minimum;
- }
-
- // Likewise from edges to edges to vertices.
- mln_niter_(higher_nbh_t) adj_e(higher_nbh, v);
- for_all(v)
- {
- label_t ref_adj_minimum = mln::literal::zero;
- for_all(adj_e)
- if (minima(adj_e) == mln::literal::zero)
- {
- // If V is adjacent to a non-minimal triangle, then it must
- // not belong to a minima.
- ref_adj_minimum = mln::literal::zero;
- break;
- }
- else
- {
- if (ref_adj_minimum == mln::literal::zero)
- // If this is the first minimum seen, use it as a reference.
- ref_adj_minimum = minima(adj_e);
- else
- // If this is not the first time a minimum is encountered,
- // ensure it is REF_ADJ_MINIMUM.
- mln_assertion(minima(adj_e) == ref_adj_minimum);
- }
- minima(v) = ref_adj_minimum;
- }
+ label_ima_t minima;
+ mln::initialize(minima, closed_ima);
+ mln::data::paste(mln::labeling::regional_minima(closed_ima | is_a_triangle,
+ nbh, nminima),
+ minima);
/*-----------------------.
| Initial binary image. |
@@ -242,13 +214,39 @@ main(int argc, char* argv[])
typedef mln_ch_value_(ima_t, bool) bin_ima_t;
bin_ima_t surface(minima.domain());
- mln::data::fill(surface, true);
- // Dig ``holes'' in the surface surface by setting minima faces to false.
- // FIXME: Use fill with an image_if instead, when available/working.
- mln_piter_(bin_ima_t) f(minima.domain());
- for_all(f)
- if (minima(f) != mln::literal::zero)
- surface(f) = false;
+
+ // Predicate type: is a face an edge (1-face)?
+ typedef mln::topo::is_n_face<mln_psite_(ima_t), 1> is_an_edge_t;
+ is_an_edge_t is_an_edge;
+ // Predicate type: is a face a vertex (0-face)?
+ typedef mln::topo::is_n_face<mln_psite_(ima_t), 0> is_a_vertex_t;
+ is_a_vertex_t is_a_vertex;
+
+ // Neighborhood type returning the set of (n+1)-faces adjacent to a
+ // an n-face.
+ typedef mln::complex_higher_neighborhood<D, G> higher_adj_nbh_t;
+ higher_adj_nbh_t higher_adj_nbh;
+
+ mln::data::fill(surface, false);
+ // Set non minima triangles to true;
+ mln::data::fill
+ ((surface |
+ mln::pw::value(minima) == mln::pw::cst(mln::literal::zero)).rw(),
+ true);
+ // Extend non minima values from triangles to edges.
+ mln::data::paste (mln::morpho::dilation(mln::extend(surface | is_an_edge,
+ surface),
+ /* Dilations require windows,
+ not neighborhoods. */
+ higher_adj_nbh.win()),
+ surface);
+ // Extend non minima values from edges to vertices.
+ mln::data::paste(mln::morpho::dilation(mln::extend(surface | is_a_vertex,
+ surface),
+ /* Dilations require windows,
+ not neighborhoods. */
+ higher_adj_nbh.win()),
+ surface);
/*-------------.
| 2-collapse. |
@@ -258,9 +256,6 @@ main(int argc, char* argv[])
// Image restricted to triangles. //
// ------------------------------- //
- // Predicate type: is a face a triangle (2-face)?
- typedef mln::topo::is_n_face<mln_psite_(bin_ima_t), D> is_a_triangle_t;
- is_a_triangle_t is_a_triangle;
// Surface image type, of which domain is restricted to triangles.
typedef mln::image_if<bin_ima_t, is_a_triangle_t> bin_triangle_only_ima_t;
// Surface image type, of which iteration (not domain) is restricted
@@ -272,14 +267,6 @@ main(int argc, char* argv[])
// Simple point predicate. //
// ------------------------ //
- // Neighborhood type returning the set of (n-1)-faces adjacent to a
- // an n-face.
- typedef mln::complex_lower_neighborhood<D, G> lower_adj_nbh_t;
- lower_adj_nbh_t lower_adj_nbh;
- // Neighborhood type returning the set of (n+1)-faces adjacent to a
- // an n-face.
- typedef mln::complex_higher_neighborhood<D, G> higher_adj_nbh_t;
- higher_adj_nbh_t higher_adj_nbh;
// Predicate type: is a triangle (2-face) simple?
typedef mln::topo::is_simple_pair< bin_triangle_ima_t,
lower_adj_nbh_t,
@@ -325,9 +312,6 @@ main(int argc, char* argv[])
// Image restricted to edges. //
// --------------------------- //
- // Predicate type: is a face an edge (1-face)?
- typedef mln::topo::is_n_face<mln_psite_(bin_ima_t), D - 1> is_an_edge_t;
- is_an_edge_t is_an_edge;
// Surface image type, of which domain is restricted to edges.
typedef mln::image_if<bin_ima_t, is_an_edge_t> bin_edge_only_ima_t;
// Surface image type, of which iteration (not domain) is restricted
diff --git a/milena/apps/mesh-segm-skel/mesh-complex-max-curv-2-collapse.cc b/milena/apps/mesh-segm-skel/mesh-complex-max-curv-2-collapse.cc
index 233756d..c88f10d 100644
--- a/milena/apps/mesh-segm-skel/mesh-complex-max-curv-2-collapse.cc
+++ b/milena/apps/mesh-segm-skel/mesh-complex-max-curv-2-collapse.cc
@@ -41,7 +41,7 @@
#include <mln/core/image/dmorph/image_if.hh>
#include <mln/core/image/dmorph/sub_image.hh>
-#include <mln/core/image/dmorph/mutable_extension_ima.hh>
+#include <mln/core/routine/extend.hh>
#include <mln/core/routine/mutable_extend.hh>
#include <mln/data/paste.hh>
@@ -49,6 +49,7 @@
#include <mln/labeling/regional_minima.hh>
#include <mln/morpho/closing/area.hh>
+#include <mln/morpho/dilation.hh>
#include <mln/topo/is_n_face.hh>
#include <mln/topo/is_simple_pair.hh>
@@ -101,31 +102,57 @@ main(int argc, char* argv[])
mln::math::sqr(curv.second(v)));
}
+ // Neighborhood type returning the set of (n-1)-faces adjacent to a
+ // an n-face.
+ typedef mln::complex_lower_neighborhood<D, G> lower_adj_nbh_t;
+ lower_adj_nbh_t lower_adj_nbh;
+
+ // Values on edges.
+ /* FIXME: We could probably simplify this by using a
+ convolution-like operator and morphers (see
+ apps/graph-morpho). */
+ mln::p_n_faces_fwd_piter<D, G> e(float_ima.domain(), 1);
+ // For each edge (1-face) E, iterate on the the set of vertices
+ // (0-faces) adjacent to E.
+ mln_niter_(lower_adj_nbh_t) adj_v(lower_adj_nbh, e);
+ // Iterate on edges (1-faces).
+ for_all(e)
+ {
+ float s = 0.0f;
+ unsigned n = 0;
+ // Iterate on vertices (0-faces).
+ for_all(adj_v)
+ {
+ s += float_ima(adj_v);
+ ++n;
+ }
+ float_ima(e) = s / n;
+ // An edge should be adjacent to exactly two vertices.
+ mln_invariant(n == 2);
+ }
+
// Values on triangles.
+ /* FIXME: We could probably simplify this by using a
+ convolution-like operator and morphers (see
+ apps/graph-morpho). */
mln::p_n_faces_fwd_piter<D, G> t(float_ima.domain(), 2);
- // For each triangle (2-face) T, iterate on the the set of vertices
- // (0-faces) transitively adjacent to T.
- typedef mln::complex_m_face_neighborhood<D, G> adj_vertices_nbh_t;
- adj_vertices_nbh_t adj_vertices_nbh;
- mln_niter_(adj_vertices_nbh_t) adj_v(adj_vertices_nbh, t);
- /* FIXME: We should be able to pass this value (m) either at the
- construction of the neighborhood or at the construction of the
- iterator. */
- adj_v.iter().set_m(0);
+ // For each triangle (2-face) T, iterate on the the set of edges
+ // (1-faces) adjacent to T.
+ mln_niter_(lower_adj_nbh_t) adj_e(lower_adj_nbh, t);
// Iterate on triangles (2-faces).
for_all(t)
{
float s = 0.0f;
unsigned n = 0;
- // Iterate on vertices (0-faces).
- for_all(adj_v)
+ // Iterate on edges (1-faces).
+ for_all(adj_e)
{
- s += float_ima(adj_v);
+ s += float_ima(adj_e);
++n;
}
float_ima(t) = s / n;
- // A triangle should be adjacent to exactly two vertices.
- mln_invariant(n <= 3);
+ // A triangle should be adjacent to exactly three edges.
+ mln_invariant(n == 3);
}
// Convert the float image into an unsigned image because some
@@ -141,14 +168,6 @@ main(int argc, char* argv[])
for_all(t)
ima(t) = 1000 * float_ima(t);
- /* FIXME: Workaround: Set maximal values on vertices and edges to
- exclude them from the set of minimal values. */
- for_all(v)
- ima(v) = mln_max(mln_value_(ima_t));
- mln::p_n_faces_fwd_piter<D, G> e(float_ima.domain(), 1);
- for_all(e)
- ima(e) = mln_max(mln_value_(ima_t));
-
/*-----------------.
| Simplification. |
`-----------------*/
@@ -157,7 +176,15 @@ main(int argc, char* argv[])
typedef mln::complex_lower_dim_connected_n_face_neighborhood<D, G> nbh_t;
nbh_t nbh;
- ima_t closed_ima = mln::morpho::closing::area(ima, nbh, lambda);
+ // Predicate type: is a face a triangle (2-face)?
+ typedef mln::topo::is_n_face<mln_psite_(ima_t), 2> is_a_triangle_t;
+ is_a_triangle_t is_a_triangle;
+
+ // Consider only triangles.
+ ima_t closed_ima = mln::duplicate(ima);
+ mln::data::paste(mln::morpho::closing::area(ima | is_a_triangle,
+ nbh, lambda),
+ closed_ima);
/*---------------.
| Local minima. |
@@ -166,68 +193,13 @@ main(int argc, char* argv[])
typedef mln::value::label_16 label_t;
label_t nminima;
- /* FIXME: We should use something like `ima_t | p_n_faces(2)' instead
- of `ima_t' here. Or better: `input' should only associate data
- to 2-faces. */
+ // Consider only triangles.
typedef mln_ch_value_(ima_t, label_t) label_ima_t;
- label_ima_t minima =
- mln::labeling::regional_minima(closed_ima, nbh, nminima);
-
- typedef mln::complex_higher_neighborhood<D, G> higher_nbh_t;
- higher_nbh_t higher_nbh;
-
- // Propagate minima values from triangles to edges.
- // FIXME: Factor this inside a function.
- mln_niter_(higher_nbh_t) adj_t(higher_nbh, e);
- for_all(e)
- {
- label_t ref_adj_minimum = mln::literal::zero;
- for_all(adj_t)
- if (minima(adj_t) == mln::literal::zero)
- {
- // If E is adjacent to a non-minimal triangle, then it must
- // not belong to a minima.
- ref_adj_minimum = mln::literal::zero;
- break;
- }
- else
- {
- if (ref_adj_minimum == mln::literal::zero)
- // If this is the first minimum seen, use it as a reference.
- ref_adj_minimum = minima(adj_t);
- else
- // If this is not the first time a minimum is encountered,
- // ensure it is REF_ADJ_MINIMUM.
- mln_assertion(minima(adj_t) == ref_adj_minimum);
- }
- minima(e) = ref_adj_minimum;
- }
-
- // Likewise from edges to edges to vertices.
- mln_niter_(higher_nbh_t) adj_e(higher_nbh, v);
- for_all(v)
- {
- label_t ref_adj_minimum = mln::literal::zero;
- for_all(adj_e)
- if (minima(adj_e) == mln::literal::zero)
- {
- // If V is adjacent to a non-minimal triangle, then it must
- // not belong to a minima.
- ref_adj_minimum = mln::literal::zero;
- break;
- }
- else
- {
- if (ref_adj_minimum == mln::literal::zero)
- // If this is the first minimum seen, use it as a reference.
- ref_adj_minimum = minima(adj_e);
- else
- // If this is not the first time a minimum is encountered,
- // ensure it is REF_ADJ_MINIMUM.
- mln_assertion(minima(adj_e) == ref_adj_minimum);
- }
- minima(v) = ref_adj_minimum;
- }
+ label_ima_t minima;
+ mln::initialize(minima, closed_ima);
+ mln::data::paste(mln::labeling::regional_minima(closed_ima | is_a_triangle,
+ nbh, nminima),
+ minima);
/*-----------------------.
| Initial binary image. |
@@ -242,13 +214,39 @@ main(int argc, char* argv[])
typedef mln_ch_value_(ima_t, bool) bin_ima_t;
bin_ima_t surface(minima.domain());
- mln::data::fill(surface, true);
- // Dig ``holes'' in the surface surface by setting minima faces to false.
- // FIXME: Use fill with an image_if instead, when available/working.
- mln_piter_(bin_ima_t) f(minima.domain());
- for_all(f)
- if (minima(f) != mln::literal::zero)
- surface(f) = false;
+
+ // Predicate type: is a face an edge (1-face)?
+ typedef mln::topo::is_n_face<mln_psite_(ima_t), 1> is_an_edge_t;
+ is_an_edge_t is_an_edge;
+ // Predicate type: is a face a vertex (0-face)?
+ typedef mln::topo::is_n_face<mln_psite_(ima_t), 0> is_a_vertex_t;
+ is_a_vertex_t is_a_vertex;
+
+ // Neighborhood type returning the set of (n+1)-faces adjacent to a
+ // an n-face.
+ typedef mln::complex_higher_neighborhood<D, G> higher_adj_nbh_t;
+ higher_adj_nbh_t higher_adj_nbh;
+
+ mln::data::fill(surface, false);
+ // Set non minima triangles to true;
+ mln::data::fill
+ ((surface |
+ mln::pw::value(minima) == mln::pw::cst(mln::literal::zero)).rw(),
+ true);
+ // Extend non minima values from triangles to edges.
+ mln::data::paste (mln::morpho::dilation(mln::extend(surface | is_an_edge,
+ surface),
+ /* Dilations require windows,
+ not neighborhoods. */
+ higher_adj_nbh.win()),
+ surface);
+ // Extend non minima values from edges to vertices.
+ mln::data::paste(mln::morpho::dilation(mln::extend(surface | is_a_vertex,
+ surface),
+ /* Dilations require windows,
+ not neighborhoods. */
+ higher_adj_nbh.win()),
+ surface);
/*-------------.
| 2-collapse. |
@@ -258,9 +256,6 @@ main(int argc, char* argv[])
// Image restricted to triangles. //
// ------------------------------- //
- // Predicate type: is a face a triangle (2-face)?
- typedef mln::topo::is_n_face<mln_psite_(bin_ima_t), D> is_a_triangle_t;
- is_a_triangle_t is_a_triangle;
// Surface image type, of which domain is restricted to triangles.
typedef mln::image_if<bin_ima_t, is_a_triangle_t> bin_triangle_only_ima_t;
// Surface image type, of which iteration (not domain) is restricted
@@ -272,14 +267,6 @@ main(int argc, char* argv[])
// Simple point predicate. //
// ------------------------ //
- // Neighborhood type returning the set of (n-1)-faces adjacent to a
- // an n-face.
- typedef mln::complex_lower_neighborhood<D, G> lower_adj_nbh_t;
- lower_adj_nbh_t lower_adj_nbh;
- // Neighborhood type returning the set of (n+1)-faces adjacent to a
- // an n-face.
- typedef mln::complex_higher_neighborhood<D, G> higher_adj_nbh_t;
- higher_adj_nbh_t higher_adj_nbh;
// Predicate type: is a triangle (2-face) simple?
typedef mln::topo::is_simple_pair< bin_triangle_ima_t,
lower_adj_nbh_t,
--
1.5.6.5
* mln/topo/is_simple_cell.hh
(mln::topo::is_simple_cell<I, N, NL, NH>::operator()): Here.
---
milena/ChangeLog | 7 +++++++
milena/mln/make/attachment.hh | 2 ++
milena/mln/make/detachment.hh | 2 ++
milena/mln/topo/detach_cell.hh | 2 ++
milena/mln/topo/is_facet.hh | 2 ++
milena/mln/topo/is_simple_cell.hh | 26 +++++++++++++++++++++++++-
6 files changed, 40 insertions(+), 1 deletions(-)
diff --git a/milena/ChangeLog b/milena/ChangeLog
index 279f0a6..d4d9a23 100644
--- a/milena/ChangeLog
+++ b/milena/ChangeLog
@@ -1,3 +1,10 @@
+2011-02-24 Roland Levillain <roland(a)lrde.epita.fr>
+
+ State a cell is not simple if it does not correspond to a facet.
+
+ * mln/topo/is_simple_cell.hh
+ (mln::topo::is_simple_cell<I, N, NL, NH>::operator()): Here.
+
2010-09-09 Roland Levillain <roland(a)lrde.epita.fr>
Use mln::data::paste to simplify mesh skeleton applications.
diff --git a/milena/mln/make/attachment.hh b/milena/mln/make/attachment.hh
index 3af5332..63eca65 100644
--- a/milena/mln/make/attachment.hh
+++ b/milena/mln/make/attachment.hh
@@ -74,6 +74,8 @@ namespace mln
attachment(const Image<I>& ima_, const mln_psite(I)& f,
const Neighborhood<N>& nbh_)
{
+ // FIXME: The current implementation of topo::is_facet is too
+ // naive: it does not take the values of the image into account.
mln_precondition(topo::is_facet(f));
mlc_equal(mln_value(I), bool)::check();
diff --git a/milena/mln/make/detachment.hh b/milena/mln/make/detachment.hh
index 6cd01ba..88b70a0 100644
--- a/milena/mln/make/detachment.hh
+++ b/milena/mln/make/detachment.hh
@@ -81,6 +81,8 @@ namespace mln
detachment(const Image<I>& ima_, const mln_psite(I)& f,
const Neighborhood<N>& nbh_)
{
+ // FIXME: The current implementation of topo::is_facet is too
+ // naive: it does not take the values of the image into account.
mln_precondition(topo::is_facet(f));
mlc_equal(mln_value(I), bool)::check();
diff --git a/milena/mln/topo/detach_cell.hh b/milena/mln/topo/detach_cell.hh
index 9e2a489..250babf 100644
--- a/milena/mln/topo/detach_cell.hh
+++ b/milena/mln/topo/detach_cell.hh
@@ -131,6 +131,8 @@ namespace mln
detach_cell<I, N>::operator()(const mln_psite(I)& f)
{
mln_precondition(ima_);
+ // FIXME: The current implementation of topo::is_facet is too
+ // naive: it does not take the values of the image into account.
mln_precondition(topo::is_facet(f));
typedef p_set<mln_psite(I)> faces_t;
diff --git a/milena/mln/topo/is_facet.hh b/milena/mln/topo/is_facet.hh
index 0b10040..675126c 100644
--- a/milena/mln/topo/is_facet.hh
+++ b/milena/mln/topo/is_facet.hh
@@ -52,6 +52,8 @@ namespace mln
# ifndef MLN_INCLUDE_ONLY
+ // FIXME: Too naive: this code does not take the values of the
+ // image into account.
template <unsigned D, typename G>
inline
bool
diff --git a/milena/mln/topo/is_simple_cell.hh b/milena/mln/topo/is_simple_cell.hh
index bf60c3d..0cf50fb 100644
--- a/milena/mln/topo/is_simple_cell.hh
+++ b/milena/mln/topo/is_simple_cell.hh
@@ -81,7 +81,12 @@ namespace mln
/// Set the underlying image.
void set_image(const mln::Image<I>& ima);
- /// Based on the algorithm A2 from couprie.08.pami.
+ /** \brief Test whether a face (expected to be facet) is a
+ simple cell.
+
+ If \a p is not a facet, return false.
+
+ Based on the algorithm A2 from couprie.08.pami. */
/* FIXME: We probably broke the compatiblity with g++ 3.3, as it
seems this compiler does not like an indirect type like the
one of the following operator's argument. Check and possibly
@@ -124,6 +129,25 @@ namespace mln
is_simple_cell<I, N, NL, NH>::operator()(const mln_psite(I)& p) const
{
mln_precondition(ima_);
+ // FIXME: Introduce `const I& ima = *ima_;' and use it instead of
+ // `ima_'. Or introduce an `ima()' accessor?
+
+ // FIXME: We should be using topo::is_facet, but this routine is
+ // too naive, and does not take the values of the image into
+ // account.
+ {
+ // This (part of) ``algorithm'' considers that looking for
+ // faces of dimension n+1 is enough (which is the case
+ // if the image is a complex).
+ NH higher_adj_nbh;
+ mln_niter(NH) n(higher_adj_nbh, p);
+ for_all(n)
+ // If the higher-dim-faces neighborhood is not empty, then P
+ // is included in a face of higher dimension.
+ if (ima_->has(n) && (*ima_)(n))
+ return false;
+ // Otherwise, F is a facet; continue.
+ }
typedef p_set<mln_psite(I)> faces_t;
--
1.5.6.5
* mln/topo/skeleton/priority_driven_thinning.hh
(mln::topo::skeleton::priority_driven_thinning): Catch up
with mln::topo::skeleton::breadth_first_thinning.
* mln/topo/detach_point.hh: Turn into a functor to match the new
interface of thinning algorithms.
* tests/topo/skeleton/breadth_first_thinning.cc
* tests/topo/skeleton/breadth_first_thinning_constrained.cc
* tests/topo/skeleton/priority_driven_thinning.cc
* tests/topo/skeleton/priority_driven_thinning_constrained.cc:
Adjust.
---
milena/ChangeLog | 15 +++++
milena/mln/topo/detach_point.hh | 66 +++++++++++++++++---
.../mln/topo/skeleton/priority_driven_thinning.hh | 23 +++++--
.../tests/topo/skeleton/breadth_first_thinning.cc | 6 +-
.../skeleton/breadth_first_thinning_constrained.cc | 6 +-
.../topo/skeleton/priority_driven_thinning.cc | 6 +-
.../priority_driven_thinning_constrained.cc | 6 +-
7 files changed, 104 insertions(+), 24 deletions(-)
diff --git a/milena/ChangeLog b/milena/ChangeLog
index d4d9a23..a4a1345 100644
--- a/milena/ChangeLog
+++ b/milena/ChangeLog
@@ -1,5 +1,20 @@
2011-02-24 Roland Levillain <roland(a)lrde.epita.fr>
+ Make the interface of thinning algorithms uniform w.r.t. functors.
+
+ * mln/topo/skeleton/priority_driven_thinning.hh
+ (mln::topo::skeleton::priority_driven_thinning): Catch up
+ with mln::topo::skeleton::breadth_first_thinning.
+ * mln/topo/detach_point.hh: Turn into a functor to match the new
+ interface of thinning algorithms.
+ * tests/topo/skeleton/breadth_first_thinning.cc
+ * tests/topo/skeleton/breadth_first_thinning_constrained.cc
+ * tests/topo/skeleton/priority_driven_thinning.cc
+ * tests/topo/skeleton/priority_driven_thinning_constrained.cc:
+ Adjust.
+
+2011-02-24 Roland Levillain <roland(a)lrde.epita.fr>
+
State a cell is not simple if it does not correspond to a facet.
* mln/topo/is_simple_cell.hh
diff --git a/milena/mln/topo/detach_point.hh b/milena/mln/topo/detach_point.hh
index 818f7cd..6e4d22d 100644
--- a/milena/mln/topo/detach_point.hh
+++ b/milena/mln/topo/detach_point.hh
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 EPITA Research and Development Laboratory (LRDE)
+// Copyright (C) 2010, 2011 EPITA Research and Development Laboratory (LRDE)
//
// This file is part of Olena.
//
@@ -29,8 +29,7 @@
/// \file
/// \brief Detaching a point from a binary image.
-// FIXME: Not generic. Swap arguments and use Image<I> and
-// mln_psite(I) as types.
+# include <mln/metal/equal.hh>
# include <mln/core/image/image2d.hh>
# include <mln/core/alias/point2d.hh>
@@ -41,19 +40,68 @@ namespace mln
namespace topo
{
- /// \brief Detach a point from a binary image.
- inline
- void
- detach_point(const mln::point2d& p, mln::image2d<bool>& ima);
+ /// \brief Functor detaching a point from a binary image.
+ template <typename I>
+ class detach_point
+ {
+ public:
+ /// Build a functor.
+ detach_point();
+
+ /// Build a functor, and assign an image to it.
+ ///
+ /// \param ima The image.
+ detach_point(Image<I>& ima);
+
+ /// Set the underlying image.
+ void set_image(Image<I>& ima);
+
+ /// \brief Detach point \a p from the image.
+ void operator()(const mln_psite(I)& p) const;
+
+ private:
+ /// The image.
+ I* ima_;
+ };
+
# ifndef MLN_INCLUDE_ONLY
+ template <typename I>
+ inline
+ detach_point<I>::detach_point()
+ : ima_(0)
+ {
+ // Ensure I is a binary image type.
+ /* FIXME: Not compatible with proxy/morphers on values. */
+ mlc_equal(mln_value(I), bool)::check();
+ }
+
+ template <typename I>
+ inline
+ detach_point<I>::detach_point(Image<I>& ima)
+ : ima_(exact(&ima))
+ {
+ // Ensure I is a binary image type.
+ /* FIXME: Not compatible with proxy/morphers on values. */
+ mlc_equal(mln_value(I), bool)::check();
+ }
+
+ template <typename I>
+ inline
+ void
+ detach_point<I>::set_image(Image<I>& ima)
+ {
+ ima_ = exact(&ima);
+ }
+
+ template <typename I>
inline
void
- detach_point(const mln::point2d& p, mln::image2d<bool>& ima)
+ detach_point<I>::operator()(const mln_psite(I)& p) const
{
- ima(p) = false;
+ (*ima_)(p) = false;
}
# endif // MLN_INCLUDE_ONLY
diff --git a/milena/mln/topo/skeleton/priority_driven_thinning.hh b/milena/mln/topo/skeleton/priority_driven_thinning.hh
index e29062f..ad8e63d 100644
--- a/milena/mln/topo/skeleton/priority_driven_thinning.hh
+++ b/milena/mln/topo/skeleton/priority_driven_thinning.hh
@@ -1,4 +1,5 @@
-// Copyright (C) 2009, 2010 EPITA Research and Development Laboratory (LRDE)
+// Copyright (C) 2009, 2010, 2011 EPITA Research and Development
+// Laboratory (LRDE)
//
// This file is part of Olena.
//
@@ -62,10 +63,14 @@ namespace mln
(sites). This functor must provide a method
<tt>void set_image(const Image<I>&)</tt>.
\param detach A function used to detach a cell from \a input.
+ This functor must provide a method
+ <tt>void set_image(const Image<I>&)</tt>.
\param priority A priority function expressed as an image.
\param constraint A constraint on point (site); if it
returns \c false for a point, this point
- will not be removed. */
+ will not be removed.
+
+ Keywords: skeletons, simple points. */
template <typename I, typename N, typename F, typename G, typename J,
typename H>
mln_concrete(I)
@@ -87,9 +92,12 @@ namespace mln
\param is_simple The predicate on the simplicity of points
(sites). This functor must provide a method
<tt>void set_image(const Image<I>&)</tt>.
- \param detach A function used to detach a cell from
- \a input.
- \param priority A priority function expressed as an image. */
+ \param detach A function used to detach a cell from \a input.
+ This functor must provide a method
+ <tt>void set_image(const Image<I>&)</tt>.
+ \param priority A priority function expressed as an image.
+
+ Keywords: skeletons, simple points. */
template <typename I, typename N, typename F, typename G, typename J>
mln_concrete(I)
priority_driven_thinning(const Image<I>& input,
@@ -121,8 +129,9 @@ namespace mln
const H& constraint = exact(constraint_);
mln_concrete(I) output = duplicate(input);
- // Attach the work image to IS_SIMPLE.
+ // Attach the work image to IS_SIMPLE and DETACH.
is_simple.set_image(output);
+ detach.set_image(output);
typedef mln_psite(I) psite;
typedef p_queue_fast<psite> queue_t;
@@ -141,7 +150,7 @@ namespace mln
psite p = queue.pop_front();
if (output(p) && constraint(p) && is_simple(p))
{
- detach(p, output);
+ detach(p);
mln_niter(N) n(nbh, p);
for_all(n)
{
diff --git a/milena/tests/topo/skeleton/breadth_first_thinning.cc b/milena/tests/topo/skeleton/breadth_first_thinning.cc
index e77421a..1ed7260 100644
--- a/milena/tests/topo/skeleton/breadth_first_thinning.cc
+++ b/milena/tests/topo/skeleton/breadth_first_thinning.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 EPITA Research and Development Laboratory (LRDE)
+// Copyright (C) 2010, 2011 EPITA Research and Development Laboratory (LRDE)
//
// This file is part of Olena.
//
@@ -64,9 +64,11 @@ int main()
// Simplicity criterion functor.
topo::is_simple_point2d<I, N> is_simple(nbh_fg, nbh_bg);
+ // Simple point detach procedure.
+ topo::detach_point<I> detach;
I output = topo::skeleton::breadth_first_thinning(input, nbh_fg,
is_simple,
- topo::detach_point);
+ detach);
io::pbm::save(output, "breadth_first_thinning-small.pbm");
}
diff --git a/milena/tests/topo/skeleton/breadth_first_thinning_constrained.cc b/milena/tests/topo/skeleton/breadth_first_thinning_constrained.cc
index 8ec213a..c8fbe52 100644
--- a/milena/tests/topo/skeleton/breadth_first_thinning_constrained.cc
+++ b/milena/tests/topo/skeleton/breadth_first_thinning_constrained.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 EPITA Research and Development Laboratory (LRDE)
+// Copyright (C) 2010, 2011 EPITA Research and Development Laboratory (LRDE)
//
// This file is part of Olena.
//
@@ -65,12 +65,14 @@ int main()
// Simplicity criterion functor.
topo::is_simple_point2d<I, N> is_simple(nbh_fg, nbh_bg);
+ // Simple point detach procedure.
+ topo::detach_point<I> detach;
// Constraint: do not collapse end points.
topo::is_not_end_point<I, N> constraint(nbh_fg, input);
I output = topo::skeleton::breadth_first_thinning(input, nbh_fg,
is_simple,
- topo::detach_point,
+ detach,
constraint);
io::pbm::save(output, "breadth_first_thinning_constrained-small.pbm");
}
diff --git a/milena/tests/topo/skeleton/priority_driven_thinning.cc b/milena/tests/topo/skeleton/priority_driven_thinning.cc
index 36470d1..6cfacae 100644
--- a/milena/tests/topo/skeleton/priority_driven_thinning.cc
+++ b/milena/tests/topo/skeleton/priority_driven_thinning.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 EPITA Research and Development Laboratory (LRDE)
+// Copyright (C) 2010, 2011 EPITA Research and Development Laboratory (LRDE)
//
// This file is part of Olena.
//
@@ -68,6 +68,8 @@ int main()
// Simplicity criterion functor.
topo::is_simple_point2d<I, N> is_simple(nbh_fg, nbh_bg);
+ // Simple point detach procedure.
+ topo::detach_point<I> detach;
// Distance type.
typedef value::int_u8 D;
@@ -84,7 +86,7 @@ int main()
I output = topo::skeleton::priority_driven_thinning(input, nbh_fg,
is_simple,
- topo::detach_point,
+ detach,
priority);
io::pbm::save(output, "priority_driven_thinning-small.pbm");
}
diff --git a/milena/tests/topo/skeleton/priority_driven_thinning_constrained.cc b/milena/tests/topo/skeleton/priority_driven_thinning_constrained.cc
index 822d5cd..32e17a1 100644
--- a/milena/tests/topo/skeleton/priority_driven_thinning_constrained.cc
+++ b/milena/tests/topo/skeleton/priority_driven_thinning_constrained.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 EPITA Research and Development Laboratory (LRDE)
+// Copyright (C) 2010, 2011 EPITA Research and Development Laboratory (LRDE)
//
// This file is part of Olena.
//
@@ -69,6 +69,8 @@ int main()
// Simplicity criterion functor.
topo::is_simple_point2d<I, N> is_simple(nbh_fg, nbh_bg);
+ // Simple point detach procedure.
+ topo::detach_point<I> detach;
// Constraint: do not collapse end points.
topo::is_not_end_point<I, N> constraint(nbh_fg, input);
@@ -87,7 +89,7 @@ int main()
I output = topo::skeleton::priority_driven_thinning(input, nbh_fg,
is_simple,
- topo::detach_point,
+ detach,
priority,
constraint);
io::pbm::save(output, "priority_driven_thinning_constrained-small.pbm");
--
1.5.6.5