• Main Page
  • Related Pages
  • Modules
  • Namespaces
  • Classes
  • Files
  • File List

topological.hh

00001 // Copyright (C) 2008, 2009 EPITA Research and Development Laboratory (LRDE)
00002 //
00003 // This file is part of the Milena Library.  This library is free
00004 // software; you can redistribute it and/or modify it under the terms
00005 // of the GNU General Public License version 2 as published by the
00006 // Free Software Foundation.
00007 //
00008 // This library is distributed in the hope that it will be useful,
00009 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00010 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011 // General Public License for more details.
00012 //
00013 // You should have received a copy of the GNU General Public License
00014 // along with this library; see the file COPYING.  If not, write to
00015 // the Free Software Foundation, 51 Franklin Street, Fifth Floor,
00016 // Boston, MA 02111-1307, USA.
00017 //
00018 // As a special exception, you may use this file as part of a free
00019 // software library without restriction.  Specifically, if other files
00020 // instantiate templates or use macros or inline functions from this
00021 // file, or you compile this file and link it with other files to
00022 // produce an executable, this file does not by itself cause the
00023 // resulting executable to be covered by the GNU General Public
00024 // License.  This exception does not however invalidate any other
00025 // reasons why the executable file might be covered by the GNU General
00026 // Public License.
00027 
00028 #ifndef MLN_MORPHO_WATERSHED_TOPOLOGICAL_HH
00029 # define MLN_MORPHO_WATERSHED_TOPOLOGICAL_HH
00030 
00040 
00041 # include <vector>
00042 # include <map>
00043 # include <queue>
00044 
00045 # include <mln/core/site_set/p_set.hh>
00046 # include <mln/core/site_set/p_priority.hh>
00047 # include <mln/core/site_set/p_queue_fast.hh>
00048 
00049 # include <mln/util/ord.hh>
00050 
00051 # include <mln/data/sort_psites.hh>
00052 # include <mln/data/fill.hh>
00053 
00054 
00055 // FIXME: Clean up and document.
00056 
00057 namespace mln
00058 {
00059 
00060   namespace morpho
00061   {
00062 
00063     namespace watershed
00064     {
00065 
00066       template <class I>
00067       mln_site(I) min (const Image<I> &ima_, p_set<mln_site(I)>& components)
00068       {
00069         const I& ima = exact(ima_);
00070 
00071         if (components.nsites() == 0)
00072           return mln_site(I)(-1, -1); // FIXME
00073 
00074         typename p_set<mln_site(I)>::fwd_piter it(components);
00075         mln_site(I) min = components[0];
00076 
00077         for_all(it)
00078           if (ima(it) < ima(min))
00079             min = it;
00080 
00081         return min;
00082       }
00083 
00084       template <class I>
00085       mln_site(I) max (p_set<mln_site(I)>& components)
00086       {
00087         if (components.nsites() == 0)
00088           return mln_site(I)(-1, -1); // FIXME
00089 
00090         typename p_set<mln_site(I)>::fwd_piter it(components);
00091         mln_site(I) max = components[0];
00092 
00093         for_all(it)
00094           if (ima(it) > ima(max))
00095             max = it;
00096 
00097         return max;
00098       }
00099 
00100 
00101       // Actually, this structure is a tree, despite its confusing name.
00102       template <class I, class N>
00103       struct topo_wst
00104       {
00105         // Typedefs
00106         // --------
00107         typedef mln_site(I) site;
00108         typedef I image_t;
00109         typedef N neighborhood_t;
00110 
00111         // Component tree management
00112         // -------------------------
00113       private:
00114         struct node {
00115           mln_value(I) level;
00116           int area;
00117           int highest;
00118           typedef mln_site(I) site;
00119           // Can't modify the sites in a p_array
00120           // p_array<mln_site(I)> children;
00121           std::vector<site> children;
00122 
00123           void addChildren(const node& n)
00124           {
00125             // typename p_array<mln_site(I)>::fwd_piter it(n.children);
00126             // for (it.start();
00127             //      it.is_valid();
00128             //      it.next())
00129             //  children.append(it.to_site());
00130             for (unsigned i=0; i < n.children.size(); ++i)
00131               children.push_back(n.children[i]);
00132           }
00133 
00134           void addChild(const site p)
00135           {
00136             // children.append(n);
00137             children.push_back(p);
00138           }
00139         }; // struct node
00140 
00141       public:
00142 
00143         mln_ch_value(I, site) Par_node;
00144         mln_ch_value(I, site) Par_tree;
00145 
00146         mln_ch_value(I, int) Rnk_tree;
00147         mln_ch_value(I, int) Rnk_node;
00148         mln_ch_value(I, site) subtreeRoot;
00149         mln_ch_value(I, node) nodes;
00150         site Root;
00151 
00152       private:
00153 
00154         void MakeSet_tree(site x);
00155         void MakeSet_node(site x);
00156         site Find_tree(site x);
00157         site Find_node(site x);
00158         void BuildComponentTree();
00159         site MergeNode(site& node1, site& node2);
00160         site Link_tree(site x, site y);
00161         site Link_node(site x, site y);
00162         node MakeNode(int level);
00163 
00164       private:
00165         mln_ch_value(I, bool) isproc;
00166 
00167         // Ctor
00168       public:
00169         topo_wst(const Image<I>& i,
00170                  const Neighborhood<N>& n);
00171 
00172       public:
00173         const I &ima;
00174         const N &nbh;
00175 
00176       public:
00177         void go();
00178 
00179       private:
00180         site min (p_set<site>& components);
00181         site max (p_set<site>& components);
00182         bool highest_fork (p_set<site>& components);
00183         bool highest_fork (p_set<site>& components, site &r);
00184 
00185         // Optimized LCA Algorithm
00186 
00187       public:
00188         site lca (site a, site b);
00189 
00190       private:
00191         int *euler;
00192         int *depth;
00193         int ctree_size;
00194         std::map<site, int, mln::util::ord<site> > pos;
00195         site *repr;
00196 
00197         int *minim;
00198         int **Minim;
00199 
00200         void compute_ctree_size (site p);
00201         void build_euler_tour_rec(site p, int &position, int d);
00202         void build_euler_tour();
00203         void build_minim ();
00204         void removeOneSonNodes(site *p, mln_ch_value(I, site) &newPar_node);
00205         void compressTree();
00206       };
00207 
00209       template <class T>
00210       typename T::image_t
00211       topological(T &tree);
00212 
00213 
00214 
00215 # ifndef MLN_INCLUDE_ONLY
00216 
00217       // Ctor
00218       template <class I, class N>
00219       topo_wst<I, N>::topo_wst(const Image<I>& i,
00220                                const Neighborhood<N>& n)
00221         : ima(exact(i)), nbh(exact(n))
00222       {
00223         initialize(Par_node, i);
00224         initialize(Par_tree, i);
00225         initialize(Rnk_tree, i);
00226         initialize(Rnk_node, i);
00227         initialize(subtreeRoot, i);
00228         initialize(nodes, i);
00229         initialize(isproc, i);
00230       }
00231 
00232       template <class I, class N>
00233       void topo_wst<I, N>::MakeSet_tree(site x)
00234       {
00235         Par_tree(x) = x;
00236         Rnk_tree(x) = 0;
00237       }
00238 
00239       template <class I, class N>
00240       void topo_wst<I, N>::MakeSet_node(site x)
00241       {
00242         Par_node(x) = x;
00243         Rnk_node(x) = 0;
00244       }
00245 
00246       template <class I, class N>
00247       typename topo_wst<I, N>::site topo_wst<I, N>::Find_tree(site x)
00248       {
00249         if (Par_tree(x) != x)
00250           Par_tree(x) = Find_tree(Par_tree(x));
00251         return Par_tree(x);
00252       }
00253 
00254       template <class I, class N>
00255       typename topo_wst<I, N>::site topo_wst<I, N>::Find_node(site x)
00256       {
00257         if (Par_node(x) != x)
00258           Par_node(x) = Find_node(Par_node(x));
00259         return Par_node(x);
00260       }
00261 
00262       template <class I, class N>
00263       void topo_wst<I, N>::go()
00264       {
00265         BuildComponentTree();
00266         compressTree();
00267 
00268         build_euler_tour();
00269         build_minim();
00270       }
00271 
00272       template <class I, class N>
00273       void topo_wst<I, N>::BuildComponentTree()
00274       {
00275         // Init:
00276 
00277         // Sort the sites in increasing order
00278         p_array<mln_site(I)> S;
00279         S = data::sort_psites_increasing(ima);
00280 
00281         // Clear the marker map
00282         data::fill(isproc, false);
00283         for (int ip = 0; ip < int(S.nsites()); ++ip)
00284           {
00285             site p = S[ip];
00286             MakeSet_node(p);
00287             MakeSet_tree(p);
00288             //        if (subtreeRoot.hold(p))
00289             subtreeRoot(p) = p;
00290             //        if (nodes.hold(p))
00291             nodes(p) = MakeNode(ima(p));
00292           }
00293 
00294 
00295 
00296         typename p_array<mln_site(I)>::fwd_piter ip(S);
00297         for_all(ip)
00298         {
00299           site p = ip.to_site();
00300 
00301           site curCanonicalElt = Find_tree(p);
00302           site curNode = Find_node(subtreeRoot(curCanonicalElt));
00303 
00304           // FIXME: Should be `n' instead of `q'.
00305           mln_niter(N) q(nbh, ip);
00306           for_all(q)
00307             if (ima.has(q) and isproc(q) and ima(q) <= ima(p))
00308               {
00309                 site adjCanonicalElt = Find_tree(q);
00310                 site adjNode = Find_node(subtreeRoot(adjCanonicalElt));
00311                 if (curNode != adjNode)
00312                   {
00313                     if (nodes(curNode).level == nodes(adjNode).level)
00314                       curNode = MergeNode(adjNode, curNode);
00315                     else
00316                       {
00317                         nodes(curNode).addChild(adjNode);
00318                         nodes(curNode).area += nodes(adjNode).area;
00319                         nodes(curNode).highest += nodes(adjNode).highest;
00320                       }
00321                   }
00322 
00323                 curCanonicalElt = Link_tree(adjCanonicalElt, curCanonicalElt);
00324                 subtreeRoot(curCanonicalElt) = curNode;
00325               }
00326           isproc(p) = true;
00327         }
00328         // Pour garder une map de correspondance site <-> local_root
00329         //            for (int ip = 0; ip < int(S.size()); ++ip)
00330         //              {
00331         //                site p = S[ip];
00332         //             M(p) = Find_node(p);
00333         //           }
00334 
00335         mln_piter(I) r(Par_node.domain());
00336         for_all(r)
00337           Par_node(r) = Find_node(r);
00338 
00339         // Find the ``first'' site of ima, according to the forward
00340         // traversal order.
00341         mln_fwd_piter(I) rp(Par_node.domain());;
00342         rp.start();
00343 
00344         Root = subtreeRoot(Find_tree(Find_node(rp)));
00345       }
00346 
00347 
00348       template <class I, class N>
00349       typename topo_wst<I, N>::site topo_wst<I, N>::MergeNode(site& node1,
00350                                                               site& node2)
00351       {
00352         site tmpNode = Link_node(node1, node2);
00353         site tmpNode2;
00354         if (tmpNode == node2)
00355           {
00356             nodes(node2).addChildren(nodes(node1));
00357             tmpNode2 = node1;
00358           }
00359         else
00360           {
00361             nodes(node1).addChildren(nodes(node2));
00362             tmpNode2 = node2;
00363           }
00364         nodes(tmpNode).area += nodes(tmpNode2).area;
00365         nodes(tmpNode).highest += nodes(tmpNode2).highest;
00366         return tmpNode;
00367       }
00368 
00369       template <class I, class N>
00370       typename topo_wst<I, N>::site topo_wst<I, N>::Link_tree(site x, site y)
00371       {
00372         if (Rnk_tree(x) > Rnk_tree(y))
00373           std::swap(x, y);
00374         else
00375           if (Rnk_tree(x) == Rnk_tree(y))
00376             Rnk_tree(y) += 1;
00377         Par_tree(x) = y;
00378         return y;
00379       }
00380 
00381       template <class I, class N>
00382       typename topo_wst<I, N>::site topo_wst<I, N>::Link_node(site x, site y)
00383       {
00384         if (Rnk_node(x) > Rnk_node(y))
00385           std::swap(x, y);
00386         else
00387           if (Rnk_node(x) == Rnk_node(y))
00388             Rnk_node(y) += 1;
00389         Par_node(x) = y;
00390         return y;
00391       }
00392 
00393       template <class I, class N>
00394       typename topo_wst<I, N>::node topo_wst<I, N>::MakeNode(int level)
00395       {
00396         node n;
00397         n.level = level;
00398         n.area = 1;
00399         n.highest = level;
00400         return n;
00401       }
00402 
00403 
00404       template <class I, class N>
00405       bool topo_wst<I, N>::highest_fork (p_set<site>& components, site &r)
00406       {
00407         if (components.nsites() == 0)
00408           {
00409             std::cerr << "highest fork : empty set" << std::endl;
00410             return false;
00411           }
00412 
00413         site
00414           m = min(components),
00415           hfork = m;
00416 
00417         typename p_set<site>::fwd_piter it(components);
00418         for_all(it)
00419         {
00420           // Can't remove the site from the set
00421           if (it.to_site() == m)
00422             continue;
00423 
00424           site c = lca(hfork, it.to_site());
00425           if (c != it.to_site())
00426             hfork = c;
00427         }
00428 
00429         if (nodes(m).level == nodes(hfork).level)
00430           return false;
00431 
00432         r = hfork;
00433         return true;
00434       }
00435 
00436       template <class I, class N>
00437       bool topo_wst<I, N>::highest_fork (p_set<site>& components) {
00438         site i;
00439         return highest_fork(components, i);
00440       }
00441 
00442       template <class I, class N>
00443       void topo_wst<I, N>::compute_ctree_size (site p)
00444       {
00445         ctree_size += 1;
00446         node& n = nodes(p);
00447 
00448         // typename p_array<mln_site(I)>::fwd_piter it(n.children);
00449         // for_all(it)
00450         //   compute_ctree_size(it.to_site());
00451 
00452         for (unsigned i=0; i < n.children.size(); ++i)
00453           compute_ctree_size(n.children[i]);
00454       }
00455 
00456 
00457       template <class I, class N>
00458       void topo_wst<I, N>::build_euler_tour_rec(site p, int &position, int d)
00459       {
00460         if (pos.find(p) == pos.end())
00461           pos[p] = position;
00462 
00463         repr[position] = p;
00464         depth[position] = d;
00465         euler[position] = pos[p];
00466         ++position;
00467         node& n = nodes(p);
00468 
00469         // typename p_array<mln_site(I)>::fwd_piter it(n.children);
00470         // for_all(it)
00471         // {
00472         //   build_euler_tour_rec(it.to_site(), position, d+1);
00473         //   depth[position] = d; // Not optimized
00474         //   euler[position] = pos[p];
00475         //   repr[position] = p; // Pas necessaire?
00476         //   ++position;
00477         // }
00478 
00479         for (unsigned i=0; i < n.children.size(); ++i)
00480           {
00481             build_euler_tour_rec(n.children[i], position, d+1);
00482             depth[position] = d; // Not optimized
00483             euler[position] = pos[p];
00484             repr[position] = p; // Pas necessaire?
00485             ++position;
00486           }
00487       }
00488 
00489 
00490       template <class I, class N>
00491       void topo_wst<I, N>::build_euler_tour ()
00492       {
00493         ctree_size = 0;
00494         compute_ctree_size(Root);
00495 
00496         int size = 2 * ctree_size - 1;
00497 
00498         // FIXME : free this
00499         euler = new int[size];
00500         depth = new int[size];
00501         repr = new site[size];
00502 
00503         int position = 0;
00504         build_euler_tour_rec(Root, position, 0);
00505       }
00506 
00507 
00508       template <class I, class N>
00509       void topo_wst<I, N>::build_minim ()
00510       {
00511         // compute_tree_size(); // Already done in build_euler_tour
00512         int size = 2 * ctree_size - 1;
00513         int logn = (int)(ceil(log((double)(size))/log(2.0)));
00514         // minim.reserve(size);
00515         minim = new int [logn * size]; // FIXME : Think about freeing this
00516         Minim = new int* [logn];
00517 
00518         Minim[0] = minim;
00519 
00520         // Init
00521         for (int i = 0; i < size - 1; ++i)
00522           if (depth[euler[i]] < depth[euler[i+1]]) {
00523             Minim[0][i] = i;
00524           } else {
00525             Minim[0][i] = i+1;
00526           }
00527 
00528         //      Minim[0][size - 1] = size - 1;
00529 
00530         int k;
00531         for (int j = 1; j < logn; j++) {
00532           k = 1 << (j - 1);
00533           Minim[j] = &minim[j * size];
00534           for (int i = 0; i < size; i++) {
00535             if ((i + (k << 1)) >= size) {
00536               //Minim[j][i] = size - 1;
00537             }
00538             else {
00539               if (depth[euler[Minim[j - 1][i]]]
00540                   <= depth[euler[Minim[j - 1][i + k]]])
00541                 Minim[j][i] = Minim[j - 1][i];
00542               else
00543                 Minim[j][i] = Minim[j - 1][i + k];
00544             }
00545           }
00546         }
00547 
00548       } // void build_minim ()
00549 
00550 
00551       template <class I, class N>
00552       typename topo_wst<I, N>::site topo_wst<I, N>::lca (site a, site b)
00553       {
00554         int
00555           m = pos[a],
00556           n = pos[b],
00557           k;
00558 
00559         if (m == n)
00560           return repr[m];
00561 
00562         if (m > n)
00563           {
00564             k = n;
00565             n = m;
00566             m = k;
00567           }
00568 
00569         k = (int)(log((double)(n - m))/log(2.));
00570 
00571         if (depth[euler[Minim[k][m]]] < depth[euler[Minim[k][n - (1 << k)]]]) {
00572           return repr[euler[Minim[k][m]]];
00573         } else {
00574           return repr[euler[Minim[k][n - (1 << k)]]];
00575         }
00576       }
00577 
00578 
00579       template <class I, class N>
00580       void topo_wst<I, N>::removeOneSonNodes(site *p,
00581                                              mln_ch_value(I, site) &newPar_node)
00582       {
00583         node &n = nodes(*p);
00584 
00585         if (n.children.size() == 1) // this node has 1 son, delete it
00586           {
00587             n.area = -1;
00588             newPar_node(*p) = n.children[0];
00589             *p = n.children[0];
00590             removeOneSonNodes(p, newPar_node);
00591           }
00592         else // there is more than one son, recursive call
00593           {
00594             for (unsigned i = 0; i < n.children.size(); ++i)
00595               removeOneSonNodes(&(n.children[i]), newPar_node);
00596           }
00597       }
00598 
00599 
00600       template <class I, class N>
00601       void topo_wst<I, N>::compressTree()
00602       {
00603         mln_ch_value(I, site) newPar_node;
00604         initialize(newPar_node, Par_node);
00605 
00606         // Remove the nodes with one son
00607         removeOneSonNodes(&Root, newPar_node);
00608 
00609         // Update the references on deleted nodes
00610         mln_piter(I) p(Par_node.domain());
00611         for_all(p)
00612           while (nodes(Par_node(p)).area == -1)
00613             Par_node(p) = newPar_node(Par_node(p));
00614       }
00615 
00616       template <class T>
00617       bool w_constructible(T &tree, typename T::site p, typename T::site &r)
00618       {
00619 
00620         typedef typename T::image_t I;
00621         typedef typename T::neighborhood_t N;
00622 
00623         const I &ima = exact(tree.ima);
00624         const N &nbh = exact(tree.nbh);
00625 
00626         // FIXME: Should be `n' instead of `q'.
00627         mln_niter(N) q(nbh, p);
00628         p_set<mln_site(I)> v;
00629 
00630         for_all(q)
00631           // FIXME: Shouldn't it be: `ima.has(q)' instead of
00632           // `ima.domain().has(q)'?
00633           if (ima.domain().has(q) && ima(q) < ima(p))
00634             v.insert(tree.Par_node(q));
00635 
00636         if (v.nsites() == 0)
00637           return false;
00638 
00639         if (v.nsites() == 1)
00640           {
00641             r = v[0];
00642             return true;
00643           }
00644 
00645         mln_site(I) c = min(ima, v);
00646         mln_site(I) cmin = c;
00647 
00648         typename p_set<mln_site(I)>::fwd_piter it(v);
00649         for_all(it)
00650         {
00651           // Can't remove the site from the set
00652           if (it.to_site() == cmin)
00653             continue;
00654 
00655           mln_site(I) c1 = tree.lca(c, it);
00656 
00657           if (c1 != it)
00658             c = c1;
00659         }
00660 
00661         if (tree.nodes(c).level >= ima(p))
00662           return false;
00663 
00664         r = c;
00665         return true;
00666       }
00667 
00668       template <class T>
00669       bool w_constructible(T &tree, typename T::site p) {
00670         typename T::site r;
00671         return w_constructible(tree, p, r);
00672       }
00673 
00674  
00675 
00676       template <class T>
00677       typename T::image_t topological(T &tree)
00678       {
00679 
00680         typedef typename T::image_t I;
00681         typedef typename T::neighborhood_t N;
00682 
00683         I ima = exact(tree.ima);
00684         const N &nbh = exact(tree.nbh);
00685 
00686         // Maxima components
00687         mln_ch_value(I, bool) cmax;
00688         initialize(cmax, ima);
00689 
00690         // Mark enqueued sites
00691         mln_ch_value(I, bool) enqueued;
00692         initialize(enqueued, ima);
00693 
00694         p_priority< mln_value(I), p_queue_fast<mln_site(I)> > l;
00695         // p_queue < site > m;
00696         std::queue<mln_site(I)> m;
00697         mln_value(I) min = mln_min(mln_value(I));
00698 
00699         std::cout << "Init" << std::endl;
00700 
00701         // Flag C-maxima
00702         data::fill(cmax, false);
00703         data::fill(enqueued, false);
00704 
00705         mln_piter(I) it(tree.Par_node.domain());
00706         for_all(it)
00707           // if (nodes(Par_node(it.to_site())).children.nsites() == 0)
00708           if (tree.nodes(tree.Par_node(it)).children.size() == 0)
00709             {
00710               cmax(it) = true;
00711               m.push(it);
00712             }
00713 
00714         while (!m.empty())
00715           {
00716             // FIXME: Should be `n' instead of `q'.
00717             mln_niter(N) q(nbh, m.front());
00718             // FIXME: Shouldn't it be: `cmax.has(q)' instead of
00719             // `cmax.domain().has(q)'?
00720             for_all(q)
00721               if (cmax.domain().has(q) && !cmax(q) && !enqueued(q))
00722                 {
00723                   enqueued(q) = true;
00724                   l.push(mln_max(mln_value(I)) - ima(q), q);
00725                 }
00726             m.pop();
00727           }
00728 
00729 
00730         std::cout << "end" << std::endl;
00731 
00732         // Main loop
00733         while (!l.is_empty())
00734           {
00735             mln_site(I) x = l.front();
00736             l.pop();
00737             enqueued(x) = false;
00738 
00739             mln_site(I) c;
00740             bool is_w = w_constructible(tree, x, c);
00741 
00742             if (is_w)
00743               {
00744                 ima(x) = tree.nodes(c).level;
00745                 tree.Par_node(x) = c;
00746 
00747                 // if (nodes(c).children.nsites() == 0)
00748                 if (tree.nodes(c).children.size() == 0)
00749                   cmax(x) = true;
00750                 else
00751                   // if (nodes(c).children.nsites() > 1)
00752                   if (tree.nodes(c).children.size() == 1)
00753                     std::cerr << "ERREUR COMPOSANTE BRANCHE "
00754                               << tree.nodes(c).children.size() << std::endl;
00755 
00756 
00757                 // FIXME: Should be `n' instead of `q'.
00758                 mln_niter(N) q(nbh, x);
00759                 // FIXME: Shouldn't it be: `ima.has(q)' instead of
00760                 // `ima.domain().has(q)'?
00761                 for_all(q)
00762                   if (ima.domain().has(q) && !cmax(q) && !enqueued(q))
00763                     {
00764                       enqueued(q) = true;
00765                       l.push(mln_max(mln_value(I)) - ima(q), q); // FIXME:
00766                       // Just
00767                       // invert
00768                       // the
00769                       // priority.
00770                     }
00771               } 
00772           } // while(!l.empty())
00773 
00774         for_all(it)
00775           ima(it) = tree.nodes(tree.Par_node(it)).level;
00776 
00777         return ima;
00778       }
00779 
00780 # endif // MLN_INCLUDE_ONLY
00781 
00782 
00783     } // end of namespace mln::morpho::watershed
00784 
00785   } // end of namespace mln::morpho
00786 
00787 } // end of namespace mln
00788 
00789 #endif // ! MLN_MORPHO_WATERSHED_TOPOLOGICAL_HH

Generated on Fri Oct 19 2012 04:16:25 for Milena (Olena) by  doxygen 1.7.1