MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
EventEngine.hpp
1#pragma once
2
3#include "../trees/MorphologicalTree.hpp"
4#include "../trees/detail/ProperPartEntryNode.hpp"
5#include "../utils/Common.hpp"
6
7#include <algorithm>
8#include <cstdint>
9#include <stdexcept>
10#include <utility>
11#include <vector>
12
13namespace mmcfilters::local_events {
14
38public:
45 struct WindowOffset {
47 int rowOffset = 0;
48
50 int colOffset = 0;
51 };
52
53private:
57 struct EntryEvent {
58 NodeId node = InvalidNode;
59 uint32_t mask = 0;
60 };
61
62public:
88 return detail::properPartEntryNode(tree, anchorPixel, samplePixel);
89 }
90
108 static NodeId entryNode(const MorphologicalTree& tree, int anchorPixel, WindowOffset offset) {
109 const int rows = tree.getNumRowsOfImage();
110 const int cols = tree.getNumColsOfImage();
111 if (rows <= 0 || cols <= 0) {
112 return InvalidNode;
113 }
115 return InvalidNode;
116 }
117
118 const auto [row, col] = ImageUtils::to2D(anchorPixel, cols);
119 const int sampleRow = row + offset.rowOffset;
120 const int sampleCol = col + offset.colOffset;
122 return InvalidNode;
123 }
124
126 }
127
163 template<class Policy>
164 static std::vector<typename Policy::Bucket> computeDeltasWithPolicy(
165 const MorphologicalTree& tree,
166 const std::vector<WindowOffset>& window,
167 const Policy& policy) {
168 requireValidInput(tree, window);
169
170 std::vector<typename Policy::Bucket> buckets(
171 static_cast<std::size_t>(tree.getNumInternalNodeSlots()));
172 std::vector<EntryEvent> events;
173 events.reserve(window.size());
174
175 const int totalPixels = tree.getNumRowsOfImage() * tree.getNumColsOfImage();
176 for (int anchorPixel = 0; anchorPixel < totalPixels; ++anchorPixel) {
177 events.clear();
178 for (std::size_t i = 0; i < window.size(); ++i) {
179 const NodeId entry = entryNode(tree, anchorPixel, window[i]);
180 if (entry != InvalidNode) {
181 events.push_back({entry, uint32_t{1} << i});
182 }
183 }
184 if (events.empty()) {
185 continue;
186 }
187
188 sortEventsBottomUp(tree, events);
189
190 uint32_t state = 0;
191 bool hasPreviousState = false;
192 for (std::size_t i = 0; i < events.size();) {
193 const NodeId eventNode = events[i].node;
195 do {
196 eventMask |= events[i].mask;
197 ++i;
198 } while (i < events.size() && events[i].node == eventNode);
199
200 const uint32_t oldState = state;
201 state |= eventMask;
202 auto& bucket = buckets[static_cast<std::size_t>(eventNode)];
203 if (!hasPreviousState) {
204 policy.applyInitial(bucket, state);
205 hasPreviousState = true;
206 } else {
207 policy.applyTransition(bucket, oldState, state);
208 }
209 }
210 }
211
212 return buckets;
213 }
214
242 template<class Policy>
243 static std::vector<typename Policy::Bucket> computeWithPolicy(
244 const MorphologicalTree& tree,
245 const std::vector<WindowOffset>& window,
246 const Policy& policy) {
247 std::vector<typename Policy::Bucket> buckets =
249 aggregateSubtreeBuckets(tree, buckets, policy);
250 return buckets;
251 }
252
253private:
257 static void requireValidInput(const MorphologicalTree& tree, const std::vector<WindowOffset>& window) {
258 if (tree.getNumRowsOfImage() <= 0 || tree.getNumColsOfImage() <= 0) {
259 throw std::invalid_argument("EventEngine requires a non-empty image domain.");
260 }
261 if (!tree.isAlive(tree.getRoot())) {
262 throw std::invalid_argument("EventEngine requires a live tree root.");
263 }
264 if (window.size() > 32) {
265 throw std::invalid_argument("EventEngine supports at most 32 window samples.");
266 }
267 }
268
278 static void sortEventsBottomUp(const MorphologicalTree& tree, std::vector<EntryEvent>& events) {
279 std::sort(events.begin(), events.end(), [&](const EntryEvent& lhs, const EntryEvent& rhs) {
280 if (lhs.node == rhs.node) {
281 return false;
282 }
283 if (tree.isStrictAncestor(lhs.node, rhs.node)) {
284 return false;
285 }
286 if (tree.isStrictAncestor(rhs.node, lhs.node)) {
287 return true;
288 }
289 return lhs.node < rhs.node;
290 });
291 }
292
302 template<class Bucket, class Policy>
303 static void aggregateSubtreeBuckets(
304 const MorphologicalTree& tree,
305 std::vector<Bucket>& values,
306 const Policy& policy) {
307 std::vector<std::pair<NodeId, bool>> stack;
308 stack.emplace_back(tree.getRoot(), false);
309
310 while (!stack.empty()) {
311 const auto [node, expanded] = stack.back();
312 stack.pop_back();
313 if (!expanded) {
314 stack.emplace_back(node, true);
315 for (NodeId child : tree.getChildren(node)) {
316 stack.emplace_back(child, false);
317 }
318 continue;
319 }
320
321 for (NodeId child : tree.getChildren(node)) {
322 policy.merge(values[static_cast<std::size_t>(node)], values[static_cast<std::size_t>(child)]);
323 }
324 }
325 }
326};
327
329using WindowOffset = EventEngine::WindowOffset;
330
331} // namespace mmcfilters::local_events
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
Definition Common.hpp:25
static std::pair< int, int > to2D(int index, int numCols) noexcept
Converts a row-major linear index to (row, col).
Definition Image.hpp:283
static int to1D(int row, int col, int numCols) noexcept
Converts (row, col) to a row-major linear index.
Definition Image.hpp:272
Mutable morphological tree built directly on proper parts and dense node ids.
int getNumInternalNodeSlots() const
Returns the size of the dense internal-node id domain.
bool isAlive(NodeId nodeId) const
Tests whether a node slot currently represents a live node.
NodeId getRoot() const
Returns the current hierarchy root.
int getNumColsOfImage() const
Returns the number of image columns in the attached 2D domain.
int getNumRowsOfImage() const
Returns the number of image rows in the attached 2D domain.
Generic event engine for finite binary local computations.
static std::vector< typename Policy::Bucket > computeDeltasWithPolicy(const MorphologicalTree &tree, const std::vector< WindowOffset > &window, const Policy &policy)
Computes local state-change deltas before subtree aggregation.
static NodeId entryNode(const MorphologicalTree &tree, int anchorPixel, int samplePixel)
Returns the first ancestor of anchorPixel that contains samplePixel.
static std::vector< typename Policy::Bucket > computeWithPolicy(const MorphologicalTree &tree, const std::vector< WindowOffset > &window, const Policy &policy)
Computes local counters with a caller-owned bucket policy.
static NodeId entryNode(const MorphologicalTree &tree, int anchorPixel, WindowOffset offset)
Returns the entry node for one translated sample around an anchor.
Owning result for one computed scalar attribute layout and buffer.
Relative integer offset of one sample in a local image window.
int colOffset
Column displacement from the anchor pixel.
int rowOffset
Row displacement from the anchor pixel.