Commit b71d21ec authored by Edwin Carlinet's avatar Edwin Carlinet

Moved rect2d impl in its own file and use periodic line decomp.

parent 464b7d0b
......@@ -59,7 +59,7 @@ Example 1 : Dilation by a square on a gray-level image
// Define a square SE of size 21x21
auto input = ...;
auto rect = mln::make_rectangle2d(21,21);
mln::se::rect2d rect(21,21);
auto output = mln::morpho::structural::dilate(input, rect);
......
#include <mln/core/image/image2d.hpp>
#include <mln/core/win2d.hpp>
#include <mln/core/se/rect2d.hpp>
#include <mln/morpho/structural/erode.hpp>
#include <mln/morpho/structural/dilate.hpp>
#include <mln/morpho/structural/opening.hpp>
......@@ -128,7 +128,7 @@ int main(int argc, char** argv)
auto nbh = mln::make_rectangle2d(size, size);
mln::se::rect2d nbh(size, size);
switch (vm["operator"].as<morpho_op_type>())
{
......
#ifndef MLN_CORE_SE_RECT2D_HPP
#define MLN_CORE_SE_RECT2D_HPP
#include <mln/core/domain/box.hpp>
#include <mln/core/neighborhood/dyn_neighborhood.hpp>
#include <mln/core/se/periodic_line2d.hpp>
/// \file
namespace mln
{
namespace se
{
struct rect2d;
/**************************/
/*** Implementation **/
/**************************/
/// \brief Define a dynamic rectangular window anchored at (0,0).
/// Its width and height are always odd numbers to ensure symmetry.
struct rect2d
#ifndef MLN_DOXYGEN
: dyn_neighborhood_base<dynamic_neighborhood_tag, rect2d>
#endif
{
typedef std::true_type is_incremental;
typedef std::true_type is_decomposable;
typedef dyn_neighborhood<box2d, dynamic_neighborhood_tag> dec_type;
typedef dyn_neighborhood<box2d, dynamic_neighborhood_tag> inc_type;
/// Construct an empty rectangle
rect2d() = default;
/// Construct a rectangle of size (Width × Height).
///
/// \param width The width of the rectangle. If \p width is even, it is
/// rounded to the closest lower odd int.
/// \param height The height of the rectangle. If \p height is even, it is
/// rounded to the closest lower odd int.
rect2d(int width, int height)
{
mln_precondition(width >= 0 && "A negative width was given.");
mln_precondition(height >= 0 && "A negative height was given.");
int xoffset = width / 2;
int yoffset = height / 2;
m_dpoints.pmin = {static_cast<short>(-yoffset), static_cast<short>(-xoffset)};
m_dpoints.pmax = {static_cast<short>(yoffset+1), static_cast<short>(xoffset+1)};
}
/// \brief A WNeighborhood to be added when used incrementally
inc_type inc() const
{
box2d b = this->m_dpoints;
b.pmin[1] = b.pmax[1] - 1;
return b;
}
/// \brief A WNeighborhood to be substracted when used incrementally
dec_type dec() const
{
box2d b = this->m_dpoints;
b.pmin[1] -= 1;
b.pmax[1] = b.pmin[1] + 1;
return b;
}
/// \brief Return a range of SE offsets
const mln::box2d& offsets() const { return m_dpoints; }
/// \brief Return true for any non-empty rectangle
bool decomposable() const { return !m_dpoints.empty(); }
/// \brief Return an horizontal line of length \p Width and a vertical
/// line of length \p Height corresponding to the SE decomposition.
std::array<periodic_line2d, 2> decompose() const
{
const int v = m_dpoints.pmax[0] - 1;
const int h = m_dpoints.pmax[1] - 1;
periodic_line2d hline(point2d{0,1}, h);
periodic_line2d vline(point2d{1,0}, v);
return {{hline, vline}};
}
private:
box2d m_dpoints;
};
} // end of namespace mln::se
} // end of namespace mln
#endif //! MLN_CORE_SE_RECT2D_HPP
......@@ -13,17 +13,6 @@
namespace mln
{
///
/// \brief Define a dynamic rectangular window
///
struct rect2d;
/// \defgroup Free functions
/// \{
rect2d make_rectangle2d(unsigned height, unsigned width);
rect2d make_rectangle2d(unsigned height, unsigned width, point2d center);
/// \}
namespace
{
......@@ -73,67 +62,6 @@ namespace mln
static const winc2_h_t winc2_h{};
}
/**************************/
/*** Implementation **/
/**************************/
struct rect2d : dyn_neighborhood_base<dynamic_neighborhood_tag, rect2d>
{
typedef std::true_type is_incremental;
typedef std::false_type is_separable;
typedef dyn_neighborhood<box2d, dynamic_neighborhood_tag> dec_type;
typedef dyn_neighborhood<box2d, dynamic_neighborhood_tag> inc_type;
rect2d() = default;
rect2d(const box2d& r) : dpoints(r) {}
inc_type inc() const
{
box2d b = this->dpoints;
b.pmin[1] = b.pmax[1] - 1;
return b;
}
dec_type dec() const
{
box2d b = this->dpoints;
b.pmin[1] -= 1;
b.pmax[1] = b.pmin[1] + 1;
return b;
}
const box2d& offsets() const { return dpoints; }
const box2d dpoints;
};
inline rect2d make_rectangle2d(unsigned height, unsigned width)
{
mln_precondition(height % 2 == 1);
mln_precondition(width % 2 == 1);
int h = height / 2;
int w = width / 2;
box2d b = {point2d(-h, -w), point2d(h + 1, w + 1)};
return b;
}
inline rect2d make_rectangle2d(unsigned height, unsigned width, point2d center)
{
mln_precondition(height % 2 == 1);
mln_precondition(width % 2 == 1);
unsigned h = height / 2;
unsigned w = width / 2;
point2d uleft = center;
point2d lright = center;
uleft[0] -= h;
uleft[1] -= w;
lright[0] += h + 1;
lright[1] += w + 1;
box2d b = {uleft, lright};
return b;
}
}
#endif // ! MLN_CORE_WIN2D_HPP
......@@ -2,7 +2,7 @@
#define MLN_MORPHO_CANVAS_DILATION_LIKE_SPE_HPP
#include <mln/core/algorithm/transpose.hpp>
#include <mln/core/win2d.hpp>
#include <mln/core/se/rect2d.hpp>
#include <mln/morpho/canvas/dilation_like.hpp>
namespace mln
......@@ -23,9 +23,9 @@ namespace mln
// might be costly due to cycle detection.
template <class I, class Compare, class J, class OpTraits>
typename std::enable_if<std::is_same<typename I::domain_type, box2d>::value>::type
dilation_like(const Image<I>& ima_, const rect2d& nbh, Compare cmp, Image<J>& output, OpTraits __op__)
dilation_like(const Image<I>& ima_, const se::rect2d& nbh, Compare cmp, Image<J>& output, OpTraits __op__)
{
box2d r = nbh.dpoints;
box2d r = nbh.offsets();
const I& ima = exact(ima_);
if (r.shape()[0] == 1)
......@@ -34,7 +34,7 @@ namespace mln
}
else if (r.shape()[1] == 1)
{
rect2d h0{box2d{{0, r.pmin[0]}, {1, r.pmax[0]}}};
se::rect2d h0(r.pmax[0] - r.pmin[0], 1);
mln_concrete(I) tmp = transpose(ima);
mln_concrete(I) out = imconcretize(tmp);
morpho::canvas::dilation_like(tmp, h0, cmp, out, __op__);
......@@ -43,8 +43,8 @@ namespace mln
else
{
rect2d h0{box2d{{0, r.pmin[0]}, {1, r.pmax[0]}}};
rect2d h1{box2d{{0, r.pmin[1]}, {1, r.pmax[1]}}};
se::rect2d h0(r.pmax[0] - r.pmin[0], 1);
se::rect2d h1(r.pmax[1] - r.pmin[1], 1);
image2d<mln_value(I)> f;
{
......
......@@ -2,7 +2,6 @@ add_executable(bench_iterator ../bench_iterator.cpp ../helpers.hpp)
add_executable(bench_zip_iterator ../bench_zip_iterator.cpp)
set(test_prefix "UTCore_")
add_core_test(${test_prefix}win2d win2d.cpp)
# test Domain
add_core_test(${test_prefix}box domain/box.cpp)
......@@ -51,3 +50,4 @@ add_core_test(${test_prefix}sort_indexes algorithm/sort_indexes.cpp)
# test SE
add_core_test(${test_prefix}disc se/disc.cpp)
add_core_test(${test_prefix}rect2d se/rect2d.cpp)
#include <mln/core/se/rect2d.hpp>
#include <gtest/gtest.h>
TEST(Core, Rect2d)
{
mln::se::rect2d win(5, 3);
mln_iter(p, win(mln::literal::zero));
p.init();
for (int i = -1; i <= 1; ++i)
for (int j = -2; j <= 2; ++j, p.next())
ASSERT_EQ(*p, mln::point2d(i, j));
}
#include <mln/core/win2d.hpp>
#include <gtest/gtest.h>
TEST(Core, win2d_general)
{
using namespace mln;
{
rect2d win = make_rectangle2d(3, 5);
mln_iter(p, win(literal::zero));
p.init();
for (int i = -1; i <= 1; ++i)
for (int j = -2; j <= 2; ++j, p.next())
ASSERT_EQ(*p, point2d(i, j));
}
}
......@@ -48,7 +48,41 @@ TEST(Dilation, Disc_euclidean)
ASSERT_IMAGES_EQ(ref, output);
}
TEST(Morpho, Generic_wide_enough_extension)
TEST(Dilation, Rectangle2d)
{
mln::box2d domain{ {0,0}, {21, 21}};
mln::image2d<mln::uint8> input(domain, 0);
input.at(10,10) = 1;
const mln::image2d<mln::uint8> ref = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, //
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} //
};
auto output = mln::morpho::structural::dilate(input, mln::se::rect2d(19, 15));
ASSERT_IMAGES_EQ(ref, output);
}
TEST(Dilation, Generic_with_wide_enough_extension)
{
image2d<uint8> ima;
io::imread(MLN_IMG_PATH "small.pgm", ima);
......@@ -72,23 +106,22 @@ TEST(Dilation, Generic_with_too_small_extension)
}
// Dilation on a with a vmorph / binary case
TEST(Morpho, dilation_dilate_2)
TEST(Dilation, Square_on_a_vmorph)
{
image2d<uint8> ima;
io::imread(MLN_IMG_PATH "small.pgm", ima);
auto win = make_rectangle2d(3, 3);
mln::se::rect2d win(3, 3);
auto out = morpho::structural::dilate(ima > 128, win);
ASSERT_TRUE(all(out >= (ima > 128))); // extensive
}
// Dilation on a with a vmorph / binary case
TEST(Morpho, dilation_dilate_view)
TEST(Dilation, Unregular_domain)
{
image2d<uint8> ima;
io::imread(MLN_IMG_PATH "small.pgm", ima);
auto win = make_rectangle2d(3, 3);
mln::se::rect2d win(3, 3);
auto out = clone(ima);
auto tmp = out | where(ima > 128);
morpho::structural::dilate(ima | where(ima > 128), win, tmp, std::less<uint8>());
......@@ -97,18 +130,18 @@ TEST(Morpho, dilation_dilate_view)
}
// Custom comparison function, erosion
TEST(Morpho, dilation_dilate_with_custom_cmp_function)
TEST(Dilation, Custom_cmp_function)
{
image2d<uint8> ima;
io::imread(MLN_IMG_PATH "small.pgm", ima);
auto win = make_rectangle2d(5, 5);
mln::se::rect2d win(5, 5);
auto out = morpho::structural::dilate(ima, win, std::greater<uint8>());
ASSERT_TRUE(all(out <= ima)); // anti-extensive
}
// Dilation of a binary image
TEST(Morpho, dilation_dilate_binary)
TEST(Dilation, Binary)
{
image2d<bool> ima(11, 11);
fill(ima, false);
......@@ -116,31 +149,46 @@ TEST(Morpho, dilation_dilate_binary)
ima.at(5, 5) = true;
ima.at(10, 10) = true;
auto win = make_rectangle2d(3, 3);
image2d<bool> ref = {
{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //
{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, //
{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, //
{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, //
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1} //
};
mln::se::rect2d win(3, 3);
auto out = morpho::structural::dilate(ima, win);
ASSERT_TRUE(all(ima <= out)); // anti-extensive
ASSERT_IMAGES_EQ(ref, out);
}
// Dilation of a bianry image
TEST(Morpho, dilation_dilate_binary_2)
TEST(Dilation, Binary_2)
{
image2d<bool> ima;
io::imread(MLN_IMG_PATH "tiny.pbm", ima);
auto win = make_rectangle2d(3, 3);
mln::se::rect2d win(3, 3);
auto out = morpho::structural::dilate(ima, win);
ASSERT_TRUE(all(ima <= out)); // anti-extensive
}
// Dilation of a rgb image
TEST(Morpho, dilation_dilate_rgb)
TEST(Dilation, RGB)
{
image2d<rgb8> ima;
io::imread(MLN_IMG_PATH "small.ppm", ima);
auto win = make_rectangle2d(5, 5);
mln::se::rect2d win(5, 5);
auto out = morpho::structural::dilate(ima, win);
ASSERT_TRUE(all(red(ima) <= red(out))); // anti-extensive
ASSERT_TRUE(all(green(ima) <= green(out))); // anti-extensive
ASSERT_TRUE(all(blue(ima) <= blue(out))); // anti-extensive
}
......@@ -2,13 +2,14 @@
#include <mln/core/grays.hpp>
#include <mln/core/image/image2d.hpp>
#include <mln/core/neighb2d.hpp>
#include <mln/core/win2d.hpp>
#include <mln/core/se/rect2d.hpp>
#include <mln/io/imread.hpp>
#include <mln/io/imsave.hpp>
#include <mln/morpho/structural/gradient.hpp>
#include <gtest/gtest.h>
using namespace mln;
TEST(Morpho, gradient_gradient_0)
......@@ -17,7 +18,7 @@ TEST(Morpho, gradient_gradient_0)
iota(ima, 10);
{ // Fast: border wide enough
auto win = make_rectangle2d(1, 3);
mln::se::rect2d win(3,1);
auto out = morpho::structural::gradient(ima, win);
static_assert(std::is_same<decltype(out)::value_type, int>::value, "Error integral promotion should give int.");
......@@ -31,7 +32,7 @@ TEST(Morpho, gradient_gradient_1)
io::imread(MLN_IMG_PATH "small.pgm", ima);
{ // Fast: border wide enough
auto win = make_rectangle2d(7, 7);
mln::se::rect2d win(7, 7);
auto out = morpho::structural::gradient(ima, win);
}
}
......@@ -44,7 +45,7 @@ TEST(Morpho, gradient_gradient_2)
io::imread(MLN_IMG_PATH "small.pgm", ima);
io::imread(MLN_IMG_PATH "small.pgm", ima2);
auto win = make_rectangle2d(3, 3);
mln::se::rect2d win(3, 3);
auto out1 = morpho::structural::gradient(ima, win);
auto out2 = morpho::structural::gradient(ima2, win);
ASSERT_TRUE(all(out1 == out2));
......@@ -57,7 +58,7 @@ TEST(Morpho, gradient_gradient_3)
io::imread(MLN_IMG_PATH "small.pgm", ima);
// Morpher has no extension
auto win = make_rectangle2d(3, 3);
mln::se::rect2d win(3, 3);
auto out = morpho::structural::gradient(ima > 128, win);
}
......@@ -67,7 +68,7 @@ TEST(Morpho, gradient_gradient_4)
image2d<uint8> ima;
io::imread(MLN_IMG_PATH "small.pgm", ima);
auto win = make_rectangle2d(3, 3);
mln::se::rect2d win(3, 3);
image2d<uint8> out;
resize(out, ima).init(0);
auto tmp = out | where(ima > 128);
......@@ -82,7 +83,7 @@ TEST(Morpho, gradient_gradient_5)
io::imread(MLN_IMG_PATH "small.ppm", ima);
io::imread(MLN_IMG_PATH "small.ppm", ima2);
auto win = make_rectangle2d(3, 3);
mln::se::rect2d win(3, 3);
auto out1 = morpho::structural::gradient(ima, win);
auto out2 = morpho::structural::gradient(ima2, win);
ASSERT_TRUE(all(out1 == out2));
......
#include <mln/core/algorithm/fill.hpp>
#include <mln/core/grays.hpp>
#include <mln/core/image/image2d.hpp>
#include <mln/core/win2d.hpp>
#include <mln/core/se/rect2d.hpp>
#include <mln/io/imread.hpp>
#include <mln/morpho/median_filter.hpp>
......@@ -14,7 +14,7 @@
using namespace mln;
image2d<uint8> naive_median(const image2d<uint8>& f, rect2d win, int sz)
image2d<uint8> naive_median(const image2d<uint8>& f, se::rect2d win, int sz)
{
mln_entering("naive_median");
......@@ -45,7 +45,7 @@ TEST(Morpho, median_filter_median_filter_0)
io::imread(MLN_IMG_PATH "lena.pgm", ima);
{ // Fast: border wide enough
auto win = make_rectangle2d(7, 7);
mln::se::rect2d win(7, 7);
auto out = morpho::median_filter(ima, win);
auto out2 = naive_median(ima, win, 49);
ASSERT_IMAGES_EQ(out2, out);
......
......@@ -2,7 +2,7 @@
#include <mln/core/grays.hpp>
#include <mln/core/image/image2d.hpp>
#include <mln/core/neighb2d.hpp>
#include <mln/core/win2d.hpp>
#include <mln/core/se/rect2d.hpp>
#include <mln/io/imread.hpp>
#include <mln/morpho/structural/closing.hpp>
#include <mln/morpho/structural/opening.hpp>
......@@ -18,7 +18,7 @@ TEST(Morpho, opening_closing_opening_0)
image2d<uint8> ima;
io::imread(MLN_IMG_PATH "small.pgm", ima);
auto win = make_rectangle2d(3, 3);
mln::se::rect2d win(3, 3);
{
auto out1 = morpho::structural::opening(ima, win);
auto out2 = morpho::structural::closing(ima, win);
......@@ -34,7 +34,7 @@ TEST(Morpho, opening_closing_opening_1)
io::imread(MLN_IMG_PATH "small.pgm", ima);
auto comp = [](uint8 x) -> uint8 { return 255 - x; };
auto win = make_rectangle2d(3, 3);
mln::se::rect2d win(3, 3);
{
auto out1 = morpho::structural::opening(imtransform(ima, comp), win);
auto out2 = morpho::structural::closing(ima, win);
......
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