
* mln/util/tree_of_shapes.hh: Remove methods related to compressed tree. * mln/util/ztree_of_shapes.hh: New. --- milena/ChangeLog | 9 + milena/mln/util/tree_of_shapes.hh | 44 +- milena/mln/util/ztree_of_shapes.hh | 788 ++++++++++++++++++++++++++++++++++++ 3 files changed, 818 insertions(+), 23 deletions(-) create mode 100644 milena/mln/util/ztree_of_shapes.hh diff --git a/milena/ChangeLog b/milena/ChangeLog index 8d00301..521a626 100644 --- a/milena/ChangeLog +++ b/milena/ChangeLog @@ -1,5 +1,14 @@ 2013-02-11 Guillaume Lazzara <z@lrde.epita.fr> + Add a new structure for compressed tree. + + * mln/util/tree_of_shapes.hh: Remove methods related to compressed + tree. + + * mln/util/ztree_of_shapes.hh: New. + +2013-02-11 Guillaume Lazzara <z@lrde.epita.fr> + Small fixes. * mln/util/map.hh: Fix prototype of element(). diff --git a/milena/mln/util/tree_of_shapes.hh b/milena/mln/util/tree_of_shapes.hh index 9bf978f..1444fb1 100644 --- a/milena/mln/util/tree_of_shapes.hh +++ b/milena/mln/util/tree_of_shapes.hh @@ -30,6 +30,10 @@ #ifndef MLN_UTIL_TREE_OF_SHAPES_HH # define MLN_UTIL_TREE_OF_SHAPES_HH +# include <vector> +# include <mln/value/rgb8.hh> +# include <mln/util/map.hh> +# include <mln/core/alias/box2d.hh> namespace mln { @@ -37,41 +41,30 @@ namespace mln namespace util { - enum Tags { None, Spurious, Noise }; - template <typename I> struct tree_of_shapes { typedef mln_site(I) P; typedef mln_value(I) V; typedef mln_equiv(V) EV; + typedef mln_ch_value(I,P) parent_t; I Fb; // Default canonicalization. std::vector<P> R; - mln_ch_value(I,P) parent; - - // 0-canonicalization. - std::vector<P> R0; - mln_ch_value(I,P) parent0; - // Attributes/tags for 0-representants. - std::vector<Tags> tag; - - // 01-canonicalization. - std::vector<P> R01; - mln_ch_value(I,P) parent01; - + parent_t parent; V level(const P& p) const; bool level_changes_at(unsigned i) const; + bool is_root(const P& p) const; + bool is_not_root(const P& p) const; + bool is_representative(const P& p) const; + bool is_not_representative(const P& p) const; bool is_0_representative(const P& p) const; - - bool is_01_representative(const P& p) const; - }; @@ -104,6 +97,13 @@ namespace mln template <typename I> bool + tree_of_shapes<I>::is_not_root(const P& p) const + { + return ! is_root(p); + } + + template <typename I> + bool tree_of_shapes<I>::is_representative(const P& p) const { return is_root(p) || Fb(parent(p)) != Fb(p); @@ -111,18 +111,16 @@ namespace mln template <typename I> bool - tree_of_shapes<I>::is_0_representative(const P& p) const + tree_of_shapes<I>::is_not_representative(const P& p) const { - return is_representative(p) && (Fb(p) == V(0)); + return ! is_not_representative(p); } template <typename I> bool - tree_of_shapes<I>::is_01_representative(const P& p) const + tree_of_shapes<I>::is_0_representative(const P& p) const { - return is_root(p) - || (Fb(p) == V(0) && Fb(parent(p)) != V(0)) - || (Fb(p) != V(0) && Fb(parent(p)) == V(0)); + return (is_root(p) || Fb(parent(p)) != Fb(p)) && (Fb(p) == V(0)); } diff --git a/milena/mln/util/ztree_of_shapes.hh b/milena/mln/util/ztree_of_shapes.hh new file mode 100644 index 0000000..641f0fc --- /dev/null +++ b/milena/mln/util/ztree_of_shapes.hh @@ -0,0 +1,788 @@ +// Copyright (C) 2012 EPITA Research and Development Laboratory (LRDE) +// +// This file is part of Olena. +// +// Olena is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation, version 2 of the License. +// +// Olena is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Olena. If not, see <http://www.gnu.org/licenses/>. +// +// As a special exception, you may use this file as part of a free +// software project without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to produce +// an executable, this file does not by itself cause the resulting +// executable to be covered by the GNU General Public License. This +// exception does not however invalidate any other reasons why the +// executable file might be covered by the GNU General Public License. + +/// \file +/// +/// \brief Tree of shapes data structure. + +#ifndef MLN_UTIL_ZTREE_OF_SHAPES_HH +# define MLN_UTIL_ZTREE_OF_SHAPES_HH + +# include <vector> +# include <mln/value/rgb8.hh> +# include <mln/util/map.hh> +# include <mln/core/image/image2d.hh> +# include <mln/util/tree_of_shapes.hh> +# include <mln/data/transform.hh> +# include <mln/border/resize.hh> + +# include <mln/debug/println.hh> +# include <mln/util/object_id.hh> + +namespace mln +{ + + // Type used to refer to a pixel in one of the ztree images. + struct face_id {}; + typedef unsigned face_id_t; + + + namespace util + { + enum Hole { Not_hole, Is_hole, Contains_hole }; + enum Tags { None = 0, Spurious, Noise, Object, Background }; + + struct attrib_t + { + attrib_t() + { + reset(); + } + + void reset() + { + tag = None; + own_area = 0; + area = 0; + nchild = 0; + color = literal::black; + is_leaf = true; + own_area_ratio = 0; + rectangularity = 0; + elongation = 0; + d_color_q = 0; + d_color_gp = 0; + dlap = 0; + max_abs_lap = 0; + hole = Not_hole; + bg_score = 0; + fg_score = 0; + is_valid = false; + } + + // It is stored on any representatives. + Tags tag; + + // Corresponds to the number of pixels of the same level as the + // related representative. + // It is stored on any representatives. + unsigned own_area; + + // Corresponds to the number of pixels of all the sub-shapes and + // at the same level. + // It is stored on any representatives. + unsigned area; + + // The bounding box. + // It is stored on 0-representatives ONLY. + box2d bbox; + + // The mean color of the shape. It relies only on values from + // 2-faces owned by this shape. + // It is stored on any representatives. + value::rgb8 color; + + // // The weighted mean color of the shape. It relies only on + // // values from 2-faces owned by this shape. It is stored on any + // // representatives. + // value::rgb8 wcolor; + + // The number of children in the tree. + // It is stored on any representatives. + unsigned nchild; + + // Is it a representative of a leaf ?. + // It is stored on any representatives. + bool is_leaf; + + // The ratio own_area / area. + // It is stored on 0-representatives ONLY. + float own_area_ratio; + + // It is stored on 0-representatives ONLY. + float rectangularity; + + // It is stored on 0-representatives ONLY. + float elongation; + + // The maximum color distance between the non-0-representative + // children and the non-0-representative parent colors. + // It is stored on 0-representatives ONLY. + // + // @ + // | + // o gp + // | + // @ + // | + // o + // @ --> d_color_gp = max(d_color_gp, l1::distance(p.color, gp.color)) + // | + // o p + unsigned d_color_q; + + // The maximum color distance between the non-0-representative + // children and the non-0-representative grandparent colors. + // It is stored on 0-representatives ONLY. + // + // @ + // | + // o gp + // | + // @ + // | + // o + // @ --> d_color_gp = max(d_color_gp, l1::distance(p.color, gp.color)) + // | + // o p + unsigned d_color_gp; + + // Maximum distance between the non-0-representative children + // and the non-0-representative parent laplacian values. + // It is stored on 0-representatives ONLY. + unsigned dlap; + + // Maximum absolute value in laplacian image for the current + // shape. + // It is stored on any representatives. + unsigned max_abs_lap; + + // Set whether this shape contains a hole or is a hole. + // It is stored on any representatives. + Hole hole; + + // The higher is this score, the higher is the probability that + // this shape is a background. It is based on the hypothesis + // that interesting objects are leaves (excepted holes). This + // score take into account the number of holes and leaves + // included in that shape. + // It is stored on non-0-representatives ONLY. + unsigned bg_score; + + // The higher is this score, the higher is the probability that + // this shape is foreground. + // It is stored on non-0-representatives ONLY. + unsigned fg_score; + + bool is_valid; + }; + + bool operator==(const attrib_t& l, const attrib_t& r) + { + return l.tag == r.tag + && l.own_area == r.own_area + && l.area == r.area + && l.bbox == r.bbox + && l.color == r.color + && l.nchild == r.nchild + && l.is_leaf == r.is_leaf + && l.own_area_ratio == r.own_area_ratio + && l.rectangularity == r.rectangularity + && l.elongation == r.elongation + && l.d_color_q == r.d_color_q + && l.d_color_gp == r.d_color_gp + && l.dlap == r.dlap + && l.max_abs_lap == r.max_abs_lap + && l.hole == r.hole + && l.bg_score == r.bg_score + && l.fg_score == r.fg_score + && l.is_valid == r.is_valid; + } + + bool operator!=(const attrib_t& l, const attrib_t& r) + { + return ! (l == r); + } + + template <typename I> + class ztree_of_shapes + { + public: + typedef mln_site(I) P; + typedef mln_value(I) V; + typedef mln_equiv(V) EV; + typedef mln_ch_value(I,face_id_t) parent_t; // FIXME: could be + // replaced by an + // array (direct + // access from + // face_id_t). + typedef I Fb_t; + + // all pixels, from root to leaf + std::vector<face_id_t> R; + // 0-representatives only, from root to leaf + std::vector<face_id_t> R0; + // representative only, from root to leaf + std::vector<face_id_t> R01; + + ztree_of_shapes(); + ztree_of_shapes(const util::tree_of_shapes<I>& tree, + const I& lap_k1); + + V level(const P& p) const; + + bool is_root(const P& p) const; + bool is_root(const face_id_t& pi) const; + + bool is_representative(const P& p) const; + bool is_representative(const face_id_t& pi) const; + + bool is_0_representative(const P& p) const; + bool is_0_representative(const face_id_t& p) const; + + bool is_non_0_representative(const P& p) const; + bool is_non_0_representative(const face_id_t& p) const; + + bool is_compressed() const; + bool is_valid() const; + + + const face_id_t& parent(const face_id_t& pi) const; + P parentp(const P& pi) const; + + const V& Fb(const face_id_t& pi) const; + const V& Fb(const P& pi) const; + const V& lap(const face_id_t& pi) const; + const V& lap(const P& pi) const; + + const box2d& parent_domain() const; + const parent_t& parent_image() const; + parent_t& parent_image_(); + + const box2d& Fb_domain() const; + const I& Fb_image() const; + I& Fb_image_(); + + I& lap_image(); + const I& lap_image() const; + + void trim(const util::array<face_id_t>& f, + const std::vector<face_id_t>& R0, + const std::vector<face_id_t>& R01, + const util::array<bool>& is_removed); + + // Debug + bool is_01_canonicalized() const; + + face_id_t offset_of(const point2d& p) const; + point2d face_at(const face_id_t& pi) const; + + unsigned nfaces() const; + + private: + + Fb_t Fb_; + // compressed parent image. + parent_t parent_; + I lap_k1_; + + void compress_tree(); + bool compressed_; + }; + + +# ifndef MLN_INCLUDE_ONLY + + namespace internal + { + + template <typename V> + bool have_same_sign(const V& v1, const V& v2) + { + // FIXME: Remove when Fb is flat. + mln_precondition(v1.is_degenerated() && v2.is_degenerated()); + if (!v1.is_degenerated() || !v2.is_degenerated()) + { + std::cout << "OUUUUPPPSSS" << std::endl; + abort(); + } + + return (v1.first() == 0 && v2.first() == 0) || (v1.first() > 0 && v2.first() > 0) || (v1.first() < 0 && v2.first() < 0); + } + + + template <typename I, typename P> + bool + is_01_representative_in_parent01(util::ztree_of_shapes<I>& t, const P& p) + { + typedef mln_value(I) V; + return t.is_root(p) + || (t.Fb(p) == V(0) && t.Fb(t.parent(p)) != V(0)) + || (t.Fb(p) != V(0) && t.Fb(t.parent(p)) == V(0)); + } + + template <typename I> + struct point_to_offset_t : Function_v2v<point_to_offset_t<I> > + { + typedef face_id_t result; + typedef point2d argument; + + point_to_offset_t(const I& Fb) : Fb_(Fb) {} + + result operator()(const argument& v) const + { + return face_id_t(Fb_.offset_of_point(v)); + } + + I Fb_; + }; + + } + + template <typename I> + ztree_of_shapes<I>::ztree_of_shapes() + : compressed_(false) + { + } + + template <typename I> + ztree_of_shapes<I>::ztree_of_shapes(const util::tree_of_shapes<I>& tree, + const I& lap_k1) + { + lap_k1_ = duplicate(lap_k1); + border::resize(lap_k1_, 1); + + // Make sure that underlying images have same border (site + // offsets are therefore equivalent). + Fb_ = duplicate(tree.Fb); + border::resize(Fb_, 1); + + internal::point_to_offset_t<I> point_to_offset(Fb_); + parent_ = data::transform(tree.parent, point_to_offset); + border::resize(parent_, 1); + + // Check image sizes. + mln_assertion(Fb_.domain() == tree.parent.domain()); + mln_assertion(Fb_.border() == parent_.border()); + mln_assertion(Fb_.domain() == parent_.domain()); + +# ifndef RELEASE + if (Fb_.domain() != tree.parent.domain() + || Fb_.border() != parent_.border() + || Fb_.domain() != parent_.domain()) + abort(); + + //Check + { + mln_piter(image2d<face_id_t>) p(parent_domain()); + for_all(p) + if (parent(offset_of(p)) >= nfaces()) + { + std::cout << "Invalid parent id!" << std::endl; + abort(); + } + } + + + // debug::println(Fb_); + // debug::println(parent_); +# endif // ! RELEASE + + for (int i = 0; i < tree.R.size(); ++i) + R.push_back(offset_of(tree.R[i])); + + // Initialize R0, 0-representative faces and R01 representative + // pixels in compressed tree. + for (unsigned i = 0; i < R.size(); ++i) + { + face_id_t pi = R[i]; + if (is_representative(pi)) + { + R01.push_back(pi); + if (is_0_representative(pi)) + R0.push_back(pi); + } + } + + compress_tree(); + } + + template <typename I> + face_id_t + ztree_of_shapes<I>::offset_of(const point2d& p) const + { + return Fb_.offset_of_point(p); + } + + template <typename I> + point2d + ztree_of_shapes<I>::face_at(const face_id_t& pi) const + { + return Fb_.point_at_offset(pi); + } + + template <typename I> + unsigned + ztree_of_shapes<I>::nfaces() const + { + return Fb_.nelements(); + } + + template <typename I> + typename ztree_of_shapes<I>::V + ztree_of_shapes<I>::level(const P& p) const + { + return Fb(p); + } + + template <typename I> + bool + ztree_of_shapes<I>::is_root(const face_id_t& pi) const + { + return parent(pi) == pi; + } + + template <typename I> + bool + ztree_of_shapes<I>::is_root(const P& p) const + { + return is_root(offset_of(p)); + } + + template <typename I> + bool + ztree_of_shapes<I>::is_0_representative(const P& p) const + { + return is_0_representative(offset_of(p)); + } + + template <typename I> + bool + ztree_of_shapes<I>::is_0_representative(const face_id_t& pi) const + { + return (is_root(pi) || Fb(parent(pi)) != Fb(pi)) && (Fb(pi) == V(0)); + } + + template <typename I> + bool + ztree_of_shapes<I>::is_non_0_representative(const P& p) const + { + return is_non_0_representative(offset_of(p)); + } + + template <typename I> + bool + ztree_of_shapes<I>::is_non_0_representative(const face_id_t& pi) const + { + return is_representative(pi) && ! is_0_representative(pi); + } + + template <typename I> + bool + ztree_of_shapes<I>::is_representative(const P& p) const + { + return is_representative(offset_of(p)); + } + + template <typename I> + bool + ztree_of_shapes<I>::is_representative(const face_id_t& pi) const + { + return is_root(pi) + || (Fb(pi) == V(0) && Fb(parent(pi)) != V(0)) + || (Fb(pi) != V(0) && Fb(parent(pi)) == V(0)); + } + + template <typename I> + bool + ztree_of_shapes<I>::is_valid() const + { + return is_compressed(); + } + + template <typename I> + bool + ztree_of_shapes<I>::is_compressed() const + { + mln_precondition(is_01_canonicalized()); + +# ifndef RELEASE + // Precondition + if (!is_01_canonicalized()) + abort(); +# endif // ! RELEASE + + return compressed_; + } + + + template <typename I> + const face_id_t& + ztree_of_shapes<I>::parent(const face_id_t& pi) const + { + return parent_.element(pi); + } + + template <typename I> + typename ztree_of_shapes<I>::P + ztree_of_shapes<I>::parentp(const P& p) const + { + return face_at(parent(offset_of(p))); + } + + template <typename I> + const typename ztree_of_shapes<I>::V& + ztree_of_shapes<I>::Fb(const face_id_t& pi) const + { + return Fb_.element(pi); + } + + template <typename I> + const typename ztree_of_shapes<I>::V& + ztree_of_shapes<I>::Fb(const P& p) const + { + return Fb(offset_of(p)); + } + + template <typename I> + const typename ztree_of_shapes<I>::V& + ztree_of_shapes<I>::lap(const face_id_t& pi) const + { + return lap_k1_.element(pi); + } + + template <typename I> + const typename ztree_of_shapes<I>::V& + ztree_of_shapes<I>::lap(const P& p) const + { + return lap(offset_of(p)); + } + + template <typename I> + const box2d& + ztree_of_shapes<I>::parent_domain() const + { + return parent_.domain(); + } + + template <typename I> + const typename ztree_of_shapes<I>::parent_t& + ztree_of_shapes<I>::parent_image() const + { + return parent_; + } + + template <typename I> + typename ztree_of_shapes<I>::parent_t& + ztree_of_shapes<I>::parent_image_() + { + return parent_; + } + + template <typename I> + const box2d& + ztree_of_shapes<I>::Fb_domain() const + { + return Fb_.domain(); + } + + template <typename I> + const I& + ztree_of_shapes<I>::Fb_image() const + { + return Fb_; + } + + template <typename I> + I& + ztree_of_shapes<I>::Fb_image_() + { + return Fb_; + } + + template <typename I> + I& + ztree_of_shapes<I>::lap_image() + { + return lap_k1_; + } + + template <typename I> + const I& + ztree_of_shapes<I>::lap_image() const + { + return lap_k1_; + } + + template <typename I> + void + ztree_of_shapes<I>::compress_tree() + { + typedef mln_site(I) P; + + unsigned N = parent_.nsites(); + + // // DEBUG + // { + // image2d<bool> imap0(parent.domain()); + // data::fill(imap0, false); + // for (unsigned i = 0; i < R0.size(); ++i) + // imap0(R0[i]) = true; + + // io::magick::save(imap0, "debug_imap0.pbm"); + // std::cout << "0-node count: " << R0.size() << std::endl; + // } + // { + // image2d<value::rgb8> imap01(parent.domain()); + // data::fill(imap01, literal::black); + // for (unsigned i = 0; i < R01.size(); ++i) + // if (is_0_zrepresentative(t, R01[i])) + // imap01(R01[i]) = literal::blue; + // else + // imap01(R01[i]) = literal::red; + + // io::magick::save(imap01, "debug_imap01.ppm"); + // std::cout << "representative count: " << R01.size() << std::endl; + // } + + + // Canonicalization + typedef mln_value(I) V; + for (unsigned i = 0; i < N; ++i) + { + face_id_t p = R[i]; // p goes from root to leaves + face_id_t q = parent(p); + if (! is_representative(q)) + parent_.element(p) = parent(q); + } + + compressed_ = true; + +# ifndef RELEASE + // Check + for (unsigned i = 0; i < N; ++i) + { + face_id_t p = R[i]; // p goes from root to leaves + face_id_t q = parent(p); + if (! is_representative(q)) + { + std::cerr << "OOOOOOOOOOOO" << std::endl; + std::abort(); + } + } + + + // Check + unsigned j = 0; + for (unsigned i = 0; i < N; ++i) + if (internal::is_01_representative_in_parent01(*this, R[i])) + if (R01[j++] != R[i]) + { + std::cout << "Oups 2" << std::endl; + std::abort(); + } +# endif // ! RELEASE + + } + + + + template <typename I> + bool + ztree_of_shapes<I>::is_01_canonicalized() const + { + mln_precondition(parent.is_valid()); + + // Check if parent relationship is ok. + mln_piter(image2d<point2d>) p(parent_domain()); + for_all(p) + { + face_id_t pi = offset_of(p); + if (! is_representative(parent(pi))) + { + std::cout << "Parent " << parent(pi) + << " is not 01-representative!" << std::endl; +# ifndef RELEASE + std::abort(); +# endif // ! RELEASE + return false; + } + + if (is_representative(pi)) + { + if (! is_root(pi)) + if (internal::have_same_sign(Fb(pi), Fb(parent(pi)))) + { + std::cout << "A repr and its parent have same sign!" << std::endl; +# ifndef RELEASE + std::abort(); +# endif // ! RELEASE + return false; + } + } + else // p is not a repr + if (! internal::have_same_sign(Fb(pi), Fb(parent(pi)))) + { + std::cout << "a regular p and its repr have not the same sign!" << std::endl; +# ifndef RELEASE + std::abort(); +# endif // ! RELEASE + return false; + } + } + return true; + } + + template <typename I> + void + ztree_of_shapes<I>::trim(const util::array<face_id_t>& f, + const std::vector<face_id_t>& R0_, + const std::vector<face_id_t>& R01_, + const util::array<bool>& is_removed) + { + // We may merge shapes and faces having a different sign value + // in Fb. It would break the tree properties for recognizing + // representative among all other faces. To fix this issue, we + // update Fb so that all faces have the same value as their + // parent. + // + // Warning: we must update Fb before parent_ in order to + // preserve the validity of is_representative() which is based + // on the difference of levels between a pixel and its parent to + // decide whether it is a representative or not. Faces parts of + // 0-representative nodes may not be updated if this order of + // updates is not preserved. + for (int i = R.size() - 1; i >= 0; --i) // leaves to root + { + face_id_t p = R[i]; + if (! is_representative(p) || is_removed(p)) + Fb_.element(p) = Fb(f(parent(p))); + } + + // Adjust parent data. + parent_ = data::transform(parent_, f); + + // Update representative arrays. + R0 = R0_; + R01 = R01_; + } + +# endif // ! MLN_INCLUDE_ONLY + + } // end of namespace mln::util + +} // end of namespace mln + +#endif // ! MLN_UTIL_ZTREE_OF_SHAPES_HH -- 1.7.2.5