Commit 2e1e8420 authored by Edwin Carlinet's avatar Edwin Carlinet
Browse files

Add padding functions.

parent fb239cb3
......@@ -52,6 +52,7 @@ target_sources(Pylene PRIVATE
src/core/init_list.cpp
src/core/ndbuffer_image.cpp
src/core/ndbuffer_image_data.cpp
src/core/padding.cpp
src/core/se/disc.cpp
src/core/se/mask2d.cpp
src/core/se/periodic_line2d.cpp
......
#pragma once
#include <cstddef>
#include <algorithm>
#include <mln/core/image/ndimage_fwd.hpp>
#include <mln/core/range/mdspan_fwd.hpp>
namespace mln
{
enum e_padding_mode
{
PAD_ZERO,
PAD_CONSTANT,
PAD_WRAP,
PAD_MIRROR,
PAD_REPLICATE,
};
/// \brief Pad an ndimage or a mdspan in-place using one of the padding strategy
///
/// Given `[0 0 1 2 3 0 0 0 0]`, borders = `{2, 4}` and value = `9`
///
/// PAD_ZERO
/// pad each dimension with value zero `[0 0 1 2 3 0 0 0 0]`
/// PAD_CONSTANT
/// pad each dimension with \p value `[9 9 1 2 3 9 9 9 9]`
/// PAD_WRAP
/// pad each dimension using periodization `[2 3 1 2 3 1 2 3 1]`
/// PAD_MIRROR
/// pad each dimension using edge mirroring `[2 1 1 2 3 3 2 1 1]`
/// PAD_MIRROR
/// pad each dimension using edge replication `[1 1 1 2 3 3 3 3 3]`
///
/// \param input The image or mdspan to pad
/// \param mode The padding mode
/// \param borders The padding region express as distances from the border
/// \param value The value uses when mode=PAD_CONSTANT
template <class T, int dim>
void pad(__ndbuffer_image<T, dim>& image, e_padding_mode mode, const int borders[][2], T value = {});
/// \overload
template <class T, std::size_t dim>
void pad(const ranges::mdspan<T, dim>& image, e_padding_mode mode, const int borders[][2], T value = {});
/******************************************/
/**** Implementation ****/
/******************************************/
template <class T>
class mdspan_padder;
template <>
class mdspan_padder<void>;
// Type erased implementation
template <>
class mdspan_padder<void>
{
static constexpr int _max_dim = 4;
public:
mdspan_padder(void* buffer, int dim, const int sizes[], const std::ptrdiff_t strides[], const int borders[][2]) noexcept;
protected:
void _pad(e_padding_mode padding, void* value) const noexcept;
void _pad(int dim, char* buffer, e_padding_mode padding, void* value) const noexcept;
void memset(int dim, char* buffer, void* value) const noexcept;
void memcpy(int dim, char* dst, char* src) const noexcept;
virtual void pad_horizontally_2d(char* buffer, e_padding_mode padding, void* value) const noexcept = 0;
virtual void memset(void* buffer, std::size_t n, void* value) const noexcept = 0;
virtual void memcpy(void* dst, void* src, std::size_t n) const noexcept = 0;
// FIXME, consider using boost::small_vector to break the limitation to _max_dim;
char* m_buffer;
std::ptrdiff_t m_byte_strides[_max_dim];
int m_sizes[_max_dim];
int m_borders[_max_dim][2]; // left/right padding in each dimension
int m_dim;
};
/// \brief This class offers padding facilities over a multidimensional span
template <class T>
class mdspan_padder : mdspan_padder<void>
{
public:
/// \brief
///
/// \param buffer Pointer over the beginning of the multidimensional buffer
/// \param dim Number of dimensions of the buffer
/// \param sizes Dimensions of the buffer
/// \param strides Number of bytes between two consecutive element
/// \param borders Padding region parameters
mdspan_padder(T* buffer, int dim, const int sizes[], const std::ptrdiff_t byte_strides[], const int borders[][2]) noexcept
: mdspan_padder<void>((void*)buffer, dim, sizes, byte_strides, borders)
{
}
/// Custom padding
///
/// \param padding padding mode (one of zero, constant, periodize, mirror, replicate)
/// \param value The value used to pad when mode is CONSTANT (default is zero)
void pad(e_padding_mode padding, T value) const noexcept;
// Constant padding with a value
void pad(T value) const noexcept;
private:
void pad_horizontally_2d(char* buffer, e_padding_mode padding, void* value) const noexcept final;
void memset(void* buffer, std::size_t n, void* value) const noexcept final;
void memcpy(void* dst, void* src, std::size_t n) const noexcept final;
};
template <class T>
void mdspan_padder<T>::pad(T value) const noexcept
{
this->_pad(PAD_CONSTANT, &value);
}
template <class T>
void mdspan_padder<T>::pad(e_padding_mode padding, T value) const noexcept
{
this->_pad(padding, &value);
}
template <class T>
void mdspan_padder<T>::memset(void* buffer, std::size_t n, void* value) const noexcept
{
T val = value ? *(T*)value : 0;
std::fill_n(reinterpret_cast<T*>(buffer), n, val);
}
template <class T>
void mdspan_padder<T>::memcpy(void* dst, void* src, std::size_t n) const noexcept
{
std::copy_n((T*)src, n, (T*)dst);
}
template <class T>
void mdspan_padder<T>::pad_horizontally_2d(char* buffer, e_padding_mode padding, void* val) const noexcept
{
const auto w = m_sizes[0];
const auto h = m_sizes[1];
const auto pitch = m_byte_strides[1];
const int y0 = m_borders[1][0];
const int y1 = h - m_borders[1][1];
const int x0 = m_borders[0][0];
const int x1 = w - m_borders[0][1];
const int w0 = x1 - x0;
T value = (val == nullptr) ? 0 : *(reinterpret_cast<const T*>(val));
auto mirror = [w0, x0](int x) {
int n = w0 * 2;
x = (x - x0) % n; // x in (-n:n)
x = (x >= 0) ? x : x + n; // x in [0:n)
x = (x < w0) ? x : (n - x - 1); // x in [0,w0)
return x0 + x;
};
auto periodize = [w0, x0](int x) {
x = (x - x0) % w0;
x = (x >= 0) ? x : x + w0;
return x0 + x;
};
switch (padding)
{
case PAD_ZERO:
case PAD_CONSTANT:
for (int y = y0; y < y1; y++)
{
T* lineptr = reinterpret_cast<T*>(buffer + y * pitch);
std::fill(lineptr, lineptr + x0, value);
std::fill(lineptr + x1, lineptr + w, value);
}
break;
case PAD_REPLICATE:
for (int y = y0; y < y1; y++)
{
T* lineptr = reinterpret_cast<T*>(buffer + y * pitch);
std::fill(lineptr, lineptr + x0, lineptr[x0]);
std::fill(lineptr + x1, lineptr + w, lineptr[x1 - 1]);
}
break;
case PAD_MIRROR:
for (int y = y0; y < y1; y++)
{
T* lineptr = reinterpret_cast<T*>(buffer + y * pitch);
for (int x = 0; x < x0; ++x)
lineptr[x] = lineptr[mirror(x)];
for (int x = x1; x < w; ++x)
lineptr[x] = lineptr[mirror(x)];
}
break;
case PAD_WRAP:
for (int y = y0; y < y1; y++)
{
T* lineptr = reinterpret_cast<T*>(buffer + y * pitch);
for (int x = 0; x < x0; ++x)
lineptr[x] = lineptr[periodize(x)];
for (int x = x1; x < w; ++x)
lineptr[x] = lineptr[periodize(x)];
}
break;
}
}
template <class T, std::size_t dim>
void pad(const ranges::mdspan<T, dim>& sp, e_padding_mode mode, const int borders[][2], T value)
{
std::ptrdiff_t strides[dim];
int sizes[dim];
for (std::size_t i = 0; i < dim; ++i)
{
sizes[i] = static_cast<int>(sp.size(i));
strides[i] = sp.byte_stride(i);
}
auto padder = mdspan_padder(sp.data(), static_cast<int>(dim), sizes, strides, borders);
padder.pad(mode, value);
}
template <class T, int dim>
void pad(__ndbuffer_image<T, dim>& f, e_padding_mode mode, const int borders[][2], T value)
{
std::ptrdiff_t strides[dim];
int sizes[dim];
for (std::size_t i = 0; i < dim; ++i)
{
sizes[i] = static_cast<int>(f.size(i));
strides[i] = f.byte_stride(i);
}
auto padder = mdspan_padder(f.buffer(), static_cast<int>(dim), sizes, strides, borders);
padder.pad(mode, value);
}
} // namespace mln
......@@ -138,9 +138,31 @@ namespace mln::ranges
m_strides[i] = m_strides[i - 1] * m_sizes[i - 1];
}
T* data() const noexcept { return m_data; }
std::size_t size(int dim) const noexcept { return m_sizes[dim]; }
std::ptrdiff_t stride(int dim) const noexcept { return m_strides[dim]; }
std::ptrdiff_t byte_stride(int dim) const noexcept { return m_strides[dim] * sizeof(T); }
mdspan<std::byte, Rank> as_writable_bytes() noexcept
{
std::ptrdiff_t strides[Rank];
for (std::size_t i = 0; i < Rank; ++i)
strides[i] = this->byte_stride(i);
return {reinterpret_cast<std::byte*>(m_data), m_sizes, strides};
}
mdspan<const std::byte, Rank> as_bytes() noexcept
{
std::ptrdiff_t strides[Rank];
for (std::size_t i = 0; i < Rank; ++i)
strides[i] = this->byte_stride(i);
return {reinterpret_cast<const std::byte*>(m_data), m_sizes, strides};
}
private:
T* m_data;
std::size_t m_sizes[Rank];
std::ptrdiff_t m_strides[Rank];
};
}
} // namespace mln::ranges
#pragma once
namespace mln::ranges
{
template <class T, std::size_t Rank>
class mdspan;
}
#include <mln/core/extension/padding.hpp>
#include <cassert>
namespace mln
{
mdspan_padder<void>::mdspan_padder(void* buffer, int dim, const int sizes[], const std::ptrdiff_t strides[],
const int borders[][2]) noexcept
: m_buffer{(char*)buffer}
, m_dim{dim}
{
assert(dim < _max_dim && "dim is currently limited to _max_dim");
for (int k = 0; k < dim; ++k)
{
m_sizes[k] = sizes[k];
m_byte_strides[k] = strides[k];
m_borders[k][0] = borders[k][0];
m_borders[k][1] = borders[k][1];
assert(m_borders[k][0] >= 0);
assert(m_borders[k][1] >= 0);
assert((m_borders[k][0] + m_borders[k][1]) < (int)sizes[k]);
}
for (int k = dim; k < _max_dim; ++k)
{
m_sizes[k] = 1;
m_byte_strides[k] = m_byte_strides[k-1];
m_borders[k][0] = 0;
m_borders[k][1] = 0;
}
}
void mdspan_padder<void>::_pad(e_padding_mode padding, void* value) const noexcept
{
if (padding == PAD_ZERO)
value = nullptr;
this->_pad(m_dim, m_buffer, padding, value);
}
void mdspan_padder<void>::_pad(int dim, char* buffer, e_padding_mode padding, void* value) const noexcept
{
assert(dim <= m_dim);
const auto n = m_sizes[dim - 1];
const int a = m_borders[dim - 1][0];
const int b = n - m_borders[dim - 1][1];
const int w0 = b - a;
if (dim == 1) {
this->pad_horizontally_2d(buffer, padding, value);
return;
}
// Pad each element of the lower dim
if (dim == 2)
{
this->pad_horizontally_2d(buffer, padding, value);
}
else
{
for (int z = a; z < b; ++z)
this->_pad(dim-1, buffer + m_byte_strides[dim-1] * z, padding, value);
}
// Pad upper border
// Pad lower border
auto mirror = [w0, a](int x) {
int n = w0 * 2;
x = (x - a) % n; // x in (-n:n)
x = (x >= 0) ? x : x + n; // x in [0:n)
x = (x < w0) ? x : (n - x - 1); // x in [0,w0)
return a + x;
};
auto periodize = [w0, a](int x) {
x = (x - a) % w0;
x = (x >= 0) ? x : x + w0;
return a + x;
};
switch (padding)
{
case PAD_ZERO:
case PAD_CONSTANT:
for (int z = 0; z < a; ++z)
this->memset(dim - 1, buffer + m_byte_strides[dim - 1] * z, value);
for (int z = b; z < n; ++z)
this->memset(dim - 1, buffer + m_byte_strides[dim - 1] * z, value);
break;
case PAD_REPLICATE:
for (int z = 0; z < a; ++z)
this->memcpy(dim - 1, buffer + m_byte_strides[dim - 1] * z, buffer + m_byte_strides[dim - 1] * a);
for (int z = b; z < n; ++z)
this->memcpy(dim - 1, buffer + m_byte_strides[dim - 1] * z, buffer + m_byte_strides[dim - 1] * (b - 1));
break;
case PAD_MIRROR:
for (int z = 0; z < a; ++z)
this->memcpy(dim - 1, buffer + m_byte_strides[dim - 1] * z, buffer + m_byte_strides[dim - 1] * mirror(z));
for (int z = b; z < n; ++z)
this->memcpy(dim - 1, buffer + m_byte_strides[dim - 1] * z, buffer + m_byte_strides[dim - 1] * mirror(z));
break;
case PAD_WRAP:
for (int z = 0; z < a; ++z)
this->memcpy(dim - 1, buffer + m_byte_strides[dim - 1] * z, buffer + m_byte_strides[dim - 1] * periodize(z));
for (int z = b; z < n; ++z)
this->memcpy(dim - 1, buffer + m_byte_strides[dim - 1] * z, buffer + m_byte_strides[dim - 1] * periodize(z));
break;
}
}
void mdspan_padder<void>::memset(int dim, char* buffer, void* value) const noexcept
{
assert(dim < m_dim);
if (dim == 1)
{
this->memset(buffer, m_sizes[dim-1], value);
return;
}
for (int z = 0; z < m_sizes[dim-1]; ++z)
this->memset(dim - 1, buffer + m_byte_strides[dim - 1] * z, value);
}
void mdspan_padder<void>::memcpy(int dim, char* dst, char* src) const noexcept
{
assert(dim < m_dim);
if (dim == 1)
{
this->memcpy(dst, src, m_sizes[dim-1]);
return;
}
for (int z = 0; z < m_sizes[dim - 1]; ++z)
this->memcpy(dim - 1, dst + m_byte_strides[dim - 1] * z, src + m_byte_strides[dim - 1] * z);
}
} // namespace mln
......@@ -14,6 +14,10 @@ add_core_test(${test_prefix}range_zip range/zip.cpp)
add_core_test(${test_prefix}range_mdspan range/mdspan.cpp)
add_core_test(${test_prefix}range_mdindex range/mdindex.cpp)
# test padding functionalities
add_core_test(${test_prefix}padding extension/padding.cpp)
# test Views
add_core_test(${test_prefix}view_adaptor image/view/adaptor.cpp)
......
#include <mln/core/extension/padding.hpp>
#include <mln/core/image/ndimage.hpp>
#include <mln/core/range/foreach.hpp>
#include <mln/io/imprint.hpp>
#include <gtest/gtest.h>
struct rgb_t
{
rgb_t() = default;
rgb_t(uint8_t x)
: r{x}
, g{x}
, b{x}
{
}
bool operator==(rgb_t o) const { return r == o.r && g == o.g && b == o.b; }
bool operator!=(rgb_t o) const { return !(*this == o); }
uint8_t r,g,b;
};
template <class T>
void check_padding(mln::image2d<T> f, mln::box2d inner, mln::e_padding_mode bm, T value)
{
mln_foreach_new(auto p, f.domain())
{
if (inner.has(p))
continue;
T val;
switch (bm)
{
case mln::PAD_ZERO: val = 0; break;
case mln::PAD_CONSTANT: val = value; break;
case mln::PAD_WRAP: val = f(inner.periodize(p)); break;
case mln::PAD_MIRROR: val = f(inner.mirror(p)); break;
case mln::PAD_REPLICATE: val = f(inner.clamp(p)); break;
}
ASSERT_EQ(f(p), val) << fmt::format("p = ({},{})", p.x(), p.y());
}
}
template <class T>
class Padding2DTest : public testing::TestWithParam<mln::e_padding_mode>
{
mln::image2d<T> input = {
{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, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 0, 0, 4, 5, 6, 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, 0, 0, 0, 0}, //
};
void test_image()
{
auto bm = GetParam();
const int borders[][2] = {{4, 8}, {3, 4}};
T v = 69;
pad(input, bm, borders, v);
// mln::io::imprint(input);
check_padding(input, mln::box2d(4, 3, 3, 2), bm, v);
}
void test_span()
{
auto bm = GetParam();
const int borders[][2] = {{4, 8}, {3, 4}};
T v = 69;
pad(input.values(), bm, borders, v);
check_padding(input, mln::box2d(4, 3, 3, 2), bm, v);
}
};
using Padding2DTestInt = Padding2DTest<int>;
using Padding2DTestRGB = Padding2DTest<rgb_t>;
TEST_P(Padding2DTestInt, Image) { this->test_image(); }
TEST_P(Padding2DTestInt, Span) { this->test_span(); }
TEST_P(Padding2DTestRGB, Image) { this->test_image(); }
TEST_P(Padding2DTestRGB, Span) { this->test_span(); }
mln::e_padding_mode paddings[] = {mln::PAD_ZERO, mln::PAD_CONSTANT, mln::PAD_WRAP, mln::PAD_MIRROR, mln::PAD_REPLICATE};
INSTANTIATE_TEST_CASE_P(bla, Padding2DTestInt, testing::ValuesIn(paddings));
INSTANTIATE_TEST_CASE_P(bla, Padding2DTestRGB, testing::ValuesIn(paddings));
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