123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- #ifndef MARCHINGSQUARES_HPP
- #define MARCHINGSQUARES_HPP
- #include <type_traits>
- #include <cstdint>
- #include <vector>
- #include <algorithm>
- #include <cassert>
- namespace marchsq {
- // Marks a square in the grid
- struct Coord {
- long r = 0, c = 0;
-
- Coord() = default;
- explicit Coord(long s) : r(s), c(s) {}
- Coord(long _r, long _c): r(_r), c(_c) {}
-
- size_t seq(const Coord &res) const { return r * res.c + c; }
- Coord& operator+=(const Coord& b) { r += b.r; c += b.c; return *this; }
- Coord operator+(const Coord& b) const { Coord a = *this; a += b; return a; }
- };
- // Closed ring of cell coordinates
- using Ring = std::vector<Coord>;
- // Specialize this struct to register a raster type for the Marching squares alg
- template<class T, class Enable = void> struct _RasterTraits {
-
- // The type of pixel cell in the raster
- using ValueType = typename T::ValueType;
-
- // Value at a given position
- static ValueType get(const T &raster, size_t row, size_t col);
-
- // Number of rows and cols of the raster
- static size_t rows(const T &raster);
- static size_t cols(const T &raster);
- };
- // Specialize this to use parellel loops within the algorithm
- template<class ExecutionPolicy, class Enable = void> struct _Loop {
- template<class It, class Fn> static void for_each(It from, It to, Fn &&fn)
- {
- for (auto it = from; it < to; ++it) fn(*it, size_t(it - from));
- }
- };
- namespace __impl {
- template<class T> using RasterTraits = _RasterTraits<std::decay_t<T>>;
- template<class T> using TRasterValue = typename RasterTraits<T>::ValueType;
- template<class T> size_t rows(const T &raster)
- {
- return RasterTraits<T>::rows(raster);
- }
- template<class T> size_t cols(const T &raster)
- {
- return RasterTraits<T>::cols(raster);
- }
- template<class T> TRasterValue<T> isoval(const T &rst, const Coord &crd)
- {
- return RasterTraits<T>::get(rst, crd.r, crd.c);
- }
- template<class ExecutionPolicy, class It, class Fn>
- void for_each(ExecutionPolicy&& policy, It from, It to, Fn &&fn)
- {
- _Loop<ExecutionPolicy>::for_each(from, to, fn);
- }
- // Type of squares (tiles) depending on which vertices are inside an ROI
- // The vertices would be marked a, b, c, d in counter clockwise order from the
- // bottom left vertex of a square.
- // d --- c
- // | |
- // | |
- // a --- b
- enum class SquareTag : uint8_t {
- // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
- none, a, b, ab, c, ac, bc, abc, d, ad, bd, abd, cd, acd, bcd, full
- };
- template<class E> constexpr std::underlying_type_t<E> _t(E e) noexcept
- {
- return static_cast<std::underlying_type_t<E>>(e);
- }
- enum class Dir: uint8_t { left, down, right, up, none};
- static const constexpr Dir NEXT_CCW[] = {
- /* 00 */ Dir::none, // SquareTag::none (empty square, nowhere to go)
- /* 01 */ Dir::left, // SquareTag::a
- /* 02 */ Dir::down, // SquareTag::b
- /* 03 */ Dir::left, // SquareTag::ab
- /* 04 */ Dir::right, // SquareTag::c
- /* 05 */ Dir::none, // SquareTag::ac (ambiguous case)
- /* 06 */ Dir::down, // SquareTag::bc
- /* 07 */ Dir::left, // SquareTag::abc
- /* 08 */ Dir::up, // SquareTag::d
- /* 09 */ Dir::up, // SquareTag::ad
- /* 10 */ Dir::none, // SquareTag::bd (ambiguous case)
- /* 11 */ Dir::up, // SquareTag::abd
- /* 12 */ Dir::right, // SquareTag::cd
- /* 13 */ Dir::right, // SquareTag::acd
- /* 14 */ Dir::down, // SquareTag::bcd
- /* 15 */ Dir::none // SquareTag::full (full covered, nowhere to go)
- };
- static const constexpr uint8_t PREV_CCW[] = {
- /* 00 */ 1 << _t(Dir::none),
- /* 01 */ 1 << _t(Dir::up),
- /* 02 */ 1 << _t(Dir::left),
- /* 03 */ 1 << _t(Dir::left),
- /* 04 */ 1 << _t(Dir::down),
- /* 05 */ 1 << _t(Dir::up) | 1 << _t(Dir::down),
- /* 06 */ 1 << _t(Dir::down),
- /* 07 */ 1 << _t(Dir::down),
- /* 08 */ 1 << _t(Dir::right),
- /* 09 */ 1 << _t(Dir::up),
- /* 10 */ 1 << _t(Dir::left) | 1 << _t(Dir::right),
- /* 11 */ 1 << _t(Dir::left),
- /* 12 */ 1 << _t(Dir::right),
- /* 13 */ 1 << _t(Dir::up),
- /* 14 */ 1 << _t(Dir::right),
- /* 15 */ 1 << _t(Dir::none)
- };
- const constexpr uint8_t DIRMASKS[] = {
- /*left: */ 0x01, /*down*/ 0x12, /*right */0x21, /*up*/ 0x10, /*none*/ 0x00
- };
- inline Coord step(const Coord &crd, Dir d)
- {
- uint8_t dd = DIRMASKS[uint8_t(d)];
- return {crd.r - 1 + (dd & 0x0f), crd.c - 1 + (dd >> 4)};
- }
- template<class Rst> class Grid {
- const Rst * m_rst = nullptr;
- Coord m_cellsize, m_res_1, m_window, m_gridsize, m_grid_1;
- std::vector<uint8_t> m_tags; // Assign tags to each square
- Coord rastercoord(const Coord &crd) const
- {
- return {(crd.r - 1) * m_window.r, (crd.c - 1) * m_window.c};
- }
- Coord bl(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, 0}; }
- Coord br(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, m_res_1.c}; }
- Coord tr(const Coord &crd) const { return tl(crd) + Coord{0, m_res_1.c}; }
- Coord tl(const Coord &crd) const { return rastercoord(crd); }
-
- bool is_within(const Coord &crd)
- {
- long R = rows(*m_rst), C = cols(*m_rst);
- return crd.r >= 0 && crd.r < R && crd.c >= 0 && crd.c < C;
- };
- // Calculate the tag for a cell (or square). The cell coordinates mark the
- // top left vertex of a square in the raster. v is the isovalue
- uint8_t get_tag_for_cell(const Coord &cell, TRasterValue<Rst> v)
- {
- Coord sqr[] = {bl(cell), br(cell), tr(cell), tl(cell)};
-
- uint8_t t = ((is_within(sqr[0]) && isoval(*m_rst, sqr[0]) >= v)) +
- ((is_within(sqr[1]) && isoval(*m_rst, sqr[1]) >= v) << 1) +
- ((is_within(sqr[2]) && isoval(*m_rst, sqr[2]) >= v) << 2) +
- ((is_within(sqr[3]) && isoval(*m_rst, sqr[3]) >= v) << 3);
-
- assert(t < 16);
- return t;
- }
-
- // Get a cell coordinate from a sequential index
- Coord coord(size_t i) const
- {
- return {long(i) / m_gridsize.c, long(i) % m_gridsize.c};
- }
- size_t seq(const Coord &crd) const { return crd.seq(m_gridsize); }
-
- bool is_visited(size_t idx, Dir d = Dir::none) const
- {
- SquareTag t = get_tag(idx);
- uint8_t ref = d == Dir::none ? PREV_CCW[_t(t)] : uint8_t(1 << _t(d));
- return t == SquareTag::full || t == SquareTag::none ||
- ((m_tags[idx] & 0xf0) >> 4) == ref;
- }
-
- void set_visited(size_t idx, Dir d = Dir::none)
- {
- m_tags[idx] |= (1 << (_t(d)) << 4);
- }
-
- bool is_ambiguous(size_t idx) const
- {
- SquareTag t = get_tag(idx);
- return t == SquareTag::ac || t == SquareTag::bd;
- }
- // Search for a new starting square
- size_t search_start_cell(size_t i = 0) const
- {
- // Skip ambiguous tags as starting tags due to unknown previous
- // direction.
- while ((i < m_tags.size()) && (is_visited(i) || is_ambiguous(i))) ++i;
-
- return i;
- }
-
- SquareTag get_tag(size_t idx) const { return SquareTag(m_tags[idx] & 0x0f); }
-
- Dir next_dir(Dir prev, SquareTag tag) const
- {
- // Treat ambiguous cases as two separate regions in one square.
- switch (tag) {
- case SquareTag::ac:
- switch (prev) {
- case Dir::down: return Dir::right;
- case Dir::up: return Dir::left;
- default: assert(false); return Dir::none;
- }
- case SquareTag::bd:
- switch (prev) {
- case Dir::right: return Dir::up;
- case Dir::left: return Dir::down;
- default: assert(false); return Dir::none;
- }
- default:
- return NEXT_CCW[uint8_t(tag)];
- }
-
- return Dir::none;
- }
-
- struct CellIt {
- Coord crd; Dir dir= Dir::none; const Rst *grid = nullptr;
-
- TRasterValue<Rst> operator*() const { return isoval(*grid, crd); }
- CellIt& operator++() { crd = step(crd, dir); return *this; }
- CellIt operator++(int) { CellIt it = *this; ++(*this); return it; }
- bool operator!=(const CellIt &it) { return crd.r != it.crd.r || crd.c != it.crd.c; }
-
- using value_type = TRasterValue<Rst>;
- using pointer = TRasterValue<Rst> *;
- using reference = TRasterValue<Rst> &;
- using difference_type = long;
- using iterator_category = std::forward_iterator_tag;
- };
-
- // Two cell iterators representing an edge of a square. This is then
- // used for binary search for the first active pixel on the edge.
- struct Edge { CellIt from, to; };
-
- Edge _edge(const Coord &ringvertex) const
- {
- size_t idx = ringvertex.r;
- Coord cell = coord(idx);
- uint8_t tg = m_tags[ringvertex.r];
- SquareTag t = SquareTag(tg & 0x0f);
-
- switch (t) {
- case SquareTag::a:
- case SquareTag::ab:
- case SquareTag::abc:
- return {{tl(cell), Dir::down, m_rst}, {bl(cell)}};
- case SquareTag::b:
- case SquareTag::bc:
- case SquareTag::bcd:
- return {{bl(cell), Dir::right, m_rst}, {br(cell)}};
- case SquareTag::c:
- return {{br(cell), Dir::up, m_rst}, {tr(cell)}};
- case SquareTag::ac:
- switch (Dir(ringvertex.c)) {
- case Dir::left: return {{tl(cell), Dir::down, m_rst}, {bl(cell)}};
- case Dir::right: return {{br(cell), Dir::up, m_rst}, {tr(cell)}};
- default: assert(false);
- }
- case SquareTag::d:
- case SquareTag::ad:
- case SquareTag::abd:
- return {{tr(cell), Dir::left, m_rst}, {tl(cell)}};
- case SquareTag::bd:
- switch (Dir(ringvertex.c)) {
- case Dir::down: return {{bl(cell), Dir::right, m_rst}, {br(cell)}};
- case Dir::up: return {{tr(cell), Dir::left, m_rst}, {tl(cell)}};
- default: assert(false);
- }
- case SquareTag::cd:
- case SquareTag::acd:
- return {{br(cell), Dir::up, m_rst}, {tr(cell)}};
- case SquareTag::full:
- case SquareTag::none: {
- Coord crd{tl(cell) + Coord{m_cellsize.r / 2, m_cellsize.c / 2}};
- return {{crd, Dir::none, m_rst}, crd};
- }
- }
-
- return {};
- }
-
- Edge edge(const Coord &ringvertex) const
- {
- const long R = rows(*m_rst), C = cols(*m_rst);
- const long R_1 = R - 1, C_1 = C - 1;
-
- Edge e = _edge(ringvertex);
- e.to.dir = e.from.dir;
- ++e.to;
-
- e.from.crd.r = std::min(e.from.crd.r, R_1);
- e.from.crd.r = std::max(e.from.crd.r, 0l);
- e.from.crd.c = std::min(e.from.crd.c, C_1);
- e.from.crd.c = std::max(e.from.crd.c, 0l);
-
- e.to.crd.r = std::min(e.to.crd.r, R);
- e.to.crd.r = std::max(e.to.crd.r, 0l);
- e.to.crd.c = std::min(e.to.crd.c, C);
- e.to.crd.c = std::max(e.to.crd.c, 0l);
-
- return e;
- }
-
- public:
- explicit Grid(const Rst &rst, const Coord &cellsz, const Coord &overlap)
- : m_rst{&rst}
- , m_cellsize{cellsz}
- , m_res_1{m_cellsize.r - 1, m_cellsize.c - 1}
- , m_window{overlap.r < cellsz.r ? cellsz.r - overlap.r : cellsz.r,
- overlap.c < cellsz.c ? cellsz.c - overlap.c : cellsz.c}
- , m_gridsize{2 + (long(rows(rst)) - overlap.r) / m_window.r,
- 2 + (long(cols(rst)) - overlap.c) / m_window.c}
- , m_tags(m_gridsize.r * m_gridsize.c, 0)
- {}
-
- // Go through the cells and mark them with the appropriate tag.
- template<class ExecutionPolicy>
- void tag_grid(ExecutionPolicy &&policy, TRasterValue<Rst> isoval)
- {
- // parallel for r
- for_each (std::forward<ExecutionPolicy>(policy),
- m_tags.begin(), m_tags.end(),
- [this, isoval](uint8_t& tag, size_t idx) {
- tag = get_tag_for_cell(coord(idx), isoval);
- });
- }
-
- // Scan for the rings on the tagged grid. Each ring vertex stores the
- // sequential index of the cell and the next direction (Dir).
- // This info can be used later to calculate the exact raster coordinate.
- std::vector<Ring> scan_rings()
- {
- std::vector<Ring> rings;
- size_t startidx = 0;
- while ((startidx = search_start_cell(startidx)) < m_tags.size()) {
- Ring ring;
-
- size_t idx = startidx;
- Dir prev = Dir::none, next = next_dir(prev, get_tag(idx));
-
- while (next != Dir::none && !is_visited(idx, prev)) {
- Coord ringvertex{long(idx), long(next)};
- ring.emplace_back(ringvertex);
- set_visited(idx, prev);
-
- idx = seq(step(coord(idx), next));
- prev = next;
- next = next_dir(next, get_tag(idx));
- }
-
- // To prevent infinite loops in case of degenerate input
- if (next == Dir::none) m_tags[startidx] = _t(SquareTag::none);
-
- if (ring.size() > 1) {
- ring.pop_back();
- rings.emplace_back(ring);
- }
- }
-
- return rings;
- }
-
- // Calculate the exact raster position from the cells which store the
- // sequantial index of the square and the next direction
- template<class ExecutionPolicy>
- void interpolate_rings(ExecutionPolicy && policy,
- std::vector<Ring> &rings,
- TRasterValue<Rst> isov)
- {
- for_each(std::forward<ExecutionPolicy>(policy),
- rings.begin(), rings.end(), [this, isov] (Ring &ring, size_t)
- {
- for (Coord &ringvertex : ring) {
- Edge e = edge(ringvertex);
-
- CellIt found = std::lower_bound(e.from, e.to, isov);
- ringvertex = found.crd;
- }
- });
- }
- };
- template<class Raster, class ExecutionPolicy>
- std::vector<marchsq::Ring> execute_with_policy(ExecutionPolicy && policy,
- const Raster & raster,
- TRasterValue<Raster> isoval,
- Coord windowsize = {})
- {
- if (!rows(raster) || !cols(raster)) return {};
-
- size_t ratio = cols(raster) / rows(raster);
-
- if (!windowsize.r) windowsize.r = 2;
- if (!windowsize.c)
- windowsize.c = std::max(2l, long(windowsize.r * ratio));
-
- Coord overlap{1};
-
- Grid<Raster> grid{raster, windowsize, overlap};
-
- grid.tag_grid(std::forward<ExecutionPolicy>(policy), isoval);
- std::vector<marchsq::Ring> rings = grid.scan_rings();
- grid.interpolate_rings(std::forward<ExecutionPolicy>(policy), rings, isoval);
-
- return rings;
- }
- template<class Raster>
- std::vector<marchsq::Ring> execute(const Raster &raster,
- TRasterValue<Raster> isoval,
- Coord windowsize = {})
- {
- return execute_with_policy(nullptr, raster, isoval, windowsize);
- }
- } // namespace __impl
- using __impl::execute_with_policy;
- using __impl::execute;
- } // namespace marchsq
- #endif // MARCHINGSQUARES_HPP
|