cleanup-2008 2283: Add some morphological reconstruction algorithms in sandbox.

https://svn.lrde.epita.fr/svn/oln/branches/cleanup-2008/milena Index: ChangeLog from Thierry Geraud <thierry.geraud@lrde.epita.fr> Add some morphological reconstruction algorithms in sandbox. * sandbox/geraud/Rd: New directory. * sandbox/geraud/Rd/hybrid.hh, * sandbox/geraud/Rd/queue_based.hh, * sandbox/geraud/Rd/parallel.cc, * sandbox/geraud/Rd/parallel_wo.cc, * sandbox/geraud/Rd/union_find.hh, * sandbox/geraud/Rd/parallel.hh, * sandbox/geraud/Rd/diff.cc, * sandbox/geraud/Rd/sequential_bench.cc, * sandbox/geraud/Rd/sequential.cc, * sandbox/geraud/Rd/utils.hh, * sandbox/geraud/Rd/deco.cc, * sandbox/geraud/Rd/hybrid.cc, * sandbox/geraud/Rd/queue_based.cc, * sandbox/geraud/Rd/sequential_bench.hh, * sandbox/geraud/Rd/min.cc, * sandbox/geraud/Rd/sequential.hh, * sandbox/geraud/Rd/debase.union_find.hh, * sandbox/geraud/Rd/union_find.cc, * sandbox/geraud/Rd/svg.queue_based.hh: New files. debase.union_find.hh | 165 +++++++++++++++++++++++++++++++ deco.cc | 71 +++++++++++++ diff.cc | 28 +++++ hybrid.cc | 51 +++++++++ hybrid.hh | 123 +++++++++++++++++++++++ min.cc | 42 +++++++ parallel.cc | 51 +++++++++ parallel.hh | 93 +++++++++++++++++ parallel_wo.cc | 46 ++++++++ queue_based.cc | 49 +++++++++ queue_based.hh | 129 ++++++++++++++++++++++++ sequential.cc | 51 +++++++++ sequential.hh | 96 ++++++++++++++++++ sequential_bench.cc | 51 +++++++++ sequential_bench.hh | 100 +++++++++++++++++++ svg.queue_based.hh | 118 ++++++++++++++++++++++ union_find.cc | 50 +++++++++ union_find.hh | 173 ++++++++++++++++++++++++++++++++ utils.hh | 268 +++++++++++++++++++++++++++++++++++++++++++++++++++ 19 files changed, 1755 insertions(+) Index: sandbox/geraud/Rd/hybrid.hh --- sandbox/geraud/Rd/hybrid.hh (revision 0) +++ sandbox/geraud/Rd/hybrid.hh (revision 0) @@ -0,0 +1,123 @@ +// Copyright (C) 2007 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this library; see the file COPYING. If not, write to +// the Free Software Foundation, 51 Franklin Street, Fifth Floor, +// Boston, MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to +// produce an executable, this file does not by itself cause the +// resulting executable to be covered by the GNU General Public +// License. This exception does not however invalidate any other +// reasons why the executable file might be covered by the GNU General +// Public License. + +#ifndef MLN_MORPHO_RD_HYBRID_HH +# define MLN_MORPHO_RD_HYBRID_HH + +# include <queue> +# include "utils.hh" + + +namespace mln +{ + + namespace morpho + { + + namespace Rd + { + + + template <typename I, typename N> + I hybrid(const I& f, const I& g, const N& nbh, + bool echo = false) + { + typedef mln_point(I) point; + std::queue<point> q; + + f.name_it("f"); + g.name_it("g"); + + // initialisation + I o(f.domain()); + o.name_it("o"); + level::paste(f, o); + // WAS: I o = clone(f); + + unsigned n_init_pushs = 0, n_body_pushs = 0, n_pops = 0; + + // sequence + { + mln_bkd_piter(I) p(f.domain()); + for_all(p) + o(p) = min( max_Nminus(o, p,nbh), g(p) ); + } + { + mln_fwd_piter(I) p(f.domain()); + mln_niter(N) n(nbh, p); + for_all(p) + { + o(p) = min( max_Nplus(o, p,nbh), g(p) ); + for_all(n) if (f.has(n) and n < p) // N+ + if (o(n) < o(p) and o(n) < g(n)) + { + q.push(p); + ++n_init_pushs; + } + } + } + + // propagation + { + point p; + mln_niter(N) n(nbh, p); + while (not q.empty()) + { + p = q.front(); + if (echo) std::cout << std::endl << "pop " << p << " :"; + q.pop(); + ++n_pops; + for_all(n) if (f.has(n)) + if (o(n) < o(p) and o(n) != g(n)) + { + o(n) = min(o(p), g(n)); + if (echo) std::cout << " push " << n; + q.push(n); + ++n_body_pushs; + } + } + if (echo) std::cout << std::endl; + } + + + std::cout << "n_init_pushs = " << n_init_pushs << std::endl + << "n_body_pushs = " << n_body_pushs << std::endl + << "n_pops = " << n_pops << std::endl; + + print_counts(); + + return o; + } + + } // end of namespace mln::morpho::Rd + + } // end of namespace mln::morpho + +} // end of namespace mln + + +#endif // ! MLN_MORPHO_RD_HYBRID_HH Index: sandbox/geraud/Rd/queue_based.hh --- sandbox/geraud/Rd/queue_based.hh (revision 0) +++ sandbox/geraud/Rd/queue_based.hh (revision 0) @@ -0,0 +1,129 @@ +// Copyright (C) 2007 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this library; see the file COPYING. If not, write to +// the Free Software Foundation, 51 Franklin Street, Fifth Floor, +// Boston, MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to +// produce an executable, this file does not by itself cause the +// resulting executable to be covered by the GNU General Public +// License. This exception does not however invalidate any other +// reasons why the executable file might be covered by the GNU General +// Public License. + +#ifndef MLN_MORPHO_RD_QUEUE_BASED_HH +# define MLN_MORPHO_RD_QUEUE_BASED_HH + +# include <queue> +# include "utils.hh" + + +namespace mln +{ + + namespace morpho + { + + namespace Rd + { + + template <typename I, typename N> + I queue_based(const I& f, const I& g, const N& nbh, + bool echo = false) + { + + mln_ch_value(I, bool) que(f.domain()); + level::fill(que, false); + + + if (echo) std::cout << std::endl; + + f.name_it("f"); + g.name_it("g"); + + typedef mln_point(I) point; + std::queue<point> q; + I o; + o.name_it("o"); + + unsigned n_init_pushs = 0, n_body_pushs = 0, n_pops = 0; + + // initialisation + { + o = regional_maxima(f, nbh); + // p in M <=> o(p) != 0 + if (echo) debug::println(o); + + mln_piter(I) p(f.domain()); + mln_niter(N) n(nbh, p); + + for_all(p) if (o(p) != 0) // p in M + for_all(n) if (f.has(n) and o(n) == 0) // n not in M + { + q.push(p); + que(p) = true; + ++n_init_pushs; + break; + } + } + + // propagation + { + point p; + mln_niter(N) n(nbh, p); + while (not q.empty()) + { + p = q.front(); + if (echo) std::cout << std::endl << "pop " << p << " :"; + q.pop(); + que(p) = false; + ++n_pops; + for_all(n) if (f.has(n)) + { + if (o(n) < o(p) and o(n) != g(n)) + { + o(n) = min(o(p), g(n)); + if (echo) std::cout << " push " << n; + if (que(n) == false) + { + q.push(n); + que(n) = true; + ++n_body_pushs; + } + } + } + } + if (echo) std::cout << std::endl; + } + + std::cout << "n_init_pushs = " << n_init_pushs << std::endl + << "n_body_pushs = " << n_body_pushs << std::endl + << "n_pops = " << n_pops << std::endl; + + print_counts(); + + return o; + } + + } // end of namespace mln::morpho::Rd + + } // end of namespace mln::morpho + +} // end of namespace mln + + +#endif // ! MLN_MORPHO_RD_QUEUE_BASED_HH Index: sandbox/geraud/Rd/parallel.cc --- sandbox/geraud/Rd/parallel.cc (revision 0) +++ sandbox/geraud/Rd/parallel.cc (revision 0) @@ -0,0 +1,51 @@ +#include <iostream> + +#include <mln/core/image2d.hh> +#include <mln/core/neighb2d.hh> +#include <mln/value/int_u8.hh> + +#include <mln/debug/println.hh> +#include <mln/io/pgm/load.hh> +#include <mln/io/pgm/save.hh> + +#include "parallel.hh" + + +void usage(char* argv[]) +{ + std::cerr << "usage: " << argv[0] << " f.pgm g.pgm c output.pgm" << std::endl + << "reconstruction by dilation (parallel version; may 2007)" << std::endl + << "f = marker (to be dilated)" << std::endl + << "g = mask (constraint >= f)" << std::endl + << "c: 4 or 8" << std::endl; + exit(1); +} + + +int main(int argc, char* argv[]) +{ + if (argc != 5) + usage(argv); + + using namespace mln; + using value::int_u8; + + typedef image2d<int_u8> I; + + int c = atoi(argv[3]); + if (c != 4 and c != 8) + usage(argv); + + I f = io::pgm::load<int_u8>(argv[1]); + I g = io::pgm::load<int_u8>(argv[2]); + + if (not (f <= g)) + { + std::cerr << "pb" << std::endl; + return 1; + } + + io::pgm::save(morpho::Rd::parallel(f, g, + (c == 4 ? c4() : c8())), + argv[4]); +} Index: sandbox/geraud/Rd/parallel_wo.cc --- sandbox/geraud/Rd/parallel_wo.cc (revision 0) +++ sandbox/geraud/Rd/parallel_wo.cc (revision 0) @@ -0,0 +1,46 @@ +#include <iostream> + +#include <mln/core/image2d.hh> +#include <mln/core/neighb2d.hh> +#include <mln/value/int_u8.hh> + +#include <mln/debug/println.hh> +#include <mln/io/pgm/load.hh> +#include <mln/io/pgm/save.hh> + +#include "parallel.hh" + + +void usage(char* argv[]) +{ + std::cerr << "usage: " << argv[0] << " f.pgm g.pgm c output.pgm" << std::endl + << "reconstruction by dilation (parallel version; may 2007)" << std::endl + << "f = marker (to be dilated)" << std::endl + << "g = mask (constraint >= f)" << std::endl + << "c: 4 or 8" << std::endl; + exit(1); +} + + +int main(int argc, char* argv[]) +{ + if (argc != 5) + usage(argv); + + using namespace mln; + using value::int_u8; + + typedef image2d<int_u8> I; + + int c = atoi(argv[3]); + if (c != 4 and c != 8) + usage(argv); + + I f = io::pgm::load<int_u8>(argv[1]); + I g = io::pgm::load<int_u8>(argv[2]); + + io::pgm::save(morpho::Rd::parallel(f, g, + (c == 4 ? c4() : c8()), + false), + argv[4]); +} Index: sandbox/geraud/Rd/union_find.hh --- sandbox/geraud/Rd/union_find.hh (revision 0) +++ sandbox/geraud/Rd/union_find.hh (revision 0) @@ -0,0 +1,173 @@ +// Copyright (C) 2007 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this library; see the file COPYING. If not, write to +// the Free Software Foundation, 51 Franklin Street, Fifth Floor, +// Boston, MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to +// produce an executable, this file does not by itself cause the +// resulting executable to be covered by the GNU General Public +// License. This exception does not however invalidate any other +// reasons why the executable file might be covered by the GNU General +// Public License. + +#ifndef MLN_MORPHO_RD_UNION_FIND_HH +# define MLN_MORPHO_RD_UNION_FIND_HH + +# include "utils.hh" + + +namespace mln +{ + + namespace morpho + { + + namespace Rd + { + + + template <typename I, typename N> + struct union_find_t + { + typedef mln_point(I) point; + typedef mln_value(I) value; + + // in: + const I f, g; + N nbh; + + // out: + I o; + + // aux: + std::vector<point> S; + // was: I data; + mln_ch_value(I, bool) isproc; + mln_ch_value(I, point) parent; + + union_find_t(const I& f, const I& g, const N& nbh) + : f(f), g(g), nbh(nbh) + { + f.name_it("f"); + g.name_it("g"); + initialize(o, f); + o.name_it("o"); + initialize(parent, f); + parent.name_it("parent"); + initialize(isproc, f); + isproc.name_it("isproc"); + // was: initialize(data, f); + + // init + + level::fill(isproc, false); + S = histo_reverse_sort(g); + level::paste(f, o); // new: replace make_set(p) { data(p) = f(p) } + + // first pass + + for (unsigned i = 0; i < S.size(); ++i) + { + point p = S[i]; + + make_set(p); + mln_niter(N) n(nbh, p); + for_all(n) + { + if (f.has(n)) + mln_invariant(isproc(n) == is_proc(n, p)); + if (f.has(n) and isproc(n)) + do_union(n, p); + } + isproc(p) = true; + } + + // second pass + + for (int i = S.size() - 1; i >= 0; --i) + { + point p = S[i]; + if (parent(p) == p) + if (o(p) == mln_max(value)) + o(p) = g(p); + else + o(p) = o(parent(p)); + } + + print_counts(); + + } + + bool is_proc(const point& n, const point& p) const + { + return g(n) > g(p) or (g(n) == g(p) and n < p); + } + + void make_set(const point& p) + { + parent(p) = p; + // was: data(p) = f(p); + // now: in "initialization" + } + + point find_root(const point& x) + { + if (parent(x) == x) + return x; + else + return parent(x) = find_root(parent(x)); + } + + void do_union(const point& n, const point& p) + { + point r = find_root(n); + if (r != p) + { + // NEW: o replaces data + + if (g(r) == g(p) or g(p) >= o(r)) // equiv test + { + parent(r) = p; + if (o(r) > o(p)) + o(p) = o(r); // increasing criterion + } + else + o(p) = mln_max(value); + } + } + + }; + + + template <typename I, typename N> + I union_find(const I& f, const I& g, const N& nbh) + { + mln_precondition(f <= g); + union_find_t<I, N> run(f, g, nbh); + return run.o; + } + + + } // end of namespace mln::morpho::Rd + + } // end of namespace mln::morpho + +} // end of namespace mln + + +#endif // ! MLN_MORPHO_RD_UNION_FIND_HH Index: sandbox/geraud/Rd/parallel.hh --- sandbox/geraud/Rd/parallel.hh (revision 0) +++ sandbox/geraud/Rd/parallel.hh (revision 0) @@ -0,0 +1,93 @@ +// Copyright (C) 2007 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this library; see the file COPYING. If not, write to +// the Free Software Foundation, 51 Franklin Street, Fifth Floor, +// Boston, MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to +// produce an executable, this file does not by itself cause the +// resulting executable to be covered by the GNU General Public +// License. This exception does not however invalidate any other +// reasons why the executable file might be covered by the GNU General +// Public License. + +#ifndef MLN_MORPHO_RD_PARALLEL_HH +# define MLN_MORPHO_RD_PARALLEL_HH + +# include "utils.hh" + + +namespace mln +{ + + namespace morpho + { + + namespace Rd + { + + + template <typename I, typename N> + I parallel(const I& f, const I& g, const N& nbh, + bool test = true) + { +// if (test) +// mln_precondition(f <= g); + + f.name_it("f"); + g.name_it("g"); + + I o_(f.domain()); + o_.name_it("o_"); + mln_piter(I) p(f.domain()); + + // initialisation + I o(f.domain()); + o.name_it("o"); + level::paste(f, o); + // WAS: I o = clone(f); + + bool stability; + do + { + level::paste(o, o_); // memorisation + + // opere + for_all(p) + o(p) = max_N(o_, p, nbh); + // conditionne + for_all(p) + o(p) = min(o(p), g(p)); + + stability = (o == o_); + } + while (not stability); + + print_counts(); + + // mln_postcondition(o <= g); + return o; + } + + } // end of namespace mln::morpho::Rd + + } // end of namespace mln::morpho + +} // end of namespace mln + + +#endif // ! MLN_MORPHO_RD_PARALLEL_HH Index: sandbox/geraud/Rd/diff.cc --- sandbox/geraud/Rd/diff.cc (revision 0) +++ sandbox/geraud/Rd/diff.cc (revision 0) @@ -0,0 +1,28 @@ +#include <oln/core/2d/image2d.hh> +#include <oln/io/load_pgm.hh> +#include <oln/level/compare.hh> + + +void usage(char* argv[]) +{ + std::cerr << "usage: " << argv[0] << " 1.pgm 2.pgm" << std::endl + << "(may 2007)" << std::endl; + exit(1); +} + + +int main(int argc, char* argv[]) +{ + if (argc != 3) + usage(argv); + + using namespace oln; + typedef image2d<unsigned char> I; + + I ima1 = io::load_pgm(argv[1]); + I ima2 = io::load_pgm(argv[2]); + + if (ima1 != ima2) + std::cout << "images differ" << std::endl; + return 0; +} Index: sandbox/geraud/Rd/sequential_bench.cc --- sandbox/geraud/Rd/sequential_bench.cc (revision 0) +++ sandbox/geraud/Rd/sequential_bench.cc (revision 0) @@ -0,0 +1,51 @@ +#include <mln/core/image2d.hh> +#include <mln/core/neighb2d.hh> + +#include <mln/debug/println.hh> +#include <mln/io/pgm/load.hh> +#include <mln/io/pgm/save.hh> + +#include "sequential_bench.hh" + + +void usage(char* argv[]) +{ + std::cerr << "usage: " << argv[0] << " f.pgm g.pgm c output.pgm" << std::endl + << "reconstruction by dilation (sequential version; may 2007)" << std::endl + << "f = marker (to be dilated)" << std::endl + << "g = mask (constraint >= f)" << std::endl + << "c: 4 or 8" << std::endl; + exit(1); +} + + +int main(int argc, char* argv[]) +{ + if (argc != 5) + usage(argv); + + using namespace mln; + using value::int_u8; + + typedef image2d<int_u8> I; + + int c = atoi(argv[3]); + if (c != 4 and c != 8) + usage(argv); + + I f = io::pgm::load<int_u8>(argv[1]); + f.name_it("main.f"); + + I g = io::pgm::load<int_u8>(argv[2]); + g.name_it("main.g"); + + if (not (f <= g)) + { + std::cerr << "pb" << std::endl; + return 1; + } + + io::pgm::save(morpho::Rd::sequential(f, g, + (c == 4 ? c4() : c8())), + argv[4]); +} Index: sandbox/geraud/Rd/sequential.cc --- sandbox/geraud/Rd/sequential.cc (revision 0) +++ sandbox/geraud/Rd/sequential.cc (revision 0) @@ -0,0 +1,51 @@ +#include <mln/core/image2d.hh> +#include <mln/core/neighb2d.hh> + +#include <mln/debug/println.hh> +#include <mln/io/pgm/load.hh> +#include <mln/io/pgm/save.hh> + +#include "sequential.hh" + + +void usage(char* argv[]) +{ + std::cerr << "usage: " << argv[0] << " f.pgm g.pgm c output.pgm" << std::endl + << "reconstruction by dilation (sequential version; may 2007)" << std::endl + << "f = marker (to be dilated)" << std::endl + << "g = mask (constraint >= f)" << std::endl + << "c: 4 or 8" << std::endl; + exit(1); +} + + +int main(int argc, char* argv[]) +{ + if (argc != 5) + usage(argv); + + using namespace mln; + using value::int_u8; + + typedef image2d<int_u8> I; + + int c = atoi(argv[3]); + if (c != 4 and c != 8) + usage(argv); + + I f = io::pgm::load<int_u8>(argv[1]); + f.name_it("main.f"); + + I g = io::pgm::load<int_u8>(argv[2]); + g.name_it("main.g"); + + if (not (f <= g)) + { + std::cerr << "pb" << std::endl; + return 1; + } + + io::pgm::save(morpho::Rd::sequential(f, g, + (c == 4 ? c4() : c8())), + argv[4]); +} Index: sandbox/geraud/Rd/utils.hh --- sandbox/geraud/Rd/utils.hh (revision 0) +++ sandbox/geraud/Rd/utils.hh (revision 0) @@ -0,0 +1,268 @@ +// Copyright (C) 2007 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this library; see the file COPYING. If not, write to +// the Free Software Foundation, 51 Franklin Street, Fifth Floor, +// Boston, MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to +// produce an executable, this file does not by itself cause the +// resulting executable to be covered by the GNU General Public +// License. This exception does not however invalidate any other +// reasons why the executable file might be covered by the GNU General +// Public License. + +#ifndef MLN_MORPHO_RD_UTILS_HH +# define MLN_MORPHO_RD_UTILS_HH + +# include <vector> + +# include <mln/core/concept/image.hh> +# include <mln/core/clone.hh> + +# include <mln/level/fill.hh> +# include <mln/level/paste.hh> +# include <mln/level/compare.hh> + + + +namespace mln +{ + + namespace morpho + { + + namespace Rd + { + + + template <typename T> + T min(T v1, T v2) + { + return v1 < v2 ? v1 : v2; + } + + + template <typename I> + I minimun(const I& ima1, const I& ima2) + { + mln_precondition(ima1.has_data() and ima2.has_data()); + mln_precondition(ima1.domain() == ima2.domain()); + I out(ima1.domain()); + mln_piter(I) p(ima1.domain()); + for_all(p) + out(p) = ima1(p) < ima2(p) ? ima1(p) : ima2(p); + return out; + } + + + template <typename I, typename P, typename N> + mln_value(I) max_N(const I& ima, const P& p, const N& nbh) + { + mln_value(I) v = ima(p); + mln_niter(N) n(nbh, p); + for_all(n) + if (ima.has(n) and ima(n) > v) + v = ima(n); + return v; + } + + + template <typename I, typename P, typename N> + mln_value(I) max_Nminus(const I& ima, const P& p, const N& nbh) + { + mln_value(I) v = ima(p); + mln_niter(N) n(nbh, p); + for_all(n) + if (ima.has(n) and n > p and ima(n) > v) + v = ima(n); + return v; + } + + template <typename I, typename P, typename N> + mln_value(I) max_Nplus(const I& ima, const P& p, const N& nbh) + { + mln_value(I) v = ima(p); + mln_niter(N) n(nbh, p); + for_all(n) + if (ima.has(n) and n < p and ima(n) > v) + v = ima(n); + return v; + } + + + template <typename I> + std::vector<unsigned> compute_histo(const I& ima) + { + std::vector<unsigned> h(256, 0); + mln_piter(I) p(ima.domain()); + for_all(p) + ++h[ima(p)]; + return h; + } + + +// template <typename I> +// std::vector<mln_point(I)> histo_sort(const I& ima) +// { +// std::vector<unsigned> h = compute_histo(ima); +// // preparing output data +// std::vector<int> loc(256); +// loc[0] = 0; +// for (int l = 1; l < 256; ++l) +// loc[l] = loc[l-1] + h[l-1]; +// std::vector<mln_point(I)> vec(ima.points().npoints()); +// // storing output data +// mln_piter(I) p(ima.points()); +// for_all(p) +// vec[loc[ima(p)]++] = p; +// return vec; +// } + + + template <typename I> + std::vector<mln_point(I)> histo_reverse_sort(const I& ima) + { + std::vector<unsigned> h = compute_histo(ima); + // preparing output data + std::vector<int> loc(256); + loc[255] = 0; + for (int l = 254; l >= 0; --l) + loc[l] = loc[l+1] + h[l+1]; + std::vector<mln_point(I)> vec(ima.domain().npoints()); + // storing output data + mln_piter(I) p(ima.domain()); + for_all(p) + vec[loc[ima(p)]++] = p; + return vec; + } + + + + template <typename I, typename N> + struct regional_maxima_t + { + typedef mln_point(I) point; + typedef mln_ch_value(I, bool) image_bool; + typedef mln_ch_value(I, point) image_point; + + // in: + I f; + N nbh; + + // out: + I o; + + // aux: + std::vector<point> S; + image_bool is_proc; + image_bool attr; + image_point parent; + + regional_maxima_t(const I& f, const N& nbh) + : f(f), nbh(nbh) + { + f.name_it("rm.f"); + initialize(o, f); + o.name_it("rm.o"); + initialize(parent, f); + parent.name_it("rm.parent"); + initialize(attr, f); + attr.name_it("rm.attr"); + initialize(is_proc, f); + is_proc.name_it("rm.is_proc"); + + // init + + level::fill(is_proc, false); + S = histo_reverse_sort(f); + + // first pass + + for (unsigned i = 0; i < S.size(); ++i) + { + point p = S[i]; + + make_set(p); + mln_niter(N) n(nbh, p); + for_all(n) + if (f.has(n) and is_proc(n)) + { + if (f(n) == f(p)) + do_union(n, p); + else // f(n) > f(p) + attr(p) = false; + } + is_proc(p) = true; + } + + // second pass + + const mln_value(I) zero = 0; + for (int i = S.size() - 1; i >= 0; --i) + { + point p = S[i]; + if (parent(p) == p) + o(p) = attr(p) ? f(p) : zero; + else + o(p) = o(parent(p)); + } + } + + void make_set(const point& p) + { + parent(p) = p; + attr(p) = true; + } + + point find_root(const point& x) + { + if (parent(x) == x) + return x; + else + return parent(x) = find_root(parent(x)); + } + + void do_union(const point& n, const point& p) + { + point r = find_root(n); + if (r != p) + { + parent(r) = p; + attr(p) = attr(p) and attr(r); + } + } + + }; + + + template <typename I, typename N> + I + regional_maxima(const I& f, const N& nbh) + { + regional_maxima_t<I, N> run(f, nbh); + return run.o; + } + + + } // end of namespace mln::morpho::Rd + + } // end of namespace mln::morpho + +} // end of namespace mln + + +#endif // ! MLN_MORPHO_RD_UTILS_HH Index: sandbox/geraud/Rd/deco.cc --- sandbox/geraud/Rd/deco.cc (revision 0) +++ sandbox/geraud/Rd/deco.cc (revision 0) @@ -0,0 +1,71 @@ +// Copyright (C) 2007 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this library; see the file COPYING. If not, write to +// the Free Software Foundation, 51 Franklin Street, Fifth Floor, +// Boston, MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library 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 tests/decorated_image.cc + * + * \brief Tests on mln::decorated_image. + * \todo Make this test work. + */ + +#include <mln/core/image2d.hh> +#include <mln/core/decorated_image.hh> + + +unsigned count_read = 0, count_write = 0; + +template <typename I> +struct counter +{ + void reading(const I&, const mln_psite(I)&) const + { + ++count_read; + } + void writing(I&, const mln_psite(I)&, const mln_value(I)&) + { + ++count_write; + } +}; + + +int main() +{ + using namespace mln; + + typedef image2d<int> I; + I ima(1, 1); + point2d p = make::point2d(0, 0); + + decorated_image< I, counter<I> > ima_ = decorate(ima, counter<I>()); + ima_(p) = ima_(p) = 51; + + std::cout << count_read << ' ' << count_write << std::endl; + + mln_assertion(count_read == 1 && count_write == 2); + + const I& imac = ima; + decorated_image< const I, counter<I> > cima_ = decorate(imac, counter<I>()); +} Index: sandbox/geraud/Rd/hybrid.cc --- sandbox/geraud/Rd/hybrid.cc (revision 0) +++ sandbox/geraud/Rd/hybrid.cc (revision 0) @@ -0,0 +1,51 @@ +#include <iostream> + +#include <mln/core/image2d.hh> +#include <mln/core/neighb2d.hh> +#include <mln/value/int_u8.hh> + +#include <mln/debug/println.hh> +#include <mln/io/pgm/load.hh> +#include <mln/io/pgm/save.hh> + +#include "hybrid.hh" + + +void usage(char* argv[]) +{ + std::cerr << "usage: " << argv[0] << " f.pgm g.pgm c output.pgm" << std::endl + << "reconstruction by dilation (hybrid version; may 2007)" << std::endl + << "f = marker (to be dilated)" << std::endl + << "g = mask (constraint >= f)" << std::endl + << "c: 4 or 8" << std::endl; + exit(1); +} + + +int main(int argc, char* argv[]) +{ + if (argc != 5) + usage(argv); + + using namespace mln; + using value::int_u8; + + typedef image2d<int_u8> I; + + int c = atoi(argv[3]); + if (c != 4 and c != 8) + usage(argv); + + I f = io::pgm::load<int_u8>(argv[1]); + I g = io::pgm::load<int_u8>(argv[2]); + + if (not (f <= g)) + { + std::cerr << "pb" << std::endl; + return 1; + } + + io::pgm::save(morpho::Rd::hybrid(f, g, + (c == 4 ? c4() : c8())), + argv[4]); +} Index: sandbox/geraud/Rd/queue_based.cc --- sandbox/geraud/Rd/queue_based.cc (revision 0) +++ sandbox/geraud/Rd/queue_based.cc (revision 0) @@ -0,0 +1,49 @@ +#include <mln/core/image2d.hh> +#include <mln/core/neighb2d.hh> +#include <mln/value/int_u8.hh> + +#include <mln/debug/println.hh> +#include <mln/io/pgm/load.hh> +#include <mln/io/pgm/save.hh> + +#include "queue_based.hh" + + +void usage(char* argv[]) +{ + std::cerr << "usage: " << argv[0] << " f.pgm g.pgm c output.pgm" << std::endl + << "reconstruction by dilation (queue_based version; may 2007)" << std::endl + << "f = marker (to be dilated)" << std::endl + << "g = mask (constraint >= f)" << std::endl + << "c: 4 or 8" << std::endl; + exit(1); +} + + +int main(int argc, char* argv[]) +{ + if (argc != 5) + usage(argv); + + using namespace mln; + using value::int_u8; + + typedef image2d<int_u8> I; + + int c = atoi(argv[3]); + if (c != 4 and c != 8) + usage(argv); + + I f = io::pgm::load<int_u8>(argv[1]); + I g = io::pgm::load<int_u8>(argv[2]); + + if (not (f <= g)) + { + std::cerr << "pb" << std::endl; + return 1; + } + + io::pgm::save(morpho::Rd::queue_based(f, g, + (c == 4 ? c4() : c8())), + argv[4]); +} Index: sandbox/geraud/Rd/sequential_bench.hh --- sandbox/geraud/Rd/sequential_bench.hh (revision 0) +++ sandbox/geraud/Rd/sequential_bench.hh (revision 0) @@ -0,0 +1,100 @@ +// Copyright (C) 2007 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this library; see the file COPYING. If not, write to +// the Free Software Foundation, 51 Franklin Street, Fifth Floor, +// Boston, MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to +// produce an executable, this file does not by itself cause the +// resulting executable to be covered by the GNU General Public +// License. This exception does not however invalidate any other +// reasons why the executable file might be covered by the GNU General +// Public License. + +#ifndef MLN_MORPHO_RD_SEQUENTIAL_HH +# define MLN_MORPHO_RD_SEQUENTIAL_HH + +# include "utils.hh" + + +namespace mln +{ + + namespace morpho + { + + namespace Rd + { + + template <typename I, typename N> + I sequential(const I& f, const I& g, const N& nbh) + { + mln_precondition(f <= g); + + f.name_it("f"); + g.name_it("g"); + + I o_(f.domain()); + o_.name_it("o_"); + + unsigned nloops = 0; + + // initialisation + I o = clone(f); + o.name_it("o"); + + bool stability; + do + { + ++nloops; + + level::paste(o, o_); // memorisation + + // passe 1 + { + mln_bkd_piter(I) p(f.domain()); + for_all(p) + o(p) = min( max_Nminus(o, p, nbh), g(p) ); + } + + // passe 2 + { + mln_fwd_piter(I) p(f.domain()); + for_all(p) + o(p) = min( max_Nplus(o, p, nbh), g(p) ); + } + + stability = (o == o_); + } + while (not stability); + + std::cout << "nloops = " << nloops << std::endl; + + print_counts(); + + mln_postcondition(o <= g); + return o; + } + + } // end of namespace mln::morpho::Rd + + } // end of namespace mln::morpho + +} // end of namespace mln + + +#endif // ! MLN_MORPHO_RD_SEQUENTIAL_HH Index: sandbox/geraud/Rd/min.cc --- sandbox/geraud/Rd/min.cc (revision 0) +++ sandbox/geraud/Rd/min.cc (revision 0) @@ -0,0 +1,42 @@ +#include <mln/core/image2d.hh> +#include <mln/value/int_u8.hh> +#include <mln/io/pgm/load.hh> +#include <mln/io/pgm/save.hh> + + +void usage(char* argv[]) +{ + std::cerr << "usage: " << argv[0] << " 1.pgm 2.pgm out.pgm" << std::endl + << "(2008 Feb)" << std::endl; + exit(1); +} + + +template <typename I> +I min(const I& ima1, const I& ima2) +{ + mln_precondition(ima1.has_data() and ima2.has_data()); + mln_precondition(ima1.domain() == ima2.domain()); + I out(ima1.domain()); + mln_piter(I) p(ima1.domain()); + for_all(p) + out(p) = ima1(p) < ima2(p) ? ima1(p) : ima2(p); + return out; +} + + +int main(int argc, char* argv[]) +{ + if (argc != 4) + usage(argv); + + using namespace mln; + using value::int_u8; + + typedef image2d<int_u8> I; + + I ima1 = io::pgm::load<int_u8>(argv[1]); + I ima2 = io::pgm::load<int_u8>(argv[2]); + + io::pgm::save(min(ima1, ima2), argv[3]); +} Index: sandbox/geraud/Rd/sequential.hh --- sandbox/geraud/Rd/sequential.hh (revision 0) +++ sandbox/geraud/Rd/sequential.hh (revision 0) @@ -0,0 +1,96 @@ +// Copyright (C) 2007 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this library; see the file COPYING. If not, write to +// the Free Software Foundation, 51 Franklin Street, Fifth Floor, +// Boston, MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to +// produce an executable, this file does not by itself cause the +// resulting executable to be covered by the GNU General Public +// License. This exception does not however invalidate any other +// reasons why the executable file might be covered by the GNU General +// Public License. + +#ifndef MLN_MORPHO_RD_SEQUENTIAL_HH +# define MLN_MORPHO_RD_SEQUENTIAL_HH + +# include "utils.hh" + + +namespace mln +{ + + namespace morpho + { + + namespace Rd + { + + template <typename I, typename N> + I sequential(const I& f, const I& g, const N& nbh) + { + mln_precondition(f <= g); + + f.name_it("f"); + g.name_it("g"); + + I o_(f.domain()); + o_.name_it("o_"); + + // initialisation + I o(f.domain()); + o.name_it("o"); + level::paste(f, o); + // WAS: I o = clone(f); + + bool stability; + do + { + level::paste(o, o_); // memorisation + + // passe 1 + { + mln_bkd_piter(I) p(f.domain()); + for_all(p) + o(p) = min( max_Nminus(o, p, nbh), g(p) ); + } + + // passe 2 + { + mln_fwd_piter(I) p(f.domain()); + for_all(p) + o(p) = min( max_Nplus(o, p, nbh), g(p) ); + } + + stability = (o == o_); + } + while (not stability); + + print_counts(); + + mln_postcondition(o <= g); + return o; + } + + } // end of namespace mln::morpho::Rd + + } // end of namespace mln::morpho + +} // end of namespace mln + + +#endif // ! MLN_MORPHO_RD_SEQUENTIAL_HH Index: sandbox/geraud/Rd/debase.union_find.hh --- sandbox/geraud/Rd/debase.union_find.hh (revision 0) +++ sandbox/geraud/Rd/debase.union_find.hh (revision 0) @@ -0,0 +1,165 @@ +// Copyright (C) 2007 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this library; see the file COPYING. If not, write to +// the Free Software Foundation, 51 Franklin Street, Fifth Floor, +// Boston, MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to +// produce an executable, this file does not by itself cause the +// resulting executable to be covered by the GNU General Public +// License. This exception does not however invalidate any other +// reasons why the executable file might be covered by the GNU General +// Public License. + +#ifndef OLN_MORPHO_RD_UNION_FIND_HH +# define OLN_MORPHO_RD_UNION_FIND_HH + +# include <oln/morpho/Rd/utils.hh> + + +namespace oln +{ + + namespace morpho + { + + namespace Rd + { + + + template <typename I, typename N> + struct union_find_t + { + typedef oln_point(I) point; + + // in: + I f, g; + N nbh; + + // out: + I o; + + // aux: + std::vector<point> S; + I data; + oln_plain_value(I, bool) is_proc; + oln_plain_value(I, point) parent; + + union_find_t(const I& f, const I& g, const N& nbh) + : f(f), g(g), nbh(nbh) + { + prepare(o, with, f); + prepare(parent, with, f); + prepare(is_proc, with, f); + prepare(data, with, f); + + // init + + std::cout << "0 "; + level::fill(inplace(is_proc), false); + S = histo_reverse_sort(g); + + // first pass + + std::cout << "1 "; + for (unsigned i = 0; i < S.size(); ++i) + { + point p = S[i]; + + make_set(p); + oln_niter(N) n(nbh, p); + for_all(n) + if (f.has(n) and is_proc(n)) + do_union(n, p); + is_proc(p) = true; + } + + // second pass + + std::cout << "2 "; + level::fill(inplace(is_proc), false); + for (int i = S.size() - 1; i >= 0; --i) + { + point p = S[i]; + assert(is_proc(p) == false); + if (parent(p) == p) + o(p) = data(p) == 255 ? g(p) : data(p); + else + { + assert(is_proc(parent(p)) == true); + o(p) = o(parent(p)); + } + is_proc(p) = true; + } + + } + + void make_set(const point& p) + { + parent(p) = p; + data(p) = f(p); + } + + point find_root(const point& x) + { + if (parent(x) == x) + return x; + else + return parent(x) = find_root(parent(x)); + } + + bool equiv(const point& r, const point& p) + { + return g(r) == g(p) or g(p) >= data(r); + } + + void do_union(const point& n, const point& p) + { + point r = find_root(n); + if (r != p) + { + if (equiv(r, p)) + { + parent(r) = p; + if (data(r) > data(p)) + data(p) = data(r); // increasing criterion + } + else + data(p) = 255; + } + } + + }; + + + template <typename I, typename N> + I union_find(const I& f, const I& g, const N& nbh) + { + precondition(f <= g); + union_find_t<I, N> run(f, g, nbh); + return run.o; + } + + + } // end of namespace oln::morpho::Rd + + } // end of namespace oln::morpho + +} // end of namespace oln + + +#endif // ! OLN_MORPHO_RD_UNION_FIND_HH Index: sandbox/geraud/Rd/union_find.cc --- sandbox/geraud/Rd/union_find.cc (revision 0) +++ sandbox/geraud/Rd/union_find.cc (revision 0) @@ -0,0 +1,50 @@ +#include <mln/core/image2d.hh> +#include <mln/core/neighb2d.hh> +#include <mln/value/int_u8.hh> + +#include <mln/debug/println.hh> +#include <mln/io/pgm/load.hh> +#include <mln/io/pgm/save.hh> + +#include "union_find.hh" + + + +void usage(char* argv[]) +{ + std::cerr << "usage: " << argv[0] << " f.pgm g.pgm c output.pgm" << std::endl + << "reconstruction by dilation (union_find version; may 2007)" << std::endl + << "f = marker (to be dilated)" << std::endl + << "g = mask (constraint >= f)" << std::endl + << "c: 4 or 8" << std::endl; + exit(1); +} + + +int main(int argc, char* argv[]) +{ + if (argc != 5) + usage(argv); + + using namespace mln; + using value::int_u8; + + typedef image2d<int_u8> I; + + int c = atoi(argv[3]); + if (c != 4 and c != 8) + usage(argv); + + I f = io::pgm::load<int_u8>(argv[1]); + I g = io::pgm::load<int_u8>(argv[2]); + + if (not (f <= g)) + { + std::cerr << "pb" << std::endl; + return 1; + } + + io::pgm::save(morpho::Rd::union_find(f, g, + (c == 4 ? c4() : c8())), + argv[4]); +} Index: sandbox/geraud/Rd/svg.queue_based.hh --- sandbox/geraud/Rd/svg.queue_based.hh (revision 0) +++ sandbox/geraud/Rd/svg.queue_based.hh (revision 0) @@ -0,0 +1,118 @@ +// Copyright (C) 2007 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this library; see the file COPYING. If not, write to +// the Free Software Foundation, 51 Franklin Street, Fifth Floor, +// Boston, MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to +// produce an executable, this file does not by itself cause the +// resulting executable to be covered by the GNU General Public +// License. This exception does not however invalidate any other +// reasons why the executable file might be covered by the GNU General +// Public License. + +#ifndef MLN_MORPHO_RD_QUEUE_BASED_HH +# define MLN_MORPHO_RD_QUEUE_BASED_HH + +# include <queue> +# include "utils.hh" + + +namespace mln +{ + + namespace morpho + { + + namespace Rd + { + + template <typename I, typename N> + I queue_based(const I& f, const I& g, const N& nbh, + bool echo = false) + { + if (echo) std::cout << std::endl; + + f.name_it("f"); + g.name_it("g"); + + typedef mln_point(I) point; + std::queue<point> q; + I o; + o.name_it("o"); + + unsigned n_init_pushs = 0, n_body_pushs = 0, n_pops = 0; + + // initialisation + { + o = regional_maxima(f, nbh); + // p in M <=> o(p) != 0 + if (echo) debug::println(o); + + mln_piter(I) p(f.domain()); + mln_niter(N) n(nbh, p); + + for_all(p) if (o(p) != 0) // p in M + for_all(n) if (f.has(n) and o(n) == 0) // n not in M + { + q.push(p); + ++n_init_pushs; + break; + } + } + + // propagation + { + point p; + mln_niter(N) n(nbh, p); + while (not q.empty()) + { + p = q.front(); + if (echo) std::cout << std::endl << "pop " << p << " :"; + q.pop(); + ++n_pops; + for_all(n) if (f.has(n)) + { + if (o(n) < o(p) and o(n) != g(n)) + { + o(n) = min(o(p), g(n)); + if (echo) std::cout << " push " << n; + q.push(n); + ++n_body_pushs; + } + } + } + if (echo) std::cout << std::endl; + } + + std::cout << "n_init_pushs = " << n_init_pushs << std::endl + << "n_body_pushs = " << n_body_pushs << std::endl + << "n_pops = " << n_pops << std::endl; + + print_counts(); + + return o; + } + + } // end of namespace mln::morpho::Rd + + } // end of namespace mln::morpho + +} // end of namespace mln + + +#endif // ! MLN_MORPHO_RD_QUEUE_BASED_HH
participants (1)
-
Thierry Geraud