Commit 407e4915 by Jordan P. Lefebvre

### WIP #14. Initial MarchingSquares algorithm. Working on connected component...

`WIP #14. Initial MarchingSquares algorithm. Working on connected component labeling detect multiple groups.`
parent 0d076c9f
Pipeline #11525 passed with stages
in 4 minutes and 35 seconds
 #ifndef RADIX_RADIXALGORITHM_MARCHINGSQUARES_HH_ #define RADIX_RADIXALGORITHM_MARCHINGSQUARES_HH_ #include #include #include #include #include "radixbug/bug.hh" namespace radix { template void marching_squares(const std::vector& points, std::vector& out, size_t rows, size_t cols, data_type isovalue, sign_type negative = 0, sign_type positive = 1); template class MarchingSquares { protected: std::vector mData; std::vector mBit; std::vector mComponent; size_t mRows; size_t mColumns; enum class StepDirection { Up, Down, Right, Left, None }; StepDirection next_step; StepDirection prev_step; /** * @brief starting_point find a starting position which matches or exceeds * isovalue * @param pos std::pair aka * @return bool true if a position was found */ bool starting_point(std::pair& pos) const; /** * @brief step Performs a step in the march * @param r row aka y index * @param c column aka x index */ void step(size_t r, size_t c); bool accepts(size_t r, size_t c) const; void union_coords(size_t r1, size_t c1, size_t r2, size_t c2) { size_t first = mColumns * c1 + r1; size_t second = mColumns * c2 + r2; if (r2 < mRows && c2 < mColumns && mBit[first] && mBit[second]) do_union(int(first), int(second)); } void do_union(int a, int b) { // get the root component of a and b, and set the one's parent to the other while (mComponent[a] != a) a = mComponent[a]; while (mComponent[b] != b) b = mComponent[b]; mComponent[b] = a; } public: /** * @brief MarchingSquares * @param data const std::vector 1D representation of 2D data * @param rows number of rows, aka y size * @param columns number of columns, aka x size */ MarchingSquares(const std::vector& data, size_t rows, size_t columns) : mData(data) , mBit(data.size(), 0) , mComponent(data.size()) , mRows(rows) , mColumns(columns) { radix_line("MarchingSquares data size(" << data.size() << ")"); radix_check(data.size() == (rows * columns)); } /** * @brief march March the data on the isovalue threshold. Can be called * multiple times for multiple grouping at threshold. * @param isovalue the threshold value of the data * @param wash_bit the value that replace all data matchings isovalue * @param wash_threshold the threshold to which washing shouldn't apply. * Primary use in multi-contour requests. * @return std::vector> listing of all x,y indices, aka * a closed polygon */ std::vector> march( data_type isovalue, data_type wash_bit = {0}, data_type wash_threshold = std::numeric_limits::max()); }; // class } // namespace /** Include implementation file */ ... ...
 #include #include #include #include "radixalgorithm/marchingsquares.hh" #include "radixbug/bug.hh" namespace radix { template void marching_squares(const std::vector& points, std::vector& out, size_t rows, size_t cols, data_type isovalue, sign_type negative, sign_type positive) template std::vector> MarchingSquares::march( data_type isovalue, data_type wash_bit, data_type wash_thresold) { radix_check(points.size() == (rows * cols)); std::pair start; std::vector> out; out.clear(); out.resize(points.size()); // // Create a 2d view of the points auto view = [&points, cols](size_t row, size_t column) { return points[cols * row + column]; }; for (size_t p_i = 0; p_i < points.size(); ++p_i) // Get a starting point first, because if there isn't a place to start // then we don't care about going any further if (starting_point(start) == false) { // select data as 0 below value or 1 greater than if (points[p_i] < isovalue) radix_tagged_line("Did not find a starting point."); // return empty listing return out; } radix_tagged_line("Starting point[" << start.first << "," << start.second << "]"); // // Initialize bit field for (size_t p_i = 0; p_i < mData.size(); ++p_i) { // select data as 1 if greater than isovalue if (mData[p_i] >= isovalue) { out[p_i] = negative; mBit[p_i] = 1; } else } // We required connect commponent labeling (CCL) // to deal with multiple groups // initialize all cells as unique component 0 -> data.size() std::iota(std::begin(mComponent), std::end(mComponent), 0); for (size_t row_i = 0; row_i < mRows; row_i++) for (size_t col_i = 0; col_i < mColumns; col_i++) { union_coords(row_i, col_i, row_i + 1, col_i); union_coords(row_i, col_i, row_i, col_i + 1); } radix_block( std::unordered_set groups(mComponent.begin(), mComponent.end())); radix_tagged_line("Number of groups(" << groups.size() << ")"); // // Walk the perimeter size_t row = start.first, column = start.second; do { step(row, column); // If our current point is within our image // add it to the list of points if (column >= 0 && column < mColumns && row >= 0 && row < mRows) out.push_back({row, column}); switch (next_step) { out[p_i] = positive; case StepDirection::Up: --row; break; case StepDirection::Left: --column; break; case StepDirection::Down: ++row; break; case StepDirection::Right: ++column; break; default: break; } } while (row != start.first || column != start.second); // // Now we need to wash out selected group within contour, leaving those at or // above threhold return out; } // march template bool MarchingSquares::starting_point( std::pair& pos) const { for (size_t i = 0; i < mData.size(); ++i) { if (mBit[i] == 1) { // save the row pos.first = i / mColumns; // save the column pos.second = i % mColumns; return true; } } return false; } template bool MarchingSquares::accepts(size_t r, size_t c) const { // Make sure we don't pick a point out of bounds if (c < 0 || r < 0 || c >= mColumns || r >= mRows) return false; // Check the data value if (mData[mColumns * r + c] > 0) return true; return false; } template void MarchingSquares::step(size_t r, size_t c) { // // The meat of the marching squares algorithm // See https://en.wikipedia.org/wiki/Marching_squares // for specifics on the algorithm // upper relations bool u_left = accepts(r - 1, c - 1); bool u_right = accepts(r - 1, c); // lower relations bool l_left = accepts(r, c - 1); bool l_right = accepts(r, c); prev_step = next_step; int state = 0; if (u_left) state |= 1; if (u_right) state |= 2; if (l_left) state |= 4; if (l_right) state |= 8; // State now contains a number between 0 and 15 // representing our state. // In binary, it looks like 0000-1111 (in binary) // An example. Let's say the top two pixels are filled, // and the bottom two are empty. // Stepping through the if statements above with a state // of 0b0000 initially produces: // upper Left == true ==> 0b0001 // upper Right == true ==> 0b0011 // The others are false, so 0b0011 is our state // (That's a result 3) // Looking at the chart above, we see that state // corresponds to a move right, so in our switch statement // below, we add a case for 3, and assign Right as the // direction of the next step. We repeat this process // for all 16 states. // So we can use a switch statement to determine our // next direction based on switch (state) { case 1: next_step = StepDirection::Up; break; case 2: next_step = StepDirection::Right; break; case 3: next_step = StepDirection::Right; break; case 4: next_step = StepDirection::Left; break; case 5: next_step = StepDirection::Up; break; case 6: if (prev_step == StepDirection::Up) { next_step = StepDirection::Left; } else { next_step = StepDirection::Right; } break; case 7: next_step = StepDirection::Right; break; case 8: next_step = StepDirection::Down; break; case 9: if (prev_step == StepDirection::Right) { next_step = StepDirection::Up; } else { next_step = StepDirection::Down; } break; case 10: next_step = StepDirection::Down; break; case 11: next_step = StepDirection::Down; break; case 12: next_step = StepDirection::Left; break; case 13: next_step = StepDirection::Up; break; case 14: next_step = StepDirection::Left; break; default: next_step = StepDirection::None; break; } } } // namespace