Olena-patches
Threads by month
- ----- 2025 -----
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- 9625 discussions

last-svn-commit-871-gf181225 Fix precision issue with algebra::vec conversion.
by Guillaume Lazzara 17 May '11
by Guillaume Lazzara 17 May '11
17 May '11
* mln/core/point.hh: Round float values.
* tests/geom/rotate.cc: Check precision issues
---
milena/ChangeLog | 9 +++++++++
milena/mln/core/point.hh | 32 ++++++++++----------------------
milena/tests/geom/rotate.cc | 22 ++++++++++++----------
3 files changed, 31 insertions(+), 32 deletions(-)
diff --git a/milena/ChangeLog b/milena/ChangeLog
index 3437368..5523182 100644
--- a/milena/ChangeLog
+++ b/milena/ChangeLog
@@ -1,5 +1,14 @@
2011-05-17 Guillaume Lazzara <z(a)lrde.epita.fr>
+ Fix precision issue with algebra::vec conversion.
+
+ * mln/core/point.hh: Round float values.
+
+ * tests/geom/rotate.cc: Add a test to be sure there is no
+ precision issues.
+
+2011-05-17 Guillaume Lazzara <z(a)lrde.epita.fr>
+
Add fastest versions for skeleton constrained related algorithms.
* mln/morpho/skeleton_constrained.hh,
diff --git a/milena/mln/core/point.hh b/milena/mln/core/point.hh
index df1fc2c..9cc57b7 100644
--- a/milena/mln/core/point.hh
+++ b/milena/mln/core/point.hh
@@ -331,16 +331,10 @@ namespace mln
mlc_bool(G::dim == n)::check();
unsigned j = 0;
- //FIXME: to be improved while adding a conversion routine.
- if (dim < 3)
- to.hook_coord_() = from;
- else
- {
- for (unsigned i = dim - 2; i < dim; ++i)
- to[i] = mln::internal::convert_data<C2>(from[j++]);
- for (unsigned i = 2; i < dim; ++i, ++j)
- to[i-j] = mln::internal::convert_data<C2>(from[j]);
- }
+ for (unsigned i = dim - 2; i < dim; ++i)
+ to[i] = mln::internal::convert_data<C2>(from[j++]);
+ for (unsigned i = 2; i < dim; ++i, ++j)
+ to[i-j] = mln::internal::convert_data<C2>(from[j]);
}
template <unsigned n, typename C1, typename G>
@@ -352,16 +346,10 @@ namespace mln
mlc_bool(G::dim == n)::check();
unsigned j = 0;
- //FIXME: to be improved while adding a conversion routine.
- if (dim < 3)
- to.hook_coord_() = from;
- else
- {
- for (unsigned i = dim - 2; i < dim; ++i)
- to[i] = from[j++];
- for (unsigned i = 2; i < dim; ++i, ++j)
- to[i-j] = from[j];
- }
+ for (unsigned i = dim - 2; i < dim; ++i)
+ to[i] = from[j++];
+ for (unsigned i = 2; i < dim; ++i, ++j)
+ to[i-j] = from[j];
}
@@ -566,9 +554,9 @@ namespace mln
mln::algebra::vec<G::dim, float> tmp;
unsigned j = 0;
for (unsigned i = dim - 2; i < dim; ++i)
- tmp[j++] = coord_[i];
+ tmp[j++] = mln::internal::convert_data<float>(coord_[i]);
for (unsigned i = 2; i < dim; ++i, ++j)
- tmp[j] = coord_[i-j];
+ tmp[j] = mln::internal::convert_data<float>(coord_[i-j]);
return tmp;
}
diff --git a/milena/tests/geom/rotate.cc b/milena/tests/geom/rotate.cc
index 0b7a842..bc7519f 100644
--- a/milena/tests/geom/rotate.cc
+++ b/milena/tests/geom/rotate.cc
@@ -1,5 +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.
//
@@ -27,18 +27,17 @@
# include <mln/core/image/image2d.hh>
# include <mln/geom/rotate.hh>
# include <mln/make/image.hh>
-# include <mln/make/box1d.hh>
# include <mln/data/compare.hh>
int main()
{
using namespace mln;
- bool ref_values[][5] = { { 0, 1, 0, 0, 0 },
- { 0, 1, 1, 0, 0 },
- { 0, 0, 1, 1, 0 },
- { 0, 0, 0, 1, 1 },
- { 0, 0, 0, 0, 1 } };
+ bool ref_values[][5] = { { 0, 0, 0, 0, 0 },
+ { 0, 1, 0, 0, 0 },
+ { 0, 0, 1, 0, 0 },
+ { 0, 0, 0, 1, 0 },
+ { 0, 0, 0, 0, 0 } };
bool values[][5] = { { 0, 0, 1, 0, 0 },
@@ -49,10 +48,13 @@ int main()
image2d<bool> ima = make::image(values);
-
image2d<bool> ref = make::image(ref_values);
- image2d<bool> ima_rot = geom::rotate(ima, 45);
+ image2d<bool> ima_rot = geom::rotate(ima, 45, false, ima.domain());
mln_assertion(ima_rot == ref);
+
+ ima_rot = geom::rotate(ima_rot, - 45, false, ima.domain());
+
+ mln_assertion(ima_rot == ima);
}
--
1.5.6.5
1
0

17 May '11
* scribo/estim/components_features.hh,
* scribo/estim/internal/compute_skeleton.hh: Here.
---
scribo/ChangeLog | 7 +++++++
scribo/scribo/estim/components_features.hh | 4 ++--
scribo/scribo/estim/internal/compute_skeleton.hh | 4 ++--
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index fedc5ec..0b6042c 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,3 +1,10 @@
+2011-05-17 Guillaume Lazzara <z(a)lrde.epita.fr>
+
+ Fix use of skeleton_constrained.
+
+ * scribo/estim/components_features.hh,
+ * scribo/estim/internal/compute_skeleton.hh: Here.
+
2011-05-12 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
Update support for PAGE XML format.
diff --git a/scribo/scribo/estim/components_features.hh b/scribo/scribo/estim/components_features.hh
index ee34123..2f3b691 100644
--- a/scribo/scribo/estim/components_features.hh
+++ b/scribo/scribo/estim/components_features.hh
@@ -124,8 +124,8 @@ namespace scribo
skel =
morpho::skeleton_constrained(bin_input, c8(),
- topo::skeleton::is_simple_point<B,neighb2d>,
- extend(K, false), dist_map);
+ topo::skeleton::is_simple_point<neighb2d>(c8()),
+ K, dist_map);
t.stop();
std::cout << "Skeleton constrained " << t << std::endl;
diff --git a/scribo/scribo/estim/internal/compute_skeleton.hh b/scribo/scribo/estim/internal/compute_skeleton.hh
index 52ecf01..cc2297d 100644
--- a/scribo/scribo/estim/internal/compute_skeleton.hh
+++ b/scribo/scribo/estim/internal/compute_skeleton.hh
@@ -94,8 +94,8 @@ namespace scribo
mln_concrete(I) skel =
morpho::skeleton_constrained(input, c8(),
- topo::skeleton::is_simple_point<I,neighb2d>,
- extend(K, false), dist_map);
+ topo::skeleton::is_simple_point<neighb2d>(c8()),
+ K, dist_map);
trace::exiting("scribo::estim::internal::compute_skeleton");
return skel;
--
1.5.6.5
1
0

17 May '11
* scribo/estim/components_features.hh,
* scribo/estim/internal/compute_skeleton.hh: Here.
---
scribo/ChangeLog | 7 +++++++
1 files changed, 7 insertions(+), 0 deletions(-)
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index 34ae595..a28bdf7 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,3 +1,10 @@
+2011-05-17 Guillaume Lazzara <z(a)lrde.epita.fr>
+
+ Fix use of skeleton_constrained.
+
+ * scribo/estim/components_features.hh,
+ * scribo/estim/internal/compute_skeleton.hh: Here.
+
2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
Add new tests.
--
1.5.6.5
1
0

last-svn-commit-869-g6790b23 Add fastest versions for skeleton constrained related algorithms.
by Guillaume Lazzara 17 May '11
by Guillaume Lazzara 17 May '11
17 May '11
* mln/morpho/skeleton_constrained.hh,
* mln/topo/skeleton/crest.hh: Add fastest versions.
* mln/topo/skeleton/is_simple_point.hh: Rewrite as functor and add
fastest versions.
* tests/topo/skeleton/is_simple_point.cc: Fix test.
---
milena/ChangeLog | 12 +
milena/mln/morpho/skeleton_constrained.hh | 271 ++++++++++++++++++++-----
milena/mln/topo/skeleton/crest.hh | 230 +++++++++++++++++----
milena/mln/topo/skeleton/is_simple_point.hh | 221 ++++++++------------
milena/tests/topo/skeleton/is_simple_point.cc | 20 ++-
5 files changed, 525 insertions(+), 229 deletions(-)
diff --git a/milena/ChangeLog b/milena/ChangeLog
index d12cc20..3437368 100644
--- a/milena/ChangeLog
+++ b/milena/ChangeLog
@@ -1,3 +1,15 @@
+2011-05-17 Guillaume Lazzara <z(a)lrde.epita.fr>
+
+ Add fastest versions for skeleton constrained related algorithms.
+
+ * mln/morpho/skeleton_constrained.hh,
+ * mln/topo/skeleton/crest.hh: Add fastest versions.
+
+ * mln/topo/skeleton/is_simple_point.hh: Rewrite as functor and add
+ fastest versions.
+
+ * tests/topo/skeleton/is_simple_point.cc: Fix test.
+
2011-05-05 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
* mln/io/magick/save.hh: Add support for opacity.
diff --git a/milena/mln/morpho/skeleton_constrained.hh b/milena/mln/morpho/skeleton_constrained.hh
index 9dcf3d6..2c986e6 100644
--- a/milena/mln/morpho/skeleton_constrained.hh
+++ b/milena/mln/morpho/skeleton_constrained.hh
@@ -1,4 +1,5 @@
-// Copyright (C) 2008, 2009 EPITA Research and Development Laboratory (LRDE)
+// Copyright (C) 2008, 2009, 2011 EPITA Research and Development
+// Laboratory (LRDE)
//
// This file is part of Olena.
//
@@ -39,6 +40,7 @@
# include <mln/extension/adjust_duplicate.hh>
# include <mln/data/fill.hh>
+# include <mln/util/timer.hh>
namespace mln
{
@@ -58,71 +60,242 @@ namespace mln
# ifndef MLN_INCLUDE_ONLY
- template <typename I,
- typename N, typename F,
- typename K, typename R>
- inline
- mln_ch_value(I, bool)
- skeleton_constrained(const Image<I>& input_,
- const Neighborhood<N>& nbh_, const F& is_simple,
- const Image<K>& constraint_, const Image<R>& priority_)
+
+ namespace impl
{
- trace::entering("morpho::skeleton_constrained");
- const I& input = exact(input_);
- const N& nbh = exact(nbh_);
- const K& constraint = exact(constraint_);
- const R& priority = exact(priority_);
+ namespace generic
+ {
- mln_precondition(input.is_valid());
- mln_precondition(nbh.is_valid());
- mln_precondition(constraint.is_valid());
- mln_precondition(priority.is_valid());
+ template <typename I,
+ typename N, typename F,
+ typename K, typename R>
+ inline
+ mln_ch_value(I, bool)
+ skeleton_constrained(const Image<I>& input_,
+ const Neighborhood<N>& nbh_, const F& is_simple,
+ const Image<K>& constraint_, const Image<R>& priority_)
+ {
+ trace::entering("morpho::skeleton_constrained");
- extension::adjust_duplicate(input, nbh);
+ const I& input = exact(input_);
+ const N& nbh = exact(nbh_);
+ const K& constraint = exact(constraint_);
+ const R& priority = exact(priority_);
- // FIXME: Tests!
+ mln_precondition(input.is_valid());
+ mln_precondition(nbh.is_valid());
+ mln_precondition(constraint.is_valid());
+ mln_precondition(priority.is_valid());
- typedef mln_psite(I) P;
- typedef p_queue_fast<P> Q;
- p_priority<mln_value(R), Q> q;
+ typedef mln_value(I) V;
+ mlc_is(V, bool)::check();
- mln_ch_value(I, bool) output;
+ extension::adjust_duplicate(input, nbh);
- // Initialization.
- {
- initialize(output, input);
- data::fill(output, input);
- extension::adjust_duplicate(output, nbh);
-
- mln_piter(I) p(input.domain());
- for_all(p)
- if (input(p) == false &&
- is_simple(input, nbh, p)) // p is a simple point of the background.
- {
- q.push(priority(p), p);
- }
- }
+ // FIXME: Tests!
- // Propagation.
- {
- P p;
- mln_niter(N) n(nbh, p);
- while (! q.is_empty())
+ typedef mln_psite(I) P;
+ typedef p_queue_fast<P> Q;
+ p_priority<mln_value(R), Q> q;
+
+ mln_concrete(I) output;
+
+ // Initialization.
+ {
+ output = duplicate(input);
+ extension::adjust_duplicate(output, nbh);
+
+ mln_piter(I) p(output.domain());
+ for_all(p)
+ if (output(p) == false &&
+ is_simple.check(input, p)) // <-- is_simple.check
+ // p is a simple point of the background.
+ {
+ q.push(priority(p), p);
+ }
+ }
+
+ // Propagation.
{
- p = q.pop_front();
- for_all(n)
- if (output.has(n) &&
- output(n) == true &&
- constraint(n) == false &&
- is_simple(output, nbh, n))
+ P p;
+ mln_niter(N) n(nbh, p);
+ while (! q.is_empty())
+ {
+ p = q.pop_front();
+ for_all(n)
+ if (output.has(n) &&
+ output(n) == true &&
+ constraint(n) == false &&
+ is_simple.check(output, n)) // <-- is_simple.check
{
output(n) = false; // Remove n from object.
q.push(priority(n), n);
}
+ }
+ }
+
+ trace::exiting("morpho::skeleton_constrained");
+ return output;
+ }
+
+ } // end of namespace mln::morpho::impl::generic
+
+
+ template <typename I,
+ typename N, typename F,
+ typename K, typename R>
+ inline
+ mln_ch_value(I, bool)
+ skeleton_constrained_fast(const Image<I>& input_,
+ const Neighborhood<N>& nbh_,
+ const F& is_simple,
+ const Image<K>& constraint_,
+ const Image<R>& priority_)
+ {
+ trace::entering("morpho::skeleton_constrained_fast");
+
+ const I& input = exact(input_);
+ const N& nbh = exact(nbh_);
+ const K& constraint = exact(constraint_);
+ const R& priority = exact(priority_);
+
+ mln_precondition(input.is_valid());
+ mln_precondition(nbh.is_valid());
+ mln_precondition(constraint.is_valid());
+ mln_precondition(priority.is_valid());
+
+ typedef mln_value(I) V;
+ mlc_is(V, bool)::check();
+
+ // Whatever the value of the extension, results of this fast
+ // version may differ from the generic one. Indeed, we
+ // cannot check whether a site belongs to the image or not,
+ // so we try to not take border site in consideration by
+ // setting their value to false.
+ extension::adjust_fill(input, nbh, false);
+ extension::adjust_fill(constraint, nbh, false);
+
+ // FIXME: Tests!
+
+ typedef p_queue_fast<unsigned> Q;
+ p_priority<mln_value(R), Q> q;
+
+ mln_concrete(I) output;
+
+ // Initialization.
+ {
+ output = duplicate(input);
+ extension::adjust_fill(output, nbh, false);
+
+ mln_pixter(I) p_out(output);
+ for_all(p_out)
+ if (p_out.val() == false &&
+ is_simple.check__(input, p_out)) // <-- is_simple.check
+ // p is a simple point of the background.
+ {
+ q.push(priority.element(p_out.offset()), p_out);
+ }
}
+
+ // Propagation.
+ {
+ mln_pixter(I) p(output);
+ mln_nixter(I, N) n(p, nbh);
+
+ util::array<int> dp = offsets_wrt(input, nbh);
+ const unsigned n_nbhs = dp.nelements();
+ while (! q.is_empty())
+ {
+ unsigned p = q.pop_front();
+
+ for (unsigned i = 0; i < n_nbhs; ++i)
+ {
+ unsigned n = p + dp[i];
+
+ if (output.element(n) == true &&
+ constraint.element(n) == false &&
+ is_simple.check__(output, n)) // <-- is_simple.check
+ {
+ output.element(n) = false; // Remove n from object.
+ q.push(priority.element(n), n);
+ }
+ }
+ }
+ }
+
+ trace::exiting("morpho::skeleton_constrained_fast");
+ return output;
+ }
+
+
+ } // end of namespace mln::morpho::impl
+
+
+ namespace internal
+ {
+
+ template <typename I,
+ typename N, typename F,
+ typename K, typename R>
+ inline
+ mln_ch_value(I, bool)
+ skeleton_constrained_dispatch(
+ mln::trait::image::value_access::any,
+ mln::trait::image::value_storage::any,
+ const Image<I>& input,
+ const Neighborhood<N>& nbh,
+ const F& is_simple,
+ const Image<K>& constraint,
+ const Image<R>& priority)
+ {
+ return impl::generic::skeleton_constrained(input, nbh,
+ is_simple,
+ constraint,
+ priority);
}
+ template <typename I,
+ typename N, typename F,
+ typename K, typename R>
+ inline
+ mln_ch_value(I, bool)
+ skeleton_constrained_dispatch(
+ mln::trait::image::value_access::direct,
+ mln::trait::image::value_storage::one_block,
+ const Image<I>& input,
+ const Neighborhood<N>& nbh,
+ const F& is_simple,
+ const Image<K>& constraint,
+ const Image<R>& priority)
+ {
+ return impl::skeleton_constrained_fast(input, nbh,
+ is_simple,
+ constraint,
+ priority);
+ }
+
+
+ } // end of namespace mln::morpho::internal
+
+
+ template <typename I,
+ typename N, typename F,
+ typename K, typename R>
+ inline
+ mln_ch_value(I, bool)
+ skeleton_constrained(const Image<I>& input,
+ const Neighborhood<N>& nbh, const F& is_simple,
+ const Image<K>& constraint, const Image<R>& priority)
+ {
+ trace::entering("morpho::skeleton_constrained");
+
+ mln_ch_value(I, bool)
+ output = internal::skeleton_constrained_dispatch(
+ mln_trait_image_value_access(I)(),
+ mln_trait_image_value_storage(I)(),
+ input, nbh, is_simple, constraint, priority);
+
trace::exiting("morpho::skeleton_constrained");
return output;
}
diff --git a/milena/mln/topo/skeleton/crest.hh b/milena/mln/topo/skeleton/crest.hh
index 73a1c66..b4b7b27 100644
--- a/milena/mln/topo/skeleton/crest.hh
+++ b/milena/mln/topo/skeleton/crest.hh
@@ -1,5 +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.
//
@@ -34,6 +34,8 @@
# include <mln/core/concept/image.hh>
# include <mln/core/concept/neighborhood.hh>
# include <mln/data/fill.hh>
+# include <mln/extension/adjust.hh>
+# include <mln/border/equalize.hh>
namespace mln
@@ -103,55 +105,205 @@ namespace mln
# ifndef MLN_INCLUDE_ONLY
+ // IMPLEMENTATIONS
- template <typename I, typename D, typename N>
- mln_concrete(I)
- crest(const Image<I>& input_, const Image<D>& dist_map_,
- const Neighborhood<N>& nbh_, unsigned psi_threshold)
+ namespace impl
{
- trace::entering("topo::skeleton::crest");
- const I& input = exact(input_);
- const D& dist_map = exact(dist_map_);
- const N& nbh = exact(nbh_);
- mlc_equal(mln_value(I), bool)::check();
- mln_precondition(input.is_valid());
- mln_precondition(dist_map.is_valid());
- mln_precondition(nbh.is_valid());
+ namespace generic
+ {
+
+ template <typename I, typename D, typename N>
+ mln_concrete(I)
+ crest(const Image<I>& input_, const Image<D>& dist_map_,
+ const Neighborhood<N>& nbh_, unsigned psi_threshold)
+ {
+ trace::entering("topo::skeleton::impl::generic::crest");
+ const I& input = exact(input_);
+ const D& dist_map = exact(dist_map_);
+ const N& nbh = exact(nbh_);
+
+ mlc_equal(mln_value(I), bool)::check();
+ mln_precondition(input.is_valid());
+ mln_precondition(dist_map.is_valid());
+ mln_precondition(nbh.is_valid());
+
+ mln_concrete(I) is_crest;
+ initialize(is_crest, input);
+ data::fill(is_crest, false);
+
+ mln_piter(I) p(input.domain());
+ mln_niter(N) n(nbh, p);
+ for_all(p)
+ {
+ if (!input(p) || dist_map(p) < static_cast<mln_value(D)>(0))
+ continue;
+
+ unsigned nb_eq = 0;
+ unsigned nb_lt = 0;
+ for_all(n)
+ if (input.domain().has(n)
+ // We want to only consider sites which are part of
+ // the skeleton. If this test is removed, sometimes
+ // edge sites are considered as sites with a high PSI
+ // which is wrong.
+ && dist_map(n) > static_cast<mln_value(D)>(0))
+ {
+ if (dist_map(n) == dist_map(p))
+ ++nb_eq;
+ else if (dist_map(n) < dist_map(p))
+ ++nb_lt;
+ }
+
+ if ((nb_lt + nb_eq) >= psi_threshold) // Pixel Superiority index
+ is_crest(p) = true;
+ }
+
+ trace::exiting("topo::skeleton::impl::generic::crest");
+ return is_crest;
+ }
+
+ } // end of namespace mln::topo::skeleton::impl::generic
- mln_concrete(I) is_crest;
- initialize(is_crest, input);
- data::fill(is_crest, false);
- mln_piter(I) p(input.domain());
- mln_niter(N) n(nbh, p);
- for_all(p)
+
+
+ template <typename I, typename D, typename N>
+ mln_concrete(I)
+ crest_fastest_2d(const Image<I>& input_, const Image<D>& dist_map_,
+ const Neighborhood<N>& nbh_, unsigned psi_threshold)
{
- if (!input(p) || dist_map(p) < static_cast<mln_value(D)>(0))
- continue;
-
- unsigned nb_eq = 0;
- unsigned nb_lt = 0;
- for_all(n)
- if (input.domain().has(n)
- // We want to only consider sites which are part of
- // the skeleton. If this test is removed, sometimes
- // edge sites are considered as sites with a high PSI
- // which is wrong.
- && dist_map(n) > static_cast<mln_value(D)>(0))
+ trace::entering("topo::skeleton::impl::crest_fastest_2d");
+
+ const I& input = exact(input_);
+ const D& dist_map = exact(dist_map_);
+ const N& nbh = exact(nbh_);
+
+ mlc_equal(mln_value(I), bool)::check();
+ mln_precondition(input.is_valid());
+ mln_precondition(dist_map.is_valid());
+ mln_precondition(nbh.is_valid());
+ mln_precondition(input.domain() == dist_map.domain());
+
+ extension::adjust(input, nbh);
+ border::equalize(input, dist_map, input.border());
+
+ mln_concrete(I) is_crest;
+ initialize(is_crest, input);
+ data::fill(is_crest, false);
+
+
+ mln_pixter(const I) p(input);
+ util::array<int> dp = mln::offsets_wrt(input, nbh);
+ unsigned n_nbhs = dp.nelements();
+ for_all(p)
+ {
+ if (!p.val()
+ || dist_map.element(p) < static_cast<mln_value(D)>(0))
+ continue;
+
+ unsigned nb_eq = 0;
+ unsigned nb_lt = 0;
+ for (unsigned i = 0; i < n_nbhs; ++i)
{
- if (dist_map(n) == dist_map(p))
- ++nb_eq;
- else if (dist_map(n) < dist_map(p))
- ++nb_lt;
+ unsigned n = p.offset() + dp[i];
+ if (// We want to only consider sites which are part of
+ // the skeleton. If this test is removed, sometimes
+ // edge sites are considered as sites with a high PSI
+ // which is wrong.
+ dist_map.element(n) > static_cast<mln_value(D)>(0))
+ {
+ if (dist_map.element(n) == dist_map.element(p))
+ ++nb_eq;
+ else if (dist_map.element(n) < dist_map.element(p))
+ ++nb_lt;
+ }
+
+ if ((nb_lt + nb_eq) >= psi_threshold) // Pixel Superiority index
+ is_crest.element(p) = true;
}
- if ((nb_lt + nb_eq) >= psi_threshold) // Pixel Superiority index
- is_crest(p) = true;
+ }
+
+ trace::exiting("topo::skeleton::impl::crest_fastest_2d");
+ return is_crest;
}
+ } // end of namespace mln::topo::skeleton::impl
+
+
+ // DISPATCH
+
+ namespace internal
+ {
+
+ template <typename I, typename D, typename N>
+ mln_concrete(I)
+ crest_dispatch_2d(mln::trait::image::value_storage::any,
+ mln::trait::image::value_access::any,
+ mln::trait::image::ext_domain::any,
+ const Image<I>& input, const Image<D>& dist_map,
+ const Neighborhood<N>& nbh, unsigned psi_threshold)
+ {
+ return skeleton::impl::generic::crest(input, dist_map,
+ nbh, psi_threshold);
+ }
+
+
+ template <typename I, typename D, typename N>
+ mln_concrete(I)
+ crest_dispatch_2d(mln::trait::image::value_storage::one_block,
+ mln::trait::image::value_access::direct,
+ mln::trait::image::ext_domain::some,
+ const Image<I>& input, const Image<D>& dist_map,
+ const Neighborhood<N>& nbh, unsigned psi_threshold)
+ {
+ return skeleton::impl::crest_fastest_2d(input, dist_map,
+ nbh, psi_threshold);
+ }
+
+
+ template <typename I, typename D, typename N>
+ mln_concrete(I)
+ crest_dispatch(const Image<I>& input, const Image<D>& dist_map,
+ const Neighborhood<N>& nbh, unsigned psi_threshold)
+ {
+ unsigned dim = mln_site_(I)::dim;
+
+ if (dim == 2)
+ return
+ crest_dispatch_2d(mln_trait_image_value_storage(I)(),
+ mln_trait_image_value_access(I)(),
+ mln_trait_image_ext_domain(I)(),
+ input, dist_map, nbh, psi_threshold);
+
+ return impl::generic::crest(input, dist_map, nbh, psi_threshold);
+ }
+
+
+ } // end of namespace mln::topo::skeleton::internal
+
+
+ // FACADES
+
+ template <typename I, typename D, typename N>
+ mln_concrete(I)
+ crest(const Image<I>& input, const Image<D>& dist_map,
+ const Neighborhood<N>& nbh, unsigned psi_threshold)
+ {
+ trace::entering("topo::skeleton::crest");
+
+ mlc_equal(mln_value(I), bool)::check();
+ mln_precondition(exact(input).is_valid());
+ mln_precondition(exact(dist_map).is_valid());
+ mln_precondition(exact(nbh).is_valid());
+
+ mln_concrete(I)
+ output = internal::crest_dispatch(input, dist_map,
+ nbh, psi_threshold);
+
trace::exiting("topo::skeleton::crest");
- return is_crest;
+ return output;
}
diff --git a/milena/mln/topo/skeleton/is_simple_point.hh b/milena/mln/topo/skeleton/is_simple_point.hh
index 590bf60..05d82a9 100644
--- a/milena/mln/topo/skeleton/is_simple_point.hh
+++ b/milena/mln/topo/skeleton/is_simple_point.hh
@@ -1,4 +1,5 @@
-// Copyright (C) 2009 EPITA Research and Development Laboratory (LRDE)
+// Copyright (C) 2009, 2011 EPITA Research and Development Laboratory
+// (LRDE)
//
// This file is part of Olena.
//
@@ -60,20 +61,35 @@ namespace mln
\endverbatim
*/
- template <typename I, typename N>
- bool
- is_simple_point(const Image<I>& ima,
- const Neighborhood<N>& nbh,
- const mln_site(I)& p);
+ template <typename N>
+ struct is_simple_point
+ {
+
+ is_simple_point(const Neighborhood<N>& nbh);
+
+ template <typename I>
+ bool check(const I& ima, const mln_psite(I)& p) const;
+ template <typename I>
+ bool check__(const I& ima, unsigned p) const;
+
+ protected:
+ const N& nbh_;
+ bool is_c8_;
+
+ template <typename I>
+ unsigned nb_connexity2d(const I&, bool nbh_c8,
+ const mln_psite(I)& p, bool object) const;
+ template <typename I>
+ unsigned nb_connexity2d__(const I&, bool nbh_c8,
+ unsigned p, bool object) const;
+ };
# ifndef MLN_INCLUDE_ONLY
namespace internal
{
-
-
static const unsigned char nb_connexity_c8[256] =
{
0, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1,
@@ -120,157 +136,94 @@ namespace mln
1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1
};
+ } // end of namespace internal
+ template <typename N>
+ is_simple_point<N>::is_simple_point(const Neighborhood<N>& nbh)
+ : nbh_(exact(nbh)), is_c8_(exact(nbh) == c8())
+ {
+ mln_assertion(nbh_ == c4() || nbh_ == c8());
+ mln_precondition(nbh_.is_valid());
+ }
- template <typename I, typename N>
- inline
- unsigned
- nb_connexity2d(const I& ima,
- const N& nbh,
- const mln_site(I)& p,
- bool object)
- {
- unsigned res = 0;
-
- mln_bkd_niter(N) n(c8(), p);
- for_all(n)
- {
- res = (res << 1);
- if (ima.domain().has(n) && ima(n) == object)
- res = res | 1;
- }
-
- if (nbh == c8())
- return nb_connexity_c8[res];
- else
- {
- mln_assertion(nbh == c4());
- return nb_connexity_c4[res];
- }
- }
-
-
- template <typename N>
- neighb2d
- complement2d(const Neighborhood<N>& nbh_)
- {
- const N& nbh = exact(nbh_);
- mln_precondition(nbh.is_valid());
- mln_precondition(nbh == c4() || nbh == c8());
-
- if (nbh == c4())
- return c8();
- else
- return c4();
- }
-
-
- // Tests.
-
- template <typename I, typename N>
- inline
- void
- is_simple_point_tests(const Image<I>& ima_,
- const Neighborhood<N>& nbh_,
- const mln_site(I)& p)
- {
- const I& ima = exact(ima_);
- const N& nbh = exact(nbh_);
-
- mln_assertion(nbh == c4() || nbh == c8());
- mln_precondition(ima.is_valid());
- mln_precondition(nbh.is_valid());
-
- (void) ima;
- (void) nbh;
- (void) p;
- }
-
- } // end of namespace mln::topo::skeleton::internal
-
-
-
- // Implementations
- namespace impl
+ template <typename N>
+ template <typename I>
+ unsigned
+ is_simple_point<N>::nb_connexity2d(const I& ima,
+ bool nbh_c8,
+ const mln_psite(I)& p,
+ bool object) const
{
+ unsigned res = 0;
- template <typename I, typename N>
- inline
- bool
- is_simple_point2d(const Image<I>& ima_,
- const Neighborhood<N>& nbh_,
- const point2d& p)
+ mln_bkd_niter(N) n(c8(), p);
+ for_all(n)
{
- const I& ima = exact(ima_);
- const N& nbh = exact(nbh_);
-
- internal::is_simple_point_tests(ima, nbh, p);
-
- bool b = (internal::nb_connexity2d(ima, nbh, p, true) == 1)
- && (internal::nb_connexity2d(ima, internal::complement2d(nbh),
- p, false) == 1);
-
- trace::exiting("topo::skeleton::is_simple_point2d");
- return b;
+ res = (res << 1);
+ if (ima.domain().has(n) && ima(n) == object)
+ res = res | 1;
}
- } // end of namespace mln::topo::skeleton::impl
-
-
-
+ if (nbh_c8)
+ return internal::nb_connexity_c8[res];
+ else
+ return internal::nb_connexity_c4[res];
+ }
- // Dispatch
- namespace internal
+ template <typename N>
+ template <typename I>
+ unsigned
+ is_simple_point<N>::nb_connexity2d__(const I& ima,
+ bool nbh_c8,
+ unsigned p,
+ bool object) const
{
+ unsigned res = 0;
- template <typename I, typename N>
- inline
- bool
- is_simple_point_dispatch(const Image<I>& ima,
- const Neighborhood<N>& nbh,
- const point2d& p)
- {
- return impl::is_simple_point2d(ima, nbh, p);
- }
-
+ static util::array<int>
+ noffset = mln::offsets_wrt(ima, c8());
- template <typename I, typename N>
- inline
- bool
- is_simple_point_dispatch(const Image<I>& ima,
- const Neighborhood<N>& nbh,
- const mln_site(I)& p)
+ for (int i = noffset.nelements() - 1; i >= 0; --i)
{
- /// Not implemented for that site type yet.
- mlc_abort(I)::check();
- return false;
+ res = (res << 1);
+ if (ima.element(p + noffset[i]) == object)
+ res = res | 1;
}
- } // end of namespace mln::topo::skeleton::internal
+ if (nbh_c8)
+ return internal::nb_connexity_c8[res];
+ else
+ return internal::nb_connexity_c4[res];
+ }
+ template <typename N>
+ template <typename I>
+ inline
+ bool
+ is_simple_point<N>::check(const I& ima,
+ const mln_psite(I)& p) const
+ {
+ mln_precondition(ima.is_valid());
+ return (nb_connexity2d(ima, is_c8_, p, true) == 1) // Consider neighbor.
+ && (nb_connexity2d(ima, !is_c8_, p, false) == 1); // Consider complement neighbor.
+ }
- // Facade
- template <typename I, typename N>
+ template <typename N>
+ template <typename I>
inline
bool
- is_simple_point(const Image<I>& ima,
- const Neighborhood<N>& nbh,
- const mln_site(I)& p)
+ is_simple_point<N>::check__(const I& ima,
+ unsigned p) const
{
- trace::entering("topo::skeleton::is_simple_point2d");
-
- internal::is_simple_point_tests(ima, nbh, p);
-
- bool b = internal::is_simple_point_dispatch(ima, nbh, p);
-
- trace::exiting("topo::skeleton::is_simple_point2d");
- return b;
+ mln_precondition(ima.is_valid());
+ return (nb_connexity2d__(ima, is_c8_, p, true) == 1) // Consider neighbor
+ && (nb_connexity2d__(ima, !is_c8_, p, false) == 1); // Consider complement neighbor.
}
diff --git a/milena/tests/topo/skeleton/is_simple_point.cc b/milena/tests/topo/skeleton/is_simple_point.cc
index 1989f94..d5ff1e1 100644
--- a/milena/tests/topo/skeleton/is_simple_point.cc
+++ b/milena/tests/topo/skeleton/is_simple_point.cc
@@ -1,4 +1,5 @@
-// Copyright (C) 2009 EPITA Research and Development Laboratory (LRDE)
+// Copyright (C) 2009, 2011 EPITA Research and Development Laboratory
+// (LRDE)
//
// This file is part of Olena.
//
@@ -27,8 +28,6 @@
#include <mln/topo/skeleton/is_simple_point.hh>
#include <mln/make/image.hh>
-#include <mln/debug/println.hh>
-
static const unsigned ref[] = { 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0 };
int main()
@@ -38,10 +37,17 @@ int main()
bool vals[][6] = { { 0, 1, 0, 0, 1, 1 },
{ 0, 0, 0, 0, 1, 1 } };
- image2d<bool> ima = make::image(vals);
+ typedef image2d<bool> I;
+ I ima = make::image(vals);
unsigned i = 0;
- mln_piter_(image2d<bool>) p(ima.domain());
- for_all(p)
- mln_assertion(ref[i++] == topo::skeleton::is_simple_point(ima, c8(), p));
+ topo::skeleton::is_simple_point<neighb2d> is_simple(c8());
+
+ // Test generic version.
+ {
+ mln_piter_(I) p(ima.domain());
+ for_all(p)
+ mln_assertion(ref[i++] == is_simple.check(ima, p));
+ }
+
}
--
1.5.6.5
1
0
* tests/filter/Makefile.am,
* tests/util/Makefile.am: Add new targets.
* tests/filter/objects_on_border.cc,
* tests/util/component_outline.cc: New.
* tests/img/comp_on_borders.pbm,
* tests/img/multi_scale.png,
* tests/img/single_object.pbm: New test data.
---
scribo/ChangeLog | 14 +++++++++
scribo/tests/filter/Makefile.am | 2 +
...{objects_with_holes.cc => objects_on_border.cc} | 16 ++++------
scribo/tests/img/comp_on_borders.pbm | Bin 0 -> 377 bytes
scribo/tests/img/multi_scale.png | Bin 0 -> 86548 bytes
.../tests/img/single_object.pbm | Bin 80 -> 80 bytes
scribo/tests/util/Makefile.am | 4 ++-
.../sauvola.cc => util/component_outline.cc} | 31 ++++++++++++-------
8 files changed, 44 insertions(+), 23 deletions(-)
copy scribo/tests/filter/{objects_with_holes.cc => objects_on_border.cc} (80%)
create mode 100644 scribo/tests/img/comp_on_borders.pbm
create mode 100644 scribo/tests/img/multi_scale.png
copy milena/demos/genericity/neighborhood/input/drawing.pbm => scribo/tests/img/single_object.pbm (60%)
copy scribo/tests/{binarization/sauvola.cc => util/component_outline.cc} (71%)
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index 8db30df..34ae595 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,19 @@
2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+ Add new tests.
+
+ * tests/filter/Makefile.am,
+ * tests/util/Makefile.am: Add new targets.
+
+ * tests/filter/objects_on_border.cc,
+ * tests/util/component_outline.cc: New.
+
+ * tests/img/comp_on_borders.pbm,
+ * tests/img/multi_scale.png,
+ * tests/img/single_object.pbm: New test data.
+
+2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+
New routine to extract both thin and thick separators.
* scribo/primitive/extract/lines_h_thick_and_thin.hh,
diff --git a/scribo/tests/filter/Makefile.am b/scribo/tests/filter/Makefile.am
index 8a27f91..906a1a7 100644
--- a/scribo/tests/filter/Makefile.am
+++ b/scribo/tests/filter/Makefile.am
@@ -19,6 +19,7 @@ include $(top_srcdir)/scribo/tests/tests.mk
check_PROGRAMS = \
objects_with_holes \
+ objects_on_border \
object_links_bbox_h_ratio \
object_links_bbox_w_ratio \
object_links_bbox_overlap \
@@ -36,6 +37,7 @@ check_PROGRAMS = \
objects_with_holes_SOURCES = objects_with_holes.cc
+objects_on_border_SOURCES = objects_on_border.cc
object_links_bbox_h_ratio_SOURCES = object_links_bbox_h_ratio.cc
object_links_bbox_w_ratio_SOURCES = object_links_bbox_w_ratio.cc
object_links_bbox_overlap_SOURCES = object_links_bbox_overlap.cc
diff --git a/scribo/tests/filter/objects_with_holes.cc b/scribo/tests/filter/objects_on_border.cc
similarity index 80%
copy from scribo/tests/filter/objects_with_holes.cc
copy to scribo/tests/filter/objects_on_border.cc
index d50d6d5..7a09928 100644
--- a/scribo/tests/filter/objects_with_holes.cc
+++ b/scribo/tests/filter/objects_on_border.cc
@@ -1,5 +1,4 @@
-// Copyright (C) 2009, 2010 EPITA Research and Development Laboratory
-// (LRDE)
+// Copyright (C) 2011 EPITA Research and Development Laboratory (LRDE)
//
// This file is part of Olena.
//
@@ -29,12 +28,9 @@
#include <mln/core/image/image2d.hh>
#include <mln/core/alias/neighb2d.hh>
#include <mln/io/pbm/load.hh>
-#include <mln/value/label_8.hh>
-#include <mln/value/label_32.hh>
-#include <mln/data/convert.hh>
-#include <mln/data/wrap.hh>
+#include <mln/value/label_16.hh>
-#include <scribo/filter/objects_with_holes.hh>
+#include <scribo/filter/objects_on_border.hh>
#include <scribo/primitive/extract/components.hh>
#include "tests/data.hh"
@@ -44,7 +40,7 @@ int main()
using namespace mln;
using namespace scribo;
- std::string img = SCRIBO_IMG_DIR "/text_to_group_and_clean.pbm";
+ std::string img = SCRIBO_IMG_DIR "/comp_on_borders.pbm";
image2d<bool> input;
io::pbm::load(input, img.c_str());
@@ -52,13 +48,13 @@ int main()
value::label_16 nlabels;
typedef component_set<image2d<value::label_16> > O;
O comps = scribo::primitive::extract::components(input, c8(), nlabels);
- O filtered_comps = scribo::filter::objects_with_holes(comps, 2, 1);
+ O filtered_comps = scribo::filter::components_on_border(comps);
unsigned valid_comps = 0;
for_all_comps(c, filtered_comps)
if (filtered_comps(c).is_valid())
++valid_comps;
- mln_assertion(valid_comps == 8u);
+ mln_assertion(valid_comps == 6u);
}
diff --git a/scribo/tests/img/comp_on_borders.pbm b/scribo/tests/img/comp_on_borders.pbm
new file mode 100644
index 0000000000000000000000000000000000000000..f59ec4f094629fdb1591fbb327a8f89be9e3c760
GIT binary patch
literal 377
zcmWGA;Zjy`4svx2@ei_6aQE~LPzdnzRdCD9DM>9-2um$0&dkqKFw`^TGBr^!G3Pq|
z|NsAg_J2?*pcoMR`TzeTHVP_7kYZ3^VQ3Iws9*o}-~Q`ANT#6LLDqoXLXcvx|HJVA
h14I4(zyJP!|3NYZ)eg1}<aez0{Q!yr<xnZGN&s_2EBgQd
literal 0
HcmV?d00001
diff --git a/scribo/tests/img/multi_scale.png b/scribo/tests/img/multi_scale.png
new file mode 100644
index 0000000000000000000000000000000000000000..0d443cc7dca5362d9a8cd8020575e232f75bc0f8
GIT binary patch
literal 86548
zcmeFa2UJwo_dj~o*szW?OArJVDG@a)3JR!TLlHp%+fafPB4R}8j2d(V3l^{<YD9vd
zAU4ziMubE~#exBY(jpd$6s0KbowN73Gh_1mzqi)=zxCc)Z@GVblOJL3+<W&qXV=f(
zdzY=vtUgMEl{k*;GiSD?9mjQjN&nZs8~HD5Vk30PzrI^NZT>WlyR@%Yt7}*Cd#7LQ
zW=-cVKKlMW`9<re*>>|e&VMAwiGJm{FXTUo-f^7I7>=uO;W(2c9H+kF#Lb0M$RE17
zS<kZMI_UqMN{c&4{!7mdvzPqBalMr3|2lEUP7I{~ar1_0tN91vdfImDOumzwV>xY+
z@84Cg)^1r`ezLJTB;{#SpPs||&(Q60Ml7AF5<6tV%Z52kvwpwi(R;1s-+Q#CKhc@5
z6|whI&$am<%hDY$y!|6ZrStM1?|Dm({?zII4WA)uzqF;Unqz5cX{JeT%2R~x+KNv6
zFXZ3-hi&LC|5Z!pyASf;IQMN*4f(I4dHqwn$bXIK*?EinH*VatlD_g^Ek^cusc@US
z-)%LO|B*YY_0>(4zulBD=No%*hkV;ez=v-~2H2qWO*l|Kd=n0^8@_27WWzV%0DSm=
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�~SI5`j%(5
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
Zl-7$xT5n>*yXEvUmkU<$uPyb9`43eSlF9%8
literal 0
HcmV?d00001
diff --git a/milena/demos/genericity/neighborhood/input/drawing.pbm b/scribo/tests/img/single_object.pbm
similarity index 60%
copy from milena/demos/genericity/neighborhood/input/drawing.pbm
copy to scribo/tests/img/single_object.pbm
index 0de5bcafe1a8d9fa72e66f8cbbb0c4e7abecff5c..5d83da459d9a4993cf7f91f591b6d553d5f274cf 100644
GIT binary patch
delta 37
bcmWFtm|&pr9}fQ1|FHjI|EC@<0A>IH6t^DD
delta 37
vcmV+=0NVdhP>?Vne*nKm-(l|q_mlg>-T$utcYCk3-`oH1`S<zz{yqLak*^qG
diff --git a/scribo/tests/util/Makefile.am b/scribo/tests/util/Makefile.am
index 8d132aa..e5b110d 100644
--- a/scribo/tests/util/Makefile.am
+++ b/scribo/tests/util/Makefile.am
@@ -18,9 +18,11 @@ include $(top_srcdir)/scribo/tests/tests.mk
check_PROGRAMS = \
color_to_hex \
- hex_to_color
+ hex_to_color \
+ component_outline
color_to_hex_SOURCES = color_to_hex.cc
hex_to_color_SOURCES = hex_to_color.cc
+component_outline_SOURCES = component_outline.cc
TESTS = $(check_PROGRAMS)
diff --git a/scribo/tests/binarization/sauvola.cc b/scribo/tests/util/component_outline.cc
similarity index 71%
copy from scribo/tests/binarization/sauvola.cc
copy to scribo/tests/util/component_outline.cc
index 33eb59f..cea9cf1 100644
--- a/scribo/tests/binarization/sauvola.cc
+++ b/scribo/tests/util/component_outline.cc
@@ -23,30 +23,37 @@
// exception does not however invalidate any other reasons why the
// executable file might be covered by the GNU General Public License.
-/// \file
+// \file
#include <mln/core/image/image2d.hh>
-#include <mln/data/compare.hh>
-#include <mln/value/int_u8.hh>
-#include <mln/io/pgm/load.hh>
+#include <mln/core/site_set/p_array.hh>
#include <mln/io/pbm/load.hh>
-#include <mln/io/pbm/save.hh>
-#include <scribo/binarization/sauvola.hh>
+#include <scribo/util/component_outline.hh>
#include "tests/data.hh"
int main()
{
using namespace mln;
+ using namespace scribo;
- image2d<value::int_u8> input;
- io::pgm::load(input, MILENA_IMG_DIR "/lena.pgm");
+ static const point2d ref[] = {
+ point2d(5,8), point2d(5,6),
+ point2d(6,5), point2d(7,5),
+ point2d(8,6), point2d(8,8),
+ point2d(7,9), point2d(6,9)
+ };
- image2d<bool> bin = scribo::binarization::sauvola(input, 101);
+ std::string f = SCRIBO_IMG_DIR "/single_object.pbm";
- image2d<bool> ref;
- io::pbm::load(ref, SCRIBO_TESTS_DIR "binarization/sauvola.ref.pbm");
+ image2d<bool> input;
+ io::pbm::load(input, f);
- mln_assertion(bin == ref);
+ p_array<point2d> par = scribo::util::component_outline(input, 0.2);
+
+ for (unsigned i = 0; i < par.nsites(); ++i)
+ mln_assertion(par[i] == ref[i]);
+
+ return 0;
}
--
1.5.6.5
1
0

last-svn-commit-877-gcc99fdd New routine to extract both thin and thick separators.
by Guillaume Lazzara 16 May '11
by Guillaume Lazzara 16 May '11
16 May '11
* scribo/primitive/extract/lines_h_thick_and_thin.hh,
* src/primitive/extract/Makefile.am,
* src/primitive/extract/lines_h_thick_and_thin.cc,
* src/primitive/extract/lines_thick_and_thin.cc: New.
---
scribo/ChangeLog | 9 +
.../primitive/extract/lines_h_thick_and_thin.hh | 514 ++++++++++++++++++++
scribo/src/primitive/extract/Makefile.am | 18 +-
.../extract/lines_h_thick_and_thin.cc} | 30 +-
.../src/primitive/extract/lines_thick_and_thin.cc | 100 ++++
5 files changed, 648 insertions(+), 23 deletions(-)
create mode 100644 scribo/scribo/primitive/extract/lines_h_thick_and_thin.hh
copy scribo/src/{misc/negate.cc => primitive/extract/lines_h_thick_and_thin.cc} (75%)
create mode 100644 scribo/src/primitive/extract/lines_thick_and_thin.cc
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index 83797f6..8db30df 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,14 @@
2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+ New routine to extract both thin and thick separators.
+
+ * scribo/primitive/extract/lines_h_thick_and_thin.hh,
+ * src/primitive/extract/Makefile.am,
+ * src/primitive/extract/lines_h_thick_and_thin.cc,
+ * src/primitive/extract/lines_thick_and_thin.cc: New.
+
+2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+
* scribo/primitive/extract/non_text_hdoc.hh: New routine for
historical documents.
diff --git a/scribo/scribo/primitive/extract/lines_h_thick_and_thin.hh b/scribo/scribo/primitive/extract/lines_h_thick_and_thin.hh
new file mode 100644
index 0000000..b9b44b4
--- /dev/null
+++ b/scribo/scribo/primitive/extract/lines_h_thick_and_thin.hh
@@ -0,0 +1,514 @@
+// Copyright (C) 2011 EPITA Research and Development Laboratory (LRDE)
+//
+// This file is part of Olena.
+//
+// Olena 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, version 2 of the License.
+//
+// Olena 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 Olena. If not, see <http://www.gnu.org/licenses/>.
+//
+// As a special exception, you may use this file as part of a free
+// software project without restriction. Specifically, if other files
+// instantiate templates or use macros or inline functions from this
+// file, or you compile this file and link it with other files to produce
+// an executable, this file does not by itself cause the resulting
+// executable to be covered by the GNU General Public License. This
+// exception does not however invalidate any other reasons why the
+// executable file might be covered by the GNU General Public License.
+
+#ifndef SCRIBO_PRIMITIVE_EXTRACT_LINES_H_THICK_AND_THIN_HH
+# define SCRIBO_PRIMITIVE_EXTRACT_LINES_H_THICK_AND_THIN_HH
+
+/// \file
+///
+/// \brief Find thick and thin lines.
+
+# include <mln/io/pbm/load.hh>
+# include <mln/io/pbm/save.hh>
+# include <mln/io/ppm/save.hh>
+
+# include <mln/logical/and.hh>
+# include <mln/logical/or.hh>
+
+# include <mln/make/w_window2d.hh>
+
+# include <mln/extension/adjust_fill.hh>
+# include <mln/accu/transform_line.hh>
+# include <mln/accu/count_value.hh>
+# include <mln/data/fill.hh>
+# include <mln/literal/grays.hh>
+# include <mln/literal/colors.hh>
+
+# include <mln/pw/all.hh>
+# include <mln/core/routine/duplicate.hh>
+# include <mln/win/rectangle2d.hh>
+# include <mln/morpho/dilation.hh>
+
+# include <mln/data/convert.hh>
+# include <mln/data/fill.hh>
+# include <mln/core/image/dmorph/image_if.hh>
+
+# include <mln/core/alias/neighb2d.hh>
+# include <mln/labeling/compute.hh>
+# include <mln/labeling/foreground.hh>
+
+# include <scribo/core/macros.hh>
+# include <scribo/core/def/lbl_type.hh>
+# include <scribo/primitive/internal/rd.hh>
+
+
+
+namespace scribo
+{
+
+ namespace primitive
+ {
+
+ namespace extract
+ {
+ using namespace mln;
+
+ template <typename I>
+ mln_concrete(I)
+ lines_h_thick_and_thin(const Image<I>& binary_image,
+ unsigned length, unsigned delta,
+ float p_few = 0.2, // very tolerant (v. severe is 0.05)
+ float p_enough = 0.6, // very tolerant (v. severe is 0.80)
+ unsigned filter_factor = 1);
+
+
+# ifndef MLN_INCLUDE_ONLY
+
+ namespace internal
+ {
+
+ const unsigned
+ tag_bg = 0, // literal::black background
+
+ tag_thin = 1, // literal::white thin
+
+ tag_top = 2, // literal::red
+ tag_mdl = 3, // literal::green thick
+ tag_bot = 4, // literal::blue
+
+ tag_spc = 5; // literal::dark_gray "white space"
+
+
+
+ image2d<value::rgb8>
+ colorize(const image2d<value::int_u8>& input)
+ {
+ image2d<value::rgb8> output(input.domain());
+ mln_piter_(box2d) p(input.domain());
+ for_all(p)
+ switch (input(p))
+ {
+ // bg:
+ case tag_bg:
+ output(p) = literal::black;
+ break;
+
+ // thin:
+ case tag_thin:
+ output(p) = literal::white;
+ break;
+
+ // thick:
+ case tag_top:
+ output(p) = literal::red;
+ break;
+ case tag_mdl:
+ output(p) = literal::green;
+ break;
+ case tag_bot:
+ output(p) = literal::blue;
+ break;
+
+ // space:
+ case tag_spc:
+ output(p) = literal::dark_gray;
+ break;
+ }
+ return output;
+ }
+
+
+ image2d<value::int_u8>
+ tag_it(const image2d<bool>& input, unsigned length, unsigned delta,
+ float p_few = 0.2, // very tolerant (v. severe is 0.05)
+ float p_enough = 0.6) // very tolerant (v. severe is 0.80)
+ {
+ unsigned
+ few = unsigned(p_few * length + 0.49999), // usage: <= few
+ enough = unsigned(p_enough * length + 0.49999), // usage: >= enough
+ a_lot = length - few; // usage: >= a_lot
+
+
+ extension::adjust_fill(input, length / 2, 0);
+ accu::count_value<bool> accu(true);
+ image2d<unsigned> count = accu::transform_line(accu, input, length, 1);
+
+ image2d<value::int_u8> output;
+ initialize(output, count);
+ data::fill(output, 0);
+
+ const unsigned nrows = count.nrows(), ncols = count.ncols();
+
+ dpoint2d up(-delta, 0), down(+delta, 0);
+ const int
+ offset_up = count.delta_index(up),
+ offset_down = count.delta_index(down);
+
+ typedef const unsigned* ptr_t;
+ value::int_u8* p_out;
+
+ for (unsigned row = 0; row < nrows; ++row)
+ {
+ ptr_t
+ p_ = & count.at_(row, -1),
+ p_top = p_ + offset_up,
+ p_bot = p_ + offset_down;
+ p_out = & output.at_(row, -1);
+ for (unsigned col = 0; col < ncols; ++col)
+ {
+ ++p_;
+ ++p_top;
+ ++p_bot;
+ ++p_out;
+
+ if (*p_top <= few && *p_bot <= few)
+ {
+ if (*p_ <= few)
+ *p_out = tag_spc;
+ else if (*p_ >= enough)
+ *p_out = tag_thin;
+ // no other case with both n_top and n_bot <= few
+ continue;
+ }
+
+ if (*p_ < a_lot)
+ continue;
+
+ if (*p_top <= few && *p_bot >= a_lot)
+ {
+ *p_out = tag_top;
+ continue;
+ }
+
+ if (*p_top >= a_lot && *p_bot <= few)
+ {
+ *p_out = tag_bot;
+ continue;
+ }
+
+ *p_out = tag_mdl;
+
+ }
+ }
+
+ return output;
+ }
+
+
+ image2d<bool>
+ image_with_tag(const image2d<value::int_u8>& input, value::int_u8 tag)
+ {
+ image2d<bool> output(input.domain());
+ mln_pixter_(image2d<bool>) p_o(output);
+ mln_pixter_(const image2d<value::int_u8>) p_i(input);
+ for_all_2(p_i, p_o)
+ p_o.val() = p_i.val() == tag;
+ return output;
+ }
+
+
+
+
+ void flush_tag(const image2d<value::int_u8>& input, unsigned col, value::int_u8 tag,
+ // out:
+ int& row, value::int_u8& next_tag)
+ {
+ int row_offset = input.delta_index(dpoint2d(+1, 0));
+ const value::int_u8* p = & input.at_(row, col);
+ while (*p == tag)
+ {
+ p += row_offset;
+ ++row;
+ }
+ next_tag = *p;
+ }
+
+
+ void draw_vertical(image2d<bool>& output, unsigned col, int row_start, int row_end)
+ {
+ const unsigned offset = output.delta_index(dpoint2d(+1, 0)); // next row
+ bool* p_out = & output.at_(row_start, col);
+ for (int row = row_start; row < row_end; ++row, p_out += offset)
+ *p_out = true;
+ }
+
+
+ image2d<bool>
+ detect_thick(const image2d<value::int_u8>& input)
+ {
+ image2d<bool> output;
+ initialize(output, input);
+ data::fill(output, false);
+
+ const mln::def::coord
+ maxrow = geom::max_row(input),
+ maxcol = geom::max_col(input);
+ mln::def::coord row, col;
+
+ col = geom::min_col(input);
+ do
+ {
+ row = geom::min_row(input) - 1;
+ do
+ {
+ ++row;
+ const value::int_u8& t = input.at_(row, col);
+ if (t != tag_top)
+ continue;
+ assert(t == tag_top);
+ int r = row;
+ value::int_u8 next_tag;
+ flush_tag(input, col, tag_top, r, next_tag); // top
+ if (next_tag == tag_mdl || next_tag == tag_thin)
+ {
+ flush_tag(input, col, next_tag, r, next_tag); // mdl or thin
+ if (next_tag != tag_bot)
+ continue;
+ flush_tag(input, col, tag_bot, r, next_tag); // bot
+ // std::cout << "found at col " << col << " from row " << row << " to " << r-1 << std::endl;
+ draw_vertical(output, col, row, r);
+ row = r - 1;
+ }
+ else if (next_tag == tag_bot)
+ {
+ flush_tag(input, col, tag_bot, r, next_tag); // bot
+ draw_vertical(output, col, row, r);
+ row = r - 1;
+ }
+ }
+ while (row <= maxrow);
+ }
+ while (++col <= maxcol);
+
+ return output;
+ }
+
+
+ void
+ add_thin(const image2d<value::int_u8>& input, image2d<bool>& output)
+ {
+ unsigned N = input.nelements();
+ if (output.nelements() != N)
+ std::abort();
+ const value::int_u8* p_in = input.buffer();
+ bool* p_out = output.buffer();
+ for (unsigned i = 0; i < N; ++i)
+ {
+ if (*p_in == tag_thin)
+ *p_out = true;
+ ++p_in;
+ ++p_out;
+ }
+ }
+
+
+ template <typename I, typename J,
+ typename N, typename W, typename D>
+ mln_concrete(I)
+ rd3_fast(const Image<I>& f_,
+ const Image<J>& g_,
+ const Neighborhood<N>& nbh_,
+ const Weighted_Window<W>& w_win_,
+ D max)
+ {
+ const I& f = exact(f_);
+ const J& g = exact(g_);
+ const N& nbh = exact(nbh_);
+ const W& w_win = exact(w_win_);
+
+ mln_precondition(f.is_valid());
+ mln_precondition(w_win.is_valid());
+
+ // Handling w_win.
+ extension::adjust(f, w_win);
+ border::resize(g, f.border());
+ const unsigned n_ws = w_win.size();
+ util::array<int> dp = offsets_wrt(f, w_win.win());
+ mln_invariant(dp.nelements() == n_ws);
+
+ // Output.
+ mln_concrete(I) o = duplicate(f);
+ if (o.border() != f.border())
+ std::abort();
+
+ // Distance map.
+ mln_ch_value(I, D) dmap;
+ initialize(dmap, f);
+ data::fill(dmap, max);
+
+ // Mod determination.
+ unsigned mod;
+ {
+ accu::stat::max<unsigned> m;
+ for (unsigned i = 0; i < w_win.size(); ++i)
+ m.take(w_win.w(i));
+ mod = unsigned(m) + 1;
+ }
+
+ // Aux data.
+ typedef std::vector<unsigned> bucket_t;
+ std::vector<bucket_t> bucket(mod);
+ unsigned bucket_size = 0;
+
+ // Initialization.
+ {
+ // For the extension to be ignored:
+ extension::fill(f, true);
+ extension::fill(dmap, D(0));
+
+ mln_pixter(const I) p(f);
+ mln_nixter(const I, N) n(p, nbh);
+ for_all(p)
+ if (p.val() == true)
+ {
+ dmap.element(p.offset()) = 0;
+ for_all(n)
+ if (n.val() == false)
+ {
+ bucket[0].push_back(p.offset());
+ ++bucket_size;
+ break;
+ }
+ }
+ } // end of Initialization.
+
+ // Propagation.
+ {
+ unsigned p;
+
+ for (unsigned d = 0; bucket_size != 0; ++d)
+ {
+ bucket_t& bucket_d = bucket[d % mod];
+ for (unsigned i = 0; i < bucket_d.size(); ++i)
+ {
+ p = bucket_d[i];
+
+ if (dmap.element(p) == max)
+ {
+ // Saturation so stop.
+ bucket_size = bucket_d.size(); // So at end bucket_size == 0.
+ break;
+ }
+
+ if (dmap.element(p) < d)
+ // p has already been processed, having a distance less than d.
+ continue;
+
+ for (unsigned i = 0; i < n_ws; ++i)
+ {
+ unsigned q = p + dp[i];
+ if (dmap.element(q) > d)
+ {
+ unsigned d_ = d + w_win.w(i);
+ if (d_ < dmap.element(q) && g.element(q) == true)
+ {
+ o.element(q) = true;
+ dmap.element(q) = d_;
+ bucket[d_ % mod].push_back(q);
+ ++bucket_size;
+ }
+ }
+ }
+ }
+ bucket_size -= bucket_d.size();
+ bucket_d.clear();
+ }
+ } // end of Propagation.
+
+ return o;
+ }
+
+
+ template <typename I, typename J>
+ inline
+ mln_concrete(I)
+ rd3_fast(const I& f, const J& g, unsigned length, unsigned delta)
+ {
+ unsigned mean = (length + delta) / 2;
+ unsigned ws[] = { mean, length, mean,
+ delta, 0, delta,
+ mean, length, mean };
+ w_window<dpoint2d,unsigned> w_win = mln::make::w_window2d(ws);
+ unsigned max = length * delta + 1;
+
+ mln_concrete(I) output = rd3_fast(f, g, c4(), w_win, max);
+
+ return output;
+ }
+
+
+ } // scribo::primitive::extract::internal
+
+
+ template <typename I>
+ mln_concrete(I)
+ lines_h_thick_and_thin(const Image<I>& binary_image_,
+ unsigned length, unsigned delta,
+ float p_few, float p_enough,
+ unsigned filter_factor)
+ {
+ trace::entering("scribo::primitive::extract::lines_h_thick_and_thin");
+
+ mlc_is(mln_value(I), bool)::check();
+
+ const I& binary_image = exact(binary_image_);
+ mln_precondition(binary_image.is_valid());
+
+ if (length % 2 == 0)
+ ++length;
+
+ // Find lines
+ mln_ch_value(I,value::int_u8)
+ tags = internal::tag_it(binary_image, length, delta, p_few, p_enough);
+ mln_concrete(I) mask = internal::detect_thick(tags);
+ internal::add_thin(tags, mask);
+
+ image2d<bool> output = internal::rd3_fast(mask, binary_image,
+ length, delta);
+
+ // Remove invalid lines
+ typedef scribo::def::lbl_type V;
+ V nlabels;
+ mln_ch_value(I,V) lbl = labeling::foreground(output, c8(), nlabels);
+ mln::util::array<box2d>
+ bbox = labeling::compute(accu::shape::bbox<point2d>(), lbl, nlabels);
+
+ for_all_ncomponents(e, nlabels)
+ if (bbox(e).width() < filter_factor * length || bbox(e).width() / bbox(e).height() < 3)
+ data::fill(((output | bbox(e)).rw() | (pw::value(lbl) == pw::cst(e))).rw(), false);
+
+ trace::exiting("scribo::primitive::extract::lines_h_thick_and_thin");
+ return output;
+ }
+
+# endif // ! MLN_INCLUDE_ONLY
+
+ } // end of namespace scribo::primitive::extract
+
+ } // end of namespace scribo::primitive
+
+} // end of namespace scribo
+
+#endif // ! SCRIBO_PRIMITIVE_EXTRACT_LINES_H_THICK_AND_THIN_HH
diff --git a/scribo/src/primitive/extract/Makefile.am b/scribo/src/primitive/extract/Makefile.am
index fef405a..1073545 100644
--- a/scribo/src/primitive/extract/Makefile.am
+++ b/scribo/src/primitive/extract/Makefile.am
@@ -18,13 +18,15 @@
include $(top_srcdir)/scribo/scribo.mk
-noinst_PROGRAMS = \
- discontinued_lines \
- discontinued_vlines \
- discontinued_hlines \
- separators_nonvisible \
- thick_vlines \
- thick_hlines \
+noinst_PROGRAMS = \
+ discontinued_lines \
+ discontinued_vlines \
+ discontinued_hlines \
+ separators_nonvisible \
+ thick_vlines \
+ thick_hlines \
+ lines_h_thick_and_thin \
+ lines_thick_and_thin \
lines_pattern
discontinued_lines_SOURCES = discontinued_lines.cc
@@ -33,6 +35,8 @@ discontinued_hlines_SOURCES = discontinued_hlines.cc
separators_nonvisible_SOURCES = separators_nonvisible.cc
thick_vlines_SOURCES = thick_vlines.cc
thick_hlines_SOURCES = thick_hlines.cc
+lines_h_thick_and_thin_SOURCES = lines_h_thick_and_thin.cc
+lines_thick_and_thin_SOURCES = lines_thick_and_thin.cc
lines_pattern_SOURCES = lines_pattern.cc
diff --git a/scribo/src/misc/negate.cc b/scribo/src/primitive/extract/lines_h_thick_and_thin.cc
similarity index 75%
copy from scribo/src/misc/negate.cc
copy to scribo/src/primitive/extract/lines_h_thick_and_thin.cc
index da6fad6..2942002 100644
--- a/scribo/src/misc/negate.cc
+++ b/scribo/src/primitive/extract/lines_h_thick_and_thin.cc
@@ -1,5 +1,4 @@
-// Copyright (C) 2009, 2010 EPITA Research and Development Laboratory
-// (LRDE)
+// Copyright (C) 2011 EPITA Research and Development Laboratory (LRDE)
//
// This file is part of Olena.
//
@@ -24,36 +23,35 @@
// exception does not however invalidate any other reasons why the
// executable file might be covered by the GNU General Public License.
-#include <mln/core/image/image2d.hh>
-#include <mln/logical/not.hh>
-#include <mln/io/pbm/all.hh>
-
-#include <scribo/debug/usage.hh>
+#include <scribo/primitive/extract/lines_h_thick_and_thin.hh>
const char *args_desc[][2] =
{
{ "input.pbm", "A binary image." },
+ { "output.pbm", " A binary image with thick and thin horizontal"
+ " separators." },
{0, 0}
};
int main(int argc, char *argv[])
{
- mln::trace::entering("main");
- using namespace mln;
-
if (argc != 3)
- return scribo::debug::usage(argv,
- "Negate a binary image",
+ return scribo::debug::usage(argv,
+ "Extract thick and thin horizontal lines",
"input.pbm output.pbm",
args_desc);
- image2d<bool> input;
- io::pbm::load(input, argv[1]);
- io::pbm::save(logical::not_(input), argv[2]);
+ using namespace mln;
+
+ typedef image2d<bool> I;
+
+ I input;
+ io::pbm::load(input, argv[1]);
- mln::trace::exiting("main");
+ I output = scribo::primitive::extract::lines_h_thick_and_thin(input, 101, 3);
+ io::pbm::save(output, argv[2]);
}
diff --git a/scribo/src/primitive/extract/lines_thick_and_thin.cc b/scribo/src/primitive/extract/lines_thick_and_thin.cc
new file mode 100644
index 0000000..f574b3d
--- /dev/null
+++ b/scribo/src/primitive/extract/lines_thick_and_thin.cc
@@ -0,0 +1,100 @@
+// Copyright (C) 2011 EPITA Research and Development Laboratory (LRDE)
+//
+// This file is part of Olena.
+//
+// Olena 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, version 2 of the License.
+//
+// Olena 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 Olena. If not, see <http://www.gnu.org/licenses/>.
+//
+// As a special exception, you may use this file as part of a free
+// software project without restriction. Specifically, if other files
+// instantiate templates or use macros or inline functions from this
+// file, or you compile this file and link it with other files to produce
+// an executable, this file does not by itself cause the resulting
+// executable to be covered by the GNU General Public License. This
+// exception does not however invalidate any other reasons why the
+// executable file might be covered by the GNU General Public License.
+
+#include <scribo/primitive/extract/lines_h_thick_and_thin.hh>
+#include <scribo/preprocessing/rotate_90.hh>
+
+#include <scribo/debug/usage.hh>
+
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/labeling/compute.hh>
+#include <mln/labeling/foreground.hh>
+
+
+const char *args_desc[][2] =
+{
+ { "input.pbm", "A binary image." },
+ { "output.pbm", " A binary image with thick and thin horizontal"
+ " separators." },
+ { "length", "The minimum length of the lines. (default : 101)" },
+ { "delta", "The lookhead distance between a line pixel and the background."
+ " (default : 3)" },
+ {0, 0}
+};
+
+
+int main(int argc, char *argv[])
+{
+ if (argc != 3 && argc != 4 && argc != 5)
+ return scribo::debug::usage(argv,
+ "Extract thick and thin horizontal lines",
+ "input.pbm output.pbm [length] [delta]",
+ args_desc);
+
+ unsigned
+ length = 101,
+ delta = 3;
+
+ if (argc >= 4)
+ length = atoi(argv[3]);
+ if (argc >= 5)
+ delta = atoi(argv[4]);
+
+ using namespace mln;
+ using namespace scribo;
+
+ typedef image2d<bool> I;
+
+ I input;
+ io::pbm::load(input, argv[1]);
+
+ // Cleanup components on borders
+ {
+ typedef scribo::def::lbl_type V;
+ V nlabels;
+ mln_ch_value_(I,V) lbl = labeling::foreground(input, c8(), nlabels);
+ mln::util::array<box2d>
+ bbox = labeling::compute(accu::shape::bbox<point2d>(), lbl, nlabels);
+
+ const box2d& b = input.domain();
+ for_all_ncomponents(e, nlabels)
+ if (bbox(e).pmin().row() == b.pmin().row()
+ || bbox(e).pmax().row() == b.pmax().row()
+ || bbox(e).pmin().col() == b.pmin().col()
+ || bbox(e).pmax().col() == b.pmax().col())
+ data::fill(((input | bbox(e)).rw() | (pw::value(lbl) == pw::cst(e))).rw(), false);
+ }
+
+ I hseparators = primitive::extract::lines_h_thick_and_thin(
+ input, length, delta);
+ I vseparators = preprocessing::rotate_90(
+ primitive::extract::lines_h_thick_and_thin(
+ preprocessing::rotate_90(input), length, delta, 0.05, 0.80, 2), false);
+
+ I separators = duplicate(vseparators);
+ separators += hseparators;
+
+ io::pbm::save(separators, argv[2]);
+}
--
1.5.6.5
1
0

last-svn-commit-876-g7f93eff scribo/primitive/extract/non_text_hdoc.hh: New routine for historical documents.
by Guillaume Lazzara 16 May '11
by Guillaume Lazzara 16 May '11
16 May '11
---
scribo/ChangeLog | 5 +
scribo/scribo/primitive/extract/non_text_hdoc.hh | 135 ++++++++++++++++++++++
2 files changed, 140 insertions(+), 0 deletions(-)
create mode 100644 scribo/scribo/primitive/extract/non_text_hdoc.hh
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index f53ea42..83797f6 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,10 @@
2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+ * scribo/primitive/extract/non_text_hdoc.hh: New routine for
+ historical documents.
+
+2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+
New object filter.
* scribo/fun/v2b/objects_on_border_filter.hh,
diff --git a/scribo/scribo/primitive/extract/non_text_hdoc.hh b/scribo/scribo/primitive/extract/non_text_hdoc.hh
new file mode 100644
index 0000000..4924189
--- /dev/null
+++ b/scribo/scribo/primitive/extract/non_text_hdoc.hh
@@ -0,0 +1,135 @@
+// Copyright (C) 2011 EPITA Research and Development Laboratory
+// (LRDE)
+//
+// This file is part of Olena.
+//
+// Olena 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, version 2 of the License.
+//
+// Olena 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 Olena. If not, see <http://www.gnu.org/licenses/>.
+//
+// As a special exception, you may use this file as part of a free
+// software project without restriction. Specifically, if other files
+// instantiate templates or use macros or inline functions from this
+// file, or you compile this file and link it with other files to produce
+// an executable, this file does not by itself cause the resulting
+// executable to be covered by the GNU General Public License. This
+// exception does not however invalidate any other reasons why the
+// executable file might be covered by the GNU General Public License.
+
+/// \file
+///
+/// \brief Find in a document non text which are not text.
+///
+/// \fixme To be optimized!
+
+#ifndef SCRIBO_PRIMITIVE_EXTRACT_NON_TEXT_HDOC_HH
+# define SCRIBO_PRIMITIVE_EXTRACT_NON_TEXT_HDOC_HH
+
+# include <mln/morpho/elementary/dilation.hh>
+
+# include <mln/draw/box_plain.hh>
+# include <mln/morpho/closing/structural.hh>
+# include <mln/win/rectangle2d.hh>
+
+# include <scribo/make/text_components_image.hh>
+# include <scribo/make/text_blocks_image.hh>
+
+# include <scribo/primitive/extract/internal/union.hh>
+# include <scribo/debug/logger.hh>
+
+# include <scribo/filter/objects_small.hh>
+# include <scribo/filter/objects_on_border.hh>
+
+//DEBUG
+#include <mln/util/timer.hh>
+#include <mln/io/pbm/save.hh>
+
+
+namespace scribo
+{
+
+ namespace primitive
+ {
+
+ namespace extract
+ {
+
+ using namespace mln;
+
+ /*! \brief Extract non text components.
+
+ Variant adapted for historical documents.
+ */
+ template <typename L>
+ component_set<L>
+ non_text_hdoc(const document<L>& doc, unsigned closing_size);
+
+
+# ifndef MLN_INCLUDE_ONLY
+
+ namespace internal
+ {
+
+ } // end of namespace scribo::primitive::extract::internal
+
+
+
+ // FACADE
+
+ template <typename L>
+ component_set<L>
+ non_text_hdoc(const document<L>& doc, unsigned closing_size)
+ {
+ trace::entering("scribo::primitive::extract::non_text_hdoc");
+
+ mln_precondition(doc.is_valid());
+ mln_precondition(doc.has_text());
+
+ mln_ch_value(L,bool)
+ element_image = duplicate(doc.binary_image_wo_seps());
+
+ for_all_lines(l, doc.lines())
+ if (doc.lines()(l).is_textline())
+ mln::draw::box_plain(element_image, doc.lines()(l).bbox(), false);
+
+ element_image = morpho::closing::structural(element_image,
+ win::rectangle2d(closing_size,
+ closing_size));
+
+ mln_value(L) ncomps;
+ component_set<L>
+ elements = primitive::extract::components(element_image,
+ c8(), ncomps);
+
+ elements = scribo::filter::components_small(elements, 200);
+ elements = scribo::filter::components_on_border(elements);
+
+ // Debug
+ {
+ debug::logger().log_image(debug::Special,
+ elements.labeled_image(),
+ "non_text_hdoc_components");
+ }
+
+ trace::exiting("scribo::primitive::extract::non_text_hdoc");
+ return elements;
+ }
+
+# endif // ! MLN_INCLUDE_ONLY
+
+
+ } // end of namespace scribo::primitive::extract
+
+ } // end of namespace scribo::primitive
+
+} // end of namespace scribo
+
+#endif // ! SCRIBO_PRIMITIVE_EXTRACT_NON_TEXT_HDOC_HH
--
1.5.6.5
1
0
* scribo/fun/v2b/objects_on_border_filter.hh,
* scribo/filter/objects_on_border.hh: New.
---
scribo/ChangeLog | 7 ++
..._links_bbox_h_ratio.hh => objects_on_border.hh} | 69 +++++++++++---------
...large_filter.hh => objects_on_border_filter.hh} | 54 ++++++---------
3 files changed, 65 insertions(+), 65 deletions(-)
copy scribo/scribo/filter/{object_links_bbox_h_ratio.hh => objects_on_border.hh} (53%)
copy scribo/scribo/fun/v2b/{objects_large_filter.hh => objects_on_border_filter.hh} (69%)
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index df242f1..f53ea42 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,12 @@
2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+ New object filter.
+
+ * scribo/fun/v2b/objects_on_border_filter.hh,
+ * scribo/filter/objects_on_border.hh: New.
+
+2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+
Various small fixes in Scribo.
* scribo/core/document.hh: Store binary image without separators.
diff --git a/scribo/scribo/filter/object_links_bbox_h_ratio.hh b/scribo/scribo/filter/objects_on_border.hh
similarity index 53%
copy from scribo/scribo/filter/object_links_bbox_h_ratio.hh
copy to scribo/scribo/filter/objects_on_border.hh
index 5573f16..d6c4cf5 100644
--- a/scribo/scribo/filter/object_links_bbox_h_ratio.hh
+++ b/scribo/scribo/filter/objects_on_border.hh
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 EPITA Research and Development Laboratory (LRDE)
+// Copyright (C) 2011 EPITA Research and Development Laboratory (LRDE)
//
// This file is part of Olena.
//
@@ -23,23 +23,28 @@
// exception does not however invalidate any other reasons why the
// executable file might be covered by the GNU General Public License.
-#ifndef SCRIBO_FILTER_OBJECT_LINKS_BBOX_H_RATIO_HH
-# define SCRIBO_FILTER_OBJECT_LINKS_BBOX_H_RATIO_HH
+#ifndef SCRIBO_FILTER_COMPONENTS_ON_BORDER_HH
+# define SCRIBO_FILTER_COMPONENTS_ON_BORDER_HH
/// \file
///
-/// Invalidate links between two objects with too different height.
-///
-/// \todo rename to object_links_bbox_v_ratio (v for vertical) to be
-/// consistent with other routine names.
+/// Remove large objects in a binary image.
+
+
+# include <mln/core/concept/image.hh>
+# include <mln/core/concept/neighborhood.hh>
+# include <mln/core/concept/function.hh>
+# include <mln/labeling/compute.hh>
+# include <mln/accu/math/count.hh>
# include <mln/util/array.hh>
-# include <scribo/core/macros.hh>
-# include <scribo/core/object_links.hh>
+# include <mln/pw/all.hh>
+
# include <scribo/core/component_set.hh>
-# include <scribo/filter/object_links_bbox_ratio.hh>
+# include <scribo/primitive/extract/components.hh>
+# include <scribo/fun/v2b/objects_on_border_filter.hh>
namespace scribo
{
@@ -49,47 +54,47 @@ namespace scribo
using namespace mln;
- /*! \brief Invalidate links between two components with too different
- height.
-
- \param[in] links Link objects information.
- \param[in] max_h_ratio The maximum height ratio of two linked
- bounding boxes.
- \result A filtered object link information.
- */
+ /// Remove too large components.
+ ///
+ /// \param[in] components An object image.
+ /// \param[in] max_size The maximum cardinality of an object.
+ ///
+ /// \return A component set with large components set to
+ /// component::Ignored.
template <typename L>
- object_links<L>
- object_links_bbox_h_ratio(const object_links<L>& links,
- float max_h_ratio);
+ inline
+ component_set<L>
+ components_on_border(const component_set<L>& components);
# ifndef MLN_INCLUDE_ONLY
template <typename L>
- object_links<L>
- object_links_bbox_h_ratio(const object_links<L>& links,
- float max_h_ratio)
+ inline
+ component_set<L>
+ components_on_border(const component_set<L>& components)
{
- trace::entering("scribo::filter::object_links_bbox_h_ratio");
+ trace::entering("scribo::filter::components_on_border");
- mln_precondition(links.is_valid());
+ mln_precondition(components.is_valid());
- object_links<L>
- output = object_links_bbox_ratio(links, 0, max_h_ratio);
+ fun::v2b::components_on_border_filter<L> f(components);
- trace::exiting("scribo::filter::object_links_bbox_h_ratio");
+ component_set<L> output = components.duplicate();
+ output.update_tags(f, component::Ignored);
+
+ trace::exiting("scribo::filter::components_on_border");
return output;
}
-# endif // ! MLN_INCLUDE_ONLY
+# endif // ! MLN_INCLUDE_ONLY
} // end of namespace scribo::filter
} // end of namespace scribo
-
-#endif // ! SCRIBO_FILTER_OBJECT_LINKS_BBOX_H_RATIO_HH
+#endif // ! SCRIBO_FILTER_COMPONENTS_ON_BORDER_HH
diff --git a/scribo/scribo/fun/v2b/objects_large_filter.hh b/scribo/scribo/fun/v2b/objects_on_border_filter.hh
similarity index 69%
copy from scribo/scribo/fun/v2b/objects_large_filter.hh
copy to scribo/scribo/fun/v2b/objects_on_border_filter.hh
index bb41850..e6f1738 100644
--- a/scribo/scribo/fun/v2b/objects_large_filter.hh
+++ b/scribo/scribo/fun/v2b/objects_on_border_filter.hh
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 EPITA Research and Development Laboratory (LRDE)
+// Copyright (C) 2011 EPITA Research and Development Laboratory (LRDE)
//
// This file is part of Olena.
//
@@ -23,8 +23,8 @@
// exception does not however invalidate any other reasons why the
// executable file might be covered by the GNU General Public License.
-#ifndef SCRIBO_FUN_V2B_COMPONENTS_LARGE_FILTER_HH
-# define SCRIBO_FUN_V2B_COMPONENTS_LARGE_FILTER_HH
+#ifndef SCRIBO_FUN_V2B_COMPONENTS_ON_BORDER_FILTER_HH
+# define SCRIBO_FUN_V2B_COMPONENTS_ON_BORDER_FILTER_HH
/// \file
///
@@ -58,8 +58,8 @@ namespace scribo
/// Filter Functor.
/// Return false for all components which are too large.
template <typename L>
- struct components_large_filter
- : Function_v2b< components_large_filter<L> >
+ struct components_on_border_filter
+ : Function_v2b< components_on_border_filter<L> >
{
typedef accu::math::count<mln_psite(L)> card_t;
@@ -68,8 +68,7 @@ namespace scribo
/// \param[in] components Component bounding boxes.
/// \param[in] max_size Maximum component size.
//
- components_large_filter(const component_set<L>& components,
- unsigned max_size);
+ components_on_border_filter(const component_set<L>& components);
/// Check if the component is large enough.
@@ -81,17 +80,11 @@ namespace scribo
//
bool operator()(const mln_value(L)& l) const;
- /// The minimum area.
- unsigned max_size_;
-
/// The component set to filter.
const component_set<L> components_;
- /// The number of labels remaining after filtering.
- mutable mln_value(L) nlabels_;
-
- /// Has already been taken into account.
- mutable util::array<bool> marked_;
+ /// Labeled image bounding box.
+ const box2d& b_;
};
@@ -102,11 +95,9 @@ namespace scribo
template <typename L>
inline
- components_large_filter<L>::components_large_filter(
- const component_set<L>& components,
- unsigned max_size)
- : max_size_(max_size), components_(components), nlabels_(0),
- marked_(mln::value::next(components.nelements()), false)
+ components_on_border_filter<L>::components_on_border_filter(
+ const component_set<L>& components)
+ : components_(components), b_(components_.labeled_image().domain())
{
}
@@ -115,21 +106,18 @@ namespace scribo
template <typename L>
inline
bool
- components_large_filter<L>::operator()(const mln_value(L)& l) const
+ components_on_border_filter<L>::operator()(const mln_value(L)& l) const
{
if (l == literal::zero)
return false;
- if (components_.info(l).card() <= max_size_)
- {
- if (!marked_(l))
- {
- nlabels_ = value::next(nlabels_);
- marked_(l) = true;
- }
- return true;
- }
-
- return false;
+
+ if (components_(l).bbox().pmin().row() == b_.pmin().row()
+ || components_(l).bbox().pmax().row() == b_.pmax().row()
+ || components_(l).bbox().pmin().col() == b_.pmin().col()
+ || components_(l).bbox().pmax().col() == b_.pmax().col())
+ return false;
+
+ return true;
}
@@ -142,4 +130,4 @@ namespace scribo
} // end of namespace scribo
-#endif // ! SCRIBO_FUN_V2B_COMPONENTS_LARGE_FILTER_HH
+#endif // ! SCRIBO_FUN_V2B_COMPONENTS_ON_BORDER_FILTER_HH
--
1.5.6.5
1
0

16 May '11
* scribo/core/document.hh: Store binary image without separators.
* scribo/filter/object_links_bbox_overlap.hh: Fix use of
object_links structure.
* scribo/io/img/internal/debug_img_visitor.hh: Do not draw invalid
elements.
* scribo/make/text_components_image.hh: Add a precondition.
* scribo/primitive/extract/alignments.hh: Add debug guards.
* scribo/primitive/extract/lines_h_pattern.hh,
* scribo/primitive/extract/lines_v_pattern.hh: Fix structural
element used for dilation.
* scribo/text/merging.hh: Reindent comments.
* scribo/src/Makefile.am: Add content_in_hdoc target.
---
scribo/ChangeLog | 24 ++++++++++++++++++++
scribo/scribo/core/document.hh | 21 +++++++++++++++++
scribo/scribo/filter/object_links_bbox_overlap.hh | 19 +++++++++------
scribo/scribo/io/img/internal/debug_img_visitor.hh | 13 ++++++++--
scribo/scribo/make/text_components_image.hh | 1 +
scribo/scribo/primitive/extract/alignments.hh | 24 +++++++++++++++++++-
scribo/scribo/primitive/extract/lines_h_pattern.hh | 10 +++-----
scribo/scribo/primitive/extract/lines_v_pattern.hh | 10 +++-----
scribo/scribo/text/merging.hh | 5 ++-
scribo/src/Makefile.am | 22 ++++++++++++++++-
10 files changed, 121 insertions(+), 28 deletions(-)
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index 8e3c903..df242f1 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,29 @@
2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+ Various small fixes in Scribo.
+
+ * scribo/core/document.hh: Store binary image without separators.
+
+ * scribo/filter/object_links_bbox_overlap.hh: Fix use of
+ object_links structure.
+
+ * scribo/io/img/internal/debug_img_visitor.hh: Do not draw invalid
+ elements.
+
+ * scribo/make/text_components_image.hh: Add a precondition.
+
+ * scribo/primitive/extract/alignments.hh: Add debug guards.
+
+ * scribo/primitive/extract/lines_h_pattern.hh,
+ * scribo/primitive/extract/lines_v_pattern.hh: Fix structural
+ element used for dilation.
+
+ * scribo/text/merging.hh: Reindent comments.
+
+ * scribo/src/Makefile.am: Add content_in_hdoc target.
+
+2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+
Add holder information to component_info.
* scribo/core/component_info.hh,
diff --git a/scribo/scribo/core/document.hh b/scribo/scribo/core/document.hh
index 0fe2be3..98c438d 100644
--- a/scribo/scribo/core/document.hh
+++ b/scribo/scribo/core/document.hh
@@ -116,11 +116,15 @@ namespace scribo
const mln::image2d<bool>& binary_image() const;
void set_binary_image(const mln::image2d<bool>& binary_image);
+ const mln::image2d<bool>& binary_image_wo_seps() const;
+ void set_binary_image_wo_seps(const mln::image2d<bool>& binary_image_wo_seps);
+
private:
std::string filename_;
mln::image2d<mln::value::rgb8> image_;
mln::image2d<bool> binary_image_;
+ mln::image2d<bool> binary_image_wo_seps_;
paragraph_set<L> parset_;
component_set<L> elements_;
@@ -437,6 +441,23 @@ namespace scribo
template <typename L>
+ const mln::image2d<bool>&
+ document<L>::binary_image_wo_seps() const
+ {
+ return binary_image_wo_seps_;
+ }
+
+
+ template <typename L>
+ void
+ document<L>::set_binary_image_wo_seps(
+ const mln::image2d<bool>& binary_image_wo_seps)
+ {
+ binary_image_wo_seps_ = binary_image_wo_seps;
+ }
+
+
+ template <typename L>
bool operator==(const document<L>& lhs, const document<L>& rhs)
{
diff --git a/scribo/scribo/filter/object_links_bbox_overlap.hh b/scribo/scribo/filter/object_links_bbox_overlap.hh
index 3bf3c50..a93d849 100644
--- a/scribo/scribo/filter/object_links_bbox_overlap.hh
+++ b/scribo/scribo/filter/object_links_bbox_overlap.hh
@@ -78,13 +78,16 @@ namespace scribo
mln_precondition(links.is_valid());
const component_set<L>& components = links.components();
- object_links<L> output(links);
+ object_links<L> output = links.duplicate();
- for_all_comps(i, components)
- if (components(i).is_valid() && links(i) && links(i) != i)
+ bool has_intersection;
+ mln_site(L) pmin, pmax;
+ float ratio_i, ratio_link_i;
+
+ for_all_links(i, links)
+ if (links.is_linked(i))
{
- bool has_intersection = true;
- mln_site(L) pmin, pmax;
+ has_intersection = true;
for (unsigned dim = 0; dim < mln_site_(L)::dim; ++dim)
{
pmin[dim] = math::max(components(i).bbox().pmin()[dim],
@@ -103,9 +106,9 @@ namespace scribo
continue;
mln_box(L) interbbox(pmin, pmax);
- float
- ratio_i = interbbox.nsites() /(float)components(i).bbox().nsites(),
- ratio_link_i = interbbox.nsites() /(float)components(links(i)).bbox().nsites();
+
+ ratio_i = interbbox.nsites() /(float)components(i).bbox().nsites();
+ ratio_link_i = interbbox.nsites() /(float)components(links(i)).bbox().nsites();
if (ratio_i >= max_overlap_ratio
|| ratio_link_i >= max_overlap_ratio)
diff --git a/scribo/scribo/io/img/internal/debug_img_visitor.hh b/scribo/scribo/io/img/internal/debug_img_visitor.hh
index 5ad1dd3..7d1d3d7 100644
--- a/scribo/scribo/io/img/internal/debug_img_visitor.hh
+++ b/scribo/scribo/io/img/internal/debug_img_visitor.hh
@@ -130,11 +130,16 @@ namespace scribo
{
// Prepare element edges
+ L lbl = duplicate(doc.elements().labeled_image());
+ for_all_comps(c, doc.elements())
+ if (! doc.elements()(c).is_valid())
+ data::fill(((lbl | doc.elements()(c).bbox()).rw()
+ | (pw::value(lbl) == pw::cst(c))).rw(), 0);
+
// FIXME: UGLY! Too slow!
scribo::def::lbl_type nlabels;
component_set<L> elts = primitive::extract::components(
- data::convert(bool(), mln::subsampling::antialiased(doc.elements().labeled_image(),
- output_ratio)),
+ data::convert(bool(), mln::subsampling::antialiased(lbl, output_ratio)),
c8(),
nlabels);
@@ -150,11 +155,13 @@ namespace scribo
}
else
for_all_comps(c, doc.elements())
+ {
elts(c).update_type(doc.elements()(c).type());
+ elts(c).update_tag(doc.elements()(c).tag());
+ }
elt_edge = morpho::elementary::gradient_external(elts.labeled_image(), c8());
-// const component_set<L>& elts = doc.elements();
for_all_comps(e, elts)
if (elts(e).is_valid())
elts(e).accept(*this);
diff --git a/scribo/scribo/make/text_components_image.hh b/scribo/scribo/make/text_components_image.hh
index 522505e..1a30a04 100644
--- a/scribo/scribo/make/text_components_image.hh
+++ b/scribo/scribo/make/text_components_image.hh
@@ -65,6 +65,7 @@ namespace scribo
trace::entering("scribo::make::text_components_image");
mln_precondition(doc.is_open());
+ mln_precondition(doc.has_text());
mln_ch_value(L,bool) output;
initialize(output, doc.image());
diff --git a/scribo/scribo/primitive/extract/alignments.hh b/scribo/scribo/primitive/extract/alignments.hh
index 1e3d835..7dbf683 100644
--- a/scribo/scribo/primitive/extract/alignments.hh
+++ b/scribo/scribo/primitive/extract/alignments.hh
@@ -50,6 +50,7 @@
# include <scribo/core/def/lbl_type.hh>
# include <scribo/primitive/extract/components.hh>
# include <scribo/filter/object_links_aligned.hh>
+# include <scribo/filter/object_links_bbox_overlap.hh>
# include <scribo/filter/object_groups_small.hh>
# include <scribo/preprocessing/denoise_fg.hh>
# include <scribo/primitive/link/internal/link_single_dmax_ratio_aligned_delta_base.hh>
@@ -252,7 +253,9 @@ namespace scribo
: super_(components, dmax_f, delta, delta_direction),
bbox_ima_(bbox_ima), delta_ws_lookup_(delta_ws_lookup)
{
+# ifndef SCRIBO_NDEBUG
debug_ = data::convert(value::rgb8(), data::convert(bool(), bbox_ima));
+# endif // ! SCRIBO_NDEBUG
}
void compute_next_site_(P& p)
@@ -286,7 +289,9 @@ namespace scribo
for (; p.col() <= this->components_(nbh).bbox().pmax().col()
&& (bbox_ima_(p) == 0);)
{
+# ifndef SCRIBO_NDEBUG
debug_(p) = literal::violet;
+# endif // ! SCRIBO_NDEBUG
++p.col();
}
@@ -304,7 +309,9 @@ namespace scribo
for (; p.col() <= this->components_(nbh).bbox().pmax().col()
&& (bbox_ima_(p) == 0);)
{
+# ifndef SCRIBO_NDEBUG
debug_(p) = literal::violet;
+# endif // ! SCRIBO_NDEBUG
++p.col();
}
@@ -322,7 +329,9 @@ namespace scribo
L bbox_ima_;
unsigned delta_ws_lookup_;
+# ifndef SCRIBO_NDEBUG
image2d<value::rgb8> debug_;
+# endif // ! SCRIBO_NDEBUG
};
@@ -346,7 +355,9 @@ namespace scribo
: super_(components, dmax_f, delta, delta_direction),
bbox_ima_(bbox_ima), delta_ws_lookup_(delta_ws_lookup)
{
+# ifndef SCRIBO_NDEBUG
debug_ = data::convert(value::rgb8(), data::convert(bool(), bbox_ima));
+# endif // ! SCRIBO_NDEBUG
}
void compute_next_site_(P& p)
@@ -381,7 +392,9 @@ namespace scribo
for (; p.col() > this->components_(nbh).bbox().pmin().col()
&& (bbox_ima_(p) == 0);)
{
+# ifndef SCRIBO_NDEBUG
debug_(p) = literal::violet;
+# endif // ! SCRIBO_NDEBUG
--p.col();
}
@@ -399,7 +412,9 @@ namespace scribo
for (; p.col() > this->components_(nbh).bbox().pmin().col()
&& (bbox_ima_(p) == 0);)
{
+# ifndef SCRIBO_NDEBUG
debug_(p) = literal::violet;
+# endif // ! SCRIBO_NDEBUG
--p.col();
}
@@ -417,7 +432,9 @@ namespace scribo
L bbox_ima_;
unsigned delta_ws_lookup_;
+# ifndef SCRIBO_NDEBUG
image2d<value::rgb8> debug_;
+# endif // ! SCRIBO_NDEBUG
};
@@ -656,11 +673,13 @@ namespace scribo
top_links = primitive::link::merge_double_link_closest_aligned(left, right,
anchor::StrictTopCenter);
+ // Remove links if component bboxes overlap too much.
+ top_links = filter::object_links_bbox_overlap(top_links, 0.80f);
+
// Remove groups with not enough links.
top_groups = primitive::group::from_single_link(top_links);
top_groups = filter::object_groups_small(top_groups, min_card);
-
// Compute char_width and char_space statistics.
//
// Here, we also compute max_char_width, in case other
@@ -889,6 +908,9 @@ namespace scribo
bot_links = primitive::link::merge_double_link_closest_aligned(left, right,
anchor::StrictBottomCenter);
+ // Remove links if component bboxes overlap too much.
+ bot_links = filter::object_links_bbox_overlap(bot_links, 0.80f);
+
// Remove groups with not enough links.
bot_groups = primitive::group::from_single_link(bot_links);
diff --git a/scribo/scribo/primitive/extract/lines_h_pattern.hh b/scribo/scribo/primitive/extract/lines_h_pattern.hh
index 6a1f7f0..3cedf53 100644
--- a/scribo/scribo/primitive/extract/lines_h_pattern.hh
+++ b/scribo/scribo/primitive/extract/lines_h_pattern.hh
@@ -1,5 +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.
//
@@ -106,12 +106,10 @@ namespace scribo
mln_concrete(I) output = lines_pattern(input, length, 1, win);
- unsigned new_length = length / 2 + delta;
- new_length += 1 - (new_length % 2); // Guaranty that new_length is odd.
-
mln_concrete(I)
output_dil = morpho::dilation(output,
- win::rectangle2d(3, new_length));
+ win::rectangle2d(2 * delta + 1,
+ length + 2));
output = scribo::primitive::internal::rd(output, input * output_dil);
diff --git a/scribo/scribo/primitive/extract/lines_v_pattern.hh b/scribo/scribo/primitive/extract/lines_v_pattern.hh
index 8a103ac..2908c8b 100644
--- a/scribo/scribo/primitive/extract/lines_v_pattern.hh
+++ b/scribo/scribo/primitive/extract/lines_v_pattern.hh
@@ -1,5 +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.
//
@@ -88,12 +88,10 @@ namespace scribo
mln_concrete(I) output = lines_pattern(input, length, 0, win);
- unsigned new_length = length / 2 + delta;
- new_length += 1 - (new_length % 2); // Guaranty that new_length is odd.
-
mln_concrete(I)
output_dil = morpho::dilation(output,
- win::rectangle2d(new_length, 3));
+ win::rectangle2d(2 * delta + 1,
+ length + 2));
output = scribo::primitive::internal::rd(output, input * output_dil);
diff --git a/scribo/scribo/text/merging.hh b/scribo/scribo/text/merging.hh
index c94f9f5..f691188 100644
--- a/scribo/scribo/text/merging.hh
+++ b/scribo/scribo/text/merging.hh
@@ -1,4 +1,5 @@
-// 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.
//
@@ -657,7 +658,7 @@ namespace scribo
x---------------x
| |
| mc |
- ml x x x mr
+ ml x x x mr
| |
| |
x---------------x
diff --git a/scribo/src/Makefile.am b/scribo/src/Makefile.am
index 6360a56..6ab5d7d 100644
--- a/scribo/src/Makefile.am
+++ b/scribo/src/Makefile.am
@@ -1,5 +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.
#
@@ -116,6 +116,24 @@ if HAVE_QT
content_in_doc_LDADD = $(LDADD) \
$(QT_LIBS)
+ utilexec_PROGRAMS += content_in_hdoc
+ content_in_hdoc_SOURCES = content_in_hdoc.cc
+ content_in_hdoc_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(TESSERACT_CPPFLAGS) \
+ $(TIFF_CPPFLAGS) \
+ $(MAGICKXX_CPPFLAGS) \
+ $(QT_CPPFLAGS) -DHAVE_QT
+ content_in_hdoc_CXXFLAGS = $(AM_CXXFLAGS) \
+ $(QT_CXXFLAGS)
+ content_in_hdoc_LDFLAGS = $(AM_LDFLAGS) \
+ $(TESSERACT_LDFLAGS) \
+ $(TIFF_LDFLAGS) \
+ $(MAGICKXX_LDFLAGS) \
+ $(QT_LDFLAGS) \
+ -lpthread
+ content_in_hdoc_LDADD = $(LDADD) \
+ $(QT_LIBS)
+
utilexec_PROGRAMS += non_text_components
non_text_components_SOURCES = non_text_components.cc
--
1.5.6.5
1
0

last-svn-commit-873-g129baa2 Add holder information to component_info.
by Guillaume Lazzara 16 May '11
by Guillaume Lazzara 16 May '11
16 May '11
* scribo/core/component_info.hh,
* scribo/core/component_set.hh,
* scribo/core/internal/doc_serializer.hh,
* scribo/io/img/internal/debug_img_visitor.hh,
* scribo/io/img/internal/draw_edges.hh,
* scribo/io/img/internal/full_img_visitor.hh,
* scribo/io/img/internal/non_text_img_visitor.hh,
* scribo/io/xml/internal/extended_page_xml_visitor.hh,
* scribo/io/xml/internal/full_xml_visitor.hh,
* scribo/io/xml/internal/page_xml_visitor.hh,
* scribo/util/component_outline.hh: Add a template parameter to
component_info structure and update its use.
---
scribo/ChangeLog | 17 +
scribo/scribo/core/component_info.hh | 128 ++++---
scribo/scribo/core/component_set.hh | 179 ++++------
scribo/scribo/core/internal/doc_serializer.hh | 6 +-
scribo/scribo/io/img/internal/debug_img_visitor.hh | 7 +-
scribo/scribo/io/img/internal/draw_edges.hh | 7 +-
scribo/scribo/io/img/internal/full_img_visitor.hh | 7 +-
.../scribo/io/img/internal/non_text_img_visitor.hh | 7 +-
.../io/xml/internal/extended_page_xml_visitor.hh | 7 +-
scribo/scribo/io/xml/internal/full_xml_visitor.hh | 7 +-
scribo/scribo/io/xml/internal/page_xml_visitor.hh | 25 +-
scribo/scribo/util/component_outline.hh | 379 ++++++++++++++++++++
12 files changed, 592 insertions(+), 184 deletions(-)
create mode 100644 scribo/scribo/util/component_outline.hh
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index d3d7f90..8e3c903 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,22 @@
2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+ Add holder information to component_info.
+
+ * scribo/core/component_info.hh,
+ * scribo/core/component_set.hh,
+ * scribo/core/internal/doc_serializer.hh,
+ * scribo/io/img/internal/debug_img_visitor.hh,
+ * scribo/io/img/internal/draw_edges.hh,
+ * scribo/io/img/internal/full_img_visitor.hh,
+ * scribo/io/img/internal/non_text_img_visitor.hh,
+ * scribo/io/xml/internal/extended_page_xml_visitor.hh,
+ * scribo/io/xml/internal/full_xml_visitor.hh,
+ * scribo/io/xml/internal/page_xml_visitor.hh,
+ * scribo/util/component_outline.hh: Add a template parameter to
+ component_info structure and update its use.
+
+2011-05-16 Guillaume Lazzara <lazzara(a)fidji.lrde.epita.fr>
+
New specific toolchain for historical documents.
* scribo/toolchain/content_in_hdoc.hh,
diff --git a/scribo/scribo/core/component_info.hh b/scribo/scribo/core/component_info.hh
index 1f94076..2fa2ad1 100644
--- a/scribo/scribo/core/component_info.hh
+++ b/scribo/scribo/core/component_info.hh
@@ -44,15 +44,21 @@
namespace scribo
{
+ // Forward declarations.
+ template <typename L> class component_set;
+
typedef mln::util::object_id<scribo::ComponentId, unsigned> component_id_t;
- class component_info : public Serializable<component_info>
+
+ template <typename L>
+ class component_info : public Serializable<component_info<L> >
{
typedef mln::util::object_id<scribo::ComponentId, unsigned> component_id_t;
public:
component_info();
- component_info(const component_id_t& id,
+ component_info(const component_set<L>& holder,
+ const component_id_t& id,
const mln::box2d& bbox,
const mln::point2d& mass_center,
unsigned card,
@@ -80,6 +86,8 @@ namespace scribo
bool is_valid() const;
+ const component_set<L>& holder() const;
+
protected:
component_id_t id_;
mln::box2d bbox_;
@@ -90,36 +98,41 @@ namespace scribo
component::Tag tag_;
component::Type type_;
+
+ component_set<L> holder_;
};
+ template <typename L>
std::ostream&
- operator<<(std::ostream& ostr, const component_info& info);
+ operator<<(std::ostream& ostr, const component_info<L>& info);
+ template <typename L>
bool
- operator==(const component_info& lhs, const component_info& rhs);
+ operator==(const component_info<L>& lhs, const component_info<L>& rhs);
# ifndef MLN_INCLUDE_ONLY
- inline
- component_info::component_info()
+ template <typename L>
+ component_info<L>::component_info()
: id_(0), tag_(component::Ignored), type_(component::Undefined)
{
}
- inline
- component_info::component_info(const component_id_t& id,
- const mln::box2d& bbox,
- const mln::point2d& mass_center,
- unsigned card,
- component::Type type)
+ template <typename L>
+ component_info<L>::component_info(const component_set<L>& holder,
+ const component_id_t& id,
+ const mln::box2d& bbox,
+ const mln::point2d& mass_center,
+ unsigned card,
+ component::Type type)
: id_(id), bbox_(bbox), mass_center_(mass_center), card_(card),
- type_(type)
+ type_(type), holder_(holder)
{
if (!bbox.is_valid())
tag_ = component::Ignored;
@@ -128,114 +141,129 @@ namespace scribo
}
- inline
- component_info::component_id_t
- component_info::id() const
+ template <typename L>
+ typename component_info<L>::component_id_t
+ component_info<L>::id() const
{
return id_;
}
- inline
+ template <typename L>
const mln::box2d&
- component_info::bbox() const
+ component_info<L>::bbox() const
{
return bbox_;
}
- inline
+ template <typename L>
const mln::point2d&
- component_info::mass_center() const
+ component_info<L>::mass_center() const
{
return mass_center_;
}
- inline
+ template <typename L>
unsigned
- component_info::card() const
+ component_info<L>::card() const
{
return card_;
}
- inline
+ template <typename L>
bool
- component_info::has_features() const
+ component_info<L>::has_features() const
{
return features_.valid;
}
- inline
+ template <typename L>
void
- component_info::update_features(const component_features_data& features)
+ component_info<L>::update_features(const component_features_data& features)
{
features_ = features;
}
- inline
+ template <typename L>
const component_features_data&
- component_info::features() const
+ component_info<L>::features() const
{
return features_;
}
- inline
+ template <typename L>
component::Tag
- component_info::tag() const
+ component_info<L>::tag() const
{
return tag_;
}
- inline
+ template <typename L>
void
- component_info::update_tag(component::Tag tag)
+ component_info<L>::update_tag(component::Tag tag)
{
tag_ = tag;
}
- inline
+ template <typename L>
component::Type
- component_info::type() const
+ component_info<L>::type() const
{
return type_;
}
- inline
+ template <typename L>
void
- component_info::update_type(component::Type type)
+ component_info<L>::update_type(component::Type type)
{
type_ = type;
}
- inline
+ template <typename L>
bool
- component_info::is_valid() const
+ component_info<L>::is_valid() const
{
return tag_ != component::Ignored && bbox_.is_valid();
}
- inline
+ template <typename L>
+ const component_set<L>&
+ component_info<L>::holder() const
+ {
+ return holder_;
+ }
+
+
+ template <typename L>
std::ostream&
- operator<<(std::ostream& ostr, const component_info& info)
+ operator<<(std::ostream& ostr, const component_info<L>& info)
{
- return ostr << "component_info("
- << "id=" << info.id()
- << ", bbox=" << info.bbox()
- << ", mass_center=" << info.mass_center()
- << ", card=" << info.card()
- << ", tag=" << info.tag()
- << ", features=" << info.features()
- << ")" << std::endl;
+ ostr << "component_info("
+ << "id=" << info.id()
+ << ", bbox=" << info.bbox()
+ << ", mass_center=" << info.mass_center()
+ << ", card=" << info.card()
+ << ", tag=" << info.tag();
+
+ if (info.features().valid)
+ ostr << ", features=" << info.features();
+ else
+ ostr << ", features=none";
+
+ ostr << ")" << std::endl;
+
+ return ostr;
}
- inline
+ template <typename L>
bool
- operator==(const component_info& lhs, const component_info& rhs)
+ operator==(const component_info<L>& lhs, const component_info<L>& rhs)
{
return
diff --git a/scribo/scribo/core/component_set.hh b/scribo/scribo/core/component_set.hh
index 3587302..4f4cd61 100644
--- a/scribo/scribo/core/component_set.hh
+++ b/scribo/scribo/core/component_set.hh
@@ -98,7 +98,7 @@ namespace scribo
component::Type type = component::Undefined);
component_set_data(const L& ima, const mln_value(L)& ncomps,
- const mln::util::array<scribo::component_info>& infos);
+ const mln::util::array<scribo::component_info<L> >& infos);
void fill_infos(const mln::util::array<pair_accu_t>& attribs,
component::Type type = component::Undefined);
@@ -111,7 +111,7 @@ namespace scribo
L ima_;
mln_value(L) ncomps_;
- mln::util::array<scribo::component_info> infos_;
+ mln::util::array<scribo::component_info<L> > infos_;
mln_ch_value(L, bool) separators_;
};
@@ -162,16 +162,16 @@ namespace scribo
mln_value(L) nelements() const;
/// Return component information for a given component id \p id.
- const component_info& info(const mln_value(L)& id) const;
+ const component_info<L>& info(const mln_value(L)& id) const;
/// Return component information for a given component id \p id.
- component_info& info(const mln_value(L)& id);
+ component_info<L>& info(const mln_value(L)& id);
/// Return component information for a given component id \p id.
- component_info& operator()(const component_id_t& id);
+ component_info<L>& operator()(const component_id_t& id);
/// Return component information for a given component id \p id.
- const component_info& operator()(const component_id_t& id) const;
+ const component_info<L>& operator()(const component_id_t& id) const;
/// Update tag of components set to 'false' in \p f with \p tag.
@@ -211,7 +211,7 @@ namespace scribo
/// @{
/// Return all the component infos.
- const mln::util::array<scribo::component_info>& infos_() const;
+ const mln::util::array<scribo::component_info<L> >& infos_() const;
/// Unique set Id.
id_t id_() const;
@@ -278,99 +278,6 @@ namespace scribo
const mln_value(L)& ncomps)
: ima_(ima), ncomps_(ncomps)
{
- initialize(separators_, ima); // FIXME: to be removed
- mln::data::fill(separators_, false);
-
- typedef mln::accu::shape::bbox<mln_site(L)> bbox_accu_t;
- typedef mln::accu::center<mln_site(L)> center_accu_t;
- typedef mln::accu::pair<bbox_accu_t, center_accu_t> pair_accu_t;
-
-
- mln::util::array<pair_accu_t> attribs;
- mln::labeling::compute(attribs, ima_, ncomps_);
-
- fill_infos(attribs);
- }
-
-
- template <typename L>
- inline
- component_set_data<L>::component_set_data(const L& ima,
- const mln_value(L)& ncomps,
- const mln::util::array<pair_accu_t>& attribs,
- component::Type type)
- : ima_(ima), ncomps_(ncomps)
- {
- initialize(separators_, ima); // FIXME: to be removed
- mln::data::fill(separators_, false);
-
- fill_infos(attribs, type);
- }
-
- template <typename L>
- inline
- component_set_data<L>::component_set_data(const L& ima,
- const mln_value(L)& ncomps,
- const mln::util::array<pair_data_t>& attribs,
- component::Type type)
- : ima_(ima), ncomps_(ncomps)
- {
- initialize(separators_, ima); // FIXME: to be removed
- mln::data::fill(separators_, false);
-
- fill_infos(attribs, type);
- }
-
- template <typename L>
- inline
- component_set_data<L>::component_set_data(const L& ima,
- const mln_value(L)& ncomps,
- const mln::util::array<scribo::component_info>& infos)
- : ima_(ima), ncomps_(ncomps), infos_(infos)
- {
- initialize(separators_, ima); // FIXME: to be removed
- mln::data::fill(separators_, false);
- }
-
-
- template <typename L>
- inline
- void
- component_set_data<L>::fill_infos(const mln::util::array<pair_accu_t>& attribs,
- component::Type type)
- {
- typedef mln_site(L) P;
-
- infos_.reserve(mln::value::next(ncomps_));
-
- infos_.append(component_info()); // Component 0, i.e. the background.
- for_all_comp_data(i, attribs)
- {
- component_info info(i, attribs[i].first(),
- attribs[i].second(), attribs[i].second_accu().nsites(),
- type);
- infos_.append(info);
- }
- }
-
- template <typename L>
- inline
- void
- component_set_data<L>::fill_infos(const mln::util::array<pair_data_t>& attribs,
- component::Type type)
- {
- typedef mln_site(L) P;
-
- infos_.reserve(mln::value::next(ncomps_));
-
- infos_.append(component_info()); // Component 0, i.e. the background.
- for_all_comp_data(i, attribs)
- {
- component_info info(i, attribs[i].first,
- attribs[i].second.first, attribs[i].second.second,
- type);
- infos_.append(info);
- }
}
@@ -383,7 +290,7 @@ namespace scribo
ncomps_ = ncomps;
infos_.reserve(ncomps_);
- infos_.append(component_info()); // Component 0, i.e. the background.
+ infos_.append(component_info<L>()); // Component 0, i.e. the background.
}
@@ -410,6 +317,30 @@ namespace scribo
component_set<L>::component_set(const L& ima, const mln_value(L)& ncomps)
{
data_ = new internal::component_set_data<L>(ima, ncomps);
+
+ initialize(data_->separators_, ima); // FIXME: to be removed
+ mln::data::fill(data_->separators_, false);
+
+ typedef mln::accu::shape::bbox<mln_site(L)> bbox_accu_t;
+ typedef mln::accu::center<mln_site(L)> center_accu_t;
+ typedef mln::accu::pair<bbox_accu_t, center_accu_t> pair_accu_t;
+
+
+ mln::util::array<pair_accu_t> attribs;
+ mln::labeling::compute(attribs, ima, ncomps);
+
+
+ typedef mln_site(L) P;
+
+ data_->infos_.reserve(mln::value::next(ncomps));
+
+ data_->infos_.append(component_info<L>()); // Component 0, i.e. the background.
+ for_all_comp_data(i, attribs)
+ {
+ component_info<L> info(*this, i, attribs[i].first(),
+ attribs[i].second(), attribs[i].second_accu().nsites());
+ data_->infos_.append(info);
+ }
}
@@ -419,7 +350,23 @@ namespace scribo
const mln::util::array<pair_accu_t>& attribs,
component::Type type)
{
- data_ = new internal::component_set_data<L>(ima, ncomps, attribs, type);
+ data_ = new internal::component_set_data<L>(ima, ncomps);
+
+ initialize(data_->separators_, ima); // FIXME: to be removed
+ mln::data::fill(data_->separators_, false);
+
+ typedef mln_site(L) P;
+
+ data_->infos_.reserve(mln::value::next(ncomps));
+
+ data_->infos_.append(component_info<L>()); // Component 0, i.e. the background.
+ for_all_comp_data(i, attribs)
+ {
+ component_info<L> info(*this, i, attribs[i].first(),
+ attribs[i].second(), attribs[i].second_accu().nsites(),
+ type);
+ data_->infos_.append(info);
+ }
}
@@ -430,7 +377,23 @@ namespace scribo
const mln::util::array<pair_data_t>& attribs,
component::Type type)
{
- data_ = new internal::component_set_data<L>(ima, ncomps, attribs, type);
+ data_ = new internal::component_set_data<L>(ima, ncomps);
+
+ initialize(data_->separators_, ima); // FIXME: to be removed
+ mln::data::fill(data_->separators_, false);
+
+ typedef mln_site(L) P;
+
+ data_->infos_.reserve(mln::value::next(ncomps));
+
+ data_->infos_.append(component_info<L>()); // Component 0, i.e. the background.
+ for_all_comp_data(i, attribs)
+ {
+ component_info<L> info(*this, i, attribs[i].first,
+ attribs[i].second.first, attribs[i].second.second,
+ type);
+ data_->infos_.append(info);
+ }
}
@@ -445,7 +408,7 @@ namespace scribo
template <typename L>
inline
- const component_info&
+ const component_info<L>&
component_set<L>::info(const mln_value(L)& id) const
{
return data_->infos_[id];
@@ -453,7 +416,7 @@ namespace scribo
template <typename L>
inline
- component_info&
+ component_info<L>&
component_set<L>::info(const mln_value(L)& id)
{
return data_->infos_[id];
@@ -461,7 +424,7 @@ namespace scribo
template <typename L>
inline
- const component_info&
+ const component_info<L>&
component_set<L>::operator()(const component_id_t& id) const
{
return data_->infos_[id];
@@ -469,7 +432,7 @@ namespace scribo
template <typename L>
inline
- component_info&
+ component_info<L>&
component_set<L>::operator()(const component_id_t& id)
{
return data_->infos_[id];
@@ -611,7 +574,7 @@ namespace scribo
template <typename L>
inline
- const mln::util::array<scribo::component_info>&
+ const mln::util::array<scribo::component_info<L> >&
component_set<L>::infos_() const
{
return data_->infos_;
diff --git a/scribo/scribo/core/internal/doc_serializer.hh b/scribo/scribo/core/internal/doc_serializer.hh
index 2655100..606716b 100644
--- a/scribo/scribo/core/internal/doc_serializer.hh
+++ b/scribo/scribo/core/internal/doc_serializer.hh
@@ -65,7 +65,8 @@ namespace scribo
template <typename L>
void visit(const component_set<L>& comp_set) const;
- void visit(const component_info& info) const;
+ template <typename L>
+ void visit(const component_info<L>& info) const;
template <typename L>
void visit(const paragraph_set<L>& parset) const;
@@ -113,8 +114,9 @@ namespace scribo
}
template <typename E>
+ template <typename L>
void
- doc_serializer<E>::visit(const component_info& info) const
+ doc_serializer<E>::visit(const component_info<L>& info) const
{
}
diff --git a/scribo/scribo/io/img/internal/debug_img_visitor.hh b/scribo/scribo/io/img/internal/debug_img_visitor.hh
index 62097f5..5ad1dd3 100644
--- a/scribo/scribo/io/img/internal/debug_img_visitor.hh
+++ b/scribo/scribo/io/img/internal/debug_img_visitor.hh
@@ -69,7 +69,8 @@ namespace scribo
template <typename L>
void visit(const document<L>& doc) const;
- void visit(const component_info& info) const;
+ template <typename L>
+ void visit(const component_info<L>& info) const;
template <typename L>
void visit(const paragraph_set<L>& parset) const;
@@ -173,9 +174,9 @@ namespace scribo
/// Component_info
//
- inline
+ template <typename L>
void
- debug_img_visitor::visit(const component_info& info) const
+ debug_img_visitor::visit(const component_info<L>& info) const
{
switch (info.type())
{
diff --git a/scribo/scribo/io/img/internal/draw_edges.hh b/scribo/scribo/io/img/internal/draw_edges.hh
index 664a352..8903a7e 100644
--- a/scribo/scribo/io/img/internal/draw_edges.hh
+++ b/scribo/scribo/io/img/internal/draw_edges.hh
@@ -60,8 +60,9 @@ namespace scribo
/*! \brief Draw component edges.
*/
+ template <typename L>
void
- draw_edges(const component_info& info,
+ draw_edges(const component_info<L>& info,
image2d<value::rgb8>& output, const value::rgb8& value,
const image2d<scribo::def::lbl_type>& edges);
@@ -69,9 +70,9 @@ namespace scribo
# ifndef MLN_INCLUDE_ONLY
- inline
+ template <typename L>
void
- draw_edges(const component_info& info,
+ draw_edges(const component_info<L>& info,
image2d<value::rgb8>& output, const value::rgb8& value,
const image2d<scribo::def::lbl_type>& edges)
{
diff --git a/scribo/scribo/io/img/internal/full_img_visitor.hh b/scribo/scribo/io/img/internal/full_img_visitor.hh
index 170b6a1..662d8d1 100644
--- a/scribo/scribo/io/img/internal/full_img_visitor.hh
+++ b/scribo/scribo/io/img/internal/full_img_visitor.hh
@@ -66,7 +66,8 @@ namespace scribo
template <typename L>
void visit(const document<L>& doc) const;
- void visit(const component_info& info) const;
+ template <typename L>
+ void visit(const component_info<L>& info) const;
template <typename L>
void visit(const paragraph_set<L>& parset) const;
@@ -129,9 +130,9 @@ namespace scribo
/// Component_info
//
- inline
+ template <typename L>
void
- full_img_visitor::visit(const component_info& info) const
+ full_img_visitor::visit(const component_info<L>& info) const
{
switch (info.type())
{
diff --git a/scribo/scribo/io/img/internal/non_text_img_visitor.hh b/scribo/scribo/io/img/internal/non_text_img_visitor.hh
index cc1acb9..475a0a1 100644
--- a/scribo/scribo/io/img/internal/non_text_img_visitor.hh
+++ b/scribo/scribo/io/img/internal/non_text_img_visitor.hh
@@ -67,7 +67,8 @@ namespace scribo
template <typename L>
void visit(const component_set<L>& comp_set) const;
- void visit(const component_info& info) const;
+ template <typename L>
+ void visit(const component_info<L>& info) const;
private: // Attributes
mln::image2d<value::rgb8>& output;
@@ -126,9 +127,9 @@ namespace scribo
/// Component_info
//
- inline
+ template <typename L>
void
- non_text_img_visitor::visit(const component_info& info) const
+ non_text_img_visitor::visit(const component_info<L>& info) const
{
switch (info.type())
{
diff --git a/scribo/scribo/io/xml/internal/extended_page_xml_visitor.hh b/scribo/scribo/io/xml/internal/extended_page_xml_visitor.hh
index 0cdebb5..2611d4a 100644
--- a/scribo/scribo/io/xml/internal/extended_page_xml_visitor.hh
+++ b/scribo/scribo/io/xml/internal/extended_page_xml_visitor.hh
@@ -80,7 +80,8 @@ namespace scribo
template <typename L>
void visit(const component_set<L>& comp_set) const;
- void visit(const component_info& info) const;
+ template <typename L>
+ void visit(const component_info<L>& info) const;
template <typename L>
void visit(const paragraph_set<L>& parset) const;
@@ -159,9 +160,9 @@ namespace scribo
/// Component_info
//
- inline
+ template <typename L>
void
- extended_page_xml_visitor::visit(const component_info& info) const
+ extended_page_xml_visitor::visit(const component_info<L>& info) const
{
switch (info.type())
{
diff --git a/scribo/scribo/io/xml/internal/full_xml_visitor.hh b/scribo/scribo/io/xml/internal/full_xml_visitor.hh
index a8dfffe..614c473 100644
--- a/scribo/scribo/io/xml/internal/full_xml_visitor.hh
+++ b/scribo/scribo/io/xml/internal/full_xml_visitor.hh
@@ -85,7 +85,8 @@ namespace scribo
template <typename L>
void visit(const component_set<L>& comp_set) const;
- void visit(const component_info& info) const;
+ template <typename L>
+ void visit(const component_info<L>& info) const;
template <typename L>
void visit(const paragraph_set<L>& parset) const;
@@ -350,9 +351,9 @@ namespace scribo
/// Component_info
//
- inline
+ template <typename L>
void
- full_xml_visitor::visit(const component_info& info) const
+ full_xml_visitor::visit(const component_info<L>& info) const
{
switch (info.type())
{
diff --git a/scribo/scribo/io/xml/internal/page_xml_visitor.hh b/scribo/scribo/io/xml/internal/page_xml_visitor.hh
index 0f3cce1..b807dbb 100644
--- a/scribo/scribo/io/xml/internal/page_xml_visitor.hh
+++ b/scribo/scribo/io/xml/internal/page_xml_visitor.hh
@@ -35,6 +35,8 @@
# include <scribo/core/internal/doc_serializer.hh>
# include <scribo/convert/to_base64.hh>
+# include <scribo/util/component_outline.hh>
+
# include <scribo/io/xml/internal/print_box_coords.hh>
# include <scribo/io/xml/internal/print_page_preambule.hh>
# include <scribo/io/xml/internal/compute_text_colour.hh>
@@ -76,7 +78,8 @@ namespace scribo
template <typename L>
void visit(const component_set<L>& comp_set) const;
- void visit(const component_info& info) const;
+ template <typename L>
+ void visit(const component_info<L>& info) const;
template <typename L>
void visit(const paragraph_set<L>& parset) const;
@@ -120,7 +123,9 @@ namespace scribo
// Page elements (Pictures, ...)
if (doc.has_elements())
+ {
doc.elements().accept(*this);
+ }
// line seraparators
if (doc.has_vline_seps())
@@ -147,10 +152,18 @@ namespace scribo
/// Component_info
//
- inline
+ template <typename L>
void
- page_xml_visitor::visit(const component_info& info) const
+ page_xml_visitor::visit(const component_info<L>& info) const
{
+ // Getting component outline
+ scribo::def::lbl_type id = (scribo::def::lbl_type)info.id().to_equiv();
+ const L& lbl = info.holder().labeled_image();
+ p_array<point2d>
+ par = util::component_outline(((lbl | info.bbox())
+ | (pw::value(lbl) == pw::cst(id))),
+ 1);
+
switch (info.type())
{
case component::VerticalLineSeparator:
@@ -159,7 +172,7 @@ namespace scribo
<< "\" orientation=\"0.000000\" "
<< " colour=\"black\">" << std::endl;
- internal::print_box_coords(output, info.bbox(), " ");
+ internal::print_image_coords(output, par, " ");
output << " </SeparatorRegion>" << std::endl;
break;
@@ -171,7 +184,7 @@ namespace scribo
<< "\" orientation=\"0.000000\" "
<< " colour=\"black\">" << std::endl;
- internal::print_box_coords(output, info.bbox(), " ");
+ internal::print_image_coords(output, par, " ");
output << " </SeparatorRegion>" << std::endl;
break;
@@ -187,7 +200,7 @@ namespace scribo
<< " embText=\"false\" "
<< " bgColour=\"white\">" << std::endl;
- internal::print_box_coords(output, info.bbox(), " ");
+ internal::print_image_coords(output, par, " ");
output << " </ImageRegion>" << std::endl;
break;
diff --git a/scribo/scribo/util/component_outline.hh b/scribo/scribo/util/component_outline.hh
new file mode 100644
index 0000000..85ab4a7
--- /dev/null
+++ b/scribo/scribo/util/component_outline.hh
@@ -0,0 +1,379 @@
+// Copyright (C) 2011 EPITA Research and Development Laboratory (LRDE)
+//
+// This file is part of Olena.
+//
+// Olena 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, version 2 of the License.
+//
+// Olena 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 Olena. If not, see <http://www.gnu.org/licenses/>.
+//
+// As a special exception, you may use this file as part of a free
+// software project without restriction. Specifically, if other files
+// instantiate templates or use macros or inline functions from this
+// file, or you compile this file and link it with other files to produce
+// an executable, this file does not by itself cause the resulting
+// executable to be covered by the GNU General Public License. This
+// exception does not however invalidate any other reasons why the
+// executable file might be covered by the GNU General Public License.
+
+#ifndef SCRIBO_UTIL_COMPONENT_OUTLINE_HH
+# define SCRIBO_UTIL_COMPONENT_OUTLINE_HH
+
+/*!
+ * \file
+ *
+ * \brief Define a function which finds a polygonal approximation
+ * of a component's contour using the Douglas-Peucker algorithm.
+ *
+ */
+
+# include <mln/core/concept/image.hh>
+# include <mln/core/site_set/p_array.hh>
+# include <mln/geom/all.hh>
+
+# include <utility>
+# include <cmath>
+
+#include <mln/accu/shape/bbox.hh>
+
+namespace scribo
+{
+
+ namespace util
+ {
+
+ /*! Finds the 'douglas-peucker' polygonal approximation.
+ *
+ * Assumes there's only one centered component.
+ * More information about the algorithm can be found here :
+ * http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
+ *
+ * Returns an ordered vector of points defining the polygonal contour.
+ *
+ * \param[in] ima_ The component whose contour is sought.
+ * \param[in] precision The max distance between the approximation
+ * and the curve.
+ */
+ template <typename I>
+ mln::p_array<mln::point2d>
+ component_outline(const mln::Image<I>& ima, float precision);
+
+
+# ifndef MLN_INCLUDE_ONLY
+
+ namespace internal
+ {
+ typedef enum
+ {
+ DIR_RIGHT = 0,
+ DIR_RIGHT_UP = 1,
+ DIR_UP = 2,
+ DIR_LEFT_UP = 3,
+ DIR_LEFT = 4,
+ DIR_LEFT_DOWN = 5,
+ DIR_DOWN = 6,
+ DIR_RIGHT_DOWN = 7,
+ DIR_NONE = 8
+ } e_dir;
+
+ /// Give the point2d which is in the given dir relatively to p.
+ static inline
+ mln::point2d
+ iterate_in_dir(const mln::point2d& p,
+ e_dir dir)
+ {
+ int dx = 0;
+ int dy = 0;
+
+ switch (dir)
+ {
+ case DIR_RIGHT:
+ dx = 1;
+ break;
+ case DIR_RIGHT_UP:
+ dx = 1;
+ dy = -1;
+ break;
+ case DIR_UP:
+ dy = -1;
+ break;
+ case DIR_LEFT_UP:
+ dx = dy = -1;
+ break;
+ case DIR_LEFT:
+ dx = -1;
+ break;
+ case DIR_LEFT_DOWN:
+ dx = -1;
+ dy = 1;
+ break;
+ case DIR_DOWN:
+ dy = 1;
+ break;
+ case DIR_RIGHT_DOWN:
+ dx = dy = 1;
+ break;
+ default:
+ break;
+ }
+
+ return mln::point2d(p[0] + dx, p[1] + dy);
+ }
+
+ /// Decrement the given direction.
+ static inline
+ void
+ dec_dir(e_dir* d)
+ {
+ *d = (e_dir) (((unsigned)*d + 7) & 7);
+ }
+
+ /// Increment the given direction (clockwise).
+ static inline
+ void
+ inc_dir(e_dir* d)
+ {
+ *d = (e_dir) (((unsigned)*d + 9) & 7);
+ }
+
+ /// Inverse the given direction.
+ static inline
+ void
+ inverse_dir(e_dir* d)
+ {
+ *d = (e_dir) (((unsigned)*d + 4) & 7);
+ }
+
+ /// Initialize the direction in which to find next points in the contour
+ template <typename I>
+ static inline
+ e_dir
+ find_first_dir(const I& ima,
+ const mln::point2d& p)
+ {
+ e_dir dir = DIR_NONE;
+
+ mln::point2d n;
+ do
+ {
+ dec_dir(&dir);
+ n = iterate_in_dir(p, dir);
+ }
+ while (ima(n) != mln::literal::zero);
+
+ while (ima(n) == mln::literal::zero)
+ {
+ inc_dir(&dir);
+ n = iterate_in_dir(p, dir);
+ }
+
+ return (dir);
+ }
+
+ /// Give the next point in the contour
+ template <typename I>
+ static
+ mln::point2d
+ next_pt_in_contour(const I& ima,
+ const mln::point2d& ori,
+ e_dir* last_dir)
+ {
+ e_dir dir = (*last_dir);
+ dec_dir(&dir);
+ mln::point2d cur;
+
+ do
+ {
+ cur = iterate_in_dir(ori, dir);
+ dec_dir(&dir);
+ }
+ while (ima(cur) == mln::literal::zero);
+
+ *last_dir = dir;
+ inverse_dir(last_dir);
+ return cur;
+ }
+
+ /// Give The farthest point from line (begin, end) or NULL
+ /// if that distance is inferior to the given precision. (bottleneck)
+ template <typename I>
+ static inline
+ mln::point2d
+ find_farthest(const I& ima,
+ const mln::point2d& begin,
+ const mln::point2d& end,
+ float precision)
+ {
+ float d = 0.;
+ float dmax = 0.;
+ e_dir dir = find_first_dir(ima, begin);
+
+ float a = end[1] - begin[1];
+ float b = begin[0] - end[0];
+ float c = begin[1] * end[0] - begin[0] * end[1];
+ float norm = sqrt ((a * a) + (b * b));
+
+ mln::point2d max;
+ mln::point2d cur;
+ mln::point2d old;
+
+ cur = begin;
+ max = cur;
+
+ while (cur != end)
+ {
+ old = cur;
+ cur = next_pt_in_contour(ima, cur, &dir);
+
+ d = a * cur[0] + cur[1] * b + c;
+
+ if (d < 0)
+ d = -d;
+
+ if (d > dmax)
+ {
+ dmax = d;
+ max = cur;
+ }
+
+ }
+
+ if (dmax > precision * norm)
+ return max;
+ else
+ return begin;
+ }
+
+ /// Split the line (begin, end) at the farthest point on the curve
+ /// and apply itself recursively.
+ template <typename I>
+ void
+ split_rec(const I& ima,
+ const mln::point2d& begin,
+ const mln::point2d& end,
+ mln::p_array<mln::point2d>& l,
+ float precision)
+ {
+ mln::point2d node = find_farthest(ima, begin, end, precision);
+
+ if (node != begin)
+ {
+ split_rec(ima, begin, node, l, precision);
+
+ l.append(node);
+
+ split_rec(ima, node, end, l, precision);
+ }
+ }
+
+ /// Init the spliting procedure on both side of the line.
+ template <typename I>
+ void
+ split(const I& ima,
+ const mln::point2d& begin,
+ const mln::point2d& end,
+ mln::p_array<mln::point2d>& l,
+ float precision)
+ {
+ mln::point2d node;
+
+ node = find_farthest(ima, begin, end, precision);
+
+ l.append(begin);
+
+ if (node != begin)
+ {
+ split_rec(ima, begin, node, l, precision);
+
+ l.append(node);
+
+ split_rec(ima, node, end, l, precision);
+ }
+
+ l.append(end);
+
+ node = find_farthest(ima, end, begin, precision);
+
+ if (node != end)
+ {
+ split_rec(ima, end, node, l, precision);
+
+ l.append(node);
+
+ split_rec(ima, node, begin, l, precision);
+ }
+ }
+
+ /// Find (simply) 2 points on the contour of the component
+ template <typename I>
+ static inline
+ std::pair<mln::point2d, mln::point2d>
+ get_initials(const I& ima)
+ {
+ mln::point2d begin(mln::geom::min_row(ima),
+ mln::geom::min_col(ima));
+ mln::point2d end(mln::geom::max_row(ima),
+ mln::geom::max_col(ima));
+
+ while (ima(begin) == mln::literal::zero)
+ begin[0]++;
+
+ while (ima(end) == mln::literal::zero)
+ end[0]--;
+
+ return std::pair<mln::point2d, mln::point2d> (begin, end);
+ }
+ } // end of namespace internal
+
+
+
+
+ template <typename I>
+ mln::p_array<mln::point2d>
+ component_outline(const mln::Image<I>& ima_,
+ float precision)
+ {
+ mln::trace::entering("scribo::component_outline");
+
+ const I& ima = exact(ima_);
+
+ mln_precondition(ima.is_valid());
+
+ mln::accu::shape::bbox<point2d> ab;
+ mln_piter(I) p(ima.domain());
+ for_all(p)
+ ab.take(p);
+
+ std::cout << ab.to_result() << std::endl;
+
+ mln::p_array<mln::point2d> contour;
+ std::pair<mln::point2d, mln::point2d> initials =
+ internal::get_initials(ima);
+
+ std::cout << " - " << initials.first << " - " << initials.second << std::endl;
+
+ internal::split(ima,
+ initials.first,
+ initials.second,
+ contour,
+ precision);
+
+ mln::trace::exiting("scribo::component_outline");
+
+ return contour;
+ }
+
+# endif // ! MLN_INCLUDE_ONLY
+
+ } // end of namespace util
+
+} // end of namespace scribo
+
+#endif // ! SCRIBO_UTIL_COMPONENT_OUTLINE_HH
--
1.5.6.5
1
0