Olena-patches
Threads by month
- ----- 2025 -----
- 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
May 2011
- 9 participants
- 171 discussions

03 May '11
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Olena, a generic and efficient image processing platform".
The branch julien_temp_paragraphs has been created
at 8bae0713f5a4e26137e59a5cc6a3f2e947592f7d (commit)
- Log -----------------------------------------------------------------
8bae071 Paragraphs extraction
-----------------------------------------------------------------------
hooks/post-receive
--
Olena, a generic and efficient image processing platform
1
0
---
scribo/scribo/core/line_info.hh | 212 +++++-
scribo/scribo/core/stats.hh | 320 +++++++++
scribo/scribo/text/look_like_text_lines.hh | 16 +
scribo/scribo/text/merging.hh | 306 ++++++---
scribo/scribo/text/paragraphs.hh | 1027 ++++++++++++++++++++++++++++
5 files changed, 1774 insertions(+), 107 deletions(-)
create mode 100644 scribo/scribo/core/stats.hh
create mode 100644 scribo/scribo/text/paragraphs.hh
diff --git a/scribo/scribo/core/line_info.hh b/scribo/scribo/core/line_info.hh
index b8a8be3..c683be4 100644
--- a/scribo/scribo/core/line_info.hh
+++ b/scribo/scribo/core/line_info.hh
@@ -61,6 +61,9 @@
# include <scribo/core/internal/sort_comp_ids.hh>
# include <scribo/core/concept/serializable.hh>
+// DEBUG
+
+# include <scribo/core/stats.hh>
namespace scribo
{
@@ -145,6 +148,11 @@ namespace scribo
private:
void init_();
+ // DEBUG
+ stats< float > meanline_clusters_;
+ stats< float > baseline_clusters_;
+ };
+
};
} // end of namespace scribo::internal
@@ -273,7 +281,8 @@ namespace scribo
/// @}
-
+ bool chars_same_width() const;
+ unsigned get_first_char_height() const;
/// Force a new computation of statistics.
void force_stats_update();
@@ -301,6 +310,9 @@ namespace scribo
void update_components_type(component::Type type);
+ int compute_baseline();
+ int compute_meanline();
+
private: // Attributes
line_id_t id_;
mln::util::tracked_ptr<data_t> data_;
@@ -810,7 +822,7 @@ namespace scribo
int
line_info<L>::delta_of_line() const
{
- return char_width() + 2 * char_space();
+ return 2 * char_width() + 2 * char_space();
// FIXME: choose between:
// not enough: char_width + char_space
// too much: 2 * char_width
@@ -960,6 +972,145 @@ namespace scribo
}
template <typename L>
+ bool
+ line_info<L>::chars_same_width() const
+ {
+ // Only for the case of two-character words
+ if (card() == 2)
+ {
+ const component_set<L>& comp_set = data_->holder_.components();
+
+ const unsigned c1 = data_->components_(0);
+ const unsigned c2 = data_->components_(1);
+
+ if (data_->holder_.components()(c1).type() == component::Punctuation
+ || data_->holder_.components()(c2).type() == component::Punctuation)
+ return false;
+
+ const mln::box2d& bb1 = comp_set(c1).bbox();
+ const mln::box2d& bb2 = comp_set(c2).bbox();
+
+ const float w1 = bb1.width();
+ const float h1 = bb1.height();
+ const float w2 = bb2.width();
+ const float h2 = bb2.height();
+
+ const float space = std::max(bb1.pmin().col(), bb2.pmin().col()) -
+ std::min(bb1.pmax().col(), bb2.pmax().col());
+
+ const int dy = bb1.pmax().row() - bb2.pmax().row();
+
+ // The two characters must be distinct
+ if (space < 0)
+ return false;
+
+ if (// Approximately the same width
+ ((std::max(w1, w2) / std::min(w1, w2)) > 1.1f ||
+ // One character must not be smaller than the space between
+ // the two characters
+ (w1 < space || w2 < space))
+ // If the two characters have a different width they must also
+ // have a different height
+ && not (std::max(h1, h2) / std::min(h1, h2) <= 1.5f))
+ return false;
+
+ // Approximately aligned on baseline
+ if (std::abs(dy) > 10)
+ return false;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ template< typename L >
+ unsigned
+ line_info<L>::get_first_char_height() const
+ {
+ const component_set<L>& comp_set = data_->holder_.components();
+ const unsigned c1 = data_->components_(0);
+ const mln::box2d& bb1 = comp_set(c1).bbox();
+
+ return bb1.height();
+ }
+
+ template <typename L>
+ int
+ line_info<L>::compute_baseline()
+ {
+ const unsigned nelements = data_->baseline_clusters_.nelements();
+
+ if (nelements == 2)
+ return data_->baseline_clusters_.mean();
+
+ mln::util::array< cluster_stats< float > >& clusters_b = data_->baseline_clusters_.clusters();
+
+ unsigned index = 0;
+ float min_base = 0.0f;
+ const unsigned clusters_b_nelements = clusters_b.nelements();
+
+ for (unsigned i = 0; i < clusters_b_nelements; ++i)
+ {
+ const unsigned clusters_b_i_nelements = clusters_b[i].nelements();
+
+ if (clusters_b_i_nelements >= min_base * 2.0f)
+ {
+ min_base = clusters_b_i_nelements;
+ index = i;
+ }
+ else if (clusters_b_i_nelements >= 0.5f * min_base)
+ {
+ if (clusters_b_i_nelements > 1 &&
+ clusters_b[index].median() > clusters_b[i].median())
+ {
+ if (clusters_b_i_nelements > min_base)
+ min_base = clusters_b_i_nelements;
+ index = i;
+ }
+ }
+ }
+
+ if (clusters_b[index].nelements() <= 2 && nelements <= 5)
+ return data_->baseline_clusters_.mean();
+
+ return clusters_b[index].median();
+ }
+
+ template <typename L>
+ int
+ line_info<L>::compute_meanline()
+ {
+ mln::util::array< cluster_stats< float > >& clusters_m = data_->meanline_clusters_.clusters();
+
+ unsigned index = 0;
+ float max_mean = 0.0f;
+ const unsigned clusters_m_nelements = clusters_m.nelements();
+
+ for (unsigned i = 0; i < clusters_m_nelements; ++i)
+ {
+ const unsigned clusters_m_i_nelements = clusters_m[i].nelements();
+
+ if (clusters_m_i_nelements >= max_mean * 2.0f)
+ {
+ max_mean = clusters_m_i_nelements;
+ index = i;
+ }
+ else if (clusters_m_i_nelements >= 0.5f * max_mean)
+ {
+ if (clusters_m[index].median() < clusters_m[i].median())
+ {
+ if (clusters_m_i_nelements > max_mean)
+ max_mean = clusters_m_i_nelements;
+ index = i;
+ }
+ }
+ }
+
+ return clusters_m[index].median();
+ }
+
+ template <typename L>
void
line_info<L>::force_stats_update()
{
@@ -970,8 +1121,8 @@ namespace scribo
typedef mln::value::int_u<12> median_data_t;
typedef mln::accu::stat::median_h<median_data_t> median_t;
median_t
- meanline,
- baseline,
+ // meanline,
+ // baseline,
char_space,
char_width;
@@ -995,6 +1146,10 @@ namespace scribo
mln::def::coord ref_line = mln_max(mln::def::coord);
+ // DEBUG
+ data_->baseline_clusters_.reset();
+ data_->meanline_clusters_.reset();
+
// Find a reference line to compute baselines and other attributes.
// Workaround to avoid overflow with int_u<12> in median accumulators.
//
@@ -1033,23 +1188,22 @@ namespace scribo
// incremented.
++used_comps;
-
// COMPUTE FEATURES DATA
- if (comp_set(c).has_features())
- {
- // Compute boldness
- boldness.take(comp_set(c).features().boldness);
- sum2_boldness += mln::math::sqr<float>(comp_set(c).features().boldness);
-
- // Compute color
- color_red.take(comp_set(c).features().color.red());
- color_green.take(comp_set(c).features().color.green());
- color_blue.take(comp_set(c).features().color.blue());
-
- sum2_red += mln::math::sqr<unsigned>(comp_set(c).features().color.red());
- sum2_green += mln::math::sqr<unsigned>(comp_set(c).features().color.green());
- sum2_blue += mln::math::sqr<unsigned>(comp_set(c).features().color.blue());
- }
+ // if (comp_set(c).has_features())
+ // {
+ // // Compute boldness
+ // boldness.take(comp_set(c).features().boldness);
+ // sum2_boldness += mln::math::sqr<float>(comp_set(c).features().boldness);
+
+ // // Compute color
+ // color_red.take(comp_set(c).features().color.red());
+ // color_green.take(comp_set(c).features().color.green());
+ // color_blue.take(comp_set(c).features().color.blue());
+
+ // sum2_red += mln::math::sqr<unsigned>(comp_set(c).features().color.red());
+ // sum2_green += mln::math::sqr<unsigned>(comp_set(c).features().color.green());
+ // sum2_blue += mln::math::sqr<unsigned>(comp_set(c).features().color.blue());
+ // }
// FIXME: we must guaranty here that the relationship is from
// right to left, otherwise, the space size computed between
@@ -1083,12 +1237,14 @@ namespace scribo
// Meanline (compute an absolute value, from the top left corner
// of the highest character bounding box, excluding
// punctuation).
- meanline.take(bb.pmin().row() - ref_line);
+ // meanline.take(bb.pmin().row() - ref_line);
+ data_->meanline_clusters_.take(bb.pmin().row());
// Baseline (compute an absolute value, from the top left corner
// of the highest character bounding box, excluding
// punctuation).
- baseline.take(bb.pmax().row() - ref_line);
+ // baseline.take(bb.pmax().row() - ref_line);
+ data_->baseline_clusters_.take(bb.pmax().row());
}
// Finalization
@@ -1140,12 +1296,14 @@ namespace scribo
else
data_->char_width_ = char_width.to_result();
- mln::def::coord
- absolute_baseline_r = baseline.to_result() + ref_line,
- absolute_meanline_r = meanline.to_result() + ref_line;
+ // // mln::def::coord
+ // // absolute_baseline_r = baseline.to_result() + ref_line,
+ // // absolute_meanline_r = meanline.to_result() + ref_line;
- data_->baseline_ = absolute_baseline_r;
- data_->meanline_ = absolute_meanline_r;
+ // data_->baseline_ = absolute_baseline_r;
+ // data_->meanline_ = absolute_meanline_r;
+ data_->baseline_ = compute_baseline();
+ data_->meanline_ = compute_meanline();
data_->x_height_ = data_->baseline_ - data_->meanline_ + 1;
data_->d_height_ = data_->baseline_ - bbox.to_result().pmax().row();
data_->a_height_ = data_->baseline_ - bbox.to_result().pmin().row() + 1;
diff --git a/scribo/scribo/core/stats.hh b/scribo/scribo/core/stats.hh
new file mode 100644
index 0000000..570325c
--- /dev/null
+++ b/scribo/scribo/core/stats.hh
@@ -0,0 +1,320 @@
+#ifndef STATS_HH_
+# define STATS_HH_
+
+# include <vector>
+# include <algorithm>
+
+# include <mln/util/array.hh>
+
+using namespace mln;
+
+//---------------------------------------------------------------------------
+// compare_values
+//---------------------------------------------------------------------------
+
+template< typename T >
+struct compare_values
+{
+ bool operator() (const T& lhs,
+ const T& rhs)
+ {
+ return (lhs < rhs);
+ }
+};
+
+
+//---------------------------------------------------------------------------
+// cluster_stats
+//---------------------------------------------------------------------------
+
+template< typename T >
+class cluster_stats
+{
+public:
+ cluster_stats()
+ : mean_needs_update_(false), median_needs_update_(false),
+ variance_needs_update_(false), std_needs_update_(false), size_(0)
+ {
+ }
+
+ cluster_stats(const unsigned size)
+ : mean_needs_update_(false), median_needs_update_(false),
+ variance_needs_update_(false), std_needs_update_(false), size_(0)
+ {
+ data_.reserve(size);
+ }
+
+ void reset()
+ {
+ std::vector< T >& data = data_.hook_std_vector_();
+ data.clear();
+
+ size_ = 0;
+ needs_update();
+ }
+
+ void take(const T& value)
+ {
+ if (not size_)
+ {
+ min_ = value;
+ max_ = value;
+ }
+ else
+ {
+ if (value < min_)
+ min_ = value;
+ else if (value > max_)
+ max_ = value;
+ }
+
+ ++size_;
+ data_.append(value);
+ needs_update();
+ }
+
+ T mean()
+ {
+ if (mean_needs_update_)
+ {
+ mean_ = 0;
+
+ for (unsigned i = 0; i < size_; ++i)
+ mean_ += data_[i];
+
+ mean_ /= size_;
+ mean_needs_update_ = false;
+ }
+
+ return mean_;
+ }
+
+ T median()
+ {
+ if (median_needs_update_)
+ {
+ std::vector< T >& data = data_.hook_std_vector_();
+ std::sort(data.begin(), data.end(), compare_values< T >());
+
+ median_ = data[(size_ - 1) >> 1];
+ median_needs_update_ = false;
+ }
+
+ return median_;
+ }
+
+ T variance()
+ {
+ if (variance_needs_update_)
+ {
+ mean();
+ variance_ = 0;
+
+ for (unsigned i = 0; i < size_; ++i)
+ {
+ const T tmp = mean_ - data_[i];
+
+ variance_ += (tmp * tmp);
+ }
+
+ variance_ /= size_;
+ std_ = sqrt(variance_);
+
+ variance_needs_update_ = false;
+ std_needs_update_ = false;
+ }
+
+ return variance_;
+ }
+
+ T standard_deviation()
+ {
+ if (std_needs_update_)
+ variance();
+
+ return std_;
+ }
+
+ T min() { return min_; }
+ T max() { return max_; }
+
+ void sort()
+ {
+ std::vector< T >& data = data_.hook_std_vector_();
+ std::sort(data.begin(), data.end(), compare_values< T >());
+ }
+
+ unsigned nelements() { return size_; }
+
+ T operator[] (const unsigned index)
+ {
+ return data_[index];
+ }
+
+private:
+ void needs_update()
+ {
+ mean_needs_update_ = true;
+ median_needs_update_ = true;
+ variance_needs_update_ = true;
+ std_needs_update_ = true;
+ }
+
+private:
+ bool mean_needs_update_ : 1;
+ bool median_needs_update_ : 1;
+ bool variance_needs_update_ : 1;
+ bool std_needs_update_ : 1;
+ T mean_;
+ T median_;
+ T min_;
+ T max_;
+ T variance_;
+ T std_;
+ unsigned size_;
+ util::array< T > data_;
+};
+
+//---------------------------------------------------------------------------
+// stats
+//---------------------------------------------------------------------------
+
+template< typename T >
+class stats
+{
+public:
+ stats()
+ : clusters_need_update_(true), data_sorted_(false)
+ {
+ }
+
+ stats(const int size)
+ : clusters_need_update_(true), data_sorted_(false), data_(size)
+ {
+ }
+
+ void reset()
+ {
+ data_.reset();
+ std::vector< cluster_stats< T > >& clusters = clusters_.hook_std_vector_();
+ clusters.clear();
+ data_sorted_ = false;
+ clusters_need_update_ = true;
+ }
+
+ void take(const T& value)
+ {
+ data_.take(value);
+ clusters_need_update_ = true;
+ }
+
+ T mean()
+ {
+ return data_.mean();
+ }
+
+ T median()
+ {
+ return data_.median();
+ }
+
+ T variance()
+ {
+ return data_.variance();
+ }
+
+ T standard_deviation()
+ {
+ return data_.standard_deviation();
+ }
+
+ T min() { return data_.min(); }
+ T max() { return data_.max(); }
+
+ unsigned nelements() { return data_.nelements(); }
+
+ util::array< cluster_stats< T > >& clusters()
+ {
+ if (clusters_need_update_)
+ {
+ compute_clusters();
+ clusters_need_update_ = false;
+ }
+
+ return clusters_;
+ }
+
+private:
+ void compute_clusters()
+ {
+ std::vector< unsigned > clusters;
+ unsigned cluster_index = 1;
+
+ clusters.reserve(data_.nelements());
+
+ if (not data_sorted_)
+ {
+ data_.sort();
+ data_sorted_ = true;
+ }
+
+ unsigned i = 0;
+ const unsigned nelements = data_.nelements();
+
+ clusters[0] = cluster_index;
+ const T std = data_.standard_deviation();
+
+ for (i = 1; i < nelements - 1; ++i)
+ {
+ const T left_distance = data_[i] - data_[i - 1];
+ const T right_distance = data_[i + 1] - data_[i];
+
+ if (not ((left_distance <= 2 || left_distance < right_distance)
+ && left_distance <= std))
+ ++cluster_index;
+
+ clusters[i] = cluster_index;
+ }
+
+ if (nelements > 1
+ && data_[i] - data_[i - 1] > std)
+ ++cluster_index;
+
+ clusters[i] = cluster_index;
+
+ clusters_.clear();
+ clusters_.reserve(cluster_index);
+ cluster_index = 1;
+
+ i = 0;
+ while (i < nelements)
+ {
+ unsigned tmp = i;
+
+ while (tmp < nelements && clusters[tmp] == clusters[i])
+ ++tmp;
+
+ cluster_stats< T > cluster(tmp - i);
+
+ tmp = i;
+ while (tmp < nelements && clusters[tmp] == clusters[i])
+ {
+ cluster.take(data_[tmp]);
+ ++tmp;
+ }
+
+ clusters_.append(cluster);
+
+ i = tmp;
+ ++cluster_index;
+ }
+ }
+
+private:
+ bool clusters_need_update_ : 1;
+ bool data_sorted_ : 1;
+ cluster_stats< T > data_;
+ util::array< cluster_stats< T > > clusters_;
+};
+
+#endif /* STATS_HH_ */
diff --git a/scribo/scribo/text/look_like_text_lines.hh b/scribo/scribo/text/look_like_text_lines.hh
index 2ced7ce..80d9995 100644
--- a/scribo/scribo/text/look_like_text_lines.hh
+++ b/scribo/scribo/text/look_like_text_lines.hh
@@ -64,6 +64,22 @@ namespace scribo
inline
bool looks_like_a_text_line(const scribo::line_info<L>& l)
{
+ // Special case for two-letter words
+ if (l.card() == 2)
+ {
+ const float ratio = (float) l.bbox().width() / l.bbox().height();
+
+ if (// Minimal width / height ratio
+ ratio > 0.4f && ratio < 2.0f
+ // Minimal height
+ && l.bbox().height() >= 15
+ // Characters must have approximately the same width
+ && l.chars_same_width())
+ {
+ return true;
+ }
+ }
+
return
l.card() >= 3 // at least 3 components
&& l.bbox().height() > 10 // and minimal height
diff --git a/scribo/scribo/text/merging.hh b/scribo/scribo/text/merging.hh
index 26e2da7..8ee349b 100644
--- a/scribo/scribo/text/merging.hh
+++ b/scribo/scribo/text/merging.hh
@@ -188,21 +188,24 @@ namespace scribo
swap_ordering(l1, l2);
parent[l2] = l1; // The smallest label value is root.
- if (lines(l2).card() > lines(l1).card())
+ line_info<L>& l1_info = lines(l1);
+ line_info<L>& l2_info = lines(l2);
+
+ if (l2_info.card() > l1_info.card())
{
// we transfer data from the largest item to the root one.
- scribo::line_info<L> tmp = lines(l1);
- std::swap(lines(l1), lines(l2));
- lines(l1).fast_merge(tmp);
+ scribo::line_info<L> tmp = l1_info;
+ std::swap(l1_info, l2_info);
+ l1_info.fast_merge(tmp);
// We must set manually the tag for lines(l2) since it is
// not used directly in merge process so its tag cannot be
// updated automatically.
- lines(l2).update_tag(line::Merged);
- lines(l2).set_hidden(true);
+ l2_info.update_tag(line::Merged);
+ l2_info.set_hidden(true);
}
else
- lines(l1).fast_merge(lines(l2));
+ l1_info.fast_merge(l2_info);
// l1's tag is automatically set to line::Needs_Precise_Stats_Update
// l2's tag is automatically set to line::Merged
@@ -226,34 +229,57 @@ namespace scribo
bool between_separators(const scribo::line_info<L>& l1,
const scribo::line_info<L>& l2)
{
- // No separators found in image.
+ // No separators found in image.
mln_precondition(l1.holder().components().has_separators());
- unsigned
- col1 = l1.bbox().pcenter().col(),
- col2 = l2.bbox().pcenter().col();
+ const box2d& l1_bbox = l1.bbox();
+ const box2d& l2_bbox = l2.bbox();
+
+ const unsigned
+ col1 = l1_bbox.pcenter().col(),
+ col2 = l2_bbox.pcenter().col();
const mln_ch_value(L, bool)&
separators = l1.holder().components().separators();
+ // Checking for separators starting from 1 / 4, 3/ 4 and the
+ // center of the box
typedef const bool* sep_ptr_t;
- sep_ptr_t sep_ptr, end;
+ sep_ptr_t sep_ptr, sep_ptr_top, sep_ptr_bottom, end;
if (col1 < col2)
{
- sep_ptr = &separators(l1.bbox().pcenter());
+ const unsigned quarter =
+ ((l1_bbox.pcenter().row() - l1_bbox.pmin().row()) >> 1);
+
+ sep_ptr = &separators(l1_bbox.pcenter());
+ sep_ptr_top = &separators(point2d(l1_bbox.pmin().row() + quarter,
+ l1_bbox.pcenter().col()));
+ sep_ptr_bottom = &separators(point2d(l1_bbox.pmax().row() - quarter,
+ l1_bbox.pcenter().col()));
end = sep_ptr + col2 - col1;
}
else
{
- sep_ptr = &separators(l2.bbox().pcenter());
+ const unsigned quarter =
+ ((l2_bbox.pcenter().row() - l2_bbox.pmin().row()) >> 1);
+
+ sep_ptr = &separators(l2_bbox.pcenter());
+ sep_ptr_top = &separators(point2d(l2_bbox.pmin().row() + quarter,
+ l2_bbox.pcenter().col()));
+ sep_ptr_bottom = &separators(point2d(l2_bbox.pmax().row() - quarter,
+ l2_bbox.pcenter().col()));
end = sep_ptr + col1 - col2;
}
// If sep_ptr is true, then a separator is reached.
- while (!*sep_ptr && sep_ptr != end)
+ while (!*sep_ptr && !*sep_ptr_top && !*sep_ptr_bottom && sep_ptr != end)
+ {
++sep_ptr;
+ ++sep_ptr_top;
+ ++sep_ptr_bottom;
+ }
- return *sep_ptr;
+ return (*sep_ptr || *sep_ptr_top || *sep_ptr_bottom);
}
@@ -266,16 +292,78 @@ namespace scribo
*/
template <typename L>
- bool lines_can_merge(const scribo::line_info<L>& l1,
+ bool lines_can_merge(scribo::line_info<L>& l1,
const scribo::line_info<L>& l2)
{
// Parameters.
- const float x_ratio_max = 1.7, baseline_delta_max = 3;
+ const float x_ratio_max = 1.7f;
+ const float baseline_delta_max =
+ 0.5f * std::min(l1.x_height(), l2.x_height());
+
+ const box2d& l1_bbox = l1.bbox();
+ const box2d& l2_bbox = l2.bbox();
+
+ const point2d& l1_pmin = l1_bbox.pmin();
+ const point2d& l2_pmin = l2_bbox.pmin();
+ const point2d& l1_pmax = l1_bbox.pmax();
+ const point2d& l2_pmax = l2_bbox.pmax();
+
+ const bool l1_has_separators = l1.holder().components().has_separators();
+ const bool l1_l2_between_separators = (l1_has_separators) ?
+ between_separators(l1, l2) : false;
+ const float l_ted_cw = l2.char_width();
+
+ const float dx = std::max(l1_pmin.col(), l2_pmin.col())
+ - std::min(l1_pmax.col(), l2_pmax.col());
+ const float dy = std::max(l1_pmin.row(), l2_pmin.row())
+ - std::min(l1_pmax.row(), l2_pmax.row());
+
+ // Particular case of "
+ {
+ if (// Must have 2 characters
+ (l1.card() == 2
+ // The box height must be smaller than the touched line x height
+ && l1_bbox.height() < l2.x_height())
+ // The line must be vertically and horizontally close to
+ // the touched line
+ && (dx < l_ted_cw && dy < 0)
+ // No separator between the two lines
+ && not (l1_l2_between_separators))
+ {
+ // Line is then considered as punctuation
+ l1.update_type(line::Punctuation);
+ return true;
+ }
+ }
+
+ // Particular case like merging between a line and [5]
+ {
+ const mln::def::coord
+ top_row_l2 = l2_pmin.row(),
+ top_row_l1 = l1_pmin.row(),
+ bot_row = l2_pmax.row();
+ const float x1 = l1.x_height(), x2 = l2.x_height();
+ const float x_ratio = std::max(x1, x2) / std::min(x1, x2);
+
+ if (// No separator
+ !l1_l2_between_separators
+ // The x height ration must be lower than 2
+ && (x_ratio < 2.0f)
+ // Baseline alignment
+ && (std::abs(bot_row - l1.baseline()) < baseline_delta_max)
+ // The top of the boxes must be aligned
+ && (std::abs(top_row_l2 - top_row_l1) < 5)
+ // Distance between the line and the touched line.
+ && dx < 5.0f * l_ted_cw)
+ {
+ return true;
+ }
+ }
// Similarity of x_height.
{
- float x1 = l1.x_height(), x2 = l2.x_height();
- float x_ratio = std::max(x1, x2) / std::min(x1, x2);
+ const float x1 = l1.x_height(), x2 = l2.x_height();
+ const float x_ratio = std::max(x1, x2) / std::min(x1, x2);
if (x_ratio > x_ratio_max)
return false;
}
@@ -287,22 +375,21 @@ namespace scribo
}
// left / right
- unsigned
- col1 = l1.bbox().pcenter().col(),
- col2 = l2.bbox().pcenter().col();
+ const unsigned
+ col1 = l1_bbox.pcenter().col(),
+ col2 = l2_bbox.pcenter().col();
if (col1 < col2)
{
- if ((col1 + l1.bbox().width() / 4) >= (col2 - l2.bbox().width() / 4))
+ if ((col1 + l1_bbox.width() / 4) >= (col2 - l2_bbox.width() / 4))
return false;
}
else
- if ((col2 + l2.bbox().width() / 4) >= (col1 - l1.bbox().width() / 4))
+ if ((col2 + l2_bbox.width() / 4) >= (col1 - l1_bbox.width() / 4))
return false;
-
// Check that there is no separator in between.
- if (l1.holder().components().has_separators())
- return ! between_separators(l1, l2);
+ if (l1_has_separators)
+ return ! l1_l2_between_separators;
return true;
}
@@ -310,14 +397,14 @@ namespace scribo
- template <typename L>
- int horizontal_distance(const scribo::line_info<L>& l1,
- const scribo::line_info<L>& l2)
+ inline
+ int horizontal_distance(const box2d& l1,
+ const box2d& l2)
{
- if (l1.bbox().pcenter().col() < l2.bbox().pcenter().col())
- return l2.bbox().pmin().col() - l1.bbox().pmax().col();
+ if (l1.pcenter().col() < l2.pcenter().col())
+ return l2.pmin().col() - l1.pmax().col();
else
- return l1.bbox().pmin().col() - l2.bbox().pmax().col();
+ return l1.pmin().col() - l2.pmax().col();
}
@@ -339,7 +426,7 @@ namespace scribo
*/
template <typename L>
- bool non_text_and_text_can_merge(const scribo::line_info<L>& l_cur, // current
+ bool non_text_and_text_can_merge(scribo::line_info<L>& l_cur, // current
const scribo::line_info<L>& l_ted) // touched
{
if (l_cur.type() == line::Text || l_ted.type() != line::Text)
@@ -353,22 +440,42 @@ namespace scribo
&& between_separators(l_cur, l_ted))
return false;
+ const box2d& l_cur_bbox = l_cur.bbox();
+ const box2d& l_ted_bbox = l_ted.bbox();
+
+ const point2d& l_cur_pmin = l_cur_bbox.pmin();
+ const point2d& l_ted_pmin = l_ted_bbox.pmin();
+ const point2d& l_cur_pmax = l_cur_bbox.pmax();
+ const point2d& l_ted_pmax = l_ted_bbox.pmax();
+
+ const float dx = std::max(l_cur_pmin.col(), l_ted_pmin.col())
+ - std::min(l_cur_pmax.col(), l_ted_pmax.col());
+ const float l_ted_cw = l_ted.char_width();
+ const float l_ted_x_height = l_ted.x_height();
+
+ const unsigned l_cur_height = l_cur_bbox.height();
+ const unsigned l_cur_width = l_cur_bbox.width();
// General case (for tiny components like --> ',:."; <--):
- if (l_cur.bbox().height() < l_ted.x_height()
- && float(l_cur.bbox().width()) / float(l_cur.card()) < l_ted.char_width())
+ if (l_cur_height < l_ted_x_height
+ && l_cur_height > 0.05f * l_ted_x_height
+ && float(l_cur_width) / float(l_cur.card()) < l_ted.char_width()
+ && dx < l_ted_cw)
+ {
+ l_cur.update_type(line::Punctuation);
return true;
-
+ }
// Special case for '---':
if (// small height:
- l_cur.bbox().height() < l_ted.x_height()
+ l_cur_height < l_ted_x_height
// // not so long width:
- && l_cur.bbox().width() < 5 * l_ted.char_width()
+ && l_cur_width > 0.8 * l_ted_cw
+ && l_cur_width < 5 * l_ted_cw
// align with the 'x' center:
&& std::abs((l_ted.baseline() + l_ted.meanline()) / 2 - l_cur.bbox().pcenter().row()) < 7
// tiny spacing:
- && unsigned(horizontal_distance(l_cur, l_ted)) < l_ted.char_width()
+ && unsigned(horizontal_distance(l_cur_bbox, l_ted_bbox)) < 2 * l_ted_cw
)
{
return true;
@@ -378,10 +485,11 @@ namespace scribo
// Special case
// Looking for alignement.
- mln::def::coord
+ const mln::def::coord
top_row = l_cur.bbox().pmin().row(),
bot_row = l_cur.bbox().pmax().row();
+ const box2d& l_ted_ebbox = l_ted.ebbox();
// std::cout << "top_row = " << top_row << " - bot_row = " << bot_row << std::endl;
// std::cout << std::abs(bot_row - l_ted.baseline())
@@ -402,10 +510,11 @@ namespace scribo
// << std::endl;
if ((std::abs(bot_row - l_ted.baseline()) < 5
- || std::abs(bot_row - l_ted.ebbox().pmax().row()) < 5)
+ || std::abs(bot_row - l_ted_ebbox.pmax().row()) < 5)
&&
(std::abs(top_row - l_ted.meanline()) < 5
- || std::abs(top_row - l_ted.ebbox().pmin().row()) < 5))
+ || std::abs(top_row - l_ted_ebbox.pmin().row()) < 5)
+ && dx < 5.0f * l_ted_cw)
{
return true;
}
@@ -536,35 +645,33 @@ namespace scribo
if (parent[l] != l) // not a root, so has already merged, thus ignore it
continue;
- box2d b = lines(l).bbox();
+ const box2d& b = lines(l).bbox();
- unsigned tl, tr, ml, mc, mr, bl, br;
+ // unsigned tl, tr, ml, mc, mr, bl, br;
+
+ const box2d& b_ = lines(l).ebbox();
+
+ /*
+ tl tr
+ x---------------x
+ | |
+ | mc |
+ ml x x x mr
+ | |
+ | |
+ x---------------x
+ bl br
+
+ */
- {
- box2d b_ = lines(l).ebbox();
-
- /*
- tl tr
- x---------------x
- | |
- | mc |
- ml x x x mr
- | |
- | |
- x---------------x
- bl br
-
- */
-
-
- tl = billboard(b_.pmin());
- tr = billboard.at_(b_.pmin().row(), b_.pmax().col());
- ml = billboard.at_(b_.pcenter().row(), b_.pmin().col());
- mc = billboard.at_(b_.pcenter().row(), b_.pcenter().col());
- mr = billboard.at_(b_.pcenter().row(), b_.pmax().col());
- bl = billboard.at_(b_.pmax().row(), b_.pmin().col());
- br = billboard(b_.pmax());
- }
+
+ const unsigned tl = billboard(b_.pmin());
+ const unsigned tr = billboard.at_(b_.pmin().row(), b_.pmax().col());
+ const unsigned ml = billboard.at_(b_.pcenter().row(), b_.pmin().col());
+ const unsigned mc = billboard.at_(b_.pcenter().row(), b_.pcenter().col());
+ const unsigned mr = billboard.at_(b_.pcenter().row(), b_.pmax().col());
+ const unsigned bl = billboard.at_(b_.pmax().row(), b_.pmin().col());
+ const unsigned br = billboard(b_.pmax());
typedef std::set<unsigned> set_t;
std::set<unsigned> labels;
@@ -596,11 +703,47 @@ namespace scribo
{
// Main case: it is an "included" box (falling in an already drawn box)
- if (lines(l).type() == line::Text) // the current object IS a text line
+ const line_info<L>& l_info = lines(l);
+ const line_info<L>& mc_info = lines(mc);
+
+ if (l_info.type() == line::Text) // the current object IS a text line
{
- if (lines(mc).type() == line::Text) // included in a text line => weird
+ if (mc_info.type() == line::Text) // included in a text line => weird
{
++count_txtline_IN_txtline;
+
+ // Particular case of "
+ // {
+ // if ((lines(l).card() == 2 &&
+ // lines(l).bbox().height() < lines(mc).x_height()) &&
+ // not (lines(l).holder().components().has_separators()
+ // && between_separators(lines(l),
+ // lines(mc))))
+
+ const box2d& l_bbox = l_info.bbox();
+ const box2d& mc_bbox = mc_info.bbox();
+
+ const point2d& l_pmin = l_bbox.pmin();
+ const point2d& mc_pmin = mc_bbox.pmin();
+ const point2d& l_pmax = l_bbox.pmax();
+ const point2d& mc_pmax = mc_bbox.pmax();
+
+ const float dx = std::max(l_pmin.col(), mc_pmin.col())
+ - std::min(l_pmax.col(), mc_pmax.col());
+ const float dy = std::max(l_pmin.row(), mc_pmin.row())
+ - std::min(l_pmax.row(), mc_pmax.row());
+ const float l_ted_cw = mc_info.char_width();
+
+ // We accept a line included into another only if it
+ // is horizontally close to the line's bbox and
+ // vertically aligned
+ // Obviously no separators between the two lines
+ if (dx < l_ted_cw && dy < 0
+ && not (l_info.holder().components().has_separators()
+ && between_separators(l_info, mc_info)))
+ l = do_union(lines, l, mc, parent);
+ // }
+
// std::cout << "weird: inclusion of a txt_line in a txt_line!" << std::endl;
/// Merge is perform if the current line is a
@@ -649,15 +792,15 @@ namespace scribo
// could be noise or garbage... So adding new
// criterions could fix this issue.
//
- //if (!non_text_and_text_can_merge(lines(l), lines(mc)))
- // continue;
+ if (!non_text_and_text_can_merge(lines(l), lines(mc)))
+ continue;
// Avoid the case when a large title ebbox overlap
// with a text column. In that case, the title may
// merge with punctuation from the text.
- if (lines(l).holder().components().has_separators()
- && between_separators(lines(l), lines(mc)))
- continue;
+ // if (lines(l).holder().components().has_separators()
+ // && between_separators(lines(l), lines(mc)))
+ // continue;
// Mark current line as punctuation.
lines(l).update_type(line::Punctuation);
@@ -832,9 +975,12 @@ namespace scribo
bool operator()(const scribo::line_id_t& l1, const scribo::line_id_t& l2) const
{
- if (lines_(l1).bbox().nsites() == lines_(l2).bbox().nsites())
+ const unsigned l1_nsites = lines_(l1).bbox().nsites();
+ const unsigned l2_nsites = lines_(l2).bbox().nsites();
+
+ if (l1_nsites == l2_nsites)
return l1 < l2;
- return lines_(l1).bbox().nsites() < lines_(l2).bbox().nsites();
+ return l1_nsites < l2_nsites;
}
scribo::line_set<L> lines_;
diff --git a/scribo/scribo/text/paragraphs.hh b/scribo/scribo/text/paragraphs.hh
new file mode 100644
index 0000000..a3ff802
--- /dev/null
+++ b/scribo/scribo/text/paragraphs.hh
@@ -0,0 +1,1027 @@
+#include <mln/util/array.hh>
+#include <mln/accu/shape/bbox.hh>
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/draw/box.hh>
+#include <mln/data/convert.hh>
+#include <mln/value/int_u16.hh>
+#include <mln/value/label_16.hh>
+#include <mln/value/int_u8.hh>
+#include <mln/value/rgb8.hh>
+#include <mln/io/ppm/save.hh>
+#include <mln/io/pgm/save.hh>
+#include <mln/geom/rotate.hh>
+#include <mln/literal/colors.hh>
+
+#include <scribo/core/macros.hh>
+#include <scribo/core/line_set.hh>
+#include <scribo/core/line_info.hh>
+
+using namespace mln;
+
+namespace scribo
+{
+
+ namespace internal
+ {
+
+//-------------------------------------
+// Extracting root of links
+//-------------------------------------
+ template <typename T>
+ inline
+ unsigned
+ find_root(util::array<T>& parent, unsigned x)
+ {
+ unsigned tmp_x = x;
+
+ while (parent(tmp_x) != tmp_x)
+ tmp_x = parent(tmp_x);
+
+ while (parent(x) != x)
+ {
+ const unsigned tmp = parent(x);
+ x = parent(x);
+ parent(tmp) = tmp_x;
+ }
+
+ return x;
+ }
+ }
+
+ namespace filter
+ {
+
+//---------------------------------------------------------------------
+// This method aims to cut the links between lines that do not fit the
+// different criteria
+//---------------------------------------------------------------------
+
+ template <typename L>
+ inline
+ void paragraph_links(const util::array<value::int_u16>& left,
+ const util::array<value::int_u16>& right,
+ util::array<value::int_u16>& output,
+ const util::array< line_info<L> >& lines,
+ const image2d<bool>& input)
+ {
+ output = left;
+
+ const unsigned nlines = lines.nelements();
+
+ // image2d<value::rgb8> links = data::convert(value::rgb8(), input);
+ // for (unsigned l = 0; l < nlines; ++l)
+ // {
+ // mln::draw::line(links, lines(l).bbox().pcenter(), lines(left(l)).bbox().pcenter(), literal::red);
+ // }
+ // mln::io::ppm::save(links, "out_links.ppm");
+
+ // For each line
+ for (unsigned l = 0; l < nlines; ++l)
+ {
+ // Neighbors
+
+ const value::int_u16 left_nbh = output(l);
+ const value::int_u16 right_nbh = right(l);
+ const value::int_u16 lol_nbh = output(left_nbh);
+
+ // Line features
+ const float x_height = lines(l).x_height();
+ const float left_x_height = lines(left_nbh).x_height();
+ const float right_x_height = lines(right_nbh).x_height();
+
+ const box2d& left_line_bbox = lines(left_nbh).bbox();
+ const box2d& current_line_bbox = lines(l).bbox();
+ const box2d& right_line_bbox = lines(right_nbh).bbox();
+ const box2d& lol_line_bbox = lines(lol_nbh).bbox(); // lol : left neighbor of the left neighbor
+
+ const int lline_col_min = left_line_bbox.pmin().col();
+ const int cline_col_min = current_line_bbox.pmin().col();
+ const int rline_col_min = right_line_bbox.pmin().col();
+ const int lolline_col_min = lol_line_bbox.pmin().col();
+
+ const int lline_col_max = left_line_bbox.pmax().col();
+ const int cline_col_max = current_line_bbox.pmax().col();
+ const int rline_col_max = right_line_bbox.pmax().col();
+
+ const int lline_cw = lines(left_nbh).char_width();
+ const int cline_cw = lines(l).char_width();
+ const int rline_cw = lines(right_nbh).char_width();
+ // Maximal x variation to consider two lines vertically aligned
+ const int delta_alignment = cline_cw;
+
+ // Checks the baseline distances of the two neighbors
+ {
+ // Current line baseline
+ const int c_baseline = lines(l).baseline();
+
+ // Baseline distance with the left and right neighbors
+ const int lc_baseline = lines(left_nbh).baseline() - c_baseline;
+ const int rc_baseline = c_baseline -lines(right_nbh).baseline();
+
+ // Max baseline distance between the two neighbors
+ // const float delta_baseline_max = std::max(lc_baseline, rc_baseline);
+ // const float delta_baseline_min = std::min(lc_baseline,
+ // rc_baseline);
+
+ // Only two lines, meaning the current line has only one neighbor
+ bool two_lines = false;
+
+ // If the current line has no left neighbor
+ if (lc_baseline == 0)
+ {
+ // ror : right neighbor of the right neighbor
+ const value::int_u16 ror_nbh = right(right_nbh);
+ const box2d& ror_line_bbox = lines(ror_nbh).bbox();
+
+ // If the current line has a ror
+ if (ror_nbh != right_nbh
+ && output(ror_nbh) == right_nbh)
+ {
+ // Distance between the current line and the right neighbor
+ const float right_distance =
+ current_line_bbox.pcenter().row() - right_line_bbox.pcenter().row();
+ // Distance between the right neighbor and the ror
+ const float ror_distance =
+ right_line_bbox.pcenter().row() - ror_line_bbox.pcenter().row();
+ // ror x_height
+ const float ror_x_height = lines(ror_nbh).x_height();
+
+ // Conditions to cut the link between the current line
+ // and its right neighbor
+ if (right_distance > 1.4f * ror_distance
+ && std::max(ror_x_height, right_x_height) <
+ 1.2f * std::min(ror_x_height, right_x_height)
+ && output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+ // Otherwise we only have a group of two lines
+ else
+ {
+ // We determine the distance between the two lines
+ const float distance = lines(l).meanline() - lines(right_nbh).baseline();
+ two_lines = true;
+
+ // If the distance between the two lines is greater than
+ // the minimum x height of the two lines then we cut the
+ // link between them
+ if (distance > std::min(x_height, right_x_height)
+ && output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+
+ // Lines features
+ const float min_x_height = std::min(x_height, right_x_height);
+ const float max_x_height = std::max(x_height, right_x_height);
+ const float min_char_width = std::min(rline_cw, cline_cw);
+ const float max_char_width = std::max(rline_cw, cline_cw);
+
+ // Condition to cut the link between the current line and
+ // its right neighbor
+ if ((max_x_height > min_x_height * 1.2f) &&
+ !(max_char_width <= 1.2f * min_char_width))
+ {
+ if (output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+
+ // If we only have two lines we stop the study
+ if (two_lines)
+ continue;
+ }
+ // If the current line has no right neighbor
+ else if (rc_baseline == 0)
+ {
+ // lol : left neighbor of the left neighbor
+
+ // If the left neighbor of the current line has a left neighbor
+ if (lol_nbh != left_nbh)
+ {
+ // Distance between the current line and its left neighbor
+ const float left_distance =
+ left_line_bbox.pcenter().row() - current_line_bbox.pcenter().row();
+ // Distance between the left neighbor and the left
+ // neighbor of its left neighbor
+ const float lol_distance =
+ lol_line_bbox.pcenter().row() - left_line_bbox.pcenter().row();
+ // lol x height
+ const float lol_x_height = lines(lol_nbh).x_height();
+
+ // Conditions to cut the link between the current line
+ // and its left neighbor
+ if (left_distance > 1.4f * lol_distance
+ && std::max(lol_x_height, left_x_height) <
+ 1.2f * std::min(lol_x_height, left_x_height))
+ {
+ output(l) = l;
+ continue;
+ }
+ }
+ // Otherwise we only have a group of two lines
+ else
+ {
+ // Distance between the current line and it left neighbor
+ const float distance = lines(left_nbh).meanline() -
+ lines(l).baseline();
+
+ two_lines = true;
+
+ // If the distance is greater than the min x height
+ // between the two lines
+ if (distance > std::min(x_height, left_x_height))
+ {
+ output(l) = l;
+ continue;
+ }
+ }
+
+ // Lines features
+ const float min_x_height = std::min(x_height, left_x_height);
+ const float max_x_height = std::max(x_height, left_x_height);
+ const float min_char_width = std::min(lline_cw, cline_cw);
+ const float max_char_width = std::max(lline_cw, cline_cw);
+
+ // Condition to cut the link between the current line and
+ // its left neighbor
+ if ((max_x_height > min_x_height * 1.2f) &&
+ !(max_char_width <= 1.2f * min_char_width))
+ {
+ output(l) = l;
+ continue;
+ }
+
+ // If we only have two lines we stop the study
+ if (two_lines)
+ continue;
+ }
+ // The current line has at least one left and one right neighbor
+ else // if (delta_baseline_max >= delta_baseline_min)
+ {
+ // Distance between the left and the current line
+ const float left_distance =
+ lines(left_nbh).meanline() - lines(l).baseline();
+ // Distance between the right and the current line
+ const float right_distance =
+ lines(l).meanline() - lines(right_nbh).baseline();
+
+ // If the left line is too far compared to the right one
+ // we cut the link with it
+ if (left_distance > 1.2f * right_distance
+ && std::max(x_height, left_x_height) > 1.2f * std::min(x_height, left_x_height))
+ {
+ output(l) = l;
+ continue;
+ }
+ // If the right line is too far compared to the left one
+ // we cut the link with it
+ else if (right_distance > 1.2f * left_distance
+ && std::max(x_height, right_x_height) > 1.2f * std::min(x_height, right_x_height)
+ && output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+
+ // If the distance between the baseline of the left
+ // neighbor and the baseline of the current line is
+ // greater than the one between the current line baseline
+ // and the right line baseline we have to study the texte
+ // features of the right and left lines
+ if (lc_baseline > rc_baseline)
+ {
+ const float cw_max = std::max(lline_cw, cline_cw);
+ const float cw_min = std::min(lline_cw, cline_cw);
+ const float min_x_height = std::min(x_height, left_x_height);
+ const float max_x_height = std::max(x_height, left_x_height);
+
+ if ((max_x_height > min_x_height * 1.2f) &&
+ !(cw_max <= 1.2f * cw_min))
+ {
+ output(l) = l;
+ continue;
+ }
+
+ {
+ const float min_x_height = std::min(x_height, right_x_height);
+ const float max_x_height = std::max(x_height, right_x_height);
+ const float cw_max = std::max(rline_cw, cline_cw);
+ const float cw_min = std::min(rline_cw, cline_cw);
+
+ if ((max_x_height > min_x_height * 1.2f)
+ && !(cw_max <= 1.2f * cw_min)
+ && output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+ }
+ else
+ {
+ const float cw_max = std::max(rline_cw, cline_cw);
+ const float cw_min = std::min(rline_cw, cline_cw);
+ const float min_x_height = std::min(x_height, right_x_height);
+ const float max_x_height = std::max(x_height, right_x_height);
+
+ if ((max_x_height > min_x_height * 1.2f)
+ && !(cw_max <= 1.2f * cw_min)
+ && output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+
+ {
+ const float min_x_height = std::min(x_height, left_x_height);
+ const float max_x_height = std::max(x_height, left_x_height);
+ const float cw_max = std::max(lline_cw, cline_cw);
+ const float cw_min = std::min(lline_cw, cline_cw);
+
+ if ((max_x_height > min_x_height * 1.2f)
+ && !(cw_max <= 1.2f * cw_min))
+ {
+ output(l) = l;
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+ // If we arrive here, it means than the lines in the
+ // neighborhood of the current line are quite similar. We can
+ // then begin to study the indentations in order to determine
+ // the beginning of new paragraphs
+
+//-----------------------------------------------------------------------------
+// ___________________________
+// |___________________________|
+// ________________________
+// |________________________|
+// ___________________________
+// |___________________________|
+// ___________________________
+// |___________________________|
+//
+// Simple case : paragraphs are justified on the left. We try to find any
+// indentation like above.
+//
+//-----------------------------------------------------------------------------
+
+ {
+ // Check if the current line neighbors are aligned
+ bool left_right_aligned = false;
+ bool left_lol_aligned = false;
+ const int dx_lr = std::abs(lline_col_min - rline_col_min);
+ const int dx_llol = std::abs(lline_col_min - lolline_col_min);
+
+ if (dx_lr < delta_alignment)
+ left_right_aligned = true;
+
+ if (dx_llol < delta_alignment)
+ left_lol_aligned = true;
+
+ if (left_right_aligned && left_lol_aligned)
+ {
+ const int left_right_col_min = std::min(lline_col_min, rline_col_min);
+ const int dx_lrc = std::abs(left_right_col_min - cline_col_min);
+ const float l_char_width = 1.5f * lines(l).char_width();
+
+ if (dx_lrc > l_char_width &&
+ dx_lrc < 3.0f * l_char_width &&
+ cline_col_min > rline_col_min &&
+ cline_col_min > lline_col_min)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+ }
+
+//-----------------------------------------------------------------------------
+// ___________________________
+// |___________________________|
+// ___________________
+// |___________________| End of the paragraph - Current line
+// ________________________
+// |________________________| Beginning of a new one
+// ___________________________
+// |___________________________| Left of left of current line
+//
+// End of paragraph case : we try to find an end to the current paragraph
+//
+//-----------------------------------------------------------------------------
+
+ {
+ // Check if the current line neighbors are aligned
+ bool left_right_max_aligned = false;
+ bool left_current_min_aligned = false;
+ bool lol_current_min_aligned = false;
+ const bool lol_is_left = output(left_nbh) == left_nbh;
+ const int dx_lr_max = std::abs(lline_col_max - rline_col_max);
+ const int dx_lc_min = std::abs(lline_col_min - cline_col_min);
+ const int dx_lolc_min = std::abs(lolline_col_min - cline_col_min);
+
+ if (dx_lr_max < delta_alignment)
+ left_right_max_aligned = true;
+
+ if (dx_lc_min < delta_alignment)
+ left_current_min_aligned = true;
+
+ if (dx_lolc_min < delta_alignment)
+ lol_current_min_aligned = true;
+
+ if (!left_current_min_aligned && left_right_max_aligned &&
+ (lol_current_min_aligned || lol_is_left))
+ {
+ const int dx_lrc = std::abs(lline_col_max - cline_col_max);
+ const int l_char_width = lines(l).char_width();
+
+ if (dx_lrc > l_char_width &&
+ cline_col_max < lline_col_max &&
+ cline_col_min < lline_col_min &&
+ (lline_col_min > lolline_col_min || lol_is_left))
+ {
+ output(l) = l;
+ continue;
+ }
+ }
+ }
+
+
+//-----------------------------------------------------------------------------
+// ___________________________
+// |___________________________|
+// ___________________________
+// |___________________________|
+// ________________________
+// |________________________|
+//
+// Simple case : paragraphs are justified on the left. We try to find any
+// indentation like above at the end of a column.
+//
+//-----------------------------------------------------------------------------
+
+ if (left_nbh == l)
+ {
+ const value::int_u16 ror_nbh = right(right_nbh);
+ const box2d& ror_line_bbox = lines(ror_nbh).bbox();
+ const int rorline_col_min = ror_line_bbox.pmin().col();
+
+ bool right_ror_min_aligned = false;
+ const int dx_rror_min = std::abs(rline_col_min - rorline_col_min);
+
+ if (dx_rror_min < delta_alignment)
+ right_ror_min_aligned = true;
+
+ if (right_ror_min_aligned)
+ {
+ const int right_ror_col_min = std::min(rline_col_min, rorline_col_min);
+ const int dx_rrorc = std::abs(right_ror_col_min - cline_col_min);
+ const float l_char_width = 1.5f * lines(l).char_width();
+
+ if (dx_rrorc > l_char_width &&
+ dx_rrorc < 3.0f * l_char_width &&
+ cline_col_min > rline_col_min &&
+ cline_col_max >= rline_col_max)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+ }
+ }
+
+
+ // Only debug
+
+ {
+ image2d<value::rgb8> debug = data::convert(value::rgb8(), input);
+
+ for (unsigned i = 0; i < output.nelements(); ++i)
+ output(i) = internal::find_root(output, i);
+
+ mln::util::array<accu::shape::bbox<point2d> > nbbox(output.nelements());
+ for (unsigned i = 0; i < nlines; ++i)
+ {
+ // if (lines(i).is_textline())
+ // {
+ // mln::draw::box(debug, lines(i).bbox(), literal::red);
+ nbbox(output(i)).take(lines(i).bbox());
+ // }
+ }
+
+ for (unsigned i = 0; i < nbbox.nelements(); ++i)
+ if (nbbox(i).is_valid())
+ {
+ box2d b = nbbox(i).to_result();
+ mln::draw::box(debug, b, literal::orange);
+ b.enlarge(1);
+ mln::draw::box(debug, b, literal::orange);
+ b.enlarge(1);
+ mln::draw::box(debug, b, literal::orange);
+ }
+
+ mln::io::ppm::save(debug, "out_paragraph.ppm");
+ }
+
+ }
+ }
+
+//-------------------------------------------------------------
+// Preparation of the lines before linking them.
+// For each line we draw the top and the bottom of it.
+// Assuming than i is the number of the line. Then the top of the line
+// will be affected with the value 2 * i in the block image and the
+// bottom with 2 * i + 1.
+//
+//-------------------------------------------------------------
+
+ template <typename L>
+ inline
+ void prepare_lines(const box2d& domain,
+ const util::array< line_info<L> >& lines,
+ image2d<value::int_u16>& blocks,
+ util::array<box2d>& rbbox)
+ {
+ std::map< int, std::vector< const box2d* > > drawn_lines;
+ const unsigned nlines = lines.nelements();
+
+ // For each line
+ for (unsigned l = 0; l < nlines; ++l)
+ {
+ // Rotation of the bounding box
+ box2d b = geom::rotate(lines(l).bbox(), -90, domain.pcenter());
+ rbbox.append(b);
+
+ const unsigned index = l + 1;
+ const unsigned even_index = 2 * index;
+ const unsigned odd_index = even_index + 1;
+
+ // Top of the line
+ {
+ bool not_finished = true;
+ int col_offset = 0;
+
+ while (not_finished)
+ {
+ // Looking for a column in the image to draw the top of the
+ // line
+
+ const int col = b.pmax().col() + col_offset;
+ std::map< int, std::vector< const box2d* > >::iterator it
+ = drawn_lines.find(col);
+
+ if (it != drawn_lines.end())
+ {
+ const std::vector< const box2d* >& lines = (*it).second;
+ const unsigned nb_lines = lines.size();
+ unsigned i = 0;
+
+ for (i = 0; i < nb_lines; ++i)
+ {
+ const box2d* box = lines[i];
+ const int min_row = std::max(b.pmin().row(), box->pmin().row());
+ const int max_row = std::min(b.pmax().row(), box->pmax().row());
+
+ if (min_row - max_row <= 0)
+ break;
+ }
+
+ if (i == nb_lines)
+ {
+ mln::draw::line(blocks, point2d(b.pmin().row(), col),
+ point2d(b.pmax().row(), col), even_index);
+ not_finished = false;
+ drawn_lines[col].push_back(&(rbbox[l]));
+ }
+ else
+ ++col_offset;
+ }
+ else
+ {
+ mln::draw::line(blocks, point2d(b.pmin().row(), col),
+ point2d(b.pmax().row(), col), even_index);
+ not_finished = false;
+ drawn_lines[col].push_back(&(rbbox[l]));
+ }
+ }
+ }
+
+ // Bottom of the line
+ {
+ bool not_finished = true;
+ int col_offset = 0;
+
+ while (not_finished)
+ {
+ // Looking for a column in the image to draw the bottom of
+ // the line
+
+ const int col = b.pmin().col() - col_offset;
+ std::map< int, std::vector< const box2d* > >::iterator it
+ = drawn_lines.find(col);
+
+ if (it != drawn_lines.end())
+ {
+ const std::vector< const box2d* >& lines = (*it).second;
+ const unsigned nb_lines = lines.size();
+ unsigned i = 0;
+
+ for (i = 0; i < nb_lines; ++i)
+ {
+ const box2d* box = lines[i];
+ const int min_row = std::max(b.pmin().row(), box->pmin().row());
+ const int max_row = std::min(b.pmax().row(), box->pmax().row());
+
+ if (min_row - max_row <= 0)
+ break;
+ }
+
+ if (i == nb_lines)
+ {
+ mln::draw::line(blocks, point2d(b.pmin().row(), col),
+ point2d(b.pmax().row(), col), odd_index);
+ not_finished = false;
+ drawn_lines[col].push_back(&(rbbox[l]));
+ }
+ else
+ ++col_offset;
+ }
+ else
+ {
+ mln::draw::line(blocks, point2d(b.pmin().row(), col),
+ point2d(b.pmax().row(), col), odd_index);
+ not_finished = false;
+ drawn_lines[col].push_back(&(rbbox[l]));
+ }
+ }
+ }
+ }
+ }
+
+ template <typename L>
+ inline
+ void
+ process_left_link(image2d<value::int_u16>& blocks,
+ const util::array<box2d>& rbbox,
+ const util::array< line_info<L> >& lines,
+ util::array<value::int_u16>& left)
+ {
+ typedef value::int_u16 V;
+
+ // At the beginning each line is its own neighbor
+ for (unsigned i = 0; i < left.nelements(); ++i)
+ left(i) = i;
+
+ const unsigned nlines = lines.nelements();
+
+ // For each line
+ for (unsigned i = 0; i < nlines; ++i)
+ {
+ // Max distance for the line search
+ int dmax = 1.5f * lines(i).x_height();
+
+ // Starting points in the current line box
+ point2d c = rbbox(i).pcenter();
+ point2d q(rbbox(i).pmin().row() + ((c.row() - rbbox(i).pmin().row()) / 4), c.col());
+
+ int
+ midcol = (rbbox(i).pmax().col()
+ - rbbox(i).pmin().col()) / 2;
+
+ // Left
+ {
+ // marge gauche
+ int
+ nleftima = c.col() - blocks.domain().pmin().col(),
+ // Distance gauche
+ nleft = std::min(nleftima, midcol + dmax);
+
+ V
+ // Starting points in the box
+ *p = &blocks(c),
+ *p2 = &blocks(q),
+ // End of search
+ *pstop = p - nleft - 1,
+ // Line neighbor
+ *nbh_p = 0;
+
+ // While we haven't found a neighbor or reached the limit
+ for (; p != pstop; --p, --p2)
+ {
+ if (*p2 != literal::zero // Not the background
+ && ((*p2 % 2) == 0) // Looking for the bottom of a line
+ && left((*p2 >> 1) - 1) != i) // No loops
+ {
+ // Neightbor found, we stop the research
+ nbh_p = p2;
+ break;
+ }
+
+ if (*p != literal::zero // Not the background
+ && ((*p % 2) == 0) // Looking for the bottom of a line
+ && left((*p >> 1) - 1) != i) // No loops
+ {
+ // Neightbor found, we stop the research
+ nbh_p = p;
+ break;
+ }
+ }
+
+ // If a neighbor was found, then we have found the top of the
+ // line. We are then looking for the bottom of the encountered
+ // line. If during the search process we find a complete line
+ // included in the touched line, this line is considered as
+ // the neighbor under certain conditions (see below)
+
+ //---------------------------------------------------------------
+ // _________________________ |
+ // |_________________________| => Current line | Search direction
+ // v
+ // => First encountered top line
+ // __________________________________________________ 2Q
+ // | Q |
+ // | _________________________ |2P
+ // | |_____________P___________| => Second top |2P + 1
+ // | line |
+ // |__________________________________________________|2Q + 1
+ //
+ //
+ //---------------------------------------------------------------
+
+ if (nbh_p)
+ {
+ std::vector<V> lines_nbh;
+ const V end_p = *nbh_p + 1;
+ const V* nbh_p_copy = nbh_p;
+
+ for (; *nbh_p != end_p; --nbh_p)
+ {
+ if ((*nbh_p) != literal::zero) // Not the background
+ {
+ if ((*nbh_p) % 2 == 0)// We have found the top of
+ // another line
+ lines_nbh.push_back(*nbh_p);
+ else
+ {
+ // We have found the bottom of a line. We are looking if
+ // we have already encountered the top of this
+ // line. If so, we link the current line with this one
+ // under certain conditions:
+
+ if (std::find(lines_nbh.begin(), lines_nbh.end(),
+ (*nbh_p) - 1) != lines_nbh.end())
+ {
+ // If we can link the complete line with the current line
+ if (// It must be in the search range
+ nbh_p > pstop
+ // Avoid loops
+ && left(((*nbh_p - 1) >> 1) - 1) != i)
+ left(i) = ((*nbh_p - 1) >> 1) - 1;
+
+ // We have found a complete line so we stop the search
+ break;
+ }
+ }
+ }
+ }
+
+
+ // If we haven't found any included line in the first
+ // neighbor, then the line is considered as the neighbor of
+ // the current line
+ if (*nbh_p == end_p)
+ left(i) = (*nbh_p_copy >> 1) - 1;
+ }
+ }
+ }
+ }
+
+
+ // We assume that the lines have been rotated
+ template <typename L>
+ inline
+ void
+ process_right_link(image2d<value::int_u16>& blocks,
+ const util::array<box2d>& rbbox,
+ const util::array< line_info<L> >& lines,
+ util::array<value::int_u16>& right)
+ {
+ typedef value::int_u16 V;
+
+ // At the beginning each line is its own neighbor
+ for (unsigned i = 0; i < right.nelements(); ++i)
+ right(i) = i;
+
+ const unsigned nlines = lines.nelements();
+
+ // For each line
+ for (unsigned i = 0; i < nlines; ++i)
+ {
+ // Max distance for the line search
+ int dmax = 1.5f * lines(i).x_height();
+
+ // Starting points in the current line box
+ point2d c = rbbox(i).pcenter();
+ point2d q(rbbox(i).pmax().row() - ((rbbox(i).pmax().row() - c.row()) / 4), c.col());
+
+ int
+ midcol = (rbbox(i).pmax().col()
+ - rbbox(i).pmin().col()) / 2;
+
+ // Right
+ {
+ int
+ nrightima = geom::ncols(blocks) - c.col() + blocks.domain().pmin().col(),
+ nright = std::min(nrightima, midcol + dmax);
+
+ V
+ // Starting points in the box
+ *p = &blocks(c),
+ *p2 = &blocks(q),
+ // End of search
+ *pstop = p + nright - 1,
+ // Line neighbor
+ *nbh_p = 0;
+
+ // While we haven't found a neighbor or reached the limit
+ for (; p != pstop; ++p, ++p2)
+ {
+ if (*p2 != literal::zero // Not the background
+ && ((*p2 % 2) == 1) // Looking for the bottom of a line
+ && right(((*p2 - 1) >> 1) - 1) != i) // No loops
+ {
+ // Neightbor found, we stop the research
+ nbh_p = p2;
+ break;
+ }
+
+ if (*p != literal::zero // Not the background
+ && ((*p % 2) == 1) // Looking for the bottom of a line
+ && right(((*p - 1) >> 1) - 1) != i) // No loops
+ {
+ // Neightbor found, we stop the research
+ nbh_p = p;
+ break;
+ }
+ }
+
+ // If a neighbor was found, then we have found the bottom of the
+ // line. We are then looking for the top of the encountered
+ // line. If during the search process we find a complete line
+ // included in the touched line, this line is considered as
+ // the neighbor under certain conditions (see below)
+
+ //---------------------------------------------------------------
+ //
+ //
+ // __________________________________________________ 2Q
+ // | Q |
+ // | _________________________ |2P
+ // | |_____________P___________| => Second bottom |2P + 1
+ // | line |
+ // |__________________________________________________|2Q + 1
+ // => First encountered bottom line
+ // _________________________ ^
+ // |_________________________| => Current line | Search direction
+ // |
+ //---------------------------------------------------------------
+
+ if (nbh_p)
+ {
+ std::vector<V> lines_nbh;
+ const V end_p = *nbh_p - 1;
+ const V* nbh_p_copy = nbh_p;
+
+ for (; *nbh_p != end_p; ++nbh_p)
+ {
+ if (*nbh_p != literal::zero) // Not the background
+ {
+ if (*nbh_p % 2 == 1) // We have found the bottom of
+ // another line
+ lines_nbh.push_back(*nbh_p);
+ else
+ {
+ // We have found the top of a line. We are looking if
+ //we have already encountered the bottom of this
+ // line. If so, we link the current line with this one
+ // under certain conditions:
+
+ if (std::find(lines_nbh.begin(), lines_nbh.end(),
+ *nbh_p + 1) != lines_nbh.end())
+ {
+ // If we can link the complete line with the current line
+ if (// It must be in the search range
+ nbh_p < pstop
+ // Avoid loops
+ && right((*nbh_p >> 1) - 1) != i)
+ right(i) = (*nbh_p >> 1) - 1;
+
+ // We have found a complete line, so we stop the search
+ break;
+ }
+ }
+ }
+ }
+
+ // If we haven't found any included line in the first
+ // neighbor, then the line is considered as the neighbor of
+ // the current line
+
+ if (*nbh_p == end_p)
+ right(i) = ((*nbh_p_copy - 1) >> 1) - 1;
+ }
+ }
+ }
+ }
+
+//-----------------------------------------------------------------------
+// Finalizing the links by merging information extracted from the left
+// and right links
+//-----------------------------------------------------------------------
+
+ template< typename L >
+ inline
+ void finalize_links(util::array<value::int_u16>& left,
+ util::array<value::int_u16>& right,
+ const util::array< line_info<L> >& lines)
+ {
+ const unsigned nlines = lines.nelements();
+
+ for (unsigned i = 0; i < nlines; ++i)
+ {
+ const unsigned left_value = left(i);
+ const unsigned right_value = right(i);
+
+ // If the right neighbor of my left neighbor is itself then its
+ // right neighbor is me
+ {
+ value::int_u16& v = right(left_value);
+
+ if (v == left_value)
+ v = i;
+ }
+
+ // If the left neighbor of my right neighbor is itself then its
+ // left neighbor is me
+ {
+ value::int_u16& v = left(right_value);
+
+ if (v == right_value)
+ v = i;
+ }
+ }
+ }
+
+ template <typename L>
+ inline
+ void extract_paragraphs(line_set<L>& lines,
+ const image2d<bool>& input)
+ {
+ typedef value::int_u16 V;
+
+ image2d<V> blocks(geom::rotate(input.domain(), -90, input.domain().pcenter()));
+ data::fill(blocks, 0);
+
+ util::array< line_info<L> > lines_info;
+
+ for_all_lines(l, lines)
+ {
+ if (lines(l).is_textline())
+ lines_info.append(lines(l));
+ }
+
+ const unsigned nlines = lines_info.nelements();
+ util::array<box2d> rbbox;
+ util::array<V> left(nlines);
+ util::array<V> right(nlines);
+ util::array<V> output;
+
+ rbbox.reserve(nlines);
+ output.reserve(nlines);
+
+ std::cout << "Preparing lines" << std::endl;
+ prepare_lines(input.domain(), lines_info, blocks, rbbox);
+// io::pgm::save(blocks, "blocks.pgm");
+ std::cout << "Linking left" << std::endl;
+ process_left_link(blocks, rbbox, lines_info, left);
+ std::cout << "Linking right" << std::endl;
+ process_right_link(blocks, rbbox, lines_info, right);
+ std::cout << "Finalizing links" << std::endl;
+ finalize_links(left, right, lines_info);
+ // std::cout << "Finalizing merging" << std::endl;
+ // finalize_line_merging(left, right, lines);
+ std::cout << "Extracting paragraphs" << std::endl;
+ filter::paragraph_links(left, right, output, lines_info, input);
+ }
+}
--
1.5.6.5
1
0
---
scribo/scribo/core/line_info.hh | 199 +++++-
scribo/scribo/core/stats.hh | 320 +++++++++
scribo/scribo/text/look_like_text_lines.hh | 16 +
scribo/scribo/text/merging.hh | 306 ++++++---
scribo/scribo/text/paragraphs.hh | 1027 ++++++++++++++++++++++++++++
5 files changed, 1767 insertions(+), 101 deletions(-)
create mode 100644 scribo/scribo/core/stats.hh
create mode 100644 scribo/scribo/text/paragraphs.hh
diff --git a/scribo/scribo/core/line_info.hh b/scribo/scribo/core/line_info.hh
index 73d7856..b44b379 100644
--- a/scribo/scribo/core/line_info.hh
+++ b/scribo/scribo/core/line_info.hh
@@ -59,6 +59,9 @@
# include <scribo/core/concept/serializable.hh>
+// DEBUG
+
+# include <scribo/core/stats.hh>
namespace scribo
{
@@ -138,6 +141,9 @@ namespace scribo
// Line set holding this element.
line_set<L> holder_;
+ // DEBUG
+ stats< float > meanline_clusters_;
+ stats< float > baseline_clusters_;
};
@@ -279,7 +285,8 @@ namespace scribo
/// @}
-
+ bool chars_same_width() const;
+ unsigned get_first_char_height() const;
/// Force a new computation of statistics.
void force_stats_update();
@@ -307,6 +314,9 @@ namespace scribo
void update_components_type(component::Type type);
+ int compute_baseline();
+ int compute_meanline();
+
private: // Attributes
line_id_t id_;
mln::util::tracked_ptr<data_t> data_;
@@ -817,7 +827,7 @@ namespace scribo
int
line_info<L>::delta_of_line() const
{
- return char_width() + 2 * char_space();
+ return 2 * char_width() + 2 * char_space();
// FIXME: choose between:
// not enough: char_width + char_space
// too much: 2 * char_width
@@ -967,6 +977,145 @@ namespace scribo
}
template <typename L>
+ bool
+ line_info<L>::chars_same_width() const
+ {
+ // Only for the case of two-character words
+ if (card() == 2)
+ {
+ const component_set<L>& comp_set = data_->holder_.components();
+
+ const unsigned c1 = data_->components_(0);
+ const unsigned c2 = data_->components_(1);
+
+ if (data_->holder_.components()(c1).type() == component::Punctuation
+ || data_->holder_.components()(c2).type() == component::Punctuation)
+ return false;
+
+ const mln::box2d& bb1 = comp_set(c1).bbox();
+ const mln::box2d& bb2 = comp_set(c2).bbox();
+
+ const float w1 = bb1.width();
+ const float h1 = bb1.height();
+ const float w2 = bb2.width();
+ const float h2 = bb2.height();
+
+ const float space = std::max(bb1.pmin().col(), bb2.pmin().col()) -
+ std::min(bb1.pmax().col(), bb2.pmax().col());
+
+ const int dy = bb1.pmax().row() - bb2.pmax().row();
+
+ // The two characters must be distinct
+ if (space < 0)
+ return false;
+
+ if (// Approximately the same width
+ ((std::max(w1, w2) / std::min(w1, w2)) > 1.1f ||
+ // One character must not be smaller than the space between
+ // the two characters
+ (w1 < space || w2 < space))
+ // If the two characters have a different width they must also
+ // have a different height
+ && not (std::max(h1, h2) / std::min(h1, h2) <= 1.5f))
+ return false;
+
+ // Approximately aligned on baseline
+ if (std::abs(dy) > 10)
+ return false;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ template< typename L >
+ unsigned
+ line_info<L>::get_first_char_height() const
+ {
+ const component_set<L>& comp_set = data_->holder_.components();
+ const unsigned c1 = data_->components_(0);
+ const mln::box2d& bb1 = comp_set(c1).bbox();
+
+ return bb1.height();
+ }
+
+ template <typename L>
+ int
+ line_info<L>::compute_baseline()
+ {
+ const unsigned nelements = data_->baseline_clusters_.nelements();
+
+ if (nelements == 2)
+ return data_->baseline_clusters_.mean();
+
+ mln::util::array< cluster_stats< float > >& clusters_b = data_->baseline_clusters_.clusters();
+
+ unsigned index = 0;
+ float min_base = 0.0f;
+ const unsigned clusters_b_nelements = clusters_b.nelements();
+
+ for (unsigned i = 0; i < clusters_b_nelements; ++i)
+ {
+ const unsigned clusters_b_i_nelements = clusters_b[i].nelements();
+
+ if (clusters_b_i_nelements >= min_base * 2.0f)
+ {
+ min_base = clusters_b_i_nelements;
+ index = i;
+ }
+ else if (clusters_b_i_nelements >= 0.5f * min_base)
+ {
+ if (clusters_b_i_nelements > 1 &&
+ clusters_b[index].median() > clusters_b[i].median())
+ {
+ if (clusters_b_i_nelements > min_base)
+ min_base = clusters_b_i_nelements;
+ index = i;
+ }
+ }
+ }
+
+ if (clusters_b[index].nelements() <= 2 && nelements <= 5)
+ return data_->baseline_clusters_.mean();
+
+ return clusters_b[index].median();
+ }
+
+ template <typename L>
+ int
+ line_info<L>::compute_meanline()
+ {
+ mln::util::array< cluster_stats< float > >& clusters_m = data_->meanline_clusters_.clusters();
+
+ unsigned index = 0;
+ float max_mean = 0.0f;
+ const unsigned clusters_m_nelements = clusters_m.nelements();
+
+ for (unsigned i = 0; i < clusters_m_nelements; ++i)
+ {
+ const unsigned clusters_m_i_nelements = clusters_m[i].nelements();
+
+ if (clusters_m_i_nelements >= max_mean * 2.0f)
+ {
+ max_mean = clusters_m_i_nelements;
+ index = i;
+ }
+ else if (clusters_m_i_nelements >= 0.5f * max_mean)
+ {
+ if (clusters_m[index].median() < clusters_m[i].median())
+ {
+ if (clusters_m_i_nelements > max_mean)
+ max_mean = clusters_m_i_nelements;
+ index = i;
+ }
+ }
+ }
+
+ return clusters_m[index].median();
+ }
+
+ template <typename L>
void
line_info<L>::force_stats_update()
{
@@ -977,8 +1126,8 @@ namespace scribo
typedef mln::value::int_u<12> median_data_t;
typedef mln::accu::stat::median_h<median_data_t> median_t;
median_t
- meanline,
- baseline,
+ // meanline,
+ // baseline,
char_space,
char_width;
@@ -1002,6 +1151,10 @@ namespace scribo
mln::def::coord ref_line = mln_max(mln::def::coord);
+ // DEBUG
+ data_->baseline_clusters_.reset();
+ data_->meanline_clusters_.reset();
+
// Find a reference line to compute baselines and other attributes.
// Workaround to avoid overflow with int_u<12> in median accumulators.
//
@@ -1040,18 +1193,18 @@ namespace scribo
// incremented.
++used_comps;
- // Compute boldness
- boldness.take(comp_set(c).features().boldness);
- sum2_boldness += mln::math::sqr<float>(comp_set(c).features().boldness);
+ // // Compute boldness
+ // boldness.take(comp_set(c).features().boldness);
+ // sum2_boldness += mln::math::sqr<float>(comp_set(c).features().boldness);
- // Compute color
- color_red.take(comp_set(c).features().color.red());
- color_green.take(comp_set(c).features().color.green());
- color_blue.take(comp_set(c).features().color.blue());
+ // // Compute color
+ // color_red.take(comp_set(c).features().color.red());
+ // color_green.take(comp_set(c).features().color.green());
+ // color_blue.take(comp_set(c).features().color.blue());
- sum2_red += mln::math::sqr<unsigned>(comp_set(c).features().color.red());
- sum2_green += mln::math::sqr<unsigned>(comp_set(c).features().color.green());
- sum2_blue += mln::math::sqr<unsigned>(comp_set(c).features().color.blue());
+ // sum2_red += mln::math::sqr<unsigned>(comp_set(c).features().color.red());
+ // sum2_green += mln::math::sqr<unsigned>(comp_set(c).features().color.green());
+ // sum2_blue += mln::math::sqr<unsigned>(comp_set(c).features().color.blue());
// FIXME: we must guaranty here that the relationship is from
@@ -1086,12 +1239,14 @@ namespace scribo
// Meanline (compute an absolute value, from the top left corner
// of the highest character bounding box, excluding
// punctuation).
- meanline.take(bb.pmin().row() - ref_line);
+ // meanline.take(bb.pmin().row() - ref_line);
+ data_->meanline_clusters_.take(bb.pmin().row());
// Baseline (compute an absolute value, from the top left corner
// of the highest character bounding box, excluding
// punctuation).
- baseline.take(bb.pmax().row() - ref_line);
+ // baseline.take(bb.pmax().row() - ref_line);
+ data_->baseline_clusters_.take(bb.pmax().row());
}
// Finalization
@@ -1143,12 +1298,14 @@ namespace scribo
else
data_->char_width_ = char_width.to_result();
- mln::def::coord
- absolute_baseline_r = baseline.to_result() + ref_line,
- absolute_meanline_r = meanline.to_result() + ref_line;
+ // // mln::def::coord
+ // // absolute_baseline_r = baseline.to_result() + ref_line,
+ // // absolute_meanline_r = meanline.to_result() + ref_line;
- data_->baseline_ = absolute_baseline_r;
- data_->meanline_ = absolute_meanline_r;
+ // data_->baseline_ = absolute_baseline_r;
+ // data_->meanline_ = absolute_meanline_r;
+ data_->baseline_ = compute_baseline();
+ data_->meanline_ = compute_meanline();
data_->x_height_ = data_->baseline_ - data_->meanline_ + 1;
data_->d_height_ = data_->baseline_ - bbox.to_result().pmax().row();
data_->a_height_ = data_->baseline_ - bbox.to_result().pmin().row() + 1;
diff --git a/scribo/scribo/core/stats.hh b/scribo/scribo/core/stats.hh
new file mode 100644
index 0000000..570325c
--- /dev/null
+++ b/scribo/scribo/core/stats.hh
@@ -0,0 +1,320 @@
+#ifndef STATS_HH_
+# define STATS_HH_
+
+# include <vector>
+# include <algorithm>
+
+# include <mln/util/array.hh>
+
+using namespace mln;
+
+//---------------------------------------------------------------------------
+// compare_values
+//---------------------------------------------------------------------------
+
+template< typename T >
+struct compare_values
+{
+ bool operator() (const T& lhs,
+ const T& rhs)
+ {
+ return (lhs < rhs);
+ }
+};
+
+
+//---------------------------------------------------------------------------
+// cluster_stats
+//---------------------------------------------------------------------------
+
+template< typename T >
+class cluster_stats
+{
+public:
+ cluster_stats()
+ : mean_needs_update_(false), median_needs_update_(false),
+ variance_needs_update_(false), std_needs_update_(false), size_(0)
+ {
+ }
+
+ cluster_stats(const unsigned size)
+ : mean_needs_update_(false), median_needs_update_(false),
+ variance_needs_update_(false), std_needs_update_(false), size_(0)
+ {
+ data_.reserve(size);
+ }
+
+ void reset()
+ {
+ std::vector< T >& data = data_.hook_std_vector_();
+ data.clear();
+
+ size_ = 0;
+ needs_update();
+ }
+
+ void take(const T& value)
+ {
+ if (not size_)
+ {
+ min_ = value;
+ max_ = value;
+ }
+ else
+ {
+ if (value < min_)
+ min_ = value;
+ else if (value > max_)
+ max_ = value;
+ }
+
+ ++size_;
+ data_.append(value);
+ needs_update();
+ }
+
+ T mean()
+ {
+ if (mean_needs_update_)
+ {
+ mean_ = 0;
+
+ for (unsigned i = 0; i < size_; ++i)
+ mean_ += data_[i];
+
+ mean_ /= size_;
+ mean_needs_update_ = false;
+ }
+
+ return mean_;
+ }
+
+ T median()
+ {
+ if (median_needs_update_)
+ {
+ std::vector< T >& data = data_.hook_std_vector_();
+ std::sort(data.begin(), data.end(), compare_values< T >());
+
+ median_ = data[(size_ - 1) >> 1];
+ median_needs_update_ = false;
+ }
+
+ return median_;
+ }
+
+ T variance()
+ {
+ if (variance_needs_update_)
+ {
+ mean();
+ variance_ = 0;
+
+ for (unsigned i = 0; i < size_; ++i)
+ {
+ const T tmp = mean_ - data_[i];
+
+ variance_ += (tmp * tmp);
+ }
+
+ variance_ /= size_;
+ std_ = sqrt(variance_);
+
+ variance_needs_update_ = false;
+ std_needs_update_ = false;
+ }
+
+ return variance_;
+ }
+
+ T standard_deviation()
+ {
+ if (std_needs_update_)
+ variance();
+
+ return std_;
+ }
+
+ T min() { return min_; }
+ T max() { return max_; }
+
+ void sort()
+ {
+ std::vector< T >& data = data_.hook_std_vector_();
+ std::sort(data.begin(), data.end(), compare_values< T >());
+ }
+
+ unsigned nelements() { return size_; }
+
+ T operator[] (const unsigned index)
+ {
+ return data_[index];
+ }
+
+private:
+ void needs_update()
+ {
+ mean_needs_update_ = true;
+ median_needs_update_ = true;
+ variance_needs_update_ = true;
+ std_needs_update_ = true;
+ }
+
+private:
+ bool mean_needs_update_ : 1;
+ bool median_needs_update_ : 1;
+ bool variance_needs_update_ : 1;
+ bool std_needs_update_ : 1;
+ T mean_;
+ T median_;
+ T min_;
+ T max_;
+ T variance_;
+ T std_;
+ unsigned size_;
+ util::array< T > data_;
+};
+
+//---------------------------------------------------------------------------
+// stats
+//---------------------------------------------------------------------------
+
+template< typename T >
+class stats
+{
+public:
+ stats()
+ : clusters_need_update_(true), data_sorted_(false)
+ {
+ }
+
+ stats(const int size)
+ : clusters_need_update_(true), data_sorted_(false), data_(size)
+ {
+ }
+
+ void reset()
+ {
+ data_.reset();
+ std::vector< cluster_stats< T > >& clusters = clusters_.hook_std_vector_();
+ clusters.clear();
+ data_sorted_ = false;
+ clusters_need_update_ = true;
+ }
+
+ void take(const T& value)
+ {
+ data_.take(value);
+ clusters_need_update_ = true;
+ }
+
+ T mean()
+ {
+ return data_.mean();
+ }
+
+ T median()
+ {
+ return data_.median();
+ }
+
+ T variance()
+ {
+ return data_.variance();
+ }
+
+ T standard_deviation()
+ {
+ return data_.standard_deviation();
+ }
+
+ T min() { return data_.min(); }
+ T max() { return data_.max(); }
+
+ unsigned nelements() { return data_.nelements(); }
+
+ util::array< cluster_stats< T > >& clusters()
+ {
+ if (clusters_need_update_)
+ {
+ compute_clusters();
+ clusters_need_update_ = false;
+ }
+
+ return clusters_;
+ }
+
+private:
+ void compute_clusters()
+ {
+ std::vector< unsigned > clusters;
+ unsigned cluster_index = 1;
+
+ clusters.reserve(data_.nelements());
+
+ if (not data_sorted_)
+ {
+ data_.sort();
+ data_sorted_ = true;
+ }
+
+ unsigned i = 0;
+ const unsigned nelements = data_.nelements();
+
+ clusters[0] = cluster_index;
+ const T std = data_.standard_deviation();
+
+ for (i = 1; i < nelements - 1; ++i)
+ {
+ const T left_distance = data_[i] - data_[i - 1];
+ const T right_distance = data_[i + 1] - data_[i];
+
+ if (not ((left_distance <= 2 || left_distance < right_distance)
+ && left_distance <= std))
+ ++cluster_index;
+
+ clusters[i] = cluster_index;
+ }
+
+ if (nelements > 1
+ && data_[i] - data_[i - 1] > std)
+ ++cluster_index;
+
+ clusters[i] = cluster_index;
+
+ clusters_.clear();
+ clusters_.reserve(cluster_index);
+ cluster_index = 1;
+
+ i = 0;
+ while (i < nelements)
+ {
+ unsigned tmp = i;
+
+ while (tmp < nelements && clusters[tmp] == clusters[i])
+ ++tmp;
+
+ cluster_stats< T > cluster(tmp - i);
+
+ tmp = i;
+ while (tmp < nelements && clusters[tmp] == clusters[i])
+ {
+ cluster.take(data_[tmp]);
+ ++tmp;
+ }
+
+ clusters_.append(cluster);
+
+ i = tmp;
+ ++cluster_index;
+ }
+ }
+
+private:
+ bool clusters_need_update_ : 1;
+ bool data_sorted_ : 1;
+ cluster_stats< T > data_;
+ util::array< cluster_stats< T > > clusters_;
+};
+
+#endif /* STATS_HH_ */
diff --git a/scribo/scribo/text/look_like_text_lines.hh b/scribo/scribo/text/look_like_text_lines.hh
index 2ced7ce..80d9995 100644
--- a/scribo/scribo/text/look_like_text_lines.hh
+++ b/scribo/scribo/text/look_like_text_lines.hh
@@ -64,6 +64,22 @@ namespace scribo
inline
bool looks_like_a_text_line(const scribo::line_info<L>& l)
{
+ // Special case for two-letter words
+ if (l.card() == 2)
+ {
+ const float ratio = (float) l.bbox().width() / l.bbox().height();
+
+ if (// Minimal width / height ratio
+ ratio > 0.4f && ratio < 2.0f
+ // Minimal height
+ && l.bbox().height() >= 15
+ // Characters must have approximately the same width
+ && l.chars_same_width())
+ {
+ return true;
+ }
+ }
+
return
l.card() >= 3 // at least 3 components
&& l.bbox().height() > 10 // and minimal height
diff --git a/scribo/scribo/text/merging.hh b/scribo/scribo/text/merging.hh
index 26e2da7..8ee349b 100644
--- a/scribo/scribo/text/merging.hh
+++ b/scribo/scribo/text/merging.hh
@@ -188,21 +188,24 @@ namespace scribo
swap_ordering(l1, l2);
parent[l2] = l1; // The smallest label value is root.
- if (lines(l2).card() > lines(l1).card())
+ line_info<L>& l1_info = lines(l1);
+ line_info<L>& l2_info = lines(l2);
+
+ if (l2_info.card() > l1_info.card())
{
// we transfer data from the largest item to the root one.
- scribo::line_info<L> tmp = lines(l1);
- std::swap(lines(l1), lines(l2));
- lines(l1).fast_merge(tmp);
+ scribo::line_info<L> tmp = l1_info;
+ std::swap(l1_info, l2_info);
+ l1_info.fast_merge(tmp);
// We must set manually the tag for lines(l2) since it is
// not used directly in merge process so its tag cannot be
// updated automatically.
- lines(l2).update_tag(line::Merged);
- lines(l2).set_hidden(true);
+ l2_info.update_tag(line::Merged);
+ l2_info.set_hidden(true);
}
else
- lines(l1).fast_merge(lines(l2));
+ l1_info.fast_merge(l2_info);
// l1's tag is automatically set to line::Needs_Precise_Stats_Update
// l2's tag is automatically set to line::Merged
@@ -226,34 +229,57 @@ namespace scribo
bool between_separators(const scribo::line_info<L>& l1,
const scribo::line_info<L>& l2)
{
- // No separators found in image.
+ // No separators found in image.
mln_precondition(l1.holder().components().has_separators());
- unsigned
- col1 = l1.bbox().pcenter().col(),
- col2 = l2.bbox().pcenter().col();
+ const box2d& l1_bbox = l1.bbox();
+ const box2d& l2_bbox = l2.bbox();
+
+ const unsigned
+ col1 = l1_bbox.pcenter().col(),
+ col2 = l2_bbox.pcenter().col();
const mln_ch_value(L, bool)&
separators = l1.holder().components().separators();
+ // Checking for separators starting from 1 / 4, 3/ 4 and the
+ // center of the box
typedef const bool* sep_ptr_t;
- sep_ptr_t sep_ptr, end;
+ sep_ptr_t sep_ptr, sep_ptr_top, sep_ptr_bottom, end;
if (col1 < col2)
{
- sep_ptr = &separators(l1.bbox().pcenter());
+ const unsigned quarter =
+ ((l1_bbox.pcenter().row() - l1_bbox.pmin().row()) >> 1);
+
+ sep_ptr = &separators(l1_bbox.pcenter());
+ sep_ptr_top = &separators(point2d(l1_bbox.pmin().row() + quarter,
+ l1_bbox.pcenter().col()));
+ sep_ptr_bottom = &separators(point2d(l1_bbox.pmax().row() - quarter,
+ l1_bbox.pcenter().col()));
end = sep_ptr + col2 - col1;
}
else
{
- sep_ptr = &separators(l2.bbox().pcenter());
+ const unsigned quarter =
+ ((l2_bbox.pcenter().row() - l2_bbox.pmin().row()) >> 1);
+
+ sep_ptr = &separators(l2_bbox.pcenter());
+ sep_ptr_top = &separators(point2d(l2_bbox.pmin().row() + quarter,
+ l2_bbox.pcenter().col()));
+ sep_ptr_bottom = &separators(point2d(l2_bbox.pmax().row() - quarter,
+ l2_bbox.pcenter().col()));
end = sep_ptr + col1 - col2;
}
// If sep_ptr is true, then a separator is reached.
- while (!*sep_ptr && sep_ptr != end)
+ while (!*sep_ptr && !*sep_ptr_top && !*sep_ptr_bottom && sep_ptr != end)
+ {
++sep_ptr;
+ ++sep_ptr_top;
+ ++sep_ptr_bottom;
+ }
- return *sep_ptr;
+ return (*sep_ptr || *sep_ptr_top || *sep_ptr_bottom);
}
@@ -266,16 +292,78 @@ namespace scribo
*/
template <typename L>
- bool lines_can_merge(const scribo::line_info<L>& l1,
+ bool lines_can_merge(scribo::line_info<L>& l1,
const scribo::line_info<L>& l2)
{
// Parameters.
- const float x_ratio_max = 1.7, baseline_delta_max = 3;
+ const float x_ratio_max = 1.7f;
+ const float baseline_delta_max =
+ 0.5f * std::min(l1.x_height(), l2.x_height());
+
+ const box2d& l1_bbox = l1.bbox();
+ const box2d& l2_bbox = l2.bbox();
+
+ const point2d& l1_pmin = l1_bbox.pmin();
+ const point2d& l2_pmin = l2_bbox.pmin();
+ const point2d& l1_pmax = l1_bbox.pmax();
+ const point2d& l2_pmax = l2_bbox.pmax();
+
+ const bool l1_has_separators = l1.holder().components().has_separators();
+ const bool l1_l2_between_separators = (l1_has_separators) ?
+ between_separators(l1, l2) : false;
+ const float l_ted_cw = l2.char_width();
+
+ const float dx = std::max(l1_pmin.col(), l2_pmin.col())
+ - std::min(l1_pmax.col(), l2_pmax.col());
+ const float dy = std::max(l1_pmin.row(), l2_pmin.row())
+ - std::min(l1_pmax.row(), l2_pmax.row());
+
+ // Particular case of "
+ {
+ if (// Must have 2 characters
+ (l1.card() == 2
+ // The box height must be smaller than the touched line x height
+ && l1_bbox.height() < l2.x_height())
+ // The line must be vertically and horizontally close to
+ // the touched line
+ && (dx < l_ted_cw && dy < 0)
+ // No separator between the two lines
+ && not (l1_l2_between_separators))
+ {
+ // Line is then considered as punctuation
+ l1.update_type(line::Punctuation);
+ return true;
+ }
+ }
+
+ // Particular case like merging between a line and [5]
+ {
+ const mln::def::coord
+ top_row_l2 = l2_pmin.row(),
+ top_row_l1 = l1_pmin.row(),
+ bot_row = l2_pmax.row();
+ const float x1 = l1.x_height(), x2 = l2.x_height();
+ const float x_ratio = std::max(x1, x2) / std::min(x1, x2);
+
+ if (// No separator
+ !l1_l2_between_separators
+ // The x height ration must be lower than 2
+ && (x_ratio < 2.0f)
+ // Baseline alignment
+ && (std::abs(bot_row - l1.baseline()) < baseline_delta_max)
+ // The top of the boxes must be aligned
+ && (std::abs(top_row_l2 - top_row_l1) < 5)
+ // Distance between the line and the touched line.
+ && dx < 5.0f * l_ted_cw)
+ {
+ return true;
+ }
+ }
// Similarity of x_height.
{
- float x1 = l1.x_height(), x2 = l2.x_height();
- float x_ratio = std::max(x1, x2) / std::min(x1, x2);
+ const float x1 = l1.x_height(), x2 = l2.x_height();
+ const float x_ratio = std::max(x1, x2) / std::min(x1, x2);
if (x_ratio > x_ratio_max)
return false;
}
@@ -287,22 +375,21 @@ namespace scribo
}
// left / right
- unsigned
- col1 = l1.bbox().pcenter().col(),
- col2 = l2.bbox().pcenter().col();
+ const unsigned
+ col1 = l1_bbox.pcenter().col(),
+ col2 = l2_bbox.pcenter().col();
if (col1 < col2)
{
- if ((col1 + l1.bbox().width() / 4) >= (col2 - l2.bbox().width() / 4))
+ if ((col1 + l1_bbox.width() / 4) >= (col2 - l2_bbox.width() / 4))
return false;
}
else
- if ((col2 + l2.bbox().width() / 4) >= (col1 - l1.bbox().width() / 4))
+ if ((col2 + l2_bbox.width() / 4) >= (col1 - l1_bbox.width() / 4))
return false;
-
// Check that there is no separator in between.
- if (l1.holder().components().has_separators())
- return ! between_separators(l1, l2);
+ if (l1_has_separators)
+ return ! l1_l2_between_separators;
return true;
}
@@ -310,14 +397,14 @@ namespace scribo
- template <typename L>
- int horizontal_distance(const scribo::line_info<L>& l1,
- const scribo::line_info<L>& l2)
+ inline
+ int horizontal_distance(const box2d& l1,
+ const box2d& l2)
{
- if (l1.bbox().pcenter().col() < l2.bbox().pcenter().col())
- return l2.bbox().pmin().col() - l1.bbox().pmax().col();
+ if (l1.pcenter().col() < l2.pcenter().col())
+ return l2.pmin().col() - l1.pmax().col();
else
- return l1.bbox().pmin().col() - l2.bbox().pmax().col();
+ return l1.pmin().col() - l2.pmax().col();
}
@@ -339,7 +426,7 @@ namespace scribo
*/
template <typename L>
- bool non_text_and_text_can_merge(const scribo::line_info<L>& l_cur, // current
+ bool non_text_and_text_can_merge(scribo::line_info<L>& l_cur, // current
const scribo::line_info<L>& l_ted) // touched
{
if (l_cur.type() == line::Text || l_ted.type() != line::Text)
@@ -353,22 +440,42 @@ namespace scribo
&& between_separators(l_cur, l_ted))
return false;
+ const box2d& l_cur_bbox = l_cur.bbox();
+ const box2d& l_ted_bbox = l_ted.bbox();
+
+ const point2d& l_cur_pmin = l_cur_bbox.pmin();
+ const point2d& l_ted_pmin = l_ted_bbox.pmin();
+ const point2d& l_cur_pmax = l_cur_bbox.pmax();
+ const point2d& l_ted_pmax = l_ted_bbox.pmax();
+
+ const float dx = std::max(l_cur_pmin.col(), l_ted_pmin.col())
+ - std::min(l_cur_pmax.col(), l_ted_pmax.col());
+ const float l_ted_cw = l_ted.char_width();
+ const float l_ted_x_height = l_ted.x_height();
+
+ const unsigned l_cur_height = l_cur_bbox.height();
+ const unsigned l_cur_width = l_cur_bbox.width();
// General case (for tiny components like --> ',:."; <--):
- if (l_cur.bbox().height() < l_ted.x_height()
- && float(l_cur.bbox().width()) / float(l_cur.card()) < l_ted.char_width())
+ if (l_cur_height < l_ted_x_height
+ && l_cur_height > 0.05f * l_ted_x_height
+ && float(l_cur_width) / float(l_cur.card()) < l_ted.char_width()
+ && dx < l_ted_cw)
+ {
+ l_cur.update_type(line::Punctuation);
return true;
-
+ }
// Special case for '---':
if (// small height:
- l_cur.bbox().height() < l_ted.x_height()
+ l_cur_height < l_ted_x_height
// // not so long width:
- && l_cur.bbox().width() < 5 * l_ted.char_width()
+ && l_cur_width > 0.8 * l_ted_cw
+ && l_cur_width < 5 * l_ted_cw
// align with the 'x' center:
&& std::abs((l_ted.baseline() + l_ted.meanline()) / 2 - l_cur.bbox().pcenter().row()) < 7
// tiny spacing:
- && unsigned(horizontal_distance(l_cur, l_ted)) < l_ted.char_width()
+ && unsigned(horizontal_distance(l_cur_bbox, l_ted_bbox)) < 2 * l_ted_cw
)
{
return true;
@@ -378,10 +485,11 @@ namespace scribo
// Special case
// Looking for alignement.
- mln::def::coord
+ const mln::def::coord
top_row = l_cur.bbox().pmin().row(),
bot_row = l_cur.bbox().pmax().row();
+ const box2d& l_ted_ebbox = l_ted.ebbox();
// std::cout << "top_row = " << top_row << " - bot_row = " << bot_row << std::endl;
// std::cout << std::abs(bot_row - l_ted.baseline())
@@ -402,10 +510,11 @@ namespace scribo
// << std::endl;
if ((std::abs(bot_row - l_ted.baseline()) < 5
- || std::abs(bot_row - l_ted.ebbox().pmax().row()) < 5)
+ || std::abs(bot_row - l_ted_ebbox.pmax().row()) < 5)
&&
(std::abs(top_row - l_ted.meanline()) < 5
- || std::abs(top_row - l_ted.ebbox().pmin().row()) < 5))
+ || std::abs(top_row - l_ted_ebbox.pmin().row()) < 5)
+ && dx < 5.0f * l_ted_cw)
{
return true;
}
@@ -536,35 +645,33 @@ namespace scribo
if (parent[l] != l) // not a root, so has already merged, thus ignore it
continue;
- box2d b = lines(l).bbox();
+ const box2d& b = lines(l).bbox();
- unsigned tl, tr, ml, mc, mr, bl, br;
+ // unsigned tl, tr, ml, mc, mr, bl, br;
+
+ const box2d& b_ = lines(l).ebbox();
+
+ /*
+ tl tr
+ x---------------x
+ | |
+ | mc |
+ ml x x x mr
+ | |
+ | |
+ x---------------x
+ bl br
+
+ */
- {
- box2d b_ = lines(l).ebbox();
-
- /*
- tl tr
- x---------------x
- | |
- | mc |
- ml x x x mr
- | |
- | |
- x---------------x
- bl br
-
- */
-
-
- tl = billboard(b_.pmin());
- tr = billboard.at_(b_.pmin().row(), b_.pmax().col());
- ml = billboard.at_(b_.pcenter().row(), b_.pmin().col());
- mc = billboard.at_(b_.pcenter().row(), b_.pcenter().col());
- mr = billboard.at_(b_.pcenter().row(), b_.pmax().col());
- bl = billboard.at_(b_.pmax().row(), b_.pmin().col());
- br = billboard(b_.pmax());
- }
+
+ const unsigned tl = billboard(b_.pmin());
+ const unsigned tr = billboard.at_(b_.pmin().row(), b_.pmax().col());
+ const unsigned ml = billboard.at_(b_.pcenter().row(), b_.pmin().col());
+ const unsigned mc = billboard.at_(b_.pcenter().row(), b_.pcenter().col());
+ const unsigned mr = billboard.at_(b_.pcenter().row(), b_.pmax().col());
+ const unsigned bl = billboard.at_(b_.pmax().row(), b_.pmin().col());
+ const unsigned br = billboard(b_.pmax());
typedef std::set<unsigned> set_t;
std::set<unsigned> labels;
@@ -596,11 +703,47 @@ namespace scribo
{
// Main case: it is an "included" box (falling in an already drawn box)
- if (lines(l).type() == line::Text) // the current object IS a text line
+ const line_info<L>& l_info = lines(l);
+ const line_info<L>& mc_info = lines(mc);
+
+ if (l_info.type() == line::Text) // the current object IS a text line
{
- if (lines(mc).type() == line::Text) // included in a text line => weird
+ if (mc_info.type() == line::Text) // included in a text line => weird
{
++count_txtline_IN_txtline;
+
+ // Particular case of "
+ // {
+ // if ((lines(l).card() == 2 &&
+ // lines(l).bbox().height() < lines(mc).x_height()) &&
+ // not (lines(l).holder().components().has_separators()
+ // && between_separators(lines(l),
+ // lines(mc))))
+
+ const box2d& l_bbox = l_info.bbox();
+ const box2d& mc_bbox = mc_info.bbox();
+
+ const point2d& l_pmin = l_bbox.pmin();
+ const point2d& mc_pmin = mc_bbox.pmin();
+ const point2d& l_pmax = l_bbox.pmax();
+ const point2d& mc_pmax = mc_bbox.pmax();
+
+ const float dx = std::max(l_pmin.col(), mc_pmin.col())
+ - std::min(l_pmax.col(), mc_pmax.col());
+ const float dy = std::max(l_pmin.row(), mc_pmin.row())
+ - std::min(l_pmax.row(), mc_pmax.row());
+ const float l_ted_cw = mc_info.char_width();
+
+ // We accept a line included into another only if it
+ // is horizontally close to the line's bbox and
+ // vertically aligned
+ // Obviously no separators between the two lines
+ if (dx < l_ted_cw && dy < 0
+ && not (l_info.holder().components().has_separators()
+ && between_separators(l_info, mc_info)))
+ l = do_union(lines, l, mc, parent);
+ // }
+
// std::cout << "weird: inclusion of a txt_line in a txt_line!" << std::endl;
/// Merge is perform if the current line is a
@@ -649,15 +792,15 @@ namespace scribo
// could be noise or garbage... So adding new
// criterions could fix this issue.
//
- //if (!non_text_and_text_can_merge(lines(l), lines(mc)))
- // continue;
+ if (!non_text_and_text_can_merge(lines(l), lines(mc)))
+ continue;
// Avoid the case when a large title ebbox overlap
// with a text column. In that case, the title may
// merge with punctuation from the text.
- if (lines(l).holder().components().has_separators()
- && between_separators(lines(l), lines(mc)))
- continue;
+ // if (lines(l).holder().components().has_separators()
+ // && between_separators(lines(l), lines(mc)))
+ // continue;
// Mark current line as punctuation.
lines(l).update_type(line::Punctuation);
@@ -832,9 +975,12 @@ namespace scribo
bool operator()(const scribo::line_id_t& l1, const scribo::line_id_t& l2) const
{
- if (lines_(l1).bbox().nsites() == lines_(l2).bbox().nsites())
+ const unsigned l1_nsites = lines_(l1).bbox().nsites();
+ const unsigned l2_nsites = lines_(l2).bbox().nsites();
+
+ if (l1_nsites == l2_nsites)
return l1 < l2;
- return lines_(l1).bbox().nsites() < lines_(l2).bbox().nsites();
+ return l1_nsites < l2_nsites;
}
scribo::line_set<L> lines_;
diff --git a/scribo/scribo/text/paragraphs.hh b/scribo/scribo/text/paragraphs.hh
new file mode 100644
index 0000000..a3ff802
--- /dev/null
+++ b/scribo/scribo/text/paragraphs.hh
@@ -0,0 +1,1027 @@
+#include <mln/util/array.hh>
+#include <mln/accu/shape/bbox.hh>
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/draw/box.hh>
+#include <mln/data/convert.hh>
+#include <mln/value/int_u16.hh>
+#include <mln/value/label_16.hh>
+#include <mln/value/int_u8.hh>
+#include <mln/value/rgb8.hh>
+#include <mln/io/ppm/save.hh>
+#include <mln/io/pgm/save.hh>
+#include <mln/geom/rotate.hh>
+#include <mln/literal/colors.hh>
+
+#include <scribo/core/macros.hh>
+#include <scribo/core/line_set.hh>
+#include <scribo/core/line_info.hh>
+
+using namespace mln;
+
+namespace scribo
+{
+
+ namespace internal
+ {
+
+//-------------------------------------
+// Extracting root of links
+//-------------------------------------
+ template <typename T>
+ inline
+ unsigned
+ find_root(util::array<T>& parent, unsigned x)
+ {
+ unsigned tmp_x = x;
+
+ while (parent(tmp_x) != tmp_x)
+ tmp_x = parent(tmp_x);
+
+ while (parent(x) != x)
+ {
+ const unsigned tmp = parent(x);
+ x = parent(x);
+ parent(tmp) = tmp_x;
+ }
+
+ return x;
+ }
+ }
+
+ namespace filter
+ {
+
+//---------------------------------------------------------------------
+// This method aims to cut the links between lines that do not fit the
+// different criteria
+//---------------------------------------------------------------------
+
+ template <typename L>
+ inline
+ void paragraph_links(const util::array<value::int_u16>& left,
+ const util::array<value::int_u16>& right,
+ util::array<value::int_u16>& output,
+ const util::array< line_info<L> >& lines,
+ const image2d<bool>& input)
+ {
+ output = left;
+
+ const unsigned nlines = lines.nelements();
+
+ // image2d<value::rgb8> links = data::convert(value::rgb8(), input);
+ // for (unsigned l = 0; l < nlines; ++l)
+ // {
+ // mln::draw::line(links, lines(l).bbox().pcenter(), lines(left(l)).bbox().pcenter(), literal::red);
+ // }
+ // mln::io::ppm::save(links, "out_links.ppm");
+
+ // For each line
+ for (unsigned l = 0; l < nlines; ++l)
+ {
+ // Neighbors
+
+ const value::int_u16 left_nbh = output(l);
+ const value::int_u16 right_nbh = right(l);
+ const value::int_u16 lol_nbh = output(left_nbh);
+
+ // Line features
+ const float x_height = lines(l).x_height();
+ const float left_x_height = lines(left_nbh).x_height();
+ const float right_x_height = lines(right_nbh).x_height();
+
+ const box2d& left_line_bbox = lines(left_nbh).bbox();
+ const box2d& current_line_bbox = lines(l).bbox();
+ const box2d& right_line_bbox = lines(right_nbh).bbox();
+ const box2d& lol_line_bbox = lines(lol_nbh).bbox(); // lol : left neighbor of the left neighbor
+
+ const int lline_col_min = left_line_bbox.pmin().col();
+ const int cline_col_min = current_line_bbox.pmin().col();
+ const int rline_col_min = right_line_bbox.pmin().col();
+ const int lolline_col_min = lol_line_bbox.pmin().col();
+
+ const int lline_col_max = left_line_bbox.pmax().col();
+ const int cline_col_max = current_line_bbox.pmax().col();
+ const int rline_col_max = right_line_bbox.pmax().col();
+
+ const int lline_cw = lines(left_nbh).char_width();
+ const int cline_cw = lines(l).char_width();
+ const int rline_cw = lines(right_nbh).char_width();
+ // Maximal x variation to consider two lines vertically aligned
+ const int delta_alignment = cline_cw;
+
+ // Checks the baseline distances of the two neighbors
+ {
+ // Current line baseline
+ const int c_baseline = lines(l).baseline();
+
+ // Baseline distance with the left and right neighbors
+ const int lc_baseline = lines(left_nbh).baseline() - c_baseline;
+ const int rc_baseline = c_baseline -lines(right_nbh).baseline();
+
+ // Max baseline distance between the two neighbors
+ // const float delta_baseline_max = std::max(lc_baseline, rc_baseline);
+ // const float delta_baseline_min = std::min(lc_baseline,
+ // rc_baseline);
+
+ // Only two lines, meaning the current line has only one neighbor
+ bool two_lines = false;
+
+ // If the current line has no left neighbor
+ if (lc_baseline == 0)
+ {
+ // ror : right neighbor of the right neighbor
+ const value::int_u16 ror_nbh = right(right_nbh);
+ const box2d& ror_line_bbox = lines(ror_nbh).bbox();
+
+ // If the current line has a ror
+ if (ror_nbh != right_nbh
+ && output(ror_nbh) == right_nbh)
+ {
+ // Distance between the current line and the right neighbor
+ const float right_distance =
+ current_line_bbox.pcenter().row() - right_line_bbox.pcenter().row();
+ // Distance between the right neighbor and the ror
+ const float ror_distance =
+ right_line_bbox.pcenter().row() - ror_line_bbox.pcenter().row();
+ // ror x_height
+ const float ror_x_height = lines(ror_nbh).x_height();
+
+ // Conditions to cut the link between the current line
+ // and its right neighbor
+ if (right_distance > 1.4f * ror_distance
+ && std::max(ror_x_height, right_x_height) <
+ 1.2f * std::min(ror_x_height, right_x_height)
+ && output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+ // Otherwise we only have a group of two lines
+ else
+ {
+ // We determine the distance between the two lines
+ const float distance = lines(l).meanline() - lines(right_nbh).baseline();
+ two_lines = true;
+
+ // If the distance between the two lines is greater than
+ // the minimum x height of the two lines then we cut the
+ // link between them
+ if (distance > std::min(x_height, right_x_height)
+ && output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+
+ // Lines features
+ const float min_x_height = std::min(x_height, right_x_height);
+ const float max_x_height = std::max(x_height, right_x_height);
+ const float min_char_width = std::min(rline_cw, cline_cw);
+ const float max_char_width = std::max(rline_cw, cline_cw);
+
+ // Condition to cut the link between the current line and
+ // its right neighbor
+ if ((max_x_height > min_x_height * 1.2f) &&
+ !(max_char_width <= 1.2f * min_char_width))
+ {
+ if (output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+
+ // If we only have two lines we stop the study
+ if (two_lines)
+ continue;
+ }
+ // If the current line has no right neighbor
+ else if (rc_baseline == 0)
+ {
+ // lol : left neighbor of the left neighbor
+
+ // If the left neighbor of the current line has a left neighbor
+ if (lol_nbh != left_nbh)
+ {
+ // Distance between the current line and its left neighbor
+ const float left_distance =
+ left_line_bbox.pcenter().row() - current_line_bbox.pcenter().row();
+ // Distance between the left neighbor and the left
+ // neighbor of its left neighbor
+ const float lol_distance =
+ lol_line_bbox.pcenter().row() - left_line_bbox.pcenter().row();
+ // lol x height
+ const float lol_x_height = lines(lol_nbh).x_height();
+
+ // Conditions to cut the link between the current line
+ // and its left neighbor
+ if (left_distance > 1.4f * lol_distance
+ && std::max(lol_x_height, left_x_height) <
+ 1.2f * std::min(lol_x_height, left_x_height))
+ {
+ output(l) = l;
+ continue;
+ }
+ }
+ // Otherwise we only have a group of two lines
+ else
+ {
+ // Distance between the current line and it left neighbor
+ const float distance = lines(left_nbh).meanline() -
+ lines(l).baseline();
+
+ two_lines = true;
+
+ // If the distance is greater than the min x height
+ // between the two lines
+ if (distance > std::min(x_height, left_x_height))
+ {
+ output(l) = l;
+ continue;
+ }
+ }
+
+ // Lines features
+ const float min_x_height = std::min(x_height, left_x_height);
+ const float max_x_height = std::max(x_height, left_x_height);
+ const float min_char_width = std::min(lline_cw, cline_cw);
+ const float max_char_width = std::max(lline_cw, cline_cw);
+
+ // Condition to cut the link between the current line and
+ // its left neighbor
+ if ((max_x_height > min_x_height * 1.2f) &&
+ !(max_char_width <= 1.2f * min_char_width))
+ {
+ output(l) = l;
+ continue;
+ }
+
+ // If we only have two lines we stop the study
+ if (two_lines)
+ continue;
+ }
+ // The current line has at least one left and one right neighbor
+ else // if (delta_baseline_max >= delta_baseline_min)
+ {
+ // Distance between the left and the current line
+ const float left_distance =
+ lines(left_nbh).meanline() - lines(l).baseline();
+ // Distance between the right and the current line
+ const float right_distance =
+ lines(l).meanline() - lines(right_nbh).baseline();
+
+ // If the left line is too far compared to the right one
+ // we cut the link with it
+ if (left_distance > 1.2f * right_distance
+ && std::max(x_height, left_x_height) > 1.2f * std::min(x_height, left_x_height))
+ {
+ output(l) = l;
+ continue;
+ }
+ // If the right line is too far compared to the left one
+ // we cut the link with it
+ else if (right_distance > 1.2f * left_distance
+ && std::max(x_height, right_x_height) > 1.2f * std::min(x_height, right_x_height)
+ && output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+
+ // If the distance between the baseline of the left
+ // neighbor and the baseline of the current line is
+ // greater than the one between the current line baseline
+ // and the right line baseline we have to study the texte
+ // features of the right and left lines
+ if (lc_baseline > rc_baseline)
+ {
+ const float cw_max = std::max(lline_cw, cline_cw);
+ const float cw_min = std::min(lline_cw, cline_cw);
+ const float min_x_height = std::min(x_height, left_x_height);
+ const float max_x_height = std::max(x_height, left_x_height);
+
+ if ((max_x_height > min_x_height * 1.2f) &&
+ !(cw_max <= 1.2f * cw_min))
+ {
+ output(l) = l;
+ continue;
+ }
+
+ {
+ const float min_x_height = std::min(x_height, right_x_height);
+ const float max_x_height = std::max(x_height, right_x_height);
+ const float cw_max = std::max(rline_cw, cline_cw);
+ const float cw_min = std::min(rline_cw, cline_cw);
+
+ if ((max_x_height > min_x_height * 1.2f)
+ && !(cw_max <= 1.2f * cw_min)
+ && output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+ }
+ else
+ {
+ const float cw_max = std::max(rline_cw, cline_cw);
+ const float cw_min = std::min(rline_cw, cline_cw);
+ const float min_x_height = std::min(x_height, right_x_height);
+ const float max_x_height = std::max(x_height, right_x_height);
+
+ if ((max_x_height > min_x_height * 1.2f)
+ && !(cw_max <= 1.2f * cw_min)
+ && output(right_nbh) == l)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+
+ {
+ const float min_x_height = std::min(x_height, left_x_height);
+ const float max_x_height = std::max(x_height, left_x_height);
+ const float cw_max = std::max(lline_cw, cline_cw);
+ const float cw_min = std::min(lline_cw, cline_cw);
+
+ if ((max_x_height > min_x_height * 1.2f)
+ && !(cw_max <= 1.2f * cw_min))
+ {
+ output(l) = l;
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+ // If we arrive here, it means than the lines in the
+ // neighborhood of the current line are quite similar. We can
+ // then begin to study the indentations in order to determine
+ // the beginning of new paragraphs
+
+//-----------------------------------------------------------------------------
+// ___________________________
+// |___________________________|
+// ________________________
+// |________________________|
+// ___________________________
+// |___________________________|
+// ___________________________
+// |___________________________|
+//
+// Simple case : paragraphs are justified on the left. We try to find any
+// indentation like above.
+//
+//-----------------------------------------------------------------------------
+
+ {
+ // Check if the current line neighbors are aligned
+ bool left_right_aligned = false;
+ bool left_lol_aligned = false;
+ const int dx_lr = std::abs(lline_col_min - rline_col_min);
+ const int dx_llol = std::abs(lline_col_min - lolline_col_min);
+
+ if (dx_lr < delta_alignment)
+ left_right_aligned = true;
+
+ if (dx_llol < delta_alignment)
+ left_lol_aligned = true;
+
+ if (left_right_aligned && left_lol_aligned)
+ {
+ const int left_right_col_min = std::min(lline_col_min, rline_col_min);
+ const int dx_lrc = std::abs(left_right_col_min - cline_col_min);
+ const float l_char_width = 1.5f * lines(l).char_width();
+
+ if (dx_lrc > l_char_width &&
+ dx_lrc < 3.0f * l_char_width &&
+ cline_col_min > rline_col_min &&
+ cline_col_min > lline_col_min)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+ }
+
+//-----------------------------------------------------------------------------
+// ___________________________
+// |___________________________|
+// ___________________
+// |___________________| End of the paragraph - Current line
+// ________________________
+// |________________________| Beginning of a new one
+// ___________________________
+// |___________________________| Left of left of current line
+//
+// End of paragraph case : we try to find an end to the current paragraph
+//
+//-----------------------------------------------------------------------------
+
+ {
+ // Check if the current line neighbors are aligned
+ bool left_right_max_aligned = false;
+ bool left_current_min_aligned = false;
+ bool lol_current_min_aligned = false;
+ const bool lol_is_left = output(left_nbh) == left_nbh;
+ const int dx_lr_max = std::abs(lline_col_max - rline_col_max);
+ const int dx_lc_min = std::abs(lline_col_min - cline_col_min);
+ const int dx_lolc_min = std::abs(lolline_col_min - cline_col_min);
+
+ if (dx_lr_max < delta_alignment)
+ left_right_max_aligned = true;
+
+ if (dx_lc_min < delta_alignment)
+ left_current_min_aligned = true;
+
+ if (dx_lolc_min < delta_alignment)
+ lol_current_min_aligned = true;
+
+ if (!left_current_min_aligned && left_right_max_aligned &&
+ (lol_current_min_aligned || lol_is_left))
+ {
+ const int dx_lrc = std::abs(lline_col_max - cline_col_max);
+ const int l_char_width = lines(l).char_width();
+
+ if (dx_lrc > l_char_width &&
+ cline_col_max < lline_col_max &&
+ cline_col_min < lline_col_min &&
+ (lline_col_min > lolline_col_min || lol_is_left))
+ {
+ output(l) = l;
+ continue;
+ }
+ }
+ }
+
+
+//-----------------------------------------------------------------------------
+// ___________________________
+// |___________________________|
+// ___________________________
+// |___________________________|
+// ________________________
+// |________________________|
+//
+// Simple case : paragraphs are justified on the left. We try to find any
+// indentation like above at the end of a column.
+//
+//-----------------------------------------------------------------------------
+
+ if (left_nbh == l)
+ {
+ const value::int_u16 ror_nbh = right(right_nbh);
+ const box2d& ror_line_bbox = lines(ror_nbh).bbox();
+ const int rorline_col_min = ror_line_bbox.pmin().col();
+
+ bool right_ror_min_aligned = false;
+ const int dx_rror_min = std::abs(rline_col_min - rorline_col_min);
+
+ if (dx_rror_min < delta_alignment)
+ right_ror_min_aligned = true;
+
+ if (right_ror_min_aligned)
+ {
+ const int right_ror_col_min = std::min(rline_col_min, rorline_col_min);
+ const int dx_rrorc = std::abs(right_ror_col_min - cline_col_min);
+ const float l_char_width = 1.5f * lines(l).char_width();
+
+ if (dx_rrorc > l_char_width &&
+ dx_rrorc < 3.0f * l_char_width &&
+ cline_col_min > rline_col_min &&
+ cline_col_max >= rline_col_max)
+ {
+ output(right_nbh) = right_nbh;
+ continue;
+ }
+ }
+ }
+ }
+
+
+ // Only debug
+
+ {
+ image2d<value::rgb8> debug = data::convert(value::rgb8(), input);
+
+ for (unsigned i = 0; i < output.nelements(); ++i)
+ output(i) = internal::find_root(output, i);
+
+ mln::util::array<accu::shape::bbox<point2d> > nbbox(output.nelements());
+ for (unsigned i = 0; i < nlines; ++i)
+ {
+ // if (lines(i).is_textline())
+ // {
+ // mln::draw::box(debug, lines(i).bbox(), literal::red);
+ nbbox(output(i)).take(lines(i).bbox());
+ // }
+ }
+
+ for (unsigned i = 0; i < nbbox.nelements(); ++i)
+ if (nbbox(i).is_valid())
+ {
+ box2d b = nbbox(i).to_result();
+ mln::draw::box(debug, b, literal::orange);
+ b.enlarge(1);
+ mln::draw::box(debug, b, literal::orange);
+ b.enlarge(1);
+ mln::draw::box(debug, b, literal::orange);
+ }
+
+ mln::io::ppm::save(debug, "out_paragraph.ppm");
+ }
+
+ }
+ }
+
+//-------------------------------------------------------------
+// Preparation of the lines before linking them.
+// For each line we draw the top and the bottom of it.
+// Assuming than i is the number of the line. Then the top of the line
+// will be affected with the value 2 * i in the block image and the
+// bottom with 2 * i + 1.
+//
+//-------------------------------------------------------------
+
+ template <typename L>
+ inline
+ void prepare_lines(const box2d& domain,
+ const util::array< line_info<L> >& lines,
+ image2d<value::int_u16>& blocks,
+ util::array<box2d>& rbbox)
+ {
+ std::map< int, std::vector< const box2d* > > drawn_lines;
+ const unsigned nlines = lines.nelements();
+
+ // For each line
+ for (unsigned l = 0; l < nlines; ++l)
+ {
+ // Rotation of the bounding box
+ box2d b = geom::rotate(lines(l).bbox(), -90, domain.pcenter());
+ rbbox.append(b);
+
+ const unsigned index = l + 1;
+ const unsigned even_index = 2 * index;
+ const unsigned odd_index = even_index + 1;
+
+ // Top of the line
+ {
+ bool not_finished = true;
+ int col_offset = 0;
+
+ while (not_finished)
+ {
+ // Looking for a column in the image to draw the top of the
+ // line
+
+ const int col = b.pmax().col() + col_offset;
+ std::map< int, std::vector< const box2d* > >::iterator it
+ = drawn_lines.find(col);
+
+ if (it != drawn_lines.end())
+ {
+ const std::vector< const box2d* >& lines = (*it).second;
+ const unsigned nb_lines = lines.size();
+ unsigned i = 0;
+
+ for (i = 0; i < nb_lines; ++i)
+ {
+ const box2d* box = lines[i];
+ const int min_row = std::max(b.pmin().row(), box->pmin().row());
+ const int max_row = std::min(b.pmax().row(), box->pmax().row());
+
+ if (min_row - max_row <= 0)
+ break;
+ }
+
+ if (i == nb_lines)
+ {
+ mln::draw::line(blocks, point2d(b.pmin().row(), col),
+ point2d(b.pmax().row(), col), even_index);
+ not_finished = false;
+ drawn_lines[col].push_back(&(rbbox[l]));
+ }
+ else
+ ++col_offset;
+ }
+ else
+ {
+ mln::draw::line(blocks, point2d(b.pmin().row(), col),
+ point2d(b.pmax().row(), col), even_index);
+ not_finished = false;
+ drawn_lines[col].push_back(&(rbbox[l]));
+ }
+ }
+ }
+
+ // Bottom of the line
+ {
+ bool not_finished = true;
+ int col_offset = 0;
+
+ while (not_finished)
+ {
+ // Looking for a column in the image to draw the bottom of
+ // the line
+
+ const int col = b.pmin().col() - col_offset;
+ std::map< int, std::vector< const box2d* > >::iterator it
+ = drawn_lines.find(col);
+
+ if (it != drawn_lines.end())
+ {
+ const std::vector< const box2d* >& lines = (*it).second;
+ const unsigned nb_lines = lines.size();
+ unsigned i = 0;
+
+ for (i = 0; i < nb_lines; ++i)
+ {
+ const box2d* box = lines[i];
+ const int min_row = std::max(b.pmin().row(), box->pmin().row());
+ const int max_row = std::min(b.pmax().row(), box->pmax().row());
+
+ if (min_row - max_row <= 0)
+ break;
+ }
+
+ if (i == nb_lines)
+ {
+ mln::draw::line(blocks, point2d(b.pmin().row(), col),
+ point2d(b.pmax().row(), col), odd_index);
+ not_finished = false;
+ drawn_lines[col].push_back(&(rbbox[l]));
+ }
+ else
+ ++col_offset;
+ }
+ else
+ {
+ mln::draw::line(blocks, point2d(b.pmin().row(), col),
+ point2d(b.pmax().row(), col), odd_index);
+ not_finished = false;
+ drawn_lines[col].push_back(&(rbbox[l]));
+ }
+ }
+ }
+ }
+ }
+
+ template <typename L>
+ inline
+ void
+ process_left_link(image2d<value::int_u16>& blocks,
+ const util::array<box2d>& rbbox,
+ const util::array< line_info<L> >& lines,
+ util::array<value::int_u16>& left)
+ {
+ typedef value::int_u16 V;
+
+ // At the beginning each line is its own neighbor
+ for (unsigned i = 0; i < left.nelements(); ++i)
+ left(i) = i;
+
+ const unsigned nlines = lines.nelements();
+
+ // For each line
+ for (unsigned i = 0; i < nlines; ++i)
+ {
+ // Max distance for the line search
+ int dmax = 1.5f * lines(i).x_height();
+
+ // Starting points in the current line box
+ point2d c = rbbox(i).pcenter();
+ point2d q(rbbox(i).pmin().row() + ((c.row() - rbbox(i).pmin().row()) / 4), c.col());
+
+ int
+ midcol = (rbbox(i).pmax().col()
+ - rbbox(i).pmin().col()) / 2;
+
+ // Left
+ {
+ // marge gauche
+ int
+ nleftima = c.col() - blocks.domain().pmin().col(),
+ // Distance gauche
+ nleft = std::min(nleftima, midcol + dmax);
+
+ V
+ // Starting points in the box
+ *p = &blocks(c),
+ *p2 = &blocks(q),
+ // End of search
+ *pstop = p - nleft - 1,
+ // Line neighbor
+ *nbh_p = 0;
+
+ // While we haven't found a neighbor or reached the limit
+ for (; p != pstop; --p, --p2)
+ {
+ if (*p2 != literal::zero // Not the background
+ && ((*p2 % 2) == 0) // Looking for the bottom of a line
+ && left((*p2 >> 1) - 1) != i) // No loops
+ {
+ // Neightbor found, we stop the research
+ nbh_p = p2;
+ break;
+ }
+
+ if (*p != literal::zero // Not the background
+ && ((*p % 2) == 0) // Looking for the bottom of a line
+ && left((*p >> 1) - 1) != i) // No loops
+ {
+ // Neightbor found, we stop the research
+ nbh_p = p;
+ break;
+ }
+ }
+
+ // If a neighbor was found, then we have found the top of the
+ // line. We are then looking for the bottom of the encountered
+ // line. If during the search process we find a complete line
+ // included in the touched line, this line is considered as
+ // the neighbor under certain conditions (see below)
+
+ //---------------------------------------------------------------
+ // _________________________ |
+ // |_________________________| => Current line | Search direction
+ // v
+ // => First encountered top line
+ // __________________________________________________ 2Q
+ // | Q |
+ // | _________________________ |2P
+ // | |_____________P___________| => Second top |2P + 1
+ // | line |
+ // |__________________________________________________|2Q + 1
+ //
+ //
+ //---------------------------------------------------------------
+
+ if (nbh_p)
+ {
+ std::vector<V> lines_nbh;
+ const V end_p = *nbh_p + 1;
+ const V* nbh_p_copy = nbh_p;
+
+ for (; *nbh_p != end_p; --nbh_p)
+ {
+ if ((*nbh_p) != literal::zero) // Not the background
+ {
+ if ((*nbh_p) % 2 == 0)// We have found the top of
+ // another line
+ lines_nbh.push_back(*nbh_p);
+ else
+ {
+ // We have found the bottom of a line. We are looking if
+ // we have already encountered the top of this
+ // line. If so, we link the current line with this one
+ // under certain conditions:
+
+ if (std::find(lines_nbh.begin(), lines_nbh.end(),
+ (*nbh_p) - 1) != lines_nbh.end())
+ {
+ // If we can link the complete line with the current line
+ if (// It must be in the search range
+ nbh_p > pstop
+ // Avoid loops
+ && left(((*nbh_p - 1) >> 1) - 1) != i)
+ left(i) = ((*nbh_p - 1) >> 1) - 1;
+
+ // We have found a complete line so we stop the search
+ break;
+ }
+ }
+ }
+ }
+
+
+ // If we haven't found any included line in the first
+ // neighbor, then the line is considered as the neighbor of
+ // the current line
+ if (*nbh_p == end_p)
+ left(i) = (*nbh_p_copy >> 1) - 1;
+ }
+ }
+ }
+ }
+
+
+ // We assume that the lines have been rotated
+ template <typename L>
+ inline
+ void
+ process_right_link(image2d<value::int_u16>& blocks,
+ const util::array<box2d>& rbbox,
+ const util::array< line_info<L> >& lines,
+ util::array<value::int_u16>& right)
+ {
+ typedef value::int_u16 V;
+
+ // At the beginning each line is its own neighbor
+ for (unsigned i = 0; i < right.nelements(); ++i)
+ right(i) = i;
+
+ const unsigned nlines = lines.nelements();
+
+ // For each line
+ for (unsigned i = 0; i < nlines; ++i)
+ {
+ // Max distance for the line search
+ int dmax = 1.5f * lines(i).x_height();
+
+ // Starting points in the current line box
+ point2d c = rbbox(i).pcenter();
+ point2d q(rbbox(i).pmax().row() - ((rbbox(i).pmax().row() - c.row()) / 4), c.col());
+
+ int
+ midcol = (rbbox(i).pmax().col()
+ - rbbox(i).pmin().col()) / 2;
+
+ // Right
+ {
+ int
+ nrightima = geom::ncols(blocks) - c.col() + blocks.domain().pmin().col(),
+ nright = std::min(nrightima, midcol + dmax);
+
+ V
+ // Starting points in the box
+ *p = &blocks(c),
+ *p2 = &blocks(q),
+ // End of search
+ *pstop = p + nright - 1,
+ // Line neighbor
+ *nbh_p = 0;
+
+ // While we haven't found a neighbor or reached the limit
+ for (; p != pstop; ++p, ++p2)
+ {
+ if (*p2 != literal::zero // Not the background
+ && ((*p2 % 2) == 1) // Looking for the bottom of a line
+ && right(((*p2 - 1) >> 1) - 1) != i) // No loops
+ {
+ // Neightbor found, we stop the research
+ nbh_p = p2;
+ break;
+ }
+
+ if (*p != literal::zero // Not the background
+ && ((*p % 2) == 1) // Looking for the bottom of a line
+ && right(((*p - 1) >> 1) - 1) != i) // No loops
+ {
+ // Neightbor found, we stop the research
+ nbh_p = p;
+ break;
+ }
+ }
+
+ // If a neighbor was found, then we have found the bottom of the
+ // line. We are then looking for the top of the encountered
+ // line. If during the search process we find a complete line
+ // included in the touched line, this line is considered as
+ // the neighbor under certain conditions (see below)
+
+ //---------------------------------------------------------------
+ //
+ //
+ // __________________________________________________ 2Q
+ // | Q |
+ // | _________________________ |2P
+ // | |_____________P___________| => Second bottom |2P + 1
+ // | line |
+ // |__________________________________________________|2Q + 1
+ // => First encountered bottom line
+ // _________________________ ^
+ // |_________________________| => Current line | Search direction
+ // |
+ //---------------------------------------------------------------
+
+ if (nbh_p)
+ {
+ std::vector<V> lines_nbh;
+ const V end_p = *nbh_p - 1;
+ const V* nbh_p_copy = nbh_p;
+
+ for (; *nbh_p != end_p; ++nbh_p)
+ {
+ if (*nbh_p != literal::zero) // Not the background
+ {
+ if (*nbh_p % 2 == 1) // We have found the bottom of
+ // another line
+ lines_nbh.push_back(*nbh_p);
+ else
+ {
+ // We have found the top of a line. We are looking if
+ //we have already encountered the bottom of this
+ // line. If so, we link the current line with this one
+ // under certain conditions:
+
+ if (std::find(lines_nbh.begin(), lines_nbh.end(),
+ *nbh_p + 1) != lines_nbh.end())
+ {
+ // If we can link the complete line with the current line
+ if (// It must be in the search range
+ nbh_p < pstop
+ // Avoid loops
+ && right((*nbh_p >> 1) - 1) != i)
+ right(i) = (*nbh_p >> 1) - 1;
+
+ // We have found a complete line, so we stop the search
+ break;
+ }
+ }
+ }
+ }
+
+ // If we haven't found any included line in the first
+ // neighbor, then the line is considered as the neighbor of
+ // the current line
+
+ if (*nbh_p == end_p)
+ right(i) = ((*nbh_p_copy - 1) >> 1) - 1;
+ }
+ }
+ }
+ }
+
+//-----------------------------------------------------------------------
+// Finalizing the links by merging information extracted from the left
+// and right links
+//-----------------------------------------------------------------------
+
+ template< typename L >
+ inline
+ void finalize_links(util::array<value::int_u16>& left,
+ util::array<value::int_u16>& right,
+ const util::array< line_info<L> >& lines)
+ {
+ const unsigned nlines = lines.nelements();
+
+ for (unsigned i = 0; i < nlines; ++i)
+ {
+ const unsigned left_value = left(i);
+ const unsigned right_value = right(i);
+
+ // If the right neighbor of my left neighbor is itself then its
+ // right neighbor is me
+ {
+ value::int_u16& v = right(left_value);
+
+ if (v == left_value)
+ v = i;
+ }
+
+ // If the left neighbor of my right neighbor is itself then its
+ // left neighbor is me
+ {
+ value::int_u16& v = left(right_value);
+
+ if (v == right_value)
+ v = i;
+ }
+ }
+ }
+
+ template <typename L>
+ inline
+ void extract_paragraphs(line_set<L>& lines,
+ const image2d<bool>& input)
+ {
+ typedef value::int_u16 V;
+
+ image2d<V> blocks(geom::rotate(input.domain(), -90, input.domain().pcenter()));
+ data::fill(blocks, 0);
+
+ util::array< line_info<L> > lines_info;
+
+ for_all_lines(l, lines)
+ {
+ if (lines(l).is_textline())
+ lines_info.append(lines(l));
+ }
+
+ const unsigned nlines = lines_info.nelements();
+ util::array<box2d> rbbox;
+ util::array<V> left(nlines);
+ util::array<V> right(nlines);
+ util::array<V> output;
+
+ rbbox.reserve(nlines);
+ output.reserve(nlines);
+
+ std::cout << "Preparing lines" << std::endl;
+ prepare_lines(input.domain(), lines_info, blocks, rbbox);
+// io::pgm::save(blocks, "blocks.pgm");
+ std::cout << "Linking left" << std::endl;
+ process_left_link(blocks, rbbox, lines_info, left);
+ std::cout << "Linking right" << std::endl;
+ process_right_link(blocks, rbbox, lines_info, right);
+ std::cout << "Finalizing links" << std::endl;
+ finalize_links(left, right, lines_info);
+ // std::cout << "Finalizing merging" << std::endl;
+ // finalize_line_merging(left, right, lines);
+ std::cout << "Extracting paragraphs" << std::endl;
+ filter::paragraph_links(left, right, output, lines_info, input);
+ }
+}
--
1.5.6.5
1
0

03 May '11
scribo/tests/Makefile.am,
scribo/tests/binarization/Makefile.am,
scribo/tests/primitive/extract/Makefile.am: Here.
---
scribo/ChangeLog | 8 ++++++++
scribo/tests/Makefile.am | 5 +++++
scribo/tests/binarization/Makefile.am | 7 ++++++-
scribo/tests/primitive/extract/Makefile.am | 6 ++++++
4 files changed, 25 insertions(+), 1 deletions(-)
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index 423c9ac..8d95b3a 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,13 @@
2011-04-07 Guillaume Lazzara <z(a)lrde.epita.fr>
+ Add test data in EXTRA_DIST.
+
+ scribo/tests/Makefile.am,
+ scribo/tests/binarization/Makefile.am,
+ scribo/tests/primitive/extract/Makefile.am: Here.
+
+2011-04-07 Guillaume Lazzara <z(a)lrde.epita.fr>
+
Add new tests for Sauvola algorithms.
* tests/binarization/Makefile.am: Add new targets.
diff --git a/scribo/tests/Makefile.am b/scribo/tests/Makefile.am
index 1f227b7..706338f 100644
--- a/scribo/tests/Makefile.am
+++ b/scribo/tests/Makefile.am
@@ -18,6 +18,11 @@
include $(srcdir)/tests.mk
EXTRA_DIST = \
+ img/alignment_1.pbm \
+ img/alignment_2.pbm \
+ img/alignment_3.pbm \
+ img/alignment_4.pbm \
+ img/phillip.ppm \
img/pixels.pbm \
img/table_to_be_repaired.pbm \
img/table_to_be_repaired2.pbm \
diff --git a/scribo/tests/binarization/Makefile.am b/scribo/tests/binarization/Makefile.am
index b894f5a..500d708 100644
--- a/scribo/tests/binarization/Makefile.am
+++ b/scribo/tests/binarization/Makefile.am
@@ -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.
#
@@ -19,6 +20,10 @@
include $(top_srcdir)/scribo/tests/tests.mk
+EXTRA_DIST = \
+ sauvola_ms.ref.pbm \
+ sauvola.ref.pbm
+
check_PROGRAMS = \
global_threshold \
local_threshold \
diff --git a/scribo/tests/primitive/extract/Makefile.am b/scribo/tests/primitive/extract/Makefile.am
index 97eb43f..33d3a96 100644
--- a/scribo/tests/primitive/extract/Makefile.am
+++ b/scribo/tests/primitive/extract/Makefile.am
@@ -16,6 +16,12 @@
include $(top_srcdir)/scribo/tests/tests.mk
+EXTRA_DIST = \
+ alignment_1.ref.png \
+ alignment_2.ref.png \
+ alignment_3.ref.png \
+ alignment_4.ref.png
+
check_PROGRAMS =
if HAVE_MAGICKXX
--
1.5.6.5
1
0

03 May '11
* tests/binarization/Makefile.am: Add new targets.
* tests/binarization/sauvola.cc,
* tests/binarization/sauvola_ms.cc: New.
* tests/binarization/sauvola.ref.pbm,
* tests/binarization/sauvola_ms.ref.pbm: New test data.
* tests/data.hh.in: Add a new path.
---
scribo/ChangeLog | 14 +++++++++
scribo/tests/binarization/Makefile.am | 6 +++-
.../tests/binarization/sauvola.cc | 29 ++++++++++---------
scribo/tests/binarization/sauvola.ref.pbm | Bin 0 -> 32884 bytes
.../tests/binarization/sauvola_ms.cc | 30 ++++++++++---------
scribo/tests/binarization/sauvola_ms.ref.pbm | Bin 0 -> 32884 bytes
scribo/tests/data.hh.in | 3 ++
7 files changed, 53 insertions(+), 29 deletions(-)
copy milena/tests/labeling/fill_holes.cc => scribo/tests/binarization/sauvola.cc (74%)
create mode 100644 scribo/tests/binarization/sauvola.ref.pbm
copy milena/tests/labeling/fill_holes.cc => scribo/tests/binarization/sauvola_ms.cc (72%)
create mode 100644 scribo/tests/binarization/sauvola_ms.ref.pbm
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index 0235162..423c9ac 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,3 +1,17 @@
+2011-04-07 Guillaume Lazzara <z(a)lrde.epita.fr>
+
+ Add new tests for Sauvola algorithms.
+
+ * tests/binarization/Makefile.am: Add new targets.
+
+ * tests/binarization/sauvola.cc,
+ * tests/binarization/sauvola_ms.cc: New.
+
+ * tests/binarization/sauvola.ref.pbm,
+ * tests/binarization/sauvola_ms.ref.pbm: New test data.
+
+ * tests/data.hh.in: Add a new path.
+
2011-04-06 Guillaume Lazzara <z(a)lrde.epita.fr>
* scribo/filter/object_groups_size_ratio.hh: Fix compilation.
diff --git a/scribo/tests/binarization/Makefile.am b/scribo/tests/binarization/Makefile.am
index c845b43..b894f5a 100644
--- a/scribo/tests/binarization/Makefile.am
+++ b/scribo/tests/binarization/Makefile.am
@@ -21,10 +21,14 @@ include $(top_srcdir)/scribo/tests/tests.mk
check_PROGRAMS = \
global_threshold \
- local_threshold
+ local_threshold \
+ sauvola \
+ sauvola_ms
global_threshold_SOURCES = global_threshold.cc
local_threshold_SOURCES = local_threshold.cc
+sauvola_SOURCES = sauvola.cc
+sauvola_ms_SOURCES = sauvola_ms.cc
TESTS = $(check_PROGRAMS)
diff --git a/milena/tests/labeling/fill_holes.cc b/scribo/tests/binarization/sauvola.cc
similarity index 74%
copy from milena/tests/labeling/fill_holes.cc
copy to scribo/tests/binarization/sauvola.cc
index bbf55b8..33eb59f 100644
--- a/milena/tests/labeling/fill_holes.cc
+++ b/scribo/tests/binarization/sauvola.cc
@@ -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.
//
@@ -24,28 +24,29 @@
// executable file might be covered by the GNU General Public License.
/// \file
-///
-/// Test of labeling::fill_holes.
-///
-/// \fixme Write a Test!
#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/io/pbm/load.hh>
-#include <mln/core/alias/neighb2d.hh>
-#include <mln/labeling/fill_holes.hh>
-#include <mln/value/label_8.hh>
-
-#include <mln/debug/println.hh>
#include <mln/io/pbm/save.hh>
-#include "tests/data.hh"
+#include <scribo/binarization/sauvola.hh>
+#include "tests/data.hh"
int main()
{
using namespace mln;
- image2d<bool> pic = io::pbm::load(MLN_IMG_DIR "/picasso.pbm");
- value::label_8 n;
- image2d<bool> out = labeling::fill_holes(pic, c4(), n);
+ image2d<value::int_u8> input;
+ io::pgm::load(input, MILENA_IMG_DIR "/lena.pgm");
+
+ image2d<bool> bin = scribo::binarization::sauvola(input, 101);
+
+ image2d<bool> ref;
+ io::pbm::load(ref, SCRIBO_TESTS_DIR "binarization/sauvola.ref.pbm");
+
+ mln_assertion(bin == ref);
}
diff --git a/scribo/tests/binarization/sauvola.ref.pbm b/scribo/tests/binarization/sauvola.ref.pbm
new file mode 100644
index 0000000000000000000000000000000000000000..6730ad45546f12a6e4dd166c41395689594bc2d1
GIT binary patch
literal 32884
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
Yn;N{gq35|3-mI*f&idB>-Ut8x0P<7s-v9sr
literal 0
HcmV?d00001
diff --git a/milena/tests/labeling/fill_holes.cc b/scribo/tests/binarization/sauvola_ms.cc
similarity index 72%
copy from milena/tests/labeling/fill_holes.cc
copy to scribo/tests/binarization/sauvola_ms.cc
index bbf55b8..31d0f96 100644
--- a/milena/tests/labeling/fill_holes.cc
+++ b/scribo/tests/binarization/sauvola_ms.cc
@@ -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.
//
@@ -24,28 +24,30 @@
// executable file might be covered by the GNU General Public License.
/// \file
-///
-/// Test of labeling::fill_holes.
-///
-/// \fixme Write a Test!
#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/io/pbm/load.hh>
-#include <mln/core/alias/neighb2d.hh>
-#include <mln/labeling/fill_holes.hh>
-#include <mln/value/label_8.hh>
-
-#include <mln/debug/println.hh>
#include <mln/io/pbm/save.hh>
-#include "tests/data.hh"
+#include <scribo/binarization/sauvola_ms.hh>
+#include "tests/data.hh"
int main()
{
using namespace mln;
- image2d<bool> pic = io::pbm::load(MLN_IMG_DIR "/picasso.pbm");
- value::label_8 n;
- image2d<bool> out = labeling::fill_holes(pic, c4(), n);
+ image2d<value::int_u8> input;
+ io::pgm::load(input, MILENA_IMG_DIR "/lena.pgm");
+
+ image2d<bool> bin = scribo::binarization::sauvola_ms(input, 101, 2);
+
+ io::pbm::save(bin, "res.pbm");
+ image2d<bool> ref;
+ io::pbm::load(ref, SCRIBO_TESTS_DIR "/binarization/sauvola_ms.ref.pbm");
+
+ mln_assertion(bin == ref);
}
diff --git a/scribo/tests/binarization/sauvola_ms.ref.pbm b/scribo/tests/binarization/sauvola_ms.ref.pbm
new file mode 100644
index 0000000000000000000000000000000000000000..685efe7175c19cbcabd696fe357bd4a69401db06
GIT binary patch
literal 32884
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
literal 0
HcmV?d00001
diff --git a/scribo/tests/data.hh.in b/scribo/tests/data.hh.in
index 501fbea..5966fba 100644
--- a/scribo/tests/data.hh.in
+++ b/scribo/tests/data.hh.in
@@ -40,4 +40,7 @@
/// \brief The absolute path to the test directory of Scribo.
# define SCRIBO_TESTS_DIR "@abs_top_srcdir@/scribo/tests/"
+/// \brief The absolute path to the img directory of Milena.
+# define MILENA_IMG_DIR "@abs_top_srcdir@/milena/img"
+
#endif // ! SCRIBO_TESTS_DATA_HH
--
1.5.6.5
1
0

last-svn-commit-842-g78b8020 scribo/filter/object_groups_size_ratio.hh: Fix compilation.
by Guillaume Lazzara 03 May '11
by Guillaume Lazzara 03 May '11
03 May '11
---
scribo/ChangeLog | 4 ++++
scribo/scribo/filter/object_groups_size_ratio.hh | 18 +++++++++---------
2 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index 0c785fc..0235162 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,9 @@
2011-04-06 Guillaume Lazzara <z(a)lrde.epita.fr>
+ * scribo/filter/object_groups_size_ratio.hh: Fix compilation.
+
+2011-04-06 Guillaume Lazzara <z(a)lrde.epita.fr>
+
Rename few routines.
* scribo/estim/object_groups_v_thickness.hh,
diff --git a/scribo/scribo/filter/object_groups_size_ratio.hh b/scribo/scribo/filter/object_groups_size_ratio.hh
index fa59e7f..01676c4 100644
--- a/scribo/scribo/filter/object_groups_size_ratio.hh
+++ b/scribo/scribo/filter/object_groups_size_ratio.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.
//
@@ -70,22 +70,22 @@ namespace scribo
// Counting the number of objects per group with a size ratio >
// max_ratio.
mln::util::array<unsigned>
- group_size(groups.size(), 0),
- invalid_object_in_group(groups.size(), 0);
+ group_size(groups.nelements(), 0),
+ invalid_object_in_group(groups.nelements(), 0);
for_all_comps(i, comps)
{
- if ((comps(i).bbox().nrows() / comps(i).bbox().ncols())
+ if ((comps(i).bbox().height() / comps(i).bbox().width())
>= max_size_ratio)
- ++invalid_object_in_group[groups[i]];
+ ++invalid_object_in_group(groups(i));
- ++group_size[groups[i]];
+ ++group_size(groups(i));
}
object_groups<L> output(groups);
output(0) = 0;
- for (unsigned i = 1; i < output.size(); ++i)
- if ((invalid_object_in_group[groups[i]] / static_cast<float>(group_size[groups[i]])) >= max_invalid_ratio_per_group
+ for (unsigned i = 1; i < output.nelements(); ++i)
+ if ((invalid_object_in_group(groups(i)) / static_cast<float>(group_size(groups(i)))) >= max_invalid_ratio_per_group
|| !comps(i).is_valid())
output(i) = 0;
--
1.5.6.5
1
0
* scribo/estim/object_groups_v_thickness.hh,
* scribo/filter/object_groups_v_thickness.hh: Rename as...
* scribo/estim/object_groups_mean_width.hh,
* scribo/filter/object_groups_mean_width.hh: ... this.
---
scribo/ChangeLog | 10 ++++++
..._v_thickness.hh => object_groups_mean_width.hh} | 26 ++++++++--------
..._v_thickness.hh => object_groups_mean_width.hh} | 32 ++++++++++----------
3 files changed, 39 insertions(+), 29 deletions(-)
rename scribo/scribo/estim/{object_groups_v_thickness.hh => object_groups_mean_width.hh} (75%)
rename scribo/scribo/filter/{object_groups_v_thickness.hh => object_groups_mean_width.hh} (68%)
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index e7ac031..0c785fc 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,15 @@
2011-04-06 Guillaume Lazzara <z(a)lrde.epita.fr>
+ Rename few routines.
+
+ * scribo/estim/object_groups_v_thickness.hh,
+ * scribo/filter/object_groups_v_thickness.hh: Rename as...
+
+ * scribo/estim/object_groups_mean_width.hh,
+ * scribo/filter/object_groups_mean_width.hh: ... this.
+
+2011-04-06 Guillaume Lazzara <z(a)lrde.epita.fr>
+
Add more tests.
* tests/filter/Makefile.am: Add new targets.
diff --git a/scribo/scribo/estim/object_groups_v_thickness.hh b/scribo/scribo/estim/object_groups_mean_width.hh
similarity index 75%
rename from scribo/scribo/estim/object_groups_v_thickness.hh
rename to scribo/scribo/estim/object_groups_mean_width.hh
index b626243..c74137a 100644
--- a/scribo/scribo/estim/object_groups_v_thickness.hh
+++ b/scribo/scribo/estim/object_groups_mean_width.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.
//
@@ -23,13 +24,13 @@
// exception does not however invalidate any other reasons why the
// executable file might be covered by the GNU General Public License.
-#ifndef SCRIBO_ESTIM_OBJECT_GROUPS_V_THICKNESS_HH
-# define SCRIBO_ESTIM_OBJECT_GROUPS_V_THICKNESS_HH
+#ifndef SCRIBO_ESTIM_OBJECT_GROUPS_MEAN_WIDTH_HH
+# define SCRIBO_ESTIM_OBJECT_GROUPS_MEAN_WIDTH_HH
/// \file
///
-/// \brief Estimate the mean object thickness for each group.
+/// \brief Estimate the mean object width for each group.
# include <mln/util/array.hh>
@@ -45,16 +46,16 @@ namespace scribo
using namespace mln;
- /*! \brief Estimate the mean object thickness for each group.
+ /*! \brief Estimate the mean object width for each group.
\param[in] groups Object groups information.
- \return An array of mean object thickness.
+ \return An array of mean object width.
*/
template <typename L>
mln::util::array<float>
- object_groups_v_thickness(const object_groups<L>& groups);
+ object_groups_mean_width(const object_groups<L>& groups);
@@ -63,9 +64,9 @@ namespace scribo
template <typename L>
util::array<float>
- object_groups_v_thickness(const object_groups<L>& groups)
+ object_groups_mean_width(const object_groups<L>& groups)
{
- trace::entering("scribo::estim::object_groups_v_thickness");
+ trace::entering("scribo::estim::object_groups_mean_width");
mln_precondition(groups.is_valid());
@@ -79,8 +80,7 @@ namespace scribo
for_all_comps(i, components)
if (components(i).is_valid())
{
- output(groups(i)) += components(i).bbox().pmax().row()
- - components(i).bbox().pmin().row();
+ output(groups(i)) += components(i).bbox().width();
++group_card(groups(i));
}
@@ -91,7 +91,7 @@ namespace scribo
else
output(i) = 0;
- trace::exiting("scribo::estim::object_groups_v_thickness");
+ trace::exiting("scribo::estim::object_groups_mean_width");
return output;
}
@@ -103,4 +103,4 @@ namespace scribo
} // end of namespace scribo
-#endif // ! SCRIBO_ESTIM_OBJECT_GROUPS_V_THICKNESS_HH
+#endif // ! SCRIBO_ESTIM_OBJECT_GROUPS_MEAN_WIDTH_HH
diff --git a/scribo/scribo/filter/object_groups_v_thickness.hh b/scribo/scribo/filter/object_groups_mean_width.hh
similarity index 68%
rename from scribo/scribo/filter/object_groups_v_thickness.hh
rename to scribo/scribo/filter/object_groups_mean_width.hh
index 717b372..0668205 100644
--- a/scribo/scribo/filter/object_groups_v_thickness.hh
+++ b/scribo/scribo/filter/object_groups_mean_width.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.
//
@@ -24,18 +24,18 @@
// 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_GROUPS_V_THICKNESS_HH
-# define SCRIBO_FILTER_OBJECT_GROUPS_V_THICKNESS_HH
+#ifndef SCRIBO_FILTER_OBJECT_GROUPS_MEAN_WIDTH_HH
+# define SCRIBO_FILTER_OBJECT_GROUPS_MEAN_WIDTH_HH
/// \file
///
-/// \brief Filter groups having their object mean thickness too low.
+/// \brief Filter groups having their object mean width too low.
# include <mln/util/array.hh>
# include <scribo/core/object_groups.hh>
-# include <scribo/estim/object_groups_v_thickness.hh>
+# include <scribo/estim/object_groups_mean_width.hh>
namespace scribo
@@ -47,10 +47,10 @@ namespace scribo
using namespace mln;
- /*! \brief Filter groups having their object mean thickness too low.
+ /*! \brief Filter groups having their object mean width too low.
\param[in] groups Object group information.
- \param[in] thickness Object group mean thickness must be greater
+ \param[in] width Object group mean width must be greater
or equal to this value.
\return Filtered object group information.
@@ -58,7 +58,7 @@ namespace scribo
*/
template <typename L>
object_groups<L>
- object_groups_v_thickness(const object_groups<L>& groups, float thickness);
+ object_groups_mean_width(const object_groups<L>& groups, float width);
# ifndef MLN_INCLUDE_ONLY
@@ -66,24 +66,24 @@ namespace scribo
template <typename L>
object_groups<L>
- object_groups_v_thickness(const object_groups<L>& groups, float thickness)
+ object_groups_mean_width(const object_groups<L>& groups, float width)
{
- trace::entering("scribo::filter::object_groups_v_thickness");
+ trace::entering("scribo::filter::object_groups_mean_width");
mln_precondition(groups.is_valid());
- mln_precondition(thickness >= 0);
+ mln_precondition(width >= 0);
mln::util::array<float>
- group_thickness = estim::object_groups_v_thickness(groups);
+ group_width = estim::object_groups_mean_width(groups);
object_groups<L> output = groups.duplicate();
output(0) = 0;
for (unsigned i = 1; i < output.nelements(); ++i)
if (groups.components()(i).is_valid()
- && group_thickness[groups(i)] < thickness)
+ && group_width[groups(i)] < width)
output(i) = 0;
- trace::exiting("scribo::filter::object_groups_v_thickness");
+ trace::exiting("scribo::filter::object_groups_mean_width");
return output;
}
@@ -96,4 +96,4 @@ namespace scribo
} // end of namespace scribo
-#endif // ! SCRIBO_FILTER_OBJECT_GROUPS_V_THICKNESS_HH
+#endif // ! SCRIBO_FILTER_OBJECT_GROUPS_MEAN_WIDTH_HH
--
1.5.6.5
1
0
* tests/filter/Makefile.am: Add new targets.
* tests/filter/object_groups_mean_width.cc,
* tests/filter/object_groups_size_ratio.cc,
* tests/filter/object_groups_small.cc,
* tests/filter/object_groups_with_holes.cc,
* tests/filter/object_links_bbox_h_ratio.cc,
* tests/filter/object_links_bbox_overlap.cc,
* tests/filter/object_links_bbox_w_ratio.cc,
* tests/filter/object_links_bottom_aligned.cc,
* tests/filter/object_links_center_aligned.cc,
* tests/filter/object_links_left_aligned.cc,
* tests/filter/object_links_right_aligned.cc,
* tests/filter/object_links_top_aligned.cc: New.
* scribo/tests/img/the_valleys.pbm: New test data.
---
scribo/ChangeLog | 21 +++++
scribo/tests/filter/Makefile.am | 28 ++++++-
scribo/tests/filter/object_groups_mean_width.cc | 72 ++++++++++++++++
scribo/tests/filter/object_groups_size_ratio.cc | 71 ++++++++++++++++
scribo/tests/filter/object_groups_small.cc | 87 ++++++++++++++++++++
scribo/tests/filter/object_groups_with_holes.cc | 71 ++++++++++++++++
scribo/tests/filter/object_links_bbox_h_ratio.cc | 69 ++++++++++++++++
scribo/tests/filter/object_links_bbox_overlap.cc | 69 ++++++++++++++++
scribo/tests/filter/object_links_bbox_w_ratio.cc | 69 ++++++++++++++++
scribo/tests/filter/object_links_bottom_aligned.cc | 69 ++++++++++++++++
scribo/tests/filter/object_links_center_aligned.cc | 70 ++++++++++++++++
scribo/tests/filter/object_links_left_aligned.cc | 69 ++++++++++++++++
scribo/tests/filter/object_links_right_aligned.cc | 69 ++++++++++++++++
scribo/tests/filter/object_links_top_aligned.cc | 69 ++++++++++++++++
scribo/tests/img/the_valleys.pbm | Bin 0 -> 1724 bytes
15 files changed, 901 insertions(+), 2 deletions(-)
create mode 100644 scribo/tests/filter/object_groups_mean_width.cc
create mode 100644 scribo/tests/filter/object_groups_size_ratio.cc
create mode 100644 scribo/tests/filter/object_groups_small.cc
create mode 100644 scribo/tests/filter/object_groups_with_holes.cc
create mode 100644 scribo/tests/filter/object_links_bbox_h_ratio.cc
create mode 100644 scribo/tests/filter/object_links_bbox_overlap.cc
create mode 100644 scribo/tests/filter/object_links_bbox_w_ratio.cc
create mode 100644 scribo/tests/filter/object_links_bottom_aligned.cc
create mode 100644 scribo/tests/filter/object_links_center_aligned.cc
create mode 100644 scribo/tests/filter/object_links_left_aligned.cc
create mode 100644 scribo/tests/filter/object_links_right_aligned.cc
create mode 100644 scribo/tests/filter/object_links_top_aligned.cc
create mode 100644 scribo/tests/img/the_valleys.pbm
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index a0d4937..e7ac031 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,26 @@
2011-04-06 Guillaume Lazzara <z(a)lrde.epita.fr>
+ Add more tests.
+
+ * tests/filter/Makefile.am: Add new targets.
+
+ * tests/filter/object_groups_mean_width.cc,
+ * tests/filter/object_groups_size_ratio.cc,
+ * tests/filter/object_groups_small.cc,
+ * tests/filter/object_groups_with_holes.cc,
+ * tests/filter/object_links_bbox_h_ratio.cc,
+ * tests/filter/object_links_bbox_overlap.cc,
+ * tests/filter/object_links_bbox_w_ratio.cc,
+ * tests/filter/object_links_bottom_aligned.cc,
+ * tests/filter/object_links_center_aligned.cc,
+ * tests/filter/object_links_left_aligned.cc,
+ * tests/filter/object_links_right_aligned.cc,
+ * tests/filter/object_links_top_aligned.cc: New.
+
+ * scribo/tests/img/the_valleys.pbm: New test data.
+
+2011-04-06 Guillaume Lazzara <z(a)lrde.epita.fr>
+
Small fixes in Scribo.
* scribo/core/component_features_data.hh: Fix default
diff --git a/scribo/tests/filter/Makefile.am b/scribo/tests/filter/Makefile.am
index 8064646..8a27f91 100644
--- a/scribo/tests/filter/Makefile.am
+++ b/scribo/tests/filter/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.
#
@@ -19,11 +19,35 @@ include $(top_srcdir)/scribo/tests/tests.mk
check_PROGRAMS = \
objects_with_holes \
+ object_links_bbox_h_ratio \
+ object_links_bbox_w_ratio \
+ object_links_bbox_overlap \
+ object_links_bottom_aligned \
+ object_links_center_aligned \
+ object_links_left_aligned \
+ object_links_right_aligned \
+ object_links_top_aligned \
+ object_groups_mean_width \
+ object_groups_size_ratio \
+ object_groups_small \
+ object_groups_with_holes \
components_small \
components_large
objects_with_holes_SOURCES = objects_with_holes.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
+object_links_bottom_aligned_SOURCES = object_links_bottom_aligned.cc
+object_links_center_aligned_SOURCES = object_links_center_aligned.cc
+object_links_left_aligned_SOURCES = object_links_left_aligned.cc
+object_links_right_aligned_SOURCES = object_links_right_aligned.cc
+object_links_top_aligned_SOURCES = object_links_top_aligned.cc
+object_groups_mean_width_SOURCES = object_groups_mean_width.cc
+object_groups_size_ratio_SOURCES = object_groups_size_ratio.cc
+object_groups_small_SOURCES = object_groups_small.cc
+object_groups_with_holes_SOURCES = object_groups_with_holes.cc
components_small_SOURCES = components_small.cc
components_large_SOURCES = components_large.cc
diff --git a/scribo/tests/filter/object_groups_mean_width.cc b/scribo/tests/filter/object_groups_mean_width.cc
new file mode 100644
index 0000000..81da661
--- /dev/null
+++ b/scribo/tests/filter/object_groups_mean_width.cc
@@ -0,0 +1,72 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/primitive/group/from_single_link.hh>
+#include <scribo/filter/object_groups_mean_width.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/the_valleys.pbm";
+
+ const unsigned ref[] = { 0, 1, 7, 3, 4, 3, 3, 7, 7, 3, 3, 3, 3 };
+ const unsigned filtered_ref[] = { 0, 0, 7, 3, 0, 3, 3, 7, 7, 3, 3, 3, 3 };
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ object_groups<L> groups = primitive::group::from_single_link(links);
+
+ for_all_groups(g, groups)
+ mln_assertion(groups(g) == ref[g]);
+
+ groups = filter::object_groups_mean_width(groups, 10);
+
+ for_all_groups(g, groups)
+ mln_assertion(groups(g) == filtered_ref[g]);
+
+}
diff --git a/scribo/tests/filter/object_groups_size_ratio.cc b/scribo/tests/filter/object_groups_size_ratio.cc
new file mode 100644
index 0000000..90e353a
--- /dev/null
+++ b/scribo/tests/filter/object_groups_size_ratio.cc
@@ -0,0 +1,71 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/primitive/group/from_single_link.hh>
+#include <scribo/filter/object_groups_size_ratio.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/the_valleys.pbm";
+
+ const unsigned ref[] = { 0, 1, 7, 3, 4, 3, 3, 7, 7, 3, 3, 3, 3 };
+ const unsigned filtered_ref[] = { 0, 1, 0, 3, 0, 3, 3, 0, 0, 3, 3, 3, 3 };
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ object_groups<L> groups = primitive::group::from_single_link(links);
+
+ for_all_groups(g, groups)
+ mln_assertion(groups(g) == ref[g]);
+
+ groups = filter::object_groups_size_ratio(groups, 1.2, 0.3);
+
+ for_all_groups(g, groups)
+ mln_assertion(groups(g) == filtered_ref[g]);
+}
diff --git a/scribo/tests/filter/object_groups_small.cc b/scribo/tests/filter/object_groups_small.cc
new file mode 100644
index 0000000..165634d
--- /dev/null
+++ b/scribo/tests/filter/object_groups_small.cc
@@ -0,0 +1,87 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/primitive/group/from_single_link.hh>
+#include <scribo/filter/object_groups_small.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/the_valleys.pbm";
+
+ const unsigned ref[] = { 0, 1, 7, 3, 4, 3, 3, 7, 7, 3, 3, 3, 3 };
+ const unsigned filtered_ref[] = { 0, 0, 0, 3, 0, 3, 3, 0, 0, 3, 3, 3, 3 };
+ const unsigned size_ref[] = { 0, 1, 0, 7, 1, 0, 0, 3, 0, 0, 0, 0, 0 };
+
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ object_groups<L> groups = primitive::group::from_single_link(links);
+
+ for_all_groups(g, groups)
+ mln_assertion(groups(g) == ref[g]);
+
+ {
+ object_groups<L> groups2 = filter::object_groups_small(groups, 4);
+
+ for_all_groups(g, groups2)
+ mln_assertion(groups2(g) == filtered_ref[g]);
+ }
+
+ {
+ mln::util::array<unsigned> group_size;
+ object_groups<L> groups2 = filter::object_groups_small(groups, 4, group_size);
+
+ for_all_groups(g, groups2)
+ {
+ mln_assertion(groups2(g) == filtered_ref[g]);
+ mln_assertion(group_size(g) == size_ref[g]);
+ }
+ }
+
+}
diff --git a/scribo/tests/filter/object_groups_with_holes.cc b/scribo/tests/filter/object_groups_with_holes.cc
new file mode 100644
index 0000000..3b5b29f
--- /dev/null
+++ b/scribo/tests/filter/object_groups_with_holes.cc
@@ -0,0 +1,71 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/primitive/group/from_single_link.hh>
+#include <scribo/filter/object_groups_with_holes.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/the_valleys.pbm";
+
+ const unsigned ref[] = { 0, 1, 7, 3, 4, 3, 3, 7, 7, 3, 3, 3, 3 };
+ const unsigned filtered_ref[] = { 0, 0, 7, 3, 0, 3, 3, 7, 7, 3, 3, 3, 3 };
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ object_groups<L> groups = primitive::group::from_single_link(links);
+
+ for_all_groups(g, groups)
+ mln_assertion(groups(g) == ref[g]);
+
+ groups = filter::object_groups_with_holes(groups, 3);
+
+ for_all_groups(g, groups)
+ mln_assertion(groups(g) == filtered_ref[g]);
+}
diff --git a/scribo/tests/filter/object_links_bbox_h_ratio.cc b/scribo/tests/filter/object_links_bbox_h_ratio.cc
new file mode 100644
index 0000000..38be9b7
--- /dev/null
+++ b/scribo/tests/filter/object_links_bbox_h_ratio.cc
@@ -0,0 +1,69 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/filter/object_links_bbox_h_ratio.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/phillip.pbm";
+
+ const unsigned ref[] = { 0, 2, 6, 8, 3, 4, 6, 9, 2, 4 };
+ const unsigned filtered_ref[] = { 0, 1, 6, 3, 3, 5, 6, 7, 8, 9 };
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == ref[l]);
+
+ links = scribo::filter::object_links_bbox_h_ratio(links, 1.2f);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == filtered_ref[l]);
+}
diff --git a/scribo/tests/filter/object_links_bbox_overlap.cc b/scribo/tests/filter/object_links_bbox_overlap.cc
new file mode 100644
index 0000000..a548e47
--- /dev/null
+++ b/scribo/tests/filter/object_links_bbox_overlap.cc
@@ -0,0 +1,69 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/filter/object_links_bbox_overlap.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/wildly.pbm";
+
+ const unsigned ref[] = { 0, 1, 1 };
+ const unsigned filtered_ref[] = { 0, 1, 2 };
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == ref[l]);
+
+ links = scribo::filter::object_links_bbox_overlap(links, 0.1f);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == filtered_ref[l]);
+}
diff --git a/scribo/tests/filter/object_links_bbox_w_ratio.cc b/scribo/tests/filter/object_links_bbox_w_ratio.cc
new file mode 100644
index 0000000..9ea699a
--- /dev/null
+++ b/scribo/tests/filter/object_links_bbox_w_ratio.cc
@@ -0,0 +1,69 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/filter/object_links_bbox_w_ratio.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/phillip.pbm";
+
+ const unsigned ref[] = { 0, 2, 6, 8, 3, 4, 6, 9, 2, 4 };
+ const unsigned filtered_ref[] = { 0, 1, 6, 8, 3, 5, 6, 7, 8, 4 };
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == ref[l]);
+
+ links = scribo::filter::object_links_bbox_w_ratio(links, 1.2f);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == filtered_ref[l]);
+}
diff --git a/scribo/tests/filter/object_links_bottom_aligned.cc b/scribo/tests/filter/object_links_bottom_aligned.cc
new file mode 100644
index 0000000..2fe783f
--- /dev/null
+++ b/scribo/tests/filter/object_links_bottom_aligned.cc
@@ -0,0 +1,69 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/filter/object_links_bottom_aligned.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/phillip.pbm";
+
+ const unsigned ref[] = { 0, 2, 6, 8, 3, 4, 6, 9, 2, 4 };
+ const unsigned filtered_ref[] = { 0, 1, 6, 3, 3, 5, 6, 7, 2, 9 };
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == ref[l]);
+
+ links = scribo::filter::object_links_bottom_aligned(links, 1.f);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == filtered_ref[l]);
+}
diff --git a/scribo/tests/filter/object_links_center_aligned.cc b/scribo/tests/filter/object_links_center_aligned.cc
new file mode 100644
index 0000000..9c9e8cc
--- /dev/null
+++ b/scribo/tests/filter/object_links_center_aligned.cc
@@ -0,0 +1,70 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/filter/object_links_center_aligned.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/phillip.pbm";
+
+ const unsigned ref[] = { 0, 2, 6, 8, 3, 4, 6, 9, 2, 4 };
+ const unsigned filtered_ref[] = { 0, 1, 2, 3, 3, 5, 6, 7, 8, 9 };
+
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == ref[l]);
+
+ links = scribo::filter::object_links_center_aligned(links, 1.f);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == filtered_ref[l]);
+}
diff --git a/scribo/tests/filter/object_links_left_aligned.cc b/scribo/tests/filter/object_links_left_aligned.cc
new file mode 100644
index 0000000..ce630f5
--- /dev/null
+++ b/scribo/tests/filter/object_links_left_aligned.cc
@@ -0,0 +1,69 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/filter/object_links_left_aligned.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/phillip.pbm";
+
+ const unsigned ref[] = { 0, 2, 6, 8, 3, 4, 6, 9, 2, 4 };
+ const unsigned filtered_ref[] = { 0, 2, 2, 8, 4, 4, 6, 9, 2, 4 };
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == ref[l]);
+
+ links = scribo::filter::object_links_left_aligned(links, 80.f);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == filtered_ref[l]);
+}
diff --git a/scribo/tests/filter/object_links_right_aligned.cc b/scribo/tests/filter/object_links_right_aligned.cc
new file mode 100644
index 0000000..375ef9c
--- /dev/null
+++ b/scribo/tests/filter/object_links_right_aligned.cc
@@ -0,0 +1,69 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/filter/object_links_right_aligned.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/phillip.pbm";
+
+ const unsigned ref[] = { 0, 2, 6, 8, 3, 4, 6, 9, 2, 4 };
+ const unsigned filtered_ref[] = { 0, 2, 2, 8, 4, 4, 6, 9, 2, 4 };
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == ref[l]);
+
+ links = scribo::filter::object_links_right_aligned(links, 80.f);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == filtered_ref[l]);
+}
diff --git a/scribo/tests/filter/object_links_top_aligned.cc b/scribo/tests/filter/object_links_top_aligned.cc
new file mode 100644
index 0000000..fe6730d
--- /dev/null
+++ b/scribo/tests/filter/object_links_top_aligned.cc
@@ -0,0 +1,69 @@
+// 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
+
+#include <iostream>
+
+#include <mln/core/image/image2d.hh>
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/io/pbm/load.hh>
+#include <scribo/core/def/lbl_type.hh>
+#include <scribo/primitive/extract/components.hh>
+#include <scribo/primitive/link/with_single_left_link.hh>
+#include <scribo/filter/object_links_top_aligned.hh>
+
+#include "tests/data.hh"
+
+int main()
+{
+ using namespace scribo;
+ using namespace mln;
+
+ std::string img = SCRIBO_IMG_DIR "/phillip.pbm";
+
+ const unsigned ref[] = { 0, 2, 6, 8, 3, 4, 6, 9, 2, 4 };
+ const unsigned filtered_ref[] = { 0, 1, 2, 3, 3, 4, 6, 7, 8, 9 };
+
+ image2d<bool> input;
+ io::pbm::load(input, img.c_str());
+
+ typedef scribo::def::lbl_type V;
+ typedef image2d<V> L;
+
+ V nbboxes;
+ component_set<L>
+ text = primitive::extract::components(input, c8(), nbboxes);
+
+ object_links<L> links = primitive::link::with_single_left_link(text, 30);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == ref[l]);
+
+ links = scribo::filter::object_links_top_aligned(links, 1.f);
+
+ for_all_links(l, links)
+ mln_assertion(links(l) == filtered_ref[l]);
+}
diff --git a/scribo/tests/img/the_valleys.pbm b/scribo/tests/img/the_valleys.pbm
new file mode 100644
index 0000000000000000000000000000000000000000..3d40b8417a9c971de4352f791618f2c7727e0d92
GIT binary patch
literal 1724
zcmd_oKZ_GV6aerITLe<LtwMAMY_!Q;{vgRA8ALQ#+!=x#wueMK5dWNqAOt3$kaE9)
z@snsmij?^Xmt~8TH_H~8gJGZV?PRk-bLqvYCiB}jdGF0@*9v#p<Ia;uFP?V}*vn&f
z+&%kr`u-iOmdg7ljCH!74_LXfS841uYV2P7sKq{<oWFX*%B9Nv|24Gr?6A$+M~Cd`
z>D%+}*}7-tQn^s6R$1*q0kXBowyRx#<K%K<Z?>EI7s9`)u`sp7mTU2KSsHg#(Oj4#
zqnZzrp9)V=d8#mi9|B}EpQz+UHGY*WAAA7K?c~Q6jR0ak+0dA;V<jw6U88i`$8R?j
zA2C&`1Xb)(ov9M|#IZX~{@2tiZCwX&=|qWHcJmb;^g-tIKG#W&sM4XFziNBb&_z_u
z^=mi8QmEA7$d36Ga;Q~h8Fie1?jfD{n6vJQfrLmJb-h3(>i(L9c(cOzD~MF8)0OgZ
zh_R#*M4D7+?o8=Luo&)>VmxY?s+Xz&qi(PSDa_5>>01;FHv~{t_ywthN`3Y+weZ%t
z<@yUyxmBz;ph$pk*c<F5YIq6ps$P-l{3G6sGZ11=M*?$h_Jn5|k2hgOJV2E*Sg2IZ
zgTojoqe+MlaskUYQOqhZQ#*zMsYD$@l;}fLKTfj3*BO7uUcpegSUJYVmgaf+CD8yT
zqo1gPYYvhWa`QX3Hd7kF{ASY~>mqg+WDihhvS^#xz>!q-dfoyHFb_%H_;aZi6+l^D
mY*F(yDb3aP)f9AojpD@Z(%U=r)=sVN(m0v^>#_pR#Qp{`{Oqm(
literal 0
HcmV?d00001
--
1.5.6.5
1
0
* scribo/core/component_features_data.hh: Fix default
attributes initialization.
* scribo/core/line_info.hh: Compute color and boldness if
available in components data.
* scribo/filter/object_links_left_aligned.hh,
* scribo/filter/object_links_right_aligned.hh: Fix doc.
* scribo/preprocessing/rotate_90.hh: Disable preconditions.
* scribo/primitive/extract/alignments.hh: Avoid a warning.
* src/preprocessing/split_bg_fg.cc: Initialize Magick++.
---
scribo/ChangeLog | 19 +++++++++++++
scribo/scribo/core/component_features_data.hh | 8 +++++
scribo/scribo/core/line_info.hh | 28 +++++++++++--------
scribo/scribo/filter/object_links_left_aligned.hh | 19 ++++++-------
scribo/scribo/filter/object_links_right_aligned.hh | 19 ++++++-------
scribo/scribo/preprocessing/rotate_90.hh | 8 ++++-
scribo/scribo/primitive/extract/alignments.hh | 1 +
scribo/src/preprocessing/split_bg_fg.cc | 6 +++-
8 files changed, 72 insertions(+), 36 deletions(-)
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index d223777..a0d4937 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,24 @@
2011-04-06 Guillaume Lazzara <z(a)lrde.epita.fr>
+ Small fixes in Scribo.
+
+ * scribo/core/component_features_data.hh: Fix default
+ attributes initialization.
+
+ * scribo/core/line_info.hh: Compute color and boldness if
+ available in components data.
+
+ * scribo/filter/object_links_left_aligned.hh,
+ * scribo/filter/object_links_right_aligned.hh: Fix doc.
+
+ * scribo/preprocessing/rotate_90.hh: Disable preconditions.
+
+ * scribo/primitive/extract/alignments.hh: Avoid a warning.
+
+ * src/preprocessing/split_bg_fg.cc: Initialize Magick++.
+
+2011-04-06 Guillaume Lazzara <z(a)lrde.epita.fr>
+
Change test image format to pnm.
* tests/img/alignment_1.png,
diff --git a/scribo/scribo/core/component_features_data.hh b/scribo/scribo/core/component_features_data.hh
index f5ba60f..07b3e4a 100644
--- a/scribo/scribo/core/component_features_data.hh
+++ b/scribo/scribo/core/component_features_data.hh
@@ -38,6 +38,8 @@ namespace scribo
struct component_features_data
{
+ component_features_data();
+
bool valid;
scribo::def::color_type color;
float boldness;
@@ -50,6 +52,12 @@ namespace scribo
# ifndef MLN_INCLUDE_ONLY
+ component_features_data::component_features_data()
+ : valid(false)
+ {
+ }
+
+
inline
std::ostream&
operator<<(std::ostream& ostr, const component_features_data& data)
diff --git a/scribo/scribo/core/line_info.hh b/scribo/scribo/core/line_info.hh
index 73d7856..e925db7 100644
--- a/scribo/scribo/core/line_info.hh
+++ b/scribo/scribo/core/line_info.hh
@@ -1040,19 +1040,23 @@ namespace scribo
// incremented.
++used_comps;
- // Compute boldness
- boldness.take(comp_set(c).features().boldness);
- sum2_boldness += mln::math::sqr<float>(comp_set(c).features().boldness);
-
- // Compute color
- color_red.take(comp_set(c).features().color.red());
- color_green.take(comp_set(c).features().color.green());
- color_blue.take(comp_set(c).features().color.blue());
-
- sum2_red += mln::math::sqr<unsigned>(comp_set(c).features().color.red());
- sum2_green += mln::math::sqr<unsigned>(comp_set(c).features().color.green());
- sum2_blue += mln::math::sqr<unsigned>(comp_set(c).features().color.blue());
+ // COMPUTE FEATURES DATA
+ if (comp_set(c).has_features())
+ {
+ // Compute boldness
+ boldness.take(comp_set(c).features().boldness);
+ sum2_boldness += mln::math::sqr<float>(comp_set(c).features().boldness);
+
+ // Compute color
+ color_red.take(comp_set(c).features().color.red());
+ color_green.take(comp_set(c).features().color.green());
+ color_blue.take(comp_set(c).features().color.blue());
+
+ sum2_red += mln::math::sqr<unsigned>(comp_set(c).features().color.red());
+ sum2_green += mln::math::sqr<unsigned>(comp_set(c).features().color.green());
+ sum2_blue += mln::math::sqr<unsigned>(comp_set(c).features().color.blue());
+ }
// FIXME: we must guaranty here that the relationship is from
// right to left, otherwise, the space size computed between
diff --git a/scribo/scribo/filter/object_links_left_aligned.hh b/scribo/scribo/filter/object_links_left_aligned.hh
index 82d4185..830678a 100644
--- a/scribo/scribo/filter/object_links_left_aligned.hh
+++ b/scribo/scribo/filter/object_links_left_aligned.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.
//
@@ -58,15 +58,14 @@ namespace scribo
\verbatim
- ~
- ~ ^
- ~ |
- ~ |
+
+ ~ ^
+ ~ |
~------ | Alpha
- ~ | | |
- ~ | | |
- ~ | | v
- ------ ~ ~ ~ | ~ ~| ~
+ ~ | | |
+ ~ | | |
+ ~ | | v
+ ~----- ~ ~ ~ | ~ ~| ~
| | | |
| x------------x |
| | | |
diff --git a/scribo/scribo/filter/object_links_right_aligned.hh b/scribo/scribo/filter/object_links_right_aligned.hh
index 07ca309..4f854e2 100644
--- a/scribo/scribo/filter/object_links_right_aligned.hh
+++ b/scribo/scribo/filter/object_links_right_aligned.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.
//
@@ -58,15 +58,14 @@ namespace scribo
\verbatim
+
+
~
- ~ ^
- ~ |
- ~ |
- ~------ | Alpha
- ~ | | |
- ~ | | |
- ~ | | v
- ------ ~ ~ ~ | ~ ~| ~
+ -----~ ^
+ | ~ | | Alpha
+ ~| | |
+ ~ | | v
+ ------ ~ ~ ~ | ~ ~| ~ ~
| | | |
| x------------x |
| | | |
diff --git a/scribo/scribo/preprocessing/rotate_90.hh b/scribo/scribo/preprocessing/rotate_90.hh
index 8db703d..e6d68c8 100644
--- a/scribo/scribo/preprocessing/rotate_90.hh
+++ b/scribo/scribo/preprocessing/rotate_90.hh
@@ -80,8 +80,12 @@ namespace scribo
const I& input = exact(input_);
mln_precondition(input.is_valid());
- mln_precondition(input.domain().pmin().row() > 0);
- mln_precondition(input.domain().pmin().col() > 0);
+
+ // FIXME: Does this routine accept images with an origin
+ // different from (0,0) and with negative coordinates?
+ //
+ // mln_precondition(input.domain().pmin().row() > 0);
+ // mln_precondition(input.domain().pmin().col() > 0);
// Works only on one block images.
mlc_is(mln_trait_image_value_access(I),
diff --git a/scribo/scribo/primitive/extract/alignments.hh b/scribo/scribo/primitive/extract/alignments.hh
index 5314683..4bd925e 100644
--- a/scribo/scribo/primitive/extract/alignments.hh
+++ b/scribo/scribo/primitive/extract/alignments.hh
@@ -559,6 +559,7 @@ namespace scribo
const object_groups<L>& groups = doc.paragraphs().lines().groups();
const line_set<L>& lines = doc.lines();
+ (void) groups; // Avoid warnings when debug is disabled.
// 1. Construct an image of group bounding boxes.
//
diff --git a/scribo/src/preprocessing/split_bg_fg.cc b/scribo/src/preprocessing/split_bg_fg.cc
index 270093c..2b68b53 100644
--- a/scribo/src/preprocessing/split_bg_fg.cc
+++ b/scribo/src/preprocessing/split_bg_fg.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.
//
@@ -49,6 +49,8 @@ int main(int argc, char *argv[])
mln::trace::entering("main");
using namespace mln;
+ Magick::InitializeMagick(0);
+
if (argc != 6)
return scribo::debug::usage(argv,
"Split background and foreground.",
--
1.5.6.5
1
0

03 May '11
* tests/img/alignment_1.png,
* tests/img/alignment_2.png,
* tests/img/alignment_3.png,
* tests/img/alignment_4.png: Rename and convert as...
* tests/img/alignment_1.pbm,
* tests/img/alignment_2.pbm,
* tests/img/alignment_3.pbm,
* tests/img/alignment_4.pbm: ... this.
* tests/primitive/extract/alignments.cc: Update loaded image.
---
scribo/ChangeLog | 16 ++++++++++++++++
scribo/tests/img/alignment_1.pbm | Bin 0 -> 12167 bytes
scribo/tests/img/alignment_1.png | Bin 3145 -> 0 bytes
scribo/tests/img/alignment_2.pbm | Bin 0 -> 12167 bytes
scribo/tests/img/alignment_2.png | Bin 3147 -> 0 bytes
scribo/tests/img/alignment_3.pbm | Bin 0 -> 12167 bytes
scribo/tests/img/alignment_3.png | Bin 3145 -> 0 bytes
scribo/tests/img/alignment_4.pbm | Bin 0 -> 12167 bytes
scribo/tests/img/alignment_4.png | Bin 3155 -> 0 bytes
scribo/tests/primitive/extract/alignments.cc | 3 +--
10 files changed, 17 insertions(+), 2 deletions(-)
create mode 100644 scribo/tests/img/alignment_1.pbm
delete mode 100644 scribo/tests/img/alignment_1.png
create mode 100644 scribo/tests/img/alignment_2.pbm
delete mode 100644 scribo/tests/img/alignment_2.png
create mode 100644 scribo/tests/img/alignment_3.pbm
delete mode 100644 scribo/tests/img/alignment_3.png
create mode 100644 scribo/tests/img/alignment_4.pbm
delete mode 100644 scribo/tests/img/alignment_4.png
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index fa834fa..d223777 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,3 +1,19 @@
+2011-04-06 Guillaume Lazzara <z(a)lrde.epita.fr>
+
+ Change test image format to pnm.
+
+ * tests/img/alignment_1.png,
+ * tests/img/alignment_2.png,
+ * tests/img/alignment_3.png,
+ * tests/img/alignment_4.png: Rename and convert as...
+
+ * tests/img/alignment_1.pbm,
+ * tests/img/alignment_2.pbm,
+ * tests/img/alignment_3.pbm,
+ * tests/img/alignment_4.pbm: ... this.
+
+ * tests/primitive/extract/alignments.cc: Update loaded image.
+
2011-04-05 Guillaume Lazzara <z(a)lrde.epita.fr>
Regen generated files.
diff --git a/scribo/tests/img/alignment_1.pbm b/scribo/tests/img/alignment_1.pbm
new file mode 100644
index 0000000000000000000000000000000000000000..41025b9771ec8522993011725fe9409858ef1bb8
GIT binary patch
literal 12167
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
SC7ApFgiE>0uuB1(A^sa0t#md3
literal 0
HcmV?d00001
diff --git a/scribo/tests/img/alignment_1.png b/scribo/tests/img/alignment_1.png
deleted file mode 100644
index f4592429963506e1c987e2b18b0969ad04a9bde0..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 3145
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
diff --git a/scribo/tests/img/alignment_2.pbm b/scribo/tests/img/alignment_2.pbm
new file mode 100644
index 0000000000000000000000000000000000000000..98a3e78a54a0cb571ee129938b40aeddaa16a864
GIT binary patch
literal 12167
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
R8E-|qxp)-TD6VGm{{|v8XMX?y
literal 0
HcmV?d00001
diff --git a/scribo/tests/img/alignment_2.png b/scribo/tests/img/alignment_2.png
deleted file mode 100644
index 9c828bb44984051f862879836b2904a1176695c2..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 3147
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
diff --git a/scribo/tests/img/alignment_3.pbm b/scribo/tests/img/alignment_3.pbm
new file mode 100644
index 0000000000000000000000000000000000000000..89ec9782af0d7d02754c1fc66721cc647f53082c
GIT binary patch
literal 12167
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
PVa4sRhPq)Pe{bw}0KjK|
literal 0
HcmV?d00001
diff --git a/scribo/tests/img/alignment_3.png b/scribo/tests/img/alignment_3.png
deleted file mode 100644
index 67c20308a0693278a5c16af4f4f9f014f30a2f8b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 3145
zcmV-P47T%$P)<h;3K|Lk000e1NJLTq00FcB007tl00000s}3uE00002VoOIv00G^K
z6QlqD010qNS#tmY07w7;07w8v$!k6U000Sga6xAP00FcB007tkdmpID000XHNkl<Z
zXx{DGO>g7I835oJkD>9w;P@6FSkga1w=xJLx~Cl5pP-y_OPJ<R1~IHrltw|3?p}K+
zvilQyEzrX#YO5R;yFKQ#7<GYefdZQ`+yjj;n!fLll4Vj~t0HND?(D*%DLtCue7+o-
zknbm%{TM6ubhG{3!S{6e|HtWwkoNAMzFZt7gk<>Y@51CAm*Z=&lk*mbyvncRemmdD
z=v~*o&F`h0OUHY<GO3M@t#fp<<9j+WcPB^SXuiqi;%dfBlHhSzcnJv=;Wsv!B!m3y
z;cK%C5|J}u8<+Hyt3pIcA~C-|dp((HLd}KX$(MODOYDr!sD09KoHNz-kHy=nte3Gg
zZ9;V1;!~Af#&O(X6UN87?mR|SJM#FbbfT|x1k;&+2xrnP&f<U%zNDju^I2b?q0^-s
zj1;&uKIBqo-7L)lK__&inojC&#(Cd4CEz15xX!V2X*Uf#Iw4ILGX+kRj!Qwu=pyIR
zuIU<Z&sD$SIzpNA4||+c<)q_(U4VP{6dlz+(?LL=<<0g>tV&hSa=LI9=IZ#}6Zoc-
zDW-zZPAzTJOXrYe()5?|!>{(=LYFb=oVLw!;>Acq=FY3!q>YYCE&~{VNwXcmy)Lu4
z>sKxqo}m*5;s70=u5t-kcCO0B;33wF$DC`CsggApkn@mav~mMzKYff&W4#2e;2`ke
zOPekvGJr;`+*nX$z;UXuoN+5|1%kXo-qv*|aBc{^svTZ{%iE=c{t|hy7jQX|-D%yy
z3h5D1gkv0}v`B^~(aweAn_1S$IsqFGwQ7tbT988m<96Z(cix-i0xIT!%4BS3_^;Qu
z%&H#QobE2Ppk_T85kXGLtMn@U&lBJIlb3gX6MH!d3+L_e=Hx9qxu=7fYahqt>f=>u
zIs9U4j<@Bv+zMRo?u1-@yxJX+vlHTBjf8mu(|qbe&aj|nv$O0bC-wmxCq+*@nHHoc
z(YbpMaRgClNb-w@qodKc1({lReS$x}ehRArULVKK^Lox_q^G`X+`o8Cs!PDnOGopL
z2P-}v2iO#65pNN#lSVapunt@R{=0LWnhs|&UAQiUC{(2#)^x-<Q{`vk%1NI=al5BJ
z8%S`f=^z-ZoP=d5=Dc(<1`+8Ix-4!wK}-gPHB~vCGkEMi8^g;62SL-4v$=D)Bql26
z6P2%ZYT+VqWqw%B&?I(Z^O&x7RI939&=In5B7d{X^)J@t=bCzvpCpTVAw`T`JZ=O`
zm2>MbKY-;uuG~S{4xQ39@(WyiLqBnD;L7RRn138N=fyaLS6rWSWq05oyK+L{49A~(
zcrrZ<2vbKZ`U~_=aPI5ar>-5S2FLETuzt{u)M?X&5Rhddvv{5rqOUK6ywP~f@4>0;
zSkS(1C&L<pv1aKk8DW#bjYjf3w~4}g0-96YGq^m(airgT+;kB*Wy^b$pZ1MBUao@o
z44sig#*J}XCTiDhNSu>1;-bct2}TXsl-4;>-5#z>>%P_7;@0PPx|ef9d=E#h-smpZ
zy<s;py7}!_>Ay}~RR;;z@DuI<cH40i3paczSMJR~f`tfQqT3QI(%C8(%@2%U{>mDz
zyOX1Px*sY(9k0tz@0TB4?F_V-F(fno1q3(|y;68nNDKa4mvbXGs|x#i!JZGU3;II)
zGxCv&_#eTs$85;Ixhlj&!5ye2J$9AX+#6r8f%?^-*uebcJ_I<ChdqdM&S>$5$G_G^
zGWa)rHfXqx$NTC6f*TWJfD`%HUp(Ql5k^1;e(!6h)Ccv6zQg$l0w0KC0t|Q}x5@?r
zA}@40!hIcxHhx)muq^jg2CKD3mql^u0&oyHsTlWlU}?RK8jewL9_BZgyAHUiM(39<
zZLCMJfR=X7=lqjAs-h(St}_i62@=lT(B@G@@3;V+xNovXew^H>)C<6|;crNkt#Y>M
zAbgmD6D~hVqIlWS%H0m{We1_SD<^Xfug?cJauG@3^K}l|p~{cVT|ffoDAWtMw^lmA
zbma!>XzsjQ*Vhxd+I^K`KZ8%G(>TC>Cg?rrCNYr67ZTjbD)-xeW`Yl(d-P2^F8lS6
zql1z}K`8Y=wHvMw!<#%0>C>VkRBv?2Zwzkcuo*YAaPV~+zM*Yj>-unjm}i_{L*SDa
zHq>F?(y75<iR3)D;rNLb*QQ78ps{cs;tirCD-#=!B}C;u+)BLMf{S&#Poh7t+qaxQ
zv~z3l9!cc;xugBqtK^<q%+ujZxcR{eM82V#KUt~5>S(Q7?4YFNTE?1Hz~12-95)PR
z)3#ULXM9t>Tx@m1z%&bsb-63U?#mtXqu{0*CfmxDXHT~}n8wKa=v+o@NVcSf%bbfV
zC%3wJo27!huYw(1C_|k*;P;K4yAPau=-ylJKGac`Btxdl=aELhnIPkCf==LSTgNba
zx0*GoyIIWN8B!h1^SeR!SKM6Ud8lUzxXZ@&JcLM@502nHJ320%dxSN`9!62;J*ed1
zxoo%y++;}1l1no7h`D-6Bk$+XKfOWSG3OLSHkD)jTwi7o5IU!Q@dRU)#&aNuc_0*#
z;Lb`%$9eWNV^46I;^^+7V-5G0oD}$&DD9Sx6j|~`#wQxz?CSvQClZZ?g4u$jBhh)~
zPO?L&m&vT=&g$hPp}*uZd@3DW@UdNX9v$0;i+ut8Gb&4B@IUrX+*{o2pgzI*vMMLI
zIOd;;%6V&D9xpqn`xM*}yX)%pLe-Iphqwb)Ij%GY)yH&;)2HTQhz}^|Zlv<I&piTN
z1lxU$gZ_16htPi24tvJO(m;49$UwPvU*~$d7xr|hE0@FEaHY8~`f&frv45fqnl6-n
zD=~>Kcglqw;I=Hw#SGTkM8#<cXD>o#$eJ{FvW;8e>gbed62kcFX+mvpxYjim1t=QL
z8SX99?>_o<x`v5Q8mAQ5A&JlTbe}cKDR*)w^)uTZ2$YF8F1)pqgIcepmsoXQr5{u3
zR<FH0F}G*cdLT6ZT*StuGv$M+)teh-^6~523n$cTnlMvtMXn$45T-b{*lxkq2J%a`
zx~T4ONTg!B!x~8!jwyVoo>ddwhU;^Y!Ln;Dxo+*l+=TZf%NMS@qw}Niffm1CIK6OG
z;}FcM2X?AnIG_JP2k^}&^jpltk}Fky1I0h2nCI7+ZQ35w2IIz=dzJt{u1&eO2#yt4
zpCPg0O6GTSOg*O3h!cFegx_h%KQcCOZ<$kJ;e80&t+=MP=7e(+H+$TNNfGn1RN%OT
za%vqg#n@eEI8!FWKhRN?%Y9xhKlzD(hzZ_EJt`S(IGOgW^p;$;+~sWng?Em@bXs%x
zEDl^ibyl{6iM_J-XKmbZ!aUK*rb|IAIe+fX;M3?be$bd;7mnzvoF)#-UH-UB_^qow
zNJ*j2mL2p@|JEgNZQSPkQ&>D@&0&=3TvX-fSI?dU1-&3c$6$5rHQHUKG5*ob-054#
zpFufc^s>+Av&e1#JO&p~;?1wGTgD!cLIv%R;^R5?h_q@m3~tC7!HHKz=IE-SBcv$n
zlhCioL2V?7TudPZC5!%YuA!HAOBXl?0m9eN?spwi;S?WX^SM*t<hgSs6u-m%atim6
zOx|@1KPdZkPnixY|9S|5J@GeiaEoBz`&T<^%e|mvnD?stOb9P;OeSQm(P_2Pk*svb
zEVtDS?E#@zSF=PV3a*XS+I>r+FI?qz_uQ-K?t@?w+SAqMq}|<1)yZwT+0$L`$JRf_
z+md$r+ut8m`VVG)5?nh!KLh{(0AzYpSaf4=ZEa<4bN~Q&+EZ{2a&-*x53*8-2vZ14
zEh^5;&r>kgGc-t4PzXvbvr;fL(l@fyw=_}E2=H@PC`c?x&QLJaGt%Tz7@|7eJ$(Zd
z0{nax+%j`YQj6fm80r~v0RXDKN0CN7B&h%Z03~!qSaf7zbY(hYa%Ew3WdJfTF)=ML
zG%YYOR53U@H8MIiHY+ePIxsL9#v084001R)MObuXVRU6WZEs|0W_bWIFflPLFf=VN
jF;p=)IyEvnH8v|SGCD9YXq%#O00000NkvXXu0mjf_0SJx
diff --git a/scribo/tests/img/alignment_4.pbm b/scribo/tests/img/alignment_4.pbm
new file mode 100644
index 0000000000000000000000000000000000000000..27eea1e317f882e0a39dcb83ac10330458ba2eee
GIT binary patch
literal 12167
zcmeI2&yVEBb;sFevJ_-xDCd_a$YN@QT<nXqBMHW=G3c@(*g_6g1R*ed^U8(*3kZ^x
zA_QY-J=K#5L>~gt27JiDP#^pU82JaZ-C<z*U}&yB7+KN`JU$4TqWZ8k*&0^9uUKR^
z+1)$ifi1v^YO%v&)%(<|-}U&aUwrn_zuJE8r7wN)Z~x|{&u@S2tJ`0F<@JAj^&kFz
z`<W+qZoIs`{n9J{^!e?br=NQI=2JJH-TusrFFe0}`{g&j`K|4pC!hZ8?=|}TUw`Gr
z?H6D8%J!FE{ku0_dA)7;&XYTjKKkr4pWA+V=W~ymD+RfR3fH8&<TaS~uY{?qVdlY=
zv^^X$W=-4vuVUO(7d5ThC0anfVn*_YQ>=+~pPimm0-06V!A`sK?ybS@(YxaK18pU}
z*r3d1FhR1sgPmBs|EHr(yWB->566xd%ey?5uEUDOj5gj>axIp(qKlDbtP0R2l@V4-
znQT$IjXO+w=5EFH=0N=TT|e8I+_*L{W+LRS_K24^xUT{aL+V0{O#&4t&)fBepPcMS
z%>rZIX4&TXgaor2!y7uBs^@x`IhxMOMD6YE?QXI3@J#a<0~i)RHMUFA?`bV~Qnp>(
zv@J>>B)PgXygp<sot|qEnu^D1=CG-%I8SG)uPsf>RH_5E#ol3Yey%wKmL?912)i2<
zcCHiJXiJaFR4Kv67Sjrt=4mQTLQ+v)R%{%vz)q(Tuy7oXp-rnH2bKV{;}+$Tutakz
z1fkVd`H|We(Y?q3tM=ak_QyxSY?s8P{Bw3MoY*C@h($6OdHW(krC3~={T~AJ{056k
z>9J!r@i;LEt+p(zv7#dEwO<HiQYD?X`={Xt?0i|<gd0>cH@U@jepX|u!Hfw%pyr}5
z3XQht5Q}^NP5A(AhqizJwLoT)s{2kG7X7c5vC}j@vlvDcU1EM@XK>oVurc+u0V|DC
zZ>n!&><~QyrcIQ}S<|+PNpa8aQ45tkjmG6zs&DI?dPrD`*{4kiEFr15oN2DasTA~v
z;M#T)kMrYP?d#nNSZ*;h1g4HiDk?D*lsRd7qQPW%d{`uEf4Di^yyi~?X)_}}$o8;+
zOyy7Rct#oJQyCN-FWJPGLAL2Vf`9&mpU|;5sRolRTA6rq-<%swW!T`bN=VFjV4GgM
zf+>I9q1x8AF6FX+KgO1N>f<i?jk~1X^}FOnJ=x7y%+cNCrpWtE1gGLWj!lZWjEk`o
zCrJ<0#4p0my4oI&+3m`SEXFU;Hfu`7b1~lE@D{`<x@c7_4#5T5?4In3T8uvw3#?V4
zg<&{Ohh$-#L0;UgmU>ceg;G>uMIEbri)Bm)qlvsuXPUefR@ZzP_O+=$2q#Pps%s=u
z+P0;!dhU^_v{s@FV*QB#CkaZm!Hrukc^sdWFb9k&l%Q^i6Qzbhjg=AX0d`#Uu>z(+
zTEdDLw-_r*#e>pndk_~&<80_-39zEj2Lf24ai|&tOmnBrgcd88Fq}_>8D>nXxy5o|
zQQb~pFvG%i7^mYB)(5pMbeJ9PFjo}|<qB=#bgDTHeiK=&@&>gwi=l0+uv8Dd0n82>
zTU3%nfmKw=8cSAUsMyyJ%@2ki<Q-;8!s-Dq$I(g*6~h?oJv!Wg*{KZktks6GUsXvm
z!hr44xFz~cTQTaAR6ekA%&S!xG{D}`yM{3Jrqx#1O3rNj{KT3$N}x6r+~g|M5JsB%
z4vkx(&PWtCE>-e@#Y|CSgix4cB0a9L@=Vc20ECA7tlkt7V9Rr|%s7uuq{iozQMvtC
ze=E#1i?aH|J@e%OgTW_I9tEdO{U;yM!r5oAegb?!z=-FQ8ykbs&>9t9Fxd@cm?7H+
zhw1A)<0>%T*rn6sXXZ4>ImIe31O^L)(HnxpYTNCuxn~oX{%FtYoTqoNd2%mSFQtXV
z7847JOHtj{%4M|}Uy@x&(DOa3PQ4sPCRrymub5m93#^_itw@6@)+VirjMns|?4|cu
z*)bRA<JJ?q=(VZjMRL9T|F@V5&4l{mu=Tr4UDrIcOIj@BPTZD)_#$@K<+YEgsW@J6
z@VF$}Gi+GI#xAXWBo4zzcpYph6n!X`UJd)5YKvV9+DD^}{1P^-Y+KaIyU2xY8<XI`
zO6&0OPzUx?EydO-g}yE}m<dCS;s&=OO;1*VFPX%*VK|-Wz&k7pl|bMpPgMLrn0icn
zH`2bJA8E37Y^VnSSP`B3z~26%`fx9pmDZy&fuRz|^1p>&=qFfGsBd&J{U_`=fqO$A
zaG#8rL|LO?ig#E!QUoQenKV~D=Yal878eF?lmmW5SPD$gxe5%%kbp&k(`Sk-TgA3*
zyY#UQH_9CxhM0n>x?N*KSUJFAi8DB*Q@Mi~%~*{I!lDQmBoa+n4sD9C*$OP|U|LYY
zg#8h8)L3F<Ut7hzj4BC7i`7<RX*GnElRFIFvi>Xq=B>iE`2XZ^9EED%u%CoWmBjj!
zHI^=F(?ggmqz&W2(j%A--7cxI+*T51MT$xa+cscU8)0N|EMet6II8Zz2|*bA6%7V!
z)M+DYY6;7aF-uy^G~;HHrjkc;1t#+2IMW9lERPChT_aOC^+XB`lgn%+t1zCN#@Rk5
z7X~LP%975(iPD@0D949aZR=mMWo=;`W#K>H5L?e>`{6+O)}wN5pguie6Seoq^YUQh
z+91=-7Up&?>1Uy2zGp_s<o3n@-ygI!2Jp3IVs!hl4Olb&h7Xgb+a=Oh=<=d4MMCRy
zRX8=~{&|zJxKG-&byNe?&m}DF=C67CZDCie<VTAguIYqsa=%L|`tG<m^f5TuFB6uq
z%RtROc{$kyso!^P?JA)*wFXVyz<Jx;uH^P>EK}^lp}X=x*YR53{heb@-kG=h8S}5a
z2n};`_Tz6(E?~8UtgQmQZ#YW}cb=R2!*(UcZE9B72TmrOzqx7V+FEUuu=29TO6;@|
zux8I$i&+S@sWIB1BUZLIHy2^GC*S0H&tj}8%{|>?-X%?Xqit8DOg24vexbHZpnxCe
zdRHV3CPOcmxA;idJSxA@rf*_^^_eLv>-+KZYE!%g%<kR_W(V$@w)k6&7=#9sn_GdA
zyW!}7EH8OBB`kE6tneY7e;+QDaXqMWEdTBSZZQ9CIOIP9R_YqdUE6p}*a2hB=G$m1
zevLUPe_p}r0XEY~^*DbkY_YJ+VM%TAK9**LO|*a!1xzK?CVM+58f`FkES4aXLZb~I
z|J<ky7@ddrC9oH^{M2E0f#GzlSRYFXGceF1(q<0Pwp+vN%?cRfBQw`w^@ly|ZWmJn
zVBe{jaN7QhlePh_pQucPx{}gQ)uJ}(PxU5zdbsqlQZ;s4z&TQf)mX-8PSRN0;WqC<
zn}pT422-wWSp|#}Q)_!)zypPG>tXO4*-AE8QI)_L24fK$h8JKb8fEh^o;oZu(Dpvx
zFiXQ-YpaL5I8~(<xHOt$IWV>%qE45zm8^$5Ih9Ho78X<2lXS(HXV*er_fRtcgdK3W
zfhtbkgTs?dzh<JaEZn9b%}9(Mzs*K<Q|Fj3?;uHv>*W1AR9@v9cvbzZ-nq_jU*+A@
z%gc0-Js#jPi;WK8{X41-%rTjUyv1CbnnyF0|Lm843M^wYGPcqtw|RDs14?YLM!-Ig
zJ8yV7&w44B!}O)FlU~`Tx)iqPl&?#;-79EiCC_?7#(b)mvJzYBICFFvtS7VLb)~iy
za=oOFv0jI?Q+Z+XdP9#nvGJ04li+$XvhVohg^PWT9op1UBfIc)SiMOrhUcM}H2H^y
zBl?(V-sjH_CTWYoX47QH7TdTGqiu1vgz=`B`O|5OZ3-ORbXVP+4<@#|>pOfd-ozX>
zw(k}@>thvfgy+u-urVYph7B;w#GnBkR*xI}4cKl>JsOaR@y}uUmKohT*b{iC;IMl4
zu$9Em6DR{Z(ooXCd>FunJ%jh-34OpR3&CRVg(sfGl^CoS?K=z(BR|Xt%Y4Lfl!Qmh
z4oHhlf*CNl=na!zMN<XiP*i^r8;X8_`y?1uoF&MoECj19nc;#%z*$r=d|=nu2@b~4
zVig=T_#p@|XG=9$Ix{CIDMfN24Xh{xcrp;7DEu-wsBp28l=hu+t+wMC>>k2UaE9Md
zP)4d2F#LweW2Dq(wOC;>x;-g!Gh~ECa14a?0I*02V7L|QV%P>B;1@g=Gv?g}6AKus
zN)6~}!|h3fVfO!!leUp}2ioek#W?C#U|C?!a@?GueK<>7Ow>~uzly1`yw{TxX3h)3
zDux06sUU4e-9aU-Hf}Lknmr85UKd!P)Fp=tSUks0H89l1mgoi`Ix`xJEH4cWz(m+Q
z1!H5}w9VXCf)yGQwGMXN#t%%D;!caS$>wKfvc-6$Oo?8Ei%)d0nW>(Ke~K^+k<A)o
z^2YaSZI~r6CWF!ZqrBEum@23QX35qqS_c&F<-m>|hHnnIRYw9XsY8w7HN?#sw0*%{
zCYqBY#c@vzDO3Ud)eROqJ|gVxFsm4T5m$fCVEt_k>=&uTk8!$?>q%OTV>97!5d{IZ
zE(-ARZeYnGNT2qKZm`<Ok1)o6siJP~+PAv>$R4j1S(P!z_^Rx6X)X31_4TDgi+yl4
e>{nOAW>>=$yL7`K*tSiJD`CG0w4m*y!TuK@ymU4I
literal 0
HcmV?d00001
diff --git a/scribo/tests/img/alignment_4.png b/scribo/tests/img/alignment_4.png
deleted file mode 100644
index df3d395baa7c9f0fa905c6e7d941b36ee3ab705e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 3155
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
diff --git a/scribo/tests/primitive/extract/alignments.cc b/scribo/tests/primitive/extract/alignments.cc
index 7bd82fb..a069149 100644
--- a/scribo/tests/primitive/extract/alignments.cc
+++ b/scribo/tests/primitive/extract/alignments.cc
@@ -47,7 +47,7 @@ int main()
for (unsigned i = 1; i < 5; ++i)
{
std::ostringstream os;
- os << SCRIBO_IMG_DIR << "/alignment_" << i << ".png";
+ os << SCRIBO_IMG_DIR << "/alignment_" << i << ".pbm";
document<L> doc(os.str().c_str());
doc.open();
@@ -67,7 +67,6 @@ int main()
os_out << SCRIBO_TESTS_DIR << "/primitive/extract/alignment_"
<< i << ".ref.pbm";
- io::pbm::save(res.second(), "res.pbm");
io::pbm::load(ref, os_out.str().c_str());
mln_assertion(ref == res.second());
--
1.5.6.5
1
0