https://svn.lrde.epita.fr/svn/oln/trunk/milena
Index: ChangeLog
from Alexandre Abraham <abraham(a)lrde.epita.fr>
Add functional M-Watershed algorithm and tests.
* sandbox/abraham/morpho/basic_najman.hh
(mln::morpho::basic_najman::lca)
(mln::morpho::basic_najman::highest_fork)
(mln::morpho::basic_najman::m-destructible)
(mln::morpho::basic_najman::w-destructible)
(mln::morpho::basic_najman::m-watershed)
(mln::morpho::basic_najman::w-watershed):
New methods.
* sandbox/abraham/morpho/test.cc: Add debug tests.
* sandbox/abraham/morpho/test_component_tree.cc: New,
Add tests for component tree algorithms.
* sandbox/abraham/morpho/images/result_m_watershed.pgm: New,
Reference for m-watershed algorithm.
* sandbox/abraham/morpho/images/result_topo_watershed.pgm: New,
Reference for w-watershed algorithm.
* sandbox/abraham/morpho/images/test_watershed.pgm: New,
Test image for watershed algorithms.
* sandbox/abraham/morpho/images/test_component_mapping.pgm: New,
Reference image for component mapping.
* sandbox/abraham/morpho/test_watershed.cc: New,
Add tests for watershed algorithms.
* sandbox/abraham/morpho/Makefile:
Add rules to compile new tests.
Makefile | 16 +-
basic_najman.hh | 295 +++++++++++++++++++++++++++++++-------
images/test_component_mapping.pgm | 5
test.cc | 15 -
test_component_tree.cc | 224 ++++++++++++++++++++++++++++
test_watershed.cc | 55 +++++++
6 files changed, 542 insertions(+), 68 deletions(-)
Index: sandbox/abraham/morpho/basic_najman.hh
--- sandbox/abraham/morpho/basic_najman.hh (revision 1870)
+++ sandbox/abraham/morpho/basic_najman.hh (working copy)
@@ -1,6 +1,10 @@
#include <mln/level/sort_psites.hh>
#include <mln/level/fill.hh>
#include <mln/core/image2d.hh>
+#include <mln/core/p_set.hh>
+//#include <mln/util/greater_psite.hh>
+#include <queue>
+#include <set>
namespace mln
{
@@ -28,19 +32,19 @@
it.next())
children.append(it.to_psite());
}
+
void addChild(const psite n)
{
children.append(n);
}
}; // struct node
-
+ I pima;
const Image<I>& ima;
const Neighborhood<N>& nbh;
mln_ch_value(I, psite) Par_node;
-
mln_ch_value(I, psite) Par_tree;
- // Par_node is handled by par (from ap_maxtree)
+
mln_ch_value(I, int) Rnk_tree;
mln_ch_value(I, int) Rnk_node;
mln_ch_value(I, psite) subtreeRoot;
@@ -52,7 +56,8 @@
basic_najman(const Image<I>& i,
const Neighborhood<N>& n)
- : ima(i),
+ : pima(exact(i)),
+ ima(i),
nbh(n),
Par_node(exact(i).domain(), exact(i).border()),
Par_tree(exact(i).domain(), exact(i).border()),
@@ -73,7 +78,7 @@
void MakeSet_node(psite x)
{
- Par_node(x) = x; // was Par_node(x) = x;
+ Par_node(x) = x;
Rnk_node(x) = 0;
}
@@ -91,24 +96,6 @@
y = memo;
}
-// bool is_root(const psite& x) const
-// {
-// return Par_node(x) == x;
-// }
-
-// bool is_level_root(const psite& x) const
-// {
-// return is_root(x) || exact(ima)(Par_node(x)) < exact(ima)(x);
-// }
-
-// psite find_level_root(const psite& x)
-// {
-// if (is_level_root(x))
-// return x;
-// else
-// return Par_node(x) = find_level_root(Par_node(x));
-// }
-
psite Find_node(psite x)
{
if (Par_node(x) != x)
@@ -148,7 +135,6 @@
for (int ip = 0; ip < int(S.npoints()); ++ip)
{
psite p = S[ip];
- // isproc(p) = true;
psite curTree = Find_tree(p);
psite curNode = Find_node(subtreeRoot(curTree));
@@ -162,36 +148,22 @@
psite adjNode = Find_node(subtreeRoot(adjTree));
if (curNode != adjNode)
{
- std::cout << "curNode != adjNode; " << nodes(curNode).level
<< " vs. " << nodes(adjNode).level << std::endl;
if (nodes(curNode).level == nodes(adjNode).level)
{
- // curNode = MergeNode(adjNode, curNode);
- psite tmpNode = MergeNode(adjNode, curNode);
- /*if (tmpNode == curNode)
- nodes(curNode).addChildren(nodes(adjNode));
- else
- nodes(adjNode).addChildren(nodes(curNode));
- nodes(adjNode) = nodes(curNode);*/
- curNode = tmpNode;
+ curNode = MergeNode(adjNode, curNode);
}
else
{
- // we have nodes[curNode].level < nodes[adjNode].level
nodes(curNode).addChild(adjNode);
- //apparemment NON :Par_node[adjNode] = curNode; // car adjNode devient fils de
curNode
nodes(curNode).area += nodes(adjNode).area;
nodes(curNode).highest += nodes(adjNode).highest;
}
- // curTree = Link_tree(adjTree, curTree);
- // subtreeRoot(curTree) = curNode;
-
}
curTree = Link_tree(adjTree, curTree);
subtreeRoot(curTree) = curNode;
}
isproc(p) = true;
}
- Root = subtreeRoot(Find_tree(Find_node(psite(0, 0))));
// Pour garder une map de correspondance point <-> local_root
// for (int ip = 0; ip < int(S.size()); ++ip)
// {
@@ -203,6 +175,7 @@
for_all(r)
Par_node(r) = Find_node(r);
+ Root = subtreeRoot(Find_tree(Find_node(psite(0, 0))));
}
@@ -243,7 +216,7 @@
else
if (Rnk_node(x) == Rnk_node(y))
Rnk_node(y) += 1;
- Par_node(x) = y; // was Par_node(x) = y;
+ Par_node(x) = y;
return y;
}
@@ -275,40 +248,254 @@
void print_tree(psite p)
{
node& n = nodes(p);
- std::cout << "psite " << p << "=" <<
(p.row() * exact(subtreeRoot).domain().len(1) + p.col()) << " : ";
+ std::cout << "psite " << p << "(" << n.level
<< ")=" << (p.row() * exact(subtreeRoot).domain().len(1) + p.col())
<< " : ";
typename p_array<mln_psite(I)>::fwd_piter it(n.children);
- for (it.start();
- it.is_valid();
- it.next())
+ for_all(it)
{
psite q = it.to_psite();
std::cout << q << "=" << (q.row() *
subtreeRoot.domain().len(1) + q.col()) << " ";
}
std::cout << std::endl;
- for (it.start();
- it.is_valid();
- it.next())
+ for_all(it)
{
print_tree(it.to_psite());
}
}
- void print_sup_tree()
+ psite lca (psite a, psite b)
{
- psite max = psite(0, 0);
+ return lca_rec(Root, Par_node(a), Par_node(b));
+ }
- for(unsigned int i = 0; i < nodes.domain().len(0); ++i)
- for(unsigned int j = 0; j < nodes.domain().len(1); ++j)
+ psite lca_rec (psite cur, psite a, psite b)
+ {
+ // FIXME : naive implementation, make something better
+
+ if (cur == a)
+ return a;
+
+ if (cur == b)
+ return b;
+
+ if (nodes(cur).children.npoints() == 0)
+ return psite (-1, -1);
+
+ psite tmp, acc = psite(-1, -1);
+ int n = 0;
+ typename p_array<mln_psite(I)>::fwd_piter it(nodes(cur).children);
+ for_all(it)
+ {
+ tmp = lca_rec(it.to_psite(), a, b);
+ if (tmp != psite(-1, -1))
+ {
+ if (tmp == a)
{
- if (nodes(psite(i, j)).area > nodes(max).area)
- max = psite(i, j);
+ acc = tmp;
+ n++;
+ continue;
}
+ if (tmp == b)
+ {
+ acc = tmp;
+ n++;
+ continue;
+ }
+ return tmp;
+ }
+ }
+ if (n == 2)
+ return cur;
+
+ return acc;
+ }
+
+ psite min (p_set<psite>& components)
+ {
+ if (components.npoints() == 0)
+ return psite(-1, -1);
+
+ typename p_set<psite>::fwd_piter it(components);
+ psite min = components[0];
+
+ for_all(it)
+ if (nodes(it.to_point()).level > nodes(min).level)
+ min = it.to_point();
+
+ return min;
+ }
+
+ psite highest_fork (p_set<psite>& components)
+ {
+ if (components.npoints() == 0)
+ return psite(-1, -1);
+
+ psite hfork = components[0], min = hfork;
+ typename p_set<psite>::fwd_piter it(components);
- print_tree(max);
+ for_all(it)
+ hfork = lca(hfork, it.to_point());
+
+ if (nodes(min).level == nodes(hfork).level)
+ return psite(-1, -1);
+
+ return hfork;
}
+ psite w_destructible(psite p)
+ {
+ mln_niter(N) q(nbh, p);
+ p_set<psite> v;
+
+ for_all(q)
+ if (exact(ima)(q) < exact(ima)(p))
+ v.append(Par_node(q));
+
+ if (v.npoints() == 0)
+ return psite(-1, -1);
+
+ psite hf = highest_fork(v);
+
+ if (hf == psite(-1, -1))
+ return min(v);
+
+ if (nodes(hf).level <= exact(ima)(p))
+ return hf;
+
+ return psite(-1, -1);
+ }
+
+ psite m_destructible(psite p)
+ {
+ mln_niter(N) q(nbh, p);
+ p_set<psite> v;
+
+ for_all(q)
+ if (exact(ima)(q) < exact(ima)(p))
+ v.insert(Par_node(q));
+
+ if (v.npoints() == 0)
+ return psite(-1, -1);
+
+ if (nodes(min(v)).children.npoints() != 0)
+ return (psite(-1, -1));
+
+ psite hf = highest_fork(v);
+
+ if (hf == psite(-1, -1))
+ return min(v);
+
+ return psite(-1, -1);
+ }
+
+ void m_watershed ()
+ {
+
+ // FIXME : make a better priority function
+ // typedef
+ // std::priority_queue< psite, std::vector<psite>,
util::greater_psite<I> >
+ // ordered_queue_type;
+
+
+ std::priority_queue<psite> l;
+
+ // Clear the marker map
+ level::fill(isproc, false);
+ mln_piter(I) it(exact(ima).domain());
+
+ for_all(it)
+ if (m_destructible(it.to_point()) != psite(-1, -1))
+ {
+ l.push(it.to_point());
+ isproc(it.to_point()) = true;
+ }
+
+ psite p, i;
+ while (!l.empty())
+ {
+ p = l.top();
+ l.pop();
+ isproc(p) = false;
+
+ i = m_destructible(p);
+
+ if (i != psite(-1, -1))
+ {
+ pima(p) = nodes(i).level ;
+ Par_node(p) = i;
+ mln_niter(N) q(nbh, p);
+
+ for_all(q)
+ if (!isproc(q))
+ if (m_destructible(q) != psite(-1, -1))
+ {
+ l.push(q);
+ isproc(q) = true;
+ }
+ }
+ }
+ }
+
+ void w_watershed()
+ {
+ p_array< p_set<psite> > L;
+ // TODO : replace int with the type of value
+ mln_ch_value(I, int) K;
+ mln_ch_value(I, psite) H;
+
+ mln_piter(I) it(exact(ima).domain());
+
+ psite i;
+ for_all(it)
+ {
+ psite p = it.to_point();
+
+ i = w_destructible(p);
+ if (i != psite(-1, -1))
+ {
+ L[nodes(i).level].insert(p);
+ K(p) = nodes(i).level;
+ H(p) = i;
+
+ typename p_array< p_set<psite> >::fwd_piter n(L);
+ for_all(n)
+ {
+ while (!n.empty())
+ {
+ psite p = *n.begin();
+ n.erase(p);
+ // TODO : replace int with the type of value
+ int k = nodes(p).level + 1;
+ if (K(p) == k)
+ {
+ pima(p) = k;
+ Par_node(p) = H(p);
+
+ mln_niter(N) q(nbh, p);
+ for_all(q)
+ if (k < pima(q))
+ {
+ psite j = w_destructible(q);
+ if (j != psite(-1, -1))
+ K(q) = -1;
+ else
+ if (K(q) != nodes(j).level)
+ {
+ n.insert(q);
+ K(q) = i-1;
+ H(q) = j;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+
}; // struct basic_najman
}; // namespace morpho
Index: sandbox/abraham/morpho/test.cc
--- sandbox/abraham/morpho/test.cc (revision 1870)
+++ sandbox/abraham/morpho/test.cc (working copy)
@@ -17,21 +17,13 @@
using namespace mln;
using value::int_u8;
- // component_tree< image2d<int_u8> > c;
-
using namespace mln;
using value::int_u8;
image2d<int_u8> input;
io::pgm::load(input, "./images/test_component_tree.pgm");
-
- for(unsigned int i = 0; i < input.domain().len(0); ++i)
- for(unsigned int j = 0; j < input.domain().len(1); ++j)
- input.at(i, j) = 255 - input.at(i, j);
-
-
- morpho::basic_najman< image2d<int_u8>, neighb2d> n(input, c8());
+ morpho::basic_najman< image2d<int_u8>, neighb2d> n(input, c4());
n.image_output(input);
n.go();
n.image_output(n.Par_tree);
@@ -42,7 +34,10 @@
std::cout << " --------------------------- " << std::endl;
n.image_output(n.Rnk_node);
std::cout << " --------------------------- " << std::endl;
- n.print_sup_tree();
+ n.print_tree(n.Root);
+ std::cout << " --------------------------- " << std::endl;
+
+ n.m_watershed();
// image2d<int_u8> output = morpho::topo_wst(input, c4());
// io::pgm::save(output, "out.pgm");
Index: sandbox/abraham/morpho/test_component_tree.cc
--- sandbox/abraham/morpho/test_component_tree.cc (revision 0)
+++ sandbox/abraham/morpho/test_component_tree.cc (revision 0)
@@ -0,0 +1,224 @@
+#include <mln/core/image2d.hh>
+#include <mln/core/window2d.hh>
+#include <mln/core/neighb2d.hh>
+#include <mln/core/p_set.hh>
+
+#include <mln/value/int_u8.hh>
+
+#include <mln/io/pgm/load.hh>
+#include <mln/io/pgm/save.hh>
+
+#include "basic_najman.hh"
+#include <string>
+#include <iostream>
+
+int print_and_exit (std::string s)
+{
+ std::cerr << s << std::endl;
+ return 1;
+}
+
+template <typename T>
+void swap (T& x, T& y)
+{
+ T memo = x;
+ x = y;
+ y = memo;
+}
+
+template <typename T>
+class test_tree;
+
+template <typename T>
+class test_tree
+{
+public:
+
+ test_tree(T pdata) : data(pdata), children(NULL), brother(NULL) { }
+
+ T data;
+ test_tree<T> *children;
+ test_tree<T> *brother;
+
+ test_tree<T>* insert_child(test_tree<T> *n)
+ {
+ test_tree<T> **p = &children;
+ while (*p && (**p).data < n->data)
+ p = &((**p).brother);
+ n->brother = *p;
+ *p = n;
+ return n;
+ }
+
+ test_tree<T>* insert_child(T pdata)
+ {
+ test_tree<T> *n = new test_tree<T>(pdata);
+ return insert_child(n);
+ }
+};
+
+template <typename T>
+bool compare_tree(test_tree<T>* t1, test_tree<T>* t2)
+{
+ if (!t1 && !t2)
+ return true;
+
+ if ((!t1 || !t2) || (t1->data != t2->data))
+ return false;
+
+ return (compare_tree(t1->brother, t2->brother)
+ && compare_tree(t1->children, t2->children));
+}
+
+template <typename T>
+void show_tree (test_tree<T> *t)
+{
+ if (!t)
+ return ;
+
+ if (t->brother)
+ {
+ std::cout << t->data << " - ";
+ show_tree(t->brother);
+ }
+ else
+ std::cout << t->data << std::endl;
+
+ if (t->children)
+ {
+ std::cout << "sons of " << t->data << " :
";
+ show_tree(t->children);
+ }
+}
+
+
+using namespace mln;
+using value::int_u8;
+
+
+test_tree<image2d<int_u8>::psite>* test_convert
(morpho::basic_najman<image2d<int_u8>, neighb2d>& ima,
image2d<int_u8>::psite root);
+
+test_tree<image2d<int_u8>::psite>* test_convert
(morpho::basic_najman<image2d<int_u8>, neighb2d>& ima,
image2d<int_u8>::psite root)
+{
+ morpho::basic_najman<image2d<int_u8>, neighb2d>::node& n =
ima.nodes(ima.Par_node(root));
+
+ test_tree<image2d<int_u8>::psite> *res = new
test_tree<image2d<int_u8>::psite>(ima.Par_node(root));
+
+ p_array<image2d<int_u8>::psite>::fwd_piter it (n.children);
+ for_all(it)
+ res->insert_child(test_convert(ima, it.to_psite()));
+
+ return res;
+}
+
+int main ()
+{
+ typedef image2d<int_u8> image2dint;
+
+ image2dint input, verif;
+ io::pgm::load(input, "./images/test_component_tree.pgm");
+ io::pgm::load(verif, "./images/test_component_mapping.pgm");
+
+ morpho::basic_najman< image2d<int_u8>, neighb2d> n(input, c4());
+ n.go();
+
+ /*
+ Component mapping :
+
+ 3 3 3 3 9 2 2 2 2
+ 1 1 1 9 7 9 2 2 2
+ 1 1 9 7 7 7 9 2 2
+ 1 9 6 9 7 9 5 9 2
+ 1 1 9 1 7 2 7 2 2
+ 1 1 1 1 7 2 2 2 2
+
+
+ Component tree
+
+ 9
+ / \
+ 7 6
+ /|\
+ 3 2 5
+ |
+ 1
+ */
+
+ // Component mapping test
+
+ image2dint::fwd_piter it(input.domain());
+ image2dint::psite c[10];
+ c[1] = n.Par_node(image2dint::psite(1, 0));
+ c[2] = n.Par_node(image2dint::psite(0, 5));
+ c[3] = n.Par_node(image2dint::psite(0, 0));
+ c[5] = n.Par_node(image2dint::psite(3, 6));
+ c[6] = n.Par_node(image2dint::psite(3, 2));
+ c[7] = n.Par_node(image2dint::psite(1, 4));
+ c[9] = n.Par_node(image2dint::psite(0, 4));
+
+ bool id = true;
+
+ for_all(it)
+ id = (c[verif(it.to_point())] == n.Par_node(it.to_point()));
+
+ if (!id)
+ {
+ std::cerr << "Component mapping is fucked up!" << std::endl;
+ return 1;
+ }
+
+ // Component tree test
+
+ test_tree<image2dint::psite> *gen, ref(c[9]), *l1, *l2;
+
+ // Create the ref
+ ref.insert_child(c[6]);
+ l1 = ref.insert_child(c[7]);
+ l1->insert_child(c[2]);
+ l1->insert_child(c[5]);
+ l2 = l1->insert_child(c[3]);
+ l2->insert_child(c[1]);
+
+ // Create the generated tree
+ gen = test_convert(n, n.Par_node(n.Root));
+
+ // Compare the trees
+ if (!compare_tree(gen, &ref))
+ {
+ std::cout << "Your tree :" << std::endl;
+ show_tree(gen);
+
+ std::cout << "Reference tree :" << std::endl;
+ show_tree(&ref);
+
+ std::cerr << "Component tree is fucked up!" << std::endl;
+ return 1;
+ }
+
+ // Test the LCA alorithm
+ image2dint::psite p(0, 0), q(0, 6), r(2, 4);
+ if (n.lca(p, q) != image2dint::psite(2,4))
+ return 1;
+
+ if (n.lca(r, q) != image2dint::psite(2,4))
+ return 1;
+
+ // Test the Highest Fork
+ p_set<image2dint::psite> comp;
+ comp.insert(image2dint::psite(0,0));
+ comp.insert(image2dint::psite(2,4));
+ comp.insert(image2dint::psite(0,6));
+ comp.insert(image2dint::psite(1,0));
+
+ if (n.highest_fork(comp) != image2dint::psite(2, 4))
+ return 1;
+
+ comp.insert(image2dint::psite(3,2));
+
+ if (n.highest_fork(comp) != image2dint::psite(1, 3))
+ return 1;
+
+ std::cout << "Test is successfull" << std::endl;
+
+ return 0;
+}
Index: sandbox/abraham/morpho/images/result_m_watershed.pgm
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: sandbox/abraham/morpho/images/result_m_watershed.pgm
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Index: sandbox/abraham/morpho/images/result_topo_watershed.pgm
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: sandbox/abraham/morpho/images/result_topo_watershed.pgm
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Index: sandbox/abraham/morpho/images/test_watershed.pgm
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: sandbox/abraham/morpho/images/test_watershed.pgm
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Index: sandbox/abraham/morpho/images/test_component_mapping.pgm
--- sandbox/abraham/morpho/images/test_component_mapping.pgm (revision 0)
+++ sandbox/abraham/morpho/images/test_component_mapping.pgm (revision 0)
@@ -0,0 +1,5 @@
+P5
+# CREATOR: GIMP PNM Filter Version 1.1
+9 6
+255
+
\ No newline at end of file
Index: sandbox/abraham/morpho/test_watershed.cc
--- sandbox/abraham/morpho/test_watershed.cc (revision 0)
+++ sandbox/abraham/morpho/test_watershed.cc (revision 0)
@@ -0,0 +1,55 @@
+#include <mln/core/image2d.hh>
+#include <mln/core/window2d.hh>
+#include <mln/core/neighb2d.hh>
+#include <mln/core/p_set.hh>
+
+#include <mln/value/int_u8.hh>
+#include <mln/level/compare.hh>
+
+#include <mln/io/pgm/load.hh>
+#include <mln/io/pgm/save.hh>
+
+#include "basic_najman.hh"
+#include <string>
+#include <iostream>
+
+int print_and_exit (std::string s)
+{
+ std::cerr << s << std::endl;
+ return 1;
+}
+
+int main ()
+{
+ using namespace mln;
+ using value::int_u8;
+
+ typedef image2d<int_u8> image2dint;
+
+ image2dint input, mverif;
+ io::pgm::load(input, "./images/test_watershed.pgm");
+ // io::pgm::load(input, "./images/lena.pgm");
+ io::pgm::load(mverif, "./images/result_m_watershed.pgm");
+
+ morpho::basic_najman< image2d<int_u8>, neighb2d> n(input, c4());
+ n.go();
+ n.m_watershed();
+
+ bool equal = true;
+
+ equal = n.pima == mverif;
+
+ if (!equal)
+ {
+ std::cout << "M-Watershed broken" << std::endl;
+ return 1;
+ }
+ else
+ std::cout << "M-Watershed good as chocolate ice cream" <<
std::endl;
+
+ // n.w_watershed();
+
+ io::pgm::save(n.pima, "out.pgm");
+
+ return 0;
+}
Index: sandbox/abraham/morpho/Makefile
--- sandbox/abraham/morpho/Makefile (revision 1870)
+++ sandbox/abraham/morpho/Makefile (working copy)
@@ -1,8 +1,16 @@
-SOURCE=test.cc
-OBJECTS=test.o
CXXFLAGS=-Wall -W -I ../../.. -g
-all:${OBJECTS}
- g++ ${FLAGS} ${OBJECTS} -o test
+test: test.o
+ g++ ${CXXFLAGS} test.o -o test
+test_component_tree: test_component_tree.o
+ g++ ${CXXFLAGS} test_component_tree.o -o test_component_tree
+
+test_watershed: test_watershed.o
+ g++ ${CXXFLAGS} test_watershed.o -o test_watershed
+
+all: tree
+
+test_watershed.o: basic_najman.hh
+test_component_tree.o: basic_najman.hh
test.o: basic_najman.hh
\ No newline at end of file