Commit d611ce4f authored by Baptiste Esteban's avatar Baptiste Esteban
Browse files

Merge branch 'development/small-fixes-hierarchies' into 'next'

Make some fixes in the hierarchical representations of Pylene

See merge request !119
parents a3bb40df 0a944f37
Pipeline #29205 passed with stages
in 27 minutes and 11 seconds
......@@ -298,24 +298,25 @@ Saliency Computation
--------------
It is also possible to compute the saliency map to obtain another visualization.
.. cpp:function:: auto saliency(Image node_map)
.. cpp:function:: auto saliency(image2d<int> node_map, ranges::span<double> values) const
Compute and return the saliency map of the tree.
Compute and return the saliency map of the tree. **Works only for 2D images and with tree node values of type** ``double``.
:param node_map: An image thats maps ``point -> node id``
:param values: the levels of the tree for each node
:return: The saliency map as an image
.. list-table::
* - .. image:: /images/watershed_hierarchy_area_gray.png
* - .. image:: /images/lena_gray.jpg
:width: 100%
- .. image:: /images/saliency_watershed.png
:width: 100%
* - Watershed hierarchy by area with a cut at a threshold of 25
- The corresponding saliency map
* - Original image
- Saliency map of the watershed hierarchy by area
A complete example
------------------
......
#include <mln/accu/accumulators/mean.hpp>
#include <mln/core/colors.hpp>
#include <mln/accu/accumulators/count.hpp>
#include <mln/core/image/ndimage.hpp>
#include <mln/core/image/view/cast.hpp>
#include <mln/core/neighborhood/c4.hpp>
#include <mln/io/imread.hpp>
#include <mln/io/imsave.hpp>
#include <mln/morpho/tos.hpp>
#include <mln/morpho/watershed_hierarchy.hpp>
#include <iostream>
void process_example(const mln::image2d<uint8_t>& img, const std::string& output_filename, const double threshold)
void process_example(const mln::image2d<uint8_t>& img, const std::string& output_filename)
{
// 2. Build the watershed hierarchy
auto area_attribute_func = [](auto tree, auto nm) -> std::vector<size_t> {
return tree.compute_attribute_on_points(nm, mln::accu::features::count<>());
auto area_attribute_func = [](auto tree, auto nm) {
return tree.compute_attribute_on_points(nm, mln::accu::features::count<double>());
};
auto [t, nm] = mln::morpho::watershed_hierarchy(img, area_attribute_func, mln::c4);
// 3. Compute the mean attribute
auto mean = t.compute_attribute_on_values(nm, img, mln::accu::accumulators::mean<uint8_t>());
// 3. Compute the saliency map
auto saliency = t.saliency(nm, ::ranges::make_span(t.values.data(), t.values.size()));
// 4. Compute a cut of the watershed hierarchy
auto cut_nm = t.horizontal_cut(threshold, nm);
// 5. Label the cut
auto cut = t.reconstruct_from(cut_nm, ::ranges::make_span(mean));
mln::io::imsave(mln::view::cast<uint8_t>(cut), output_filename);
mln::image2d<uint8_t> in;
mln::io::imread(output_filename, in);
auto [t2, node_map] = mln::morpho::tos(in, {0, 0});
auto saliency = t2.saliency(in);
// 5. Save the output
mln::io::imsave(mln::view::cast<uint8_t>(saliency), output_filename);
// 4. Save the output
mln::io::imsave(mln::view::cast<std::uint16_t>(saliency), output_filename);
}
int main(int argc, char* argv[])
......@@ -58,7 +41,7 @@ int main(int argc, char* argv[])
if (in.sample_type() == mln::sample_type_id::UINT8)
{
const auto* img = in.cast_to<std::uint8_t, 2>();
process_example(*img, out_filename, 25);
process_example(*img, out_filename);
}
else
{
......
......@@ -109,7 +109,7 @@ namespace mln
typedef typename accu_of<AccuLike, T>::type accu_type;
public:
typedef typename std::result_of<Extractor(accu_type)>::type type;
typedef typename std::invoke_result<Extractor, accu_type>::type type;
};
template <class A, class T, class E = default_extractor>
......
......@@ -90,7 +90,7 @@ namespace mln::morpho
/// \param acc Accumulator to apply on values
template <class I, class J, class Accu>
std::vector<typename accu::result_of<Accu, image_value_t<J>>::type> //
compute_attribute_on_values(I node_map, J values, Accu acc);
compute_attribute_on_values(I node_map, J values, Accu acc) const;
/// \brief Compute attribute on values
///
......@@ -101,7 +101,7 @@ namespace mln::morpho
/// \param acc Accumulator to apply on points
template <class I, class Accu>
std::vector<typename accu::result_of<Accu, image_point_t<I>>::type> //
compute_attribute_on_points(I node_map, Accu acc);
compute_attribute_on_points(I node_map, Accu acc) const;
/// \brief Compute attribute on pixels
......@@ -113,7 +113,7 @@ namespace mln::morpho
/// \param acc Accumulator to apply on values
template <class I, class J, class Accu>
std::vector<typename accu::result_of<Accu, image_pixel_t<J>>::type> //
compute_attribute_on_pixels(I node_map, J values, Accu acc);
compute_attribute_on_pixels(I node_map, J values, Accu acc) const;
/// \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
......@@ -135,9 +135,10 @@ namespace mln::morpho
using node_t = int;
/// \brief Produce a visualization of the given Component Tree using the Khalimsky grid of the saliency map
/// The component_tree must be built on a 2D image with a 4-connectivity.
///
/// \param node_map Image point -> node_id mapping
mln::image2d<double> saliency(mln::image2d<uint8_t> node_map);
mln::image2d<double> saliency(mln::image2d<int> node_map, ::ranges::span<double> values) const;
std::vector<node_t> parent;
......@@ -296,7 +297,7 @@ 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)
component_tree<void>::compute_attribute_on_points(I node_map, Accu acc) const
{
mln_entering("mln::morpho::component_tree::compute_attribute_on_points");
......@@ -329,7 +330,7 @@ namespace mln::morpho
template <class I, class J, class Accu>
std::vector<typename accu::result_of<Accu, image_value_t<J>>::type> //
component_tree<void>::compute_attribute_on_values(I node_map, J input, Accu acc)
component_tree<void>::compute_attribute_on_values(I node_map, J input, Accu acc) const
{
mln_entering("mln::morpho::component_tree::compute_attribute_on_values");
......@@ -363,7 +364,7 @@ namespace mln::morpho
template <class I, class J, class Accu>
std::vector<typename accu::result_of<Accu, image_pixel_t<J>>::type> //
component_tree<void>::compute_attribute_on_pixels(I node_map, J values, Accu acc)
component_tree<void>::compute_attribute_on_pixels(I node_map, J values, Accu acc) const
{
mln_entering("mln::morpho::component_tree::compute_attribute_on_pixels");
......
#include <mln/morpho/component_tree.hpp>
namespace mln::morpho
{
namespace internal
......@@ -33,82 +32,49 @@ namespace mln::morpho
return depth;
}
static std::vector<internal::edge_t<int, double>> saliency_map(mln::image2d<uint8_t> node_map)
mln::image2d<double> component_tree<void>::saliency(mln::image2d<int> node_map, ::ranges::span<double> values) const
{
std::vector<internal::edge_t<int, double>> res;
auto width = node_map.width();
auto dom = node_map.domain();
mln_foreach (auto p, dom)
{
for (auto q : mln::c4.after(p))
auto lca = [parent=parent, d=compute_depth()](int a, int b) {
while (a != b)
{
if (dom.has(q))
if (d[a] < d[b])
b = parent[b];
else if (d[a] > d[b])
a = parent[a];
else
{
auto offset_p = p[0] + width * p[1];
auto offset_q = q[0] + width * q[1];
internal::edge_t<int, double> edge = {offset_p, offset_q, 0};
edge.w = std::abs(node_map(p) - node_map(q));
res.emplace_back(edge);
a = parent[a];
b = parent[b];
}
}
}
return res;
}
mln::image2d<double> component_tree<void>::saliency(mln::image2d<uint8_t> node_map)
{
int height = node_map.height();
int width = node_map.width();
int res_height = 2 * height + 1;
int res_width = 2 * width + 1;
return b;
};
image2d<double> res(res_width, res_height);
const auto kwidth = node_map.width() * 2 - 1;
const auto kheight = node_map.height() * 2 - 1;
image2d<double> res(kwidth, kheight);
fill(res, 0);
const std::vector<internal::edge_t<int, double>>& s_map = saliency_map(node_map);
for (const auto [u, v, w] : s_map)
{
int u_pos[2] = {u % width, u / width};
int v_pos[2] = {v % width, v / width};
int res_offset[2] = {u_pos[0] - v_pos[0], u_pos[1] - v_pos[1]};
int res_edge_pos[2] = {2 * v_pos[0] + 1 + res_offset[0], 2 * v_pos[1] + 1 + res_offset[1]};
res({res_edge_pos[0], res_edge_pos[1]}) = w;
}
for (int y = 0; y < res_height; y += 2)
mln_foreach (auto p, node_map.domain())
{
for (int x = 0; x < res_width; x += 2)
for (auto q : c4.after(p))
{
double max = std::numeric_limits<double>::min();
if (y + 1 < height)
max = std::max(max, res({x, y + 1}));
if (x + 1 < width)
max = std::max(max, res({x + 1, y}));
if (y - 1 >= 0)
max = std::max(max, res({x, y - 1}));
if (x - 1 >= 0)
max = std::max(max, res({x - 1, y}));
res({x, y}) = max;
if (node_map.domain().has(q))
{
const auto dir = q - p;
res(point2d{2 * p[0], 2 * p[1]} + dir) = values[lca(node_map(p), node_map(q))];
}
}
}
for (int y = 0; y < res_height; y++)
mln_foreach(auto p, res.domain())
{
for (int x = 0; x < res_width; x++)
if (p[0] % 2 == 1 && p[1] % 2 == 1)
{
res({x, y}) = 255 - res({x, y});
for (auto q : c4(p))
{
if (res.domain().has(q) && res(p) < res(q))
res(p) = res(q);
}
}
}
......
#include <gtest/gtest.h>
#include <iostream>
#include <mln/core/image/ndimage.hpp>
#include <mln/io/imread.hpp>
#include <mln/io/imsave.hpp>
#include <mln/morpho/component_tree.hpp>
#include <mln/morpho/tos.hpp>
using node_t = int;
static std::vector<mln::morpho::edge_t<int, double>> saliency_map(mln::image2d<uint8_t> node_map)
{
std::vector<mln::morpho::edge_t<int, double>> res;
auto width = node_map.width();
auto dom = node_map.domain();
mln_foreach (auto p, dom)
{
for (auto q : mln::c4.after(p))
{
if (dom.has(q))
{
mln::morpho::edge_t<int, double> edge = {p[0] + width * p[1], q[0] + width * q[1], 0};
// std::cout << p[0] + width * p[1] << ' ' << node_map.index_of_point(p) << '\n';
// std::cout << q[0] + width * q[1] << ' ' << node_map.index_of_point(q) << '\n';
// std::cout << "\n\n";
edge.w = std::abs(node_map(p) - node_map(q));
res.emplace_back(edge);
}
}
}
return res;
}
TEST(Component_tree, Saliency_Map)
{
mln::image2d<uint8_t> img = {{0, 0, 100, 0}, {0, 0, 75, 0}, {95, 80, 60, 0}, {0, 0, 0, 0}};
int expected_s_map_val[24][3] = {{0, 1, 0}, {0, 4, 0}, {1, 2, 100}, {1, 5, 0}, {2, 3, 100}, {2, 6, 25},
{3, 7, 0}, {4, 5, 0}, {4, 8, 95}, {5, 6, 75}, {5, 9, 80}, {6, 7, 75},
{6, 10, 15}, {7, 11, 0}, {8, 9, 15}, {8, 12, 95}, {9, 10, 20}, {9, 13, 80},
{10, 11, 60}, {10, 14, 60}, {11, 15, 0}, {12, 13, 0}, {13, 14, 0}, {14, 15, 0}};
auto [t, node_map] = mln::morpho::tos(img, {0, 0});
#include <mln/morpho/alphatree.hpp>
const std::vector<mln::morpho::edge_t<int, double>>& s_map = saliency_map(img);
for (int i = 0; i < 24; ++i)
{
auto expected_edge = expected_s_map_val[i];
ASSERT_EQ(expected_edge[2], s_map[i].w);
}
}
#include <fixtures/ImageCompare/image_compare.hpp>
#include <gtest/gtest.h>
TEST(Component_tree, Saliency)
TEST(Morpho, SaliencyAlphaTree)
{
mln::image2d<uint8_t> img = {{0, 0, 100, 0}, {0, 0, 75, 0}, {95, 80, 60, 0}, {0, 0, 0, 0}};
int expected_saliency[9 * 9] = {255, 255, 255, 255, 155, 255, 155, 255, 255, //
255, 255, 255, 255, 155, 255, 155, 255, 255, //
255, 255, 255, 255, 155, 230, 155, 255, 255, //
255, 255, 255, 255, 180, 255, 180, 255, 255, //
160, 160, 160, 175, 175, 240, 180, 255, 255, //
255, 255, 240, 255, 235, 255, 195, 255, 255, //
160, 160, 160, 175, 175, 195, 195, 255, 255, //
255, 255, 255, 255, 255, 255, 255, 255, 255, //
255, 255, 255, 255, 255, 255, 255, 255, 255};
auto [t, node_map] = mln::morpho::tos(img, {0, 0});
auto saliency = t.saliency(img);
for (int i = 0; i < 9 * 9; ++i)
{
ASSERT_EQ(expected_saliency[i], static_cast<int>(saliency({i % 9, i / 9})));
}
mln::image2d<std::uint8_t> input = {
{1, 1, 3, 1}, //
{2, 6, 8, 7}, //
{5, 3, 2, 2} //
};
mln::image2d<double> expected = {
{0, 0, 0, 2, 0, 2, 0}, //
{1, 3, 3, 3, 3, 3, 3}, //
{0, 3, 0, 2, 0, 1, 0}, //
{3, 3, 3, 3, 3, 3, 3}, //
{0, 2, 0, 1, 0, 0, 0} //
};
auto [t, nm] =
mln::morpho::alphatree(input, mln::c4, [](auto a, auto b) -> double { return mln::functional::l2dist(a, b); });
const auto res = t.saliency(nm, ::ranges::make_span(t.values.data(), t.values.size()));
ASSERT_IMAGES_EQ_EXP(res, expected);
}
\ No newline at end of file
Markdown is supported
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