37 using EdgeDeltas = contours::detail::ContourEdgeDeltaStore;
38 using ForegroundConnectivity = contours::detail::ForegroundConnectivity;
39 using BoundaryTracer = contours::detail::ContourBoundaryTracer;
40 using VertexIndex = contours::detail::ContourVertexIndex;
43 struct ConstructionData {
44 EdgeDeltas edgeDeltas;
45 std::vector<ForegroundConnectivity> connectivityByNode;
49 struct SharedIndexes {
51 std::size_t mutationVersion = 0;
52 EdgeDeltas edgeDeltas;
53 std::vector<ForegroundConnectivity> connectivityByNode;
57 : tree(source), mutationVersion(source.
getMutationVersion()), edgeDeltas(std::move(data.edgeDeltas)),
58 connectivityByNode(std::move(data.connectivityByNode)) {}
61 void requireStableTree()
const { tree.
requireMutationVersion(mutationVersion,
"ContourTraceComputation"); }
65 struct TraversalState {
66 std::shared_ptr<const SharedIndexes> indexes;
67 contours::detail::ContourTraceTraversal traversal;
68 bool hasCurrentTrace =
false;
71 explicit TraversalState(std::shared_ptr<const SharedIndexes> source)
72 : indexes(std::move(source)), traversal(indexes->tree, indexes->edgeDeltas, indexes->connectivityByNode),
73 hasCurrentTrace(traversal.advance()) {}
102 if (!state_ || !state_->hasCurrentTrace) {
103 throw std::out_of_range(
"Contour trace iterator is exhausted.");
105 return state_->traversal.current();
113 if (!state_ || !state_->hasCurrentTrace) {
114 throw std::out_of_range(
"Contour trace iterator is exhausted.");
116 state_->hasCurrentTrace = state_->traversal.advance();
132 explicit iterator(std::shared_ptr<const SharedIndexes> indexes)
135 std::shared_ptr<TraversalState> state_;
145 return contours::detail::packContourEdge(pixel, side);
154 return contours::detail::unpackContourEdge(
packedEdge);
162 : indexes_(std::
make_shared<SharedIndexes>(tree, prepareConstructionData(tree))) {}
168 template <AltitudeValue T>
170 : indexes_(std::
make_shared<SharedIndexes>(view.topology(), prepareConstructionData(view))) {}
181 indexes_->requireStableTree();
182 if (!indexes_->tree.isAlive(node)) {
183 throw std::invalid_argument(
"ContourTraceComputation::trace requires a live internal NodeId.");
187 for (
PixelId pixel : indexes_->tree.nodeSupport(node)) {
188 for (ContourSide side : contourSides()) {
196 throw std::logic_error(
"ContourTraceComputation::trace produced an empty edge set for a live node.");
199 std::vector<ContourBoundary> boundaries;
200 BoundaryTracer
tracer(indexes_->tree.numRows(), indexes_->tree.numColumns(), VertexIndex::Sparse);
201 tracer.trace(
packedEdges, boundaries, indexes_->connectivityByNode[
static_cast<std::size_t
>(node)]);
210 indexes_->requireStableTree();
228 indexes_->requireStableTree();
234 [[
nodiscard]]
static constexpr std::array<ContourSide, 4> contourSides() {
235 return {ContourSide::North, ContourSide::West, ContourSide::East, ContourSide::South};
243 [[nodiscard]]
static ForegroundConnectivity foregroundConnectivity(
const RegularGridAdjacency2D& adjacency) {
244 if (adjacency.is4connectivity()) {
245 return ForegroundConnectivity::Four;
247 if (adjacency.is8connectivity()) {
248 return ForegroundConnectivity::Eight;
250 return ForegroundConnectivity::Unknown;
258 [[nodiscard]]
static std::array<ForegroundConnectivity, 2> shapeForegroundConnectivities(
const MorphologicalTree& tree) {
259 if (
const auto* adjacency = detail::constructionAdjacency(tree)) {
260 const auto connectivity = foregroundConnectivity(*adjacency);
261 return {connectivity, connectivity};
263 if (
const auto* adjacencies = detail::complementaryAdjacencies(tree)) {
264 return {foregroundConnectivity(adjacencies->minAdjacency), foregroundConnectivity(adjacencies->maxAdjacency)};
266 if (
const auto* convention = tree.topographicConvention();
267 convention && std::holds_alternative<SelfDualSpanImmersion>(convention->immersion)) {
268 return {ForegroundConnectivity::Four, ForegroundConnectivity::Four};
270 return {ForegroundConnectivity::Unknown, ForegroundConnectivity::Unknown};
278 [[nodiscard]]
static std::vector<ForegroundConnectivity> foregroundConnectivityByNode(
const MorphologicalTree& tree) {
279 const auto [lowerShape, upperShape] = shapeForegroundConnectivities(tree);
280 return std::vector<ForegroundConnectivity>(
static_cast<std::size_t
>(tree.numInternalNodeSlots()),
281 lowerShape == upperShape ? lowerShape : ForegroundConnectivity::Unknown);
289 template <AltitudeValue T>
290 [[nodiscard]]
static std::vector<ForegroundConnectivity> foregroundConnectivityByNode(
const ValuedMorphologicalTreeView<T>& view) {
291 const MorphologicalTree& tree = view.topology();
292 auto connectivityByNode = foregroundConnectivityByNode(tree);
293 const auto [lowerShape, upperShape] = shapeForegroundConnectivities(tree);
294 if (lowerShape != upperShape) {
295 for (NodeId node : tree.aliveNodeIds()) {
296 if (tree.isRoot(node)) {
299 const auto altitude = view.nodeAltitude(node);
300 const auto parentAltitude = view.nodeAltitude(tree.parent(node));
301 if (altitude < parentAltitude) {
302 connectivityByNode[
static_cast<std::size_t
>(node)] = lowerShape;
303 }
else if (altitude > parentAltitude) {
304 connectivityByNode[
static_cast<std::size_t
>(node)] = upperShape;
308 return connectivityByNode;
318 [[nodiscard]]
static PixelId adjacentPixel(
const MorphologicalTree& tree, PixelId pixel, ContourSide side) {
319 const int rows = tree.numRows();
320 const int columns = tree.numColumns();
324 case ContourSide::North:
326 case ContourSide::West:
328 case ContourSide::East:
330 case ContourSide::South:
341 [[nodiscard]]
static EdgeDeltas prepareEdgeDeltas(
const MorphologicalTree& tree) {
342 if (tree.numRows() <= 0 || tree.numColumns() <= 0) {
343 throw std::invalid_argument(
"Contour tracing requires a non-empty image domain.");
345 if (!tree.isAlive(tree.root())) {
346 throw std::invalid_argument(
"Contour tracing requires a live tree root.");
349 const int numNodes = tree.numInternalNodeSlots();
350 std::vector<EdgeDeltas::Event> additions;
351 std::vector<EdgeDeltas::Event> removals;
352 additions.reserve(
static_cast<std::size_t
>(std::max(tree.numPixels(), 1)));
353 removals.reserve(
static_cast<std::size_t
>(std::max(tree.numPixels(), 1)));
354 const int rows = tree.numRows();
355 const int columns = tree.numColumns();
356 const std::span<const NodeId> smallestNodes = tree.smallestNodeMap();
358 const auto addBorderEdge = [&](
PixelId pixel, ContourSide side) {
359 additions.push_back({smallestNodes[
static_cast<std::size_t
>(pixel)],
packEdge(pixel, side)});
361 for (
int column = 0; column < columns; ++column) {
362 addBorderEdge(column, ContourSide::North);
363 addBorderEdge((rows - 1) * columns + column, ContourSide::South);
365 for (
int row = 0; row < rows; ++row) {
366 addBorderEdge(row * columns, ContourSide::West);
367 addBorderEdge(row * columns + columns - 1, ContourSide::East);
370 std::vector<uint8_t> isRightBorder(
static_cast<std::size_t
>(tree.numPixels()), uint8_t{0});
371 for (
int row = 0; row < rows; ++row) {
372 isRightBorder[
static_cast<std::size_t
>(row * columns + columns - 1)] = uint8_t{1};
374 const std::size_t numAdjacentQueries = 2 *
static_cast<std::size_t
>(tree.numPixels());
375 const auto adjacentPixels = [&](std::size_t queryIndex) {
377 const bool horizontal = (queryIndex & 1) == 0;
379 const PixelId secondPixel = isRightBorder[
static_cast<std::size_t
>(firstPixel)] ? firstPixel : firstPixel + 1;
380 return std::pair{firstPixel, secondPixel};
382 const PixelId secondPixel = firstPixel >= tree.numPixels() - columns ? firstPixel : firstPixel + columns;
383 return std::pair{firstPixel, secondPixel};
385 const auto lcaQuery = [&](std::size_t queryIndex) {
386 const auto [firstPixel, secondPixel] = adjacentPixels(queryIndex);
387 return std::pair{smallestNodes[
static_cast<std::size_t
>(firstPixel)],
388 smallestNodes[
static_cast<std::size_t
>(secondPixel)]};
390 detail::CommittedTreeAccess::forEachLowestCommonAncestor(
391 tree, numAdjacentQueries, lcaQuery,
392 [&](std::size_t queryIndex, NodeId entryNode) {
393 const auto [firstPixel, secondPixel] = adjacentPixels(queryIndex);
394 if (firstPixel == secondPixel) {
397 const bool horizontal = (queryIndex & 1) == 0;
398 const ContourSide firstSide = horizontal ? ContourSide::East : ContourSide::South;
399 const ContourSide secondSide = horizontal ? ContourSide::West : ContourSide::North;
400 const NodeId firstNode = smallestNodes[
static_cast<std::size_t
>(firstPixel)];
401 const NodeId secondNode = smallestNodes[
static_cast<std::size_t
>(secondPixel)];
402 if (firstNode != entryNode) {
403 const int packedEdge =
packEdge(firstPixel, firstSide);
404 additions.push_back({firstNode, packedEdge});
405 removals.push_back({entryNode, packedEdge});
407 if (secondNode != entryNode) {
408 const int packedEdge =
packEdge(secondPixel, secondSide);
409 additions.push_back({secondNode, packedEdge});
410 removals.push_back({entryNode, packedEdge});
413 return EdgeDeltas::groupDistinct(numNodes, additions, removals);
421 [[nodiscard]]
static ConstructionData prepareConstructionData(
const MorphologicalTree& tree) {
422 tree.requireNotEditing(
"ContourTraceComputation");
423 return {prepareEdgeDeltas(tree), foregroundConnectivityByNode(tree)};
431 template <AltitudeValue T>
432 [[nodiscard]]
static ConstructionData prepareConstructionData(
const ValuedMorphologicalTreeView<T>& view) {
433 view.requireTopologyUnchanged(
"ContourTraceComputation");
434 view.topology().requireNotEditing(
"ContourTraceComputation");
435 return {prepareEdgeDeltas(view.topology()), foregroundConnectivityByNode(view)};
438 std::shared_ptr<const SharedIndexes> indexes_;