Commit 0697169a authored by Baptiste Esteban's avatar Baptiste Esteban
Browse files

Make horizontal_cut member of component_tree + update doc

parent 38cb3771
Pipeline #28140 failed with stage
in 19 minutes and 40 seconds
......@@ -10,7 +10,7 @@ Alpha Tree
Compute the alpha tree (also known as quasi-flat zone hierarchy) and returns a pair
`(tree, node_map)`. See :doc:`component_tree` for more information about the
representation of tree.
representation of tree. The implementation is based on the Kruskal algorithm [Naj13]_.
:param input: The input image
:param nbh: The neighborhood
......@@ -54,24 +54,29 @@ between a node of the tree and a pixel of the image being represented by blue da
Example
-------
This example is used to generate the grayscale lena cut with a threshold of 3 below.
::
#include <mln/accu/accumulators/mean.hpp>
#include <mln/morpho/alphatree.hpp>
#include <mln/core/image/ndimage.hpp>
#include <mln/core/neighborhood/c4.hpp>
#include <mln/morpho/cut.hpp> // for horizontal_cut_labelization_from
mln::image2d<uint8_t> input = ...;
// Compute the alpha tree
auto [tree, node_map] = mln::morpho::alphatree(input, mln::c4);
// Compute an attribute (for example the average pixels value at each node, as below)
auto mean = t.compute_attribute_on_values(node_map, input, mln::accu::accumulators::mean<std::uint8_t>());
auto mean = t.compute_attribute_on_values(node_map, input, mln::accu::accumulators::mean<uint8_t>());
// Making an horizontal cut of the tree
auto th = ...;
auto cut = mln::morpho::horizontal_cut_labelization_from(t, node_map, th, mean);
const auto threshold = 3; // Threshold of the horizontal cut, that means the lowest alpha in the cut
auto nodemap_cut = t.horizontal_cut(threshold, node_map); // Return a new nodemap associated to the cut
// Labelizing the cut with the mean values of each node
auto out = t.reconstruct_from(nodemap_cut, ranges::make_span(mean)); // Using range-v3 span
.. list-table::
......@@ -93,3 +98,5 @@ Complexity
References
----------
.. [Naj13] Laurent Najman, Jean Cousty, and Benjamin Perret (2013). Playing with kruskal: algorithms for morphological trees in edge-weighted graphs. *International Symposium on Mathematical Morphology and Its Applications to Signal and Image Processing*. Springer, Berlin, Heidelberg. 135-146
\ No newline at end of file
......@@ -285,17 +285,14 @@ Horizontal cut
When the tree is a hierarchy of partition, such as the :doc:`alphatree`, it is possible
to make an horizontal cut of this tree.
* Include :file:`<mln/morpho/cut.hpp>`
.. cpp:function:: I horizontal_cut(const T threshold, I nodemap) const
I horizontal_cut_from_levels(const T threshold, I nodemap, ::ranges::span<V> levels) const
.. cpp:function:: auto horizontal_cut_labelization_from(const component_tree<V>& t, Nodemap nm, V th, const std::vector<L>& vals)
auto horizontal_cut_labelization_from(const component_tree<V>& t, Nodemap nm, V th, ::ranges::span<L> vals)
Make an horizontal cut at threshold ``threshold`` of the tree and return the nodemap associated to the cut.
Make an horizontal cut at threshold ``th`` of the tree and labelize the node on a reconstructed image with the value ``vals``.
:param t: The tree.
:param nm: The node map.
:param th: The threshold of the cut.
:param vals: the value assigned to each node of the tree for the labelization.
:param threshold: The threshold of the horizontal cut
:param nodemap: An image thats maps ``point -> node id``
:param levels: (Optional) The altitude of each node in the tree (for example the :math:`\alpha` associated to each node for the alphatree).
A complete example
------------------
......
......@@ -6,12 +6,11 @@
#include <mln/io/imread.hpp>
#include <mln/io/imsave.hpp>
#include <mln/morpho/alphatree.hpp>
#include <mln/morpho/cut.hpp>
#include <iostream>
template <typename V>
void process_example(const mln::image2d<V>& img, const std::string& cut_filename, double threshold)
void process_example(const mln::image2d<V>& img, const std::string& cut_filename, const double threshold)
{
// 2. Build the alphatree
auto [t, nm] = mln::morpho::alphatree(img, mln::c4);
......@@ -22,7 +21,10 @@ void process_example(const mln::image2d<V>& img, const std::string& cut_filename
auto mean = t.compute_attribute_on_values(nm, img, mln::accu::accumulators::mean<V>());
// 4. Compute a cut of the alphatree
auto cut = mln::morpho::horizontal_cut_labelization_from(t, nm, th_value_type(threshold), mean);
auto cut_nm = t.horizontal_cut(th_value_type(threshold), nm);
// 5. Labelize the cut
auto cut = t.reconstruct_from(cut_nm, ::ranges::make_span(mean));
// 5. Save the output cut
mln::io::imsave(mln::view::cast<V>(cut), cut_filename);
......
......@@ -2,6 +2,7 @@
#include <mln/accu/accumulator.hpp>
#include <mln/core/algorithm/clone.hpp>
#include <mln/core/image/view/zip.hpp>
#include <mln/core/range/foreach.hpp>
#include <mln/core/trace.hpp>
......@@ -26,13 +27,12 @@ namespace mln::morpho
class component_tree;
template <>
class component_tree<void>
{
public:
using node_id_type = int;
using node_map_t = int;
using node_map_t = int;
/// \brief Filter the tree given a predicate that removes some nodes according to the selected strategy.
......@@ -67,7 +67,6 @@ namespace mln::morpho
void filter(ct_filtering strategy, I node_map, F pred);
/// \brief Compute the depth attribute over a tree
std::vector<int> compute_depth() const;
......@@ -106,7 +105,14 @@ namespace mln::morpho
std::vector<typename accu::result_of<Accu, image_pixel_t<J>>::type> //
compute_attribute_on_pixels(I node_map, J values, Accu acc);
/// \brief Compute the horizontal cut of a hierarchie at level `threshold` and return a nodemap
/// valued with the node indices of the lowest nodes satisfying levels[n] > threshold
///
/// \param threshold The threshold of the cut
/// \param nodemap Image point -> node_id mapping
/// \param levels Altitude of each node in the tree
template <class T, class I, class V>
I horizontal_cut_from_levels(const T threshold, I nodemap, ::ranges::span<V> levels) const;
/// \brief Reconstruct an image from an attribute map
......@@ -117,7 +123,6 @@ namespace mln::morpho
image_ch_value_t<I, V> reconstruct_from(I node_map, ::ranges::span<V> values) const;
using node_t = int;
std::vector<node_t> parent;
......@@ -137,6 +142,11 @@ namespace mln::morpho
class component_tree : public component_tree<void>
{
public:
template <class I>
I horizontal_cut(const T threshold, I nodemap) const
{
return this->horizontal_cut_from_levels(threshold, nodemap, ::ranges::make_span(values.data(), values.size()));
}
template <class I>
image_ch_value_t<std::remove_reference_t<I>, T> reconstruct(I&& node_map)
......@@ -167,7 +177,7 @@ namespace mln::morpho
template <class I, class F>
void component_tree<void>::update_node_map(I node_map, F pred) const
{
mln_foreach(auto& id, node_map.values())
mln_foreach (auto& id, node_map.values())
{
if (id > 0 && !pred(id))
id = this->parent[id];
......@@ -175,7 +185,6 @@ namespace mln::morpho
}
template <class F>
void component_tree<void>::filter(ct_filtering strategy, F pred)
{
......@@ -213,7 +222,7 @@ namespace mln::morpho
// Propagate upward
for (std::size_t i = n - 1; i > 0; --i)
{
pass[i] = pass[i] || pred(i);
pass[i] = pass[i] || pred(i);
pass[parent[i]] = pass[parent[i]] || pass[i];
}
......@@ -260,7 +269,7 @@ namespace mln::morpho
// Propagate upward
for (std::size_t i = n - 1; i > 0; --i)
{
pass[i] = pass[i] || pred(static_cast<int>(i));
pass[i] = pass[i] || pred(static_cast<int>(i));
pass[parent[i]] = pass[parent[i]] || pass[i];
}
this->filter_direct(pass);
......@@ -270,9 +279,6 @@ namespace mln::morpho
}
template <class I, class Accu>
std::vector<typename accu::result_of<Accu, image_point_t<I>>::type>
component_tree<void>::compute_attribute_on_points(I node_map, Accu acc)
......@@ -288,7 +294,7 @@ namespace mln::morpho
std::vector<decltype(a)> attr(parent.size(), a);
// Accumulate for each point
mln_foreach(auto px, node_map.pixels())
mln_foreach (auto px, node_map.pixels())
attr[px.val()].take(px.point());
......@@ -322,7 +328,7 @@ namespace mln::morpho
// Accumulate for each point
auto zz = mln::view::zip(node_map, input);
mln_foreach((auto [node_id, val]), zz.values())
mln_foreach ((auto [node_id, val]), zz.values())
attr[node_id].take(val);
......@@ -373,7 +379,23 @@ namespace mln::morpho
return out;
}
template <class T, class I, class V>
I component_tree<void>::horizontal_cut_from_levels(const T threshold, I nodemap, ::ranges::span<V> levels) const
{
mln_entering("mln::morpho::component_tree::horizontal_cut_from_levels");
auto root_cut_cc = std::vector<int>(parent.size());
for (std::size_t node = 0; node < parent.size(); ++node)
{
int parent_node = parent[node];
root_cut_cc[node] = levels[parent_node] > threshold ? node : root_cut_cc[parent_node];
}
auto out = mln::clone(nodemap);
mln_foreach (auto px, out.pixels())
out(px.point()) = root_cut_cc[px.val()];
return out;
}
template <class I, class V>
image_ch_value_t<I, V> component_tree<void>::reconstruct_from(I node_map, ::ranges::span<V> values) const
......@@ -390,4 +412,4 @@ namespace mln::morpho
}
} // namespace mln::morpho::
} // namespace mln::morpho
#pragma once
#include <mln/morpho/component_tree.hpp>
namespace mln::morpho
{
template <typename V, typename Nodemap, typename Th, typename L>
auto horizontal_cut_labelization_from(const component_tree<V>& t, Nodemap nm, Th th, ::ranges::span<L> vals);
template <typename V, typename Nodemap, typename Th, typename L>
auto horizontal_cut_labelization_from(const component_tree<V>& t, Nodemap nm, Th th, const std::vector<L>& vals);
/******************
* Implementation *
******************/
template <typename V, typename Nodemap, typename Th, typename L>
requires(std::is_convertible_v<Th, V>) auto horizontal_cut_labelization_from(const component_tree<V>& t, Nodemap nm,
Th th, ::ranges::span<L> vals)
{
static_assert(mln::is_a_v<Nodemap, mln::details::Image>);
static_assert(std::is_same_v<image_value_t<Nodemap>, int>);
using ValueType = std::remove_cv_t<L>;
assert(static_cast<std::size_t>(vals.size()) == t.parent.size());
image_ch_value_t<Nodemap, ValueType> lbl = imchvalue<ValueType>(nm);
std::size_t n = t.parent.size();
// Root of cut connected component
std::vector<std::size_t> root_cut_cc(n);
for (std::size_t node = 0; node < n; ++node)
{
std::size_t parent_node = t.parent[node];
root_cut_cc[node] = t.values[parent_node] > th ? node : root_cut_cc[parent_node];
}
// Reconstruction of image
mln_foreach (auto px, nm.pixels())
lbl(px.point()) = vals[root_cut_cc[px.val()]];
return lbl;
}
template <typename V, typename Nodemap, typename Th, typename L>
requires(std::is_convertible_v<Th, V>) auto horizontal_cut_labelization_from(const component_tree<V>& t, Nodemap nm,
Th th, const std::vector<L>& vals)
{
return horizontal_cut_labelization_from(t, nm, th, ::ranges::make_span(vals.data(), vals.size()));
}
} // namespace mln::morpho
\ No newline at end of file
......@@ -20,7 +20,6 @@ add_core_test(${test_prefix}ToS tos.cpp tos_tests_helper.cpp)
add_core_test(${test_prefix}depth_first depthfirst.cpp)
add_core_test(${test_prefix}maxtree maxtree.cpp)
add_core_test(${test_prefix}alphatree alphatree.cpp)
add_core_test(${test_prefix}cut cut.cpp)
add_core_test(${test_prefix}private_directional_hqueue directional_hqueue.cpp)
......
#include <mln/accu/accumulators/mean.hpp>
#include <mln/core/colors.hpp>
#include <mln/core/image/ndimage.hpp>
#include <mln/core/neighborhood/c26.hpp>
#include <mln/core/neighborhood/c4.hpp>
#include <mln/core/neighborhood/c8.hpp>
#include <mln/morpho/alphatree.hpp>
#include <mln/morpho/cut.hpp>
#include <fixtures/ImageCompare/image_compare.hpp>
......@@ -358,4 +358,41 @@ TEST(Morpho, AlphaTree3DImageHQUEUE)
ASSERT_IMAGES_EQ_EXP(cut(t, nm, 0u), ref_0);
ASSERT_IMAGES_EQ_EXP(cut(t, nm, 16u), ref_16);
}
TEST(Morpho, AlphaTreeCutMeanLabelization)
{
mln::image2d<std::uint8_t> ima = {
{1, 1, 5}, //
{2, 5, 6}, //
{0, 1, 4} //
};
mln::image2d<std::uint8_t> cut_1 = {
{1, 1, 5}, //
{1, 5, 5}, //
{0, 0, 4} //
};
mln::image2d<std::uint8_t> cut_2 = {
{1, 1, 5}, //
{1, 5, 5}, //
{1, 1, 5} //
};
mln::image2d<std::uint8_t> cut_3 = {
{2, 2, 2}, //
{2, 2, 2}, //
{2, 2, 2} //
};
auto [t, nm] = mln::morpho::alphatree(ima, mln::c4);
auto val = t.compute_attribute_on_values(nm, ima, mln::accu::accumulators::mean<std::uint8_t, std::uint8_t>());
auto make_cut = [&t, &nm, &val](const typename decltype(t.values)::value_type threshold) {
auto lbl = t.horizontal_cut(threshold, nm);
return t.reconstruct_from(lbl, ::ranges::make_span(val));
};
ASSERT_IMAGES_EQ_EXP(make_cut(0), ima);
ASSERT_IMAGES_EQ_EXP(make_cut(1), cut_1);
ASSERT_IMAGES_EQ_EXP(make_cut(2), cut_2);
ASSERT_IMAGES_EQ_EXP(make_cut(3), cut_3);
}
\ No newline at end of file
#include <mln/morpho/alphatree.hpp>
#include <mln/morpho/cut.hpp>
#include <mln/accu/accumulators/mean.hpp>
#include <mln/core/image/ndimage.hpp>
#include <mln/core/neighborhood/c4.hpp>
#include <fixtures/ImageCompare/image_compare.hpp>
#include <gtest/gtest.h>
#include <numeric>
TEST(Morpho, AlphaTreeCutMeanLabelization)
{
mln::image2d<std::uint8_t> ima = {
{1, 1, 5}, //
{2, 5, 6}, //
{0, 1, 4} //
};
mln::image2d<std::uint8_t> cut_1 = {
{1, 1, 5}, //
{1, 5, 5}, //
{0, 0, 4} //
};
mln::image2d<std::uint8_t> cut_2 = {
{1, 1, 5}, //
{1, 5, 5}, //
{1, 1, 5} //
};
mln::image2d<std::uint8_t> cut_3 = {
{2, 2, 2}, //
{2, 2, 2}, //
{2, 2, 2} //
};
auto [t, nm] = mln::morpho::alphatree(ima, mln::c4);
auto val = t.compute_attribute_on_values(nm, ima, mln::accu::accumulators::mean<std::uint8_t, std::uint8_t>());
ASSERT_IMAGES_EQ_EXP(mln::morpho::horizontal_cut_labelization_from(t, nm, 0, val), ima);
ASSERT_IMAGES_EQ_EXP(mln::morpho::horizontal_cut_labelization_from(t, nm, 1, val), cut_1);
ASSERT_IMAGES_EQ_EXP(mln::morpho::horizontal_cut_labelization_from(t, nm, 2, val), cut_2);
ASSERT_IMAGES_EQ_EXP(mln::morpho::horizontal_cut_labelization_from(t, nm, 3, val), cut_3);
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment