Commit 3afcc66a authored by Edwin Carlinet's avatar Edwin Carlinet
Browse files

Added immersion ToS algorithm.

parent 3ce2c29e
......@@ -62,6 +62,7 @@ target_sources(Pylene PRIVATE
src/io/imread.cpp
src/io/imprint.cpp
src/morpho/maxtree.cpp
src/morpho/immersion.cpp
src/morpho/component_tree.cpp
src/morpho/hvector.cpp
)
......
#pragma once
#include <mln/core/concept/new/images.hpp>
#include <mln/morpho/experimental/private/immersion.spe.hpp>
#include <mln/core/trace.hpp>
#include <utility>
#include <algorithm>
namespace mln::morpho::experimental::details
{
/// \brief Immerse a 2d or 3d image into twice-as-big 2d image
/// where the in-between pixel are intervals.
///
/// Given 4 2D-pixels:
/// a b
/// c d
/// The resulting image is the intervaled valued image:
///
/// [a] [ (a∧b) (a∨b)] [b]
/// [(a∧c) (a∨c)] [(a∧b∧c∧d) (a∨c∨d∨d)] [(d∧b) (d∨b)]
/// [c] [ (c∧d) (c∨d)] [d]
///
/// \param[in] ima The input 2D image
/// \return An intervaled valued 2D image
template <class I>
std::pair<image_concrete_t<I>, image_concrete_t<I>> //
immersion(I ima);
/******************************************/
/**** Implementation ****/
/******************************************/
namespace impl
{
// Generic implementation
template <class I, class P>
[[gnu::noinline]] //
void
line_immersion_T(I& f, P p, int xmin, int xmax, image_concrete_t<I>& inf, image_concrete_t<I>& sup)
{
if (xmin >= xmax)
return;
p.x() = xmin;
auto q = 2 * p;
auto a = f(p);
inf(q) = a;
sup(q) = a;
for (int x = xmin + 1; x < xmax; ++x)
{
p.x() = x;
auto prev = a;
a = f(p);
auto [m, M] = std::minmax(a, prev);
q.x() = 2 * x - 1;
inf(q) = m;
sup(q) = M;
q.x() = 2 * x;
inf(q) = a;
sup(q) = a;
}
}
template <class I>
[[gnu::noinline]] //
void
line_interpolation2d_T(int y, int xmin, int xmax, I& inf, I& sup)
{
for (int x = xmin; x < xmax; ++x)
{
inf({x, y}) = std::min(inf({x, y - 1}), inf({x, y + 1}));
sup({x, y}) = std::max(sup({x, y - 1}), sup({x, y + 1}));
}
}
// 2D generic version of immersion
template <class I>
void immersion_T(I& f, const mln::experimental::box2d& roi, image_concrete_t<I>& inf, image_concrete_t<I>& sup)
{
if (roi.empty())
return;
int xmin = roi.tl().x();
int ymin = roi.tl().y();
int xmax = roi.br().x();
int ymax = roi.br().y();
// First line
line_immersion_T(f, mln::experimental::point2d{0, ymin}, xmin, xmax, inf, sup);
for (int y = ymin + 1; y < ymax; ++y)
{
line_immersion_T(f, mln::experimental::point2d{0, y}, xmin, xmax, inf, sup);
line_interpolation2d_T(2 * y - 1, xmin * 2, xmax * 2 - 1, inf, sup);
}
}
// Dispatch
// \{
// For 2d-buffer images
template <class T>
void immersion(mln::experimental::image2d<T>& f, mln::experimental::box2d, mln::experimental::image2d<T>& inf, mln::experimental::image2d<T>& sup)
{
mln_entering("mln::morpho::experimental::details::immersion (2d-buffer)")
immersion_impl_table_t<T> impl;
immersion_ndimage(f, inf, sup, &impl);
}
// For 3d-buffer images (To be implemented)
template <class T>
void immersion(mln::experimental::image3d<T>& f, mln::experimental::box3d, mln::experimental::image3d<T>& inf, mln::experimental::image3d<T>& sup)
{
mln_entering("mln::morpho::experimental::details::immersion (3d-buffer)")
immersion_impl_table_t<T> impl;
immersion_ndimage(f, inf, sup, &impl);
}
// Fallback for 2d-images
template <class I>
void immersion(mln::experimental::Image<I>& f, mln::experimental::box2d roi, image_concrete_t<I>& inf, image_concrete_t<I>& sup)
{
mln_entering("mln::morpho::experimental::details::immersion (generic)")
immersion_T(static_cast<I&>(f), roi, inf, sup);
}
// Fallback for 3d-images (To be implemented)
template <class I>
void immersion(mln::experimental::Image<I>& f, mln::experimental::box3d roi, image_concrete_t<I>& inf, image_concrete_t<I>& sup);
// \}
} // namespace impl
template <class I>
std::pair<image_concrete_t<I>, image_concrete_t<I>> //
immersion(I ima)
{
static_assert(mln::is_a<I, mln::experimental::Image>());
static_assert(std::is_same_v<image_domain_t<I>, mln::experimental::box2d> ||
std::is_same_v<image_domain_t<I>, mln::experimental::box3d>,
"Input domain must be a box2d or a box3d");
int dim = image_point_t<I>::ndim;
// Compute the extended domain
auto dom = ima.domain();
for (int d = 0; d < dim; ++d)
{
dom.tl()[d] = dom.tl()[d] * 2;
dom.br()[d] = dom.br()[d] * 2 - 1;
}
image_concrete_t<I> inf(dom);
image_concrete_t<I> sup(dom);
impl::immersion(ima, ima.domain(), inf, sup);
return { std::move(inf), std::move(sup) };
}
}
#pragma once
#include <mln/core/image/experimental/ndbuffer_image.hpp>
#include <mln/core/image/experimental/ndimage_fwd.hpp>
#include <cstddef>
#include <cstdint>
namespace mln::morpho::experimental::details
{
/// Immersion algorithm for raw-contiguous buffer
///
/// Given an input buffer of size \c n
/// [a, b, c, d, e],
/// it computes:
/// inf = [a; min(a,b); b; min(b,c); c; min(c,d); d; min(d,e); e]
/// sup = [a; max(a,b); b; max(b,c); c; max(c,d); d; max(d,e); e]
template <class T>
void buffer_immersion(const T* __restrict input, std::size_t n, T* __restrict inf, T* __restrict sup);
/// Interpolation algorithm for raw-contiguous buffer.
/// Compute the PW min/max between two buffer
///
/// Given two input buffers
/// inf = [a; min(a,b); b; min(b,c); c; min(c,d); d; min(d,e); e]
/// sup = [a; max(a,b); b; max(b,c); c; max(c,d); d; max(d,e); e]
template <class T>
void buffer_interpolation_max(const T* __restrict A, const T* __restrict B, std::size_t n, T* __restrict out);
template <class T>
void buffer_interpolation_min(const T* __restrict A, const T* __restrict B, std::size_t n, T* __restrict out);
struct immersion_impl_table_base_t
{
virtual void immersion(void* input, std::size_t n, void* inf, void* sup) = 0;
virtual void interpolation_max(void* A, void* B, std::size_t n, void* out) = 0;
virtual void interpolation_min(void* A, void* B, std::size_t n, void* out) = 0;
};
/// Type-erased immersion algorithm for any buffer-encoded image
void immersion_ndimage(ndbuffer_image& input,
ndbuffer_image& inf,
ndbuffer_image& sup,
immersion_impl_table_base_t* impl);
template <class T>
struct immersion_impl_table_t : immersion_impl_table_base_t
{
void immersion(void* input, std::size_t n, void* inf, void* sup) final
{
buffer_immersion(static_cast<const T*>(input), n, static_cast<T*>(inf), static_cast<T*>(sup));
}
void interpolation_max(void* A, void* B, std::size_t n, void* out) final
{
buffer_interpolation_max(static_cast<T*>(A), static_cast<T*>(B), n, static_cast<T*>(out));
}
void interpolation_min(void* A, void* B, std::size_t n, void* out) final
{
buffer_interpolation_min(static_cast<T*>(A), static_cast<T*>(B), n, static_cast<T*>(out));
}
};
/******************************************/
/**** Implementation ****/
/******************************************/
template <class T>
void buffer_immersion(const T* __restrict f, std::size_t n, T* __restrict inf, T* __restrict sup)
{
if (n == 0)
return;
inf[0] = f[0];
sup[0] = f[0];
for (std::size_t x = 1; x < n; ++x)
{
auto [m, M] = std::minmax(f[x-1], f[x]);
inf[2 * x - 1] = m;
sup[2 * x - 1] = M;
inf[2 * x] = f[x];
sup[2 * x] = f[x];
}
}
template <class T>
void buffer_interpolation_max(const T* __restrict A, const T* __restrict B, std::size_t n, T* __restrict out)
{
for (std::size_t i = 0; i < n; ++i)
out[i] = std::max(A[i], B[i]);
}
template <class T>
void buffer_interpolation_min(const T* __restrict A, const T* __restrict B, std::size_t n, T* __restrict out)
{
for (std::size_t i = 0; i < n; ++i)
out[i] = std::min(A[i], B[i]);
}
}
#include <mln/morpho/experimental/private/immersion.spe.hpp>
namespace mln::morpho::experimental::details
{
namespace
{
void immersion_image2d(std::byte* i_buffer, //
std::byte* inf_buffer, //
std::byte* sup_buffer, //
std::size_t width, //
std::size_t height, //
std::ptrdiff_t i_stride, //
std::ptrdiff_t inf_stride, //
std::ptrdiff_t sup_stride, //
immersion_impl_table_base_t* impl)
{
impl->immersion(i_buffer, width, inf_buffer, sup_buffer);
for (std::size_t y = 1; y < height; ++y)
{
impl->immersion(i_buffer + y * i_stride, width, inf_buffer + 2 * y * inf_stride,
sup_buffer + 2 * y * sup_stride);
impl->interpolation_min(inf_buffer + (2 * (y - 1)) * inf_stride, inf_buffer + (2 * y) * inf_stride,
2 * width - 1, inf_buffer + (2 * y - 1) * inf_stride);
impl->interpolation_max(sup_buffer + (2 * (y - 1)) * sup_stride, sup_buffer + (2 * y) * sup_stride,
2 * width - 1, sup_buffer + (2 * y - 1) * sup_stride);
}
}
void interpolate_image2d_min(std::byte* buffer, std::size_t width, std::size_t height, std::ptrdiff_t line_stride,
std::ptrdiff_t slice_stride, immersion_impl_table_base_t* impl)
{
std::byte* prev = buffer - slice_stride;
std::byte* next = buffer + slice_stride;
for (std::size_t y = 0; y < height; ++y)
impl->interpolation_min(prev + y * line_stride, next + y * line_stride, width, buffer + y * line_stride);
}
void interpolate_image2d_max(std::byte* buffer, std::size_t width, std::size_t height, std::ptrdiff_t line_stride,
std::ptrdiff_t slice_stride, immersion_impl_table_base_t* impl)
{
std::byte* prev = buffer - slice_stride;
std::byte* next = buffer + slice_stride;
for (std::size_t y = 0; y < height; ++y)
impl->interpolation_max(prev + y * line_stride, next + y * line_stride, width, buffer + y * line_stride);
}
} // namespace
void immersion_ndimage(ndbuffer_image& input,
ndbuffer_image& inf,
ndbuffer_image& sup,
immersion_impl_table_base_t* impl)
{
[[maybe_unused]] int pdim = input.pdim();
assert(pdim == 2 || pdim == 3);
assert(input.pdim() == pdim);
assert(inf.pdim() == pdim);
assert(sup.pdim() == pdim);
const int depth = input.depth();
const int height = input.height();
const int width = input.width();
std::byte* i_buffer = input.buffer();
std::byte* inf_buffer = inf.buffer();
std::byte* sup_buffer = sup.buffer();
const std::ptrdiff_t i_stride = input.byte_stride();
const std::ptrdiff_t inf_stride = inf.byte_stride();
const std::ptrdiff_t sup_stride = sup.byte_stride();
immersion_image2d(i_buffer, inf_buffer, sup_buffer, width, height, i_stride, inf_stride, sup_stride, impl);
for (int z = 1; z < depth; ++z)
{
immersion_image2d(i_buffer + z * input.byte_stride(2), //
inf_buffer + (2 * z) * inf.byte_stride(2), //
sup_buffer + (2 * z) * sup.byte_stride(2), //
width, height, i_stride, inf_stride, sup_stride, impl);
interpolate_image2d_min(inf_buffer + (2 * z - 1) * inf.byte_stride(2), 2 * width -1, 2 * height -1, inf_stride, inf.byte_stride(2), impl);
interpolate_image2d_max(sup_buffer + (2 * z - 1) * sup.byte_stride(2), 2 * width -1, 2 * height -1, sup_stride, sup.byte_stride(2), impl);
}
}
}
#include <mln/core/image/image2d.hpp>
#include <mln/core/image/image3d.hpp>
#include <mln/core/image/morphers/casted_image.hpp>
#include <mln/core/image/experimental/ndimage.hpp>
#include <mln/morpho/experimental/private/immersion.hpp>
#include <mln/accu/accumulators/max.hpp>
#include <mln/core/algorithm/accumulate.hpp>
#include <mln/morpho/tos/private/immersion.hpp>
#include <mln/morpho/tos/private/propagation.hpp>
#include <mln/morpho/tos/tos.hpp>
#include <mln/core/image/view/operators.hpp>
#include "tos_tests_helper.hpp"
#include <fixtures/ImageCompare/image_compare.hpp>
#include <algorithm>
#include <gtest/gtest.h>
using mln::uint8;
template <class I, class J>
void test_propagation(const mln::Image<I>& f, const mln::Image<J>& ref)
TEST(ToSImmersion, twodimensional)
{
using P = typename I::size_type;
std::vector<P> indexes;
int max_depth;
auto g = mln::morpho::ToS::impl::immersion(f);
mln_point(I) pmin = 0;
auto ord = mln::morpho::ToS::impl::propagation(g, pmin, max_depth, &indexes);
auto ima_ref = exact(ref);
int expected_max_depth = mln::accumulate(ima_ref, mln::accu::features::max<>());
const mln::experimental::image2d<uint8_t> f = {{0, 1, 2}, //
{3, 0, 1}};
ASSERT_IMAGES_EQ(ord, ref);
ASSERT_EQ(expected_max_depth, max_depth);
ASSERT_TRUE(std::is_sorted(indexes.begin(), indexes.end(), [&ord](P i, P j) { return ord[i] < ord[j]; }));
}
const mln::experimental::image2d<uint8_t> ref_inf = {{0, 0, 1, 1, 2}, //
{0, 0, 0, 0, 1}, //
{3, 0, 0, 0, 1}};
template <class I, class J>
void test_construction(const mln::Image<I>& f, const mln::Image<J>& ref_parent, const mln::Image<J>& ref_roots)
{
auto tree = mln::morpho::tos(f);
const mln::experimental::image2d<uint8_t> ref_sup = {{0, 1, 1, 2, 2}, //
{3, 3, 1, 2, 2}, //
{3, 3, 0, 1, 1}};
mln_ch_value(J, mln_point(J)) roots;
auto par = tree_as_parent_image(tree, roots);
ASSERT_IMAGES_EQ(par, ref_parent);
ASSERT_IMAGES_EQ(roots, ref_roots);
auto [inf, sup] = mln::morpho::experimental::details::immersion(f);
ASSERT_IMAGES_EQ_EXP(ref_inf, inf);
ASSERT_IMAGES_EQ_EXP(ref_sup, sup);
}
TEST(ToSImmersion, twodimensional)
TEST(ToSImmersion, twodimensional_generic)
{
const mln::image2d<mln::uint8> f = {{0, 1, 2}, {3, 0, 1}};
using namespace mln::view::ops;
const mln::experimental::image2d<uint8_t> f = {{0, 1, 2}, //
{3, 0, 1}};
auto fprime = f * uint8_t(1);
const mln::experimental::image2d<uint8_t> ref_inf = {{0, 0, 1, 1, 2}, //
{0, 0, 0, 0, 1}, //
{3, 0, 0, 0, 1}};
using R = mln::morpho::ToS::impl::irange<mln::uint8>;
const mln::experimental::image2d<uint8_t> ref_sup = {{0, 1, 1, 2, 2}, //
{3, 3, 1, 2, 2}, //
{3, 3, 0, 1, 1}};
const mln::image2d<R> ref = {{{0, 0}, {0, 1}, {1, 1}, {1, 2}, {2, 2}},
{{0, 3}, {0, 3}, {0, 1}, {0, 2}, {1, 2}},
{{3, 3}, {0, 3}, {0, 0}, {0, 1}, {1, 1}}};
auto g = mln::morpho::ToS::impl::immersion(f);
ASSERT_IMAGES_EQ(ref, g);
auto [inf, sup] = mln::morpho::experimental::details::immersion(fprime);
ASSERT_IMAGES_EQ_EXP(ref_inf, inf);
ASSERT_IMAGES_EQ_EXP(ref_sup, sup);
}
TEST(ToSImmersion, threedimensional)
{
const mln::image3d<mln::uint8> f = {
{{0, 1, 2}, {3, 0, 1}},
const mln::experimental::image3d<uint8_t> f = {
{{0, 1, 2}, {3, 0, 1}}, //
{{2, 0, 4}, {0, 0, 2}},
};
using R = mln::morpho::ToS::impl::irange<mln::uint8>;
const mln::image3d<R> ref = {
{{{0, 0}, {0, 1}, {1, 1}, {1, 2}, {2, 2}},
{{0, 3}, {0, 3}, {0, 1}, {0, 2}, {1, 2}},
{{3, 3}, {0, 3}, {0, 0}, {0, 1}, {1, 1}}},
{{{0, 2}, {0, 2}, {0, 1}, {0, 4}, {2, 4}},
{{0, 3}, {0, 3}, {0, 1}, {0, 4}, {1, 4}},
{{0, 3}, {0, 3}, {0, 0}, {0, 2}, {1, 2}}},
{{{2, 2}, {0, 2}, {0, 0}, {0, 4}, {4, 4}},
{{0, 2}, {0, 2}, {0, 0}, {0, 4}, {2, 4}},
{{0, 0}, {0, 0}, {0, 0}, {0, 2}, {2, 2}}},
const mln::experimental::image3d<uint8_t> ref_inf = {
{{0, 0, 1, 1, 2}, //
{0, 0, 0, 0, 1}, //
{3, 0, 0, 0, 1}}, //
{{0, 0, 0, 0, 2}, //
{0, 0, 0, 0, 1}, //
{0, 0, 0, 0, 1}}, //
{{2, 0, 0, 0, 4}, //
{0, 0, 0, 0, 2}, //
{0, 0, 0, 0, 2}}, //
};
auto g = mln::morpho::ToS::impl::immersion(f);
ASSERT_IMAGES_EQ(ref, g);
}
template <class T>
class ToSPropagation : public ::testing::Test
{
};
typedef ::testing::Types<mln::uint8, float> TestValueTypes;
TYPED_TEST_CASE(ToSPropagation, TestValueTypes);
TYPED_TEST(ToSPropagation, saddle_point)
{
const mln::image2d<TypeParam> f = {{0, 0, 0, 0}, //
{0, 2, 0, 0}, //
{0, 0, 2, 0}, //
{0, 0, 0, 0}};
const mln::image2d<uint8> ref = {{0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 1, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 1, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0}};
test_propagation(f, ref);
}
TYPED_TEST(ToSPropagation, two_branches_with_parallel_flooding)
{
const mln::image2d<TypeParam> f = {{0, 0, 0, 0}, //
{0, 2, 3, 0}, //
{0, 0, 2, 0}, //
{1, 2, 0, 0}};
const mln::image2d<uint8> ref = {{0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 2, 2, 3, 0, 0}, //
{0, 0, 0, 0, 2, 0, 0}, //
{0, 0, 0, 0, 2, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0}, //
{1, 1, 2, 0, 0, 0, 0}};
test_propagation(f, ref);
}
TYPED_TEST(ToSPropagation, two_branches_with_parallel_flooding_3d)
{
const mln::image3d<TypeParam> f = {{{0, 0, 0}, //
{0, 2, 3}, //
{0, 0, 2}}, //
{{1, 0, 1}, //
{0, 2, 1}, //
{1, 0, 2}}};
const mln::image3d<uint8> ref = {{{0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0}, //
{0, 0, 2, 2, 3}, //
{0, 0, 0, 0, 2}, //