mmcfilters
Public API documentation
Loading...
Searching...
No Matches
ComponentTreeBuilder.hpp
1#pragma once
2
4#include "NativeHierarchyOwned.hpp"
5#include "../utils/Altitude.hpp"
6#include "../utils/Common.hpp"
7#include "../utils/Image.hpp"
8#include "../utils/RegularGridAdjacency2D.hpp"
9#include "../utils/CommittedGridAccess.hpp"
10
11#include <algorithm>
12#include <cassert>
13#include <cstddef>
14#include <cstdint>
15#include <iostream>
16#include <numeric>
17#include <tuple>
18#include <type_traits>
19#include <utility>
20#include <vector>
21
22namespace mmcfilters {
23
50 RegularGridAdjacency2D adjacency_;
52 bool isMaxTree_;
53
66 template <typename PixelType> [[nodiscard]] std::vector<PixelId> sort(const ImagePtr<PixelType>& image) const {
67 const int numPixels = image->getSize();
68 std::vector<PixelId> orderedPixels(static_cast<std::size_t>(numPixels));
69 const PixelType* values = image->rawData();
70
71 if constexpr (!std::is_same_v<PixelType, std::uint8_t>) {
72 if (PRINT_LOG) {
73 std::cout << "Sorting image with comparison sort, size: " << numPixels << std::endl;
74 }
75 std::iota(orderedPixels.begin(), orderedPixels.end(), 0);
76 if (isMaxTree_) {
77 std::stable_sort(orderedPixels.begin(), orderedPixels.end(), [&](PixelId lhs, PixelId rhs) { return values[lhs] < values[rhs]; });
78 } else {
79 std::stable_sort(orderedPixels.begin(), orderedPixels.end(), [&](PixelId lhs, PixelId rhs) { return values[lhs] > values[rhs]; });
80 }
81 } else {
82 if (PRINT_LOG) {
83 std::cout << "Sorting uint8 image with counting sort, size: " << numPixels << std::endl;
84 }
85
86 int maxValue = static_cast<int>(values[0]);
87 for (int index = 1; index < numPixels; ++index) {
88 if (maxValue < values[index]) {
89 maxValue = values[index];
90 }
91 }
92
93 std::vector<std::uint32_t> counter(static_cast<std::size_t>(maxValue) + 1, 0);
94 if (isMaxTree_) {
95 for (int index = 0; index < numPixels; ++index) {
96 ++counter[values[index]];
97 }
98 for (int level = 1; level <= maxValue; ++level) {
99 counter[level] += counter[level - 1];
100 }
101 for (int index = numPixels - 1; index >= 0; --index) {
102 orderedPixels[--counter[values[index]]] = index;
103 }
104 } else {
105 for (int index = 0; index < numPixels; ++index) {
106 ++counter[maxValue - values[index]];
107 }
108 for (int level = 1; level <= maxValue; ++level) {
109 counter[level] += counter[level - 1];
110 }
111 for (int index = numPixels - 1; index >= 0; --index) {
112 orderedPixels[--counter[maxValue - values[index]]] = index;
113 }
114 }
115 }
116 return orderedPixels;
117 }
118
132 template <typename PixelType>
133 [[nodiscard]] std::tuple<std::vector<PixelId>, std::vector<PixelId>, int> buildTree(const ImagePtr<PixelType>& image) const {
134 std::vector<PixelId> orderedPixels = sort(image);
135 const PixelType* values = image->rawData();
136
137 const int numPixels = image->getSize();
138 std::vector<PixelId> unionParent(static_cast<std::size_t>(numPixels), InvalidPixel);
139 std::vector<PixelId> pixelParent(static_cast<std::size_t>(numPixels), InvalidPixel);
140 auto findRoot = [&](PixelId pixel) {
141 while (unionParent[pixel] != pixel) {
142 unionParent[pixel] = unionParent[unionParent[pixel]];
143 pixel = unionParent[pixel];
144 }
145 return pixel;
146 };
147
148 for (int index = numPixels - 1; index >= 0; --index) {
149 const PixelId pixel = orderedPixels[static_cast<std::size_t>(index)];
150 pixelParent[pixel] = pixel;
151 unionParent[pixel] = pixel;
152 for (PixelId neighbor : detail::CommittedGridAccess::neighbors(adjacency_, pixel)) {
154 continue;
155 }
157 if (pixel != neighborRoot) {
158 pixelParent[neighborRoot] = pixel;
159 unionParent[neighborRoot] = pixel;
160 }
161 }
162 }
163
164 int numNodes = 0;
165 for (int index = 0; index < numPixels; ++index) {
166 const PixelId pixel = orderedPixels[static_cast<std::size_t>(index)];
167 const PixelId parent = pixelParent[pixel];
168 if (values[pixelParent[parent]] == values[parent]) {
169 pixelParent[pixel] = pixelParent[parent];
170 }
171 if (pixelParent[pixel] == pixel || values[pixelParent[pixel]] != values[pixel]) {
172 ++numNodes;
173 }
174 }
175
176 return {std::move(pixelParent), std::move(orderedPixels), numNodes};
177 }
178
197 template <AltitudeValue T>
198 [[nodiscard]] NativeHierarchyOwned<T> computeIntrinsicAttributes(const ImagePtr<T>& image, const std::vector<PixelId>& pixelParent,
199 const std::vector<PixelId>& orderedPixels, int numNodes) {
200 const MorphologicalTreeKind kind = isMaxTree_ ? MorphologicalTreeKind::MaxTree : MorphologicalTreeKind::MinTree;
201 const GridDomain2D gridDomain{image->getNumRows(), image->getNumColumns()};
202 const auto* imageValues = image->rawData();
203 std::vector<NodeId> nodeParent(static_cast<std::size_t>(numNodes));
204 std::vector<T> nodeValuation(static_cast<std::size_t>(numNodes));
205 std::vector<NodeId> smallestNode(static_cast<std::size_t>(image->getSize()));
206
207 NodeId idNode = 0;
208 for (const PixelId pixel : orderedPixels) {
209 const PixelId parent = pixelParent[pixel];
210 if (pixel == parent || imageValues[pixel] != imageValues[parent]) {
211 assert(pixel != parent || idNode == 0);
212 smallestNode[pixel] = idNode;
213 nodeParent[idNode] = pixel == parent ? idNode : smallestNode[parent];
215 ++idNode;
216 } else {
217 smallestNode[pixel] = smallestNode[parent];
218 }
219 }
220
221 assert(idNode == numNodes);
222 return makeNativeHierarchyOwned<T>(std::move(nodeParent), std::move(smallestNode), std::move(nodeValuation), 0, gridDomain,
223 makeMorphologicalTreeSemantics(kind, SharedAdjacencyContext{std::move(adjacency_)}));
224 }
225
226 public:
233 : adjacency_(std::move(adjacency)), isMaxTree_(isMaxTree) {}
234
244 template <AltitudeValue T> [[nodiscard]] NativeHierarchyOwned<T> build(const ImagePtr<T>& image) && {
245 assert(adjacency_.getNumRows() == image->getNumRows() && adjacency_.getNumColumns() == image->getNumColumns());
246 auto [pixelParent, orderedPixels, numNodes] = buildTree(image);
247 return computeIntrinsicAttributes(image, pixelParent, orderedPixels, numNodes);
248 }
249};
250
251} // namespace mmcfilters
constexpr PixelId InvalidPixel
Sentinel value used to denote an invalid pixel identifier.
Definition Common.hpp:43
constexpr bool PRINT_LOG
Compile-time switch for optional diagnostic logging.
Definition Common.hpp:83
Immutable scientific metadata attached to a morphological tree.
MorphologicalTreeSemantics makeMorphologicalTreeSemantics(MorphologicalTreeKind kind, MorphologicalTreeConstructionContext constructionContext=NoConstructionContext{})
Constructs semantics using the conventional altitude order of kind.
MorphologicalTreeKind
Declared construction family of one morphological tree.
Builds max-trees and min-trees with union-find.
ComponentTreeBuilder(RegularGridAdjacency2D adjacency, bool isMaxTree) noexcept
Configures max-tree or min-tree construction with an owned adjacency.
NativeHierarchyOwned< T > build(const ImagePtr< T > &image) &&
Builds and returns the native hierarchy, consuming the builder.
Immutable regular-grid 2D adjacency with allocation-free traversal.
int getNumColumns() const noexcept
Returns the number of columns in the attached grid domain.
int getNumRows() const noexcept
Returns the number of rows in the attached grid domain.
Owning result for one computed scalar attribute layout and buffer.
Shape metadata optionally attached to the pixel domain.
Records one adjacency shared by both construction polarities.