mmcfilters
Public API documentation
Loading...
Searching...
No Matches
TreeTopologyComputer.hpp
1#pragma once
2
3#include "AttributeComputerDomain.hpp"
4#include "AttributeComputerFamily.hpp"
5#include "../detail/AttributeKernelSupport.hpp"
6#include "../../trees/detail/CommittedTreeAccess.hpp"
7#include "../../trees/detail/TreeTraversalDetail.hpp"
8#include "../../trees/MorphologicalTree.hpp"
9#include "../../utils/Contract.hpp"
10
11#include <algorithm>
12#include <array>
13#include <concepts>
14#include <limits>
15#include <string_view>
16#include <vector>
17
18namespace mmcfilters::attributes::computers {
19
20namespace detail {
21
23struct TreeTopologyRequest {
24 bool height = false;
25 bool depth = false;
26 bool isLeaf = false;
27 bool isRoot = false;
28 bool numChildren = false;
29 bool numSiblings = false;
30 bool numDescendants = false;
31 bool numLeafDescendants = false;
32 bool leafRatio = false;
33 bool balance = false;
34 bool avgChildHeight = false;
35
37 [[nodiscard]] bool any() const noexcept {
38 return height || depth || isLeaf || isRoot || numChildren || numSiblings || numDescendants || numLeafDescendants || leafRatio || balance ||
39 avgChildHeight;
40 }
41
43 [[nodiscard]] bool needsHeight() const noexcept { return height || balance || avgChildHeight; }
45 [[nodiscard]] bool needsDescendantCounts() const noexcept { return numDescendants || numLeafDescendants || leafRatio; }
46
52 [[nodiscard]] static TreeTopologyRequest from(std::span<const Attribute> requestedAttributes) {
53 return {.height = requestsAttribute(requestedAttributes, SubtreeHeight),
54 .depth = requestsAttribute(requestedAttributes, DepthNode),
55 .isLeaf = requestsAttribute(requestedAttributes, IsLeafNode),
56 .isRoot = requestsAttribute(requestedAttributes, IsRootNode),
57 .numChildren = requestsAttribute(requestedAttributes, NumChildrenNode),
58 .numSiblings = requestsAttribute(requestedAttributes, NumSiblingsNode),
59 .numDescendants = requestsAttribute(requestedAttributes, NumDescendantsNode),
60 .numLeafDescendants = requestsAttribute(requestedAttributes, NumLeafDescendantsNode),
61 .leafRatio = requestsAttribute(requestedAttributes, LeafRatioNode),
62 .balance = requestsAttribute(requestedAttributes, BalanceNode),
63 .avgChildHeight = requestsAttribute(requestedAttributes, AvgChildHeightNode)};
64 }
65};
66
67namespace kernel {
68
74template <std::floating_point Real>
75inline void computeTreeTopology(const AttributeComputeContext<Real>& context, const TreeTopologyRequest& request) {
76 if (!request.any()) {
77 return;
78 }
79
80 const int stride = context.attrNames.NUM_ATTRIBUTES;
81 const auto offsetOf = [&](Attribute attribute) { return context.attrNames.indexMap.find(attribute)->second; };
82 const int heightOffset = request.height ? offsetOf(SubtreeHeight) : 0;
83 const int depthOffset = request.depth ? offsetOf(DepthNode) : 0;
84 const int isLeafOffset = request.isLeaf ? offsetOf(IsLeafNode) : 0;
85 const int isRootOffset = request.isRoot ? offsetOf(IsRootNode) : 0;
86 const int numChildrenOffset = request.numChildren ? offsetOf(NumChildrenNode) : 0;
87 const int numSiblingsOffset = request.numSiblings ? offsetOf(NumSiblingsNode) : 0;
88 const int numDescendantsOffset = request.numDescendants ? offsetOf(NumDescendantsNode) : 0;
89 const int numLeafDescendantsOffset = request.numLeafDescendants ? offsetOf(NumLeafDescendantsNode) : 0;
90 const int leafRatioOffset = request.leafRatio ? offsetOf(LeafRatioNode) : 0;
91 const int balanceOffset = request.balance ? offsetOf(BalanceNode) : 0;
92 const int avgChildHeightOffset = request.avgChildHeight ? offsetOf(AvgChildHeightNode) : 0;
93 const auto outputIndex = [&](NodeId node, int offset) { return static_cast<std::size_t>(node * stride + offset); };
94
95 const int numNodeSlots = context.tree.numInternalNodeSlots();
96 std::vector<Real> heightStorage(request.needsHeight() && !request.height ? static_cast<std::size_t>(numNodeSlots) : 0, Real{0});
97 std::vector<Real> numDescendantStorage(request.needsDescendantCounts() && !request.numDescendants ? static_cast<std::size_t>(numNodeSlots) : 0,
98 Real{0});
99 std::vector<Real> numLeafDescendantStorage(
100 request.needsDescendantCounts() && !request.numLeafDescendants ? static_cast<std::size_t>(numNodeSlots) : 0, Real{0});
101 std::vector<Real> minimumChildHeight(request.balance ? static_cast<std::size_t>(numNodeSlots) : 0, Real{0});
102
103 const auto heightAt = [&](NodeId node) -> Real& {
104 return request.height ? context.buffer[outputIndex(node, heightOffset)] : heightStorage[static_cast<std::size_t>(node)];
105 };
106 const auto numDescendantsAt = [&](NodeId node) -> Real& {
107 return request.numDescendants ? context.buffer[outputIndex(node, numDescendantsOffset)]
108 : numDescendantStorage[static_cast<std::size_t>(node)];
109 };
110 const auto numLeafDescendantsAt = [&](NodeId node) -> Real& {
111 return request.numLeafDescendants ? context.buffer[outputIndex(node, numLeafDescendantsOffset)]
112 : numLeafDescendantStorage[static_cast<std::size_t>(node)];
113 };
114
115 const NodeId root = context.tree.root();
116 ::mmcfilters::detail::kernel::traversePostOrder(
117 context.tree, root,
118 [&](NodeId node) {
119 const bool isRoot = node == root;
120 const NodeId parent = isRoot ? InvalidNode : ::mmcfilters::detail::CommittedTreeAccess::nodeParent(context.tree, node);
121 const int numChildren = ::mmcfilters::detail::CommittedTreeAccess::numChildren(context.tree, node);
122 const bool isLeaf = numChildren == 0;
123
124 if (request.depth) {
125 context.buffer[outputIndex(node, depthOffset)] =
126 isRoot ? Real{0} : context.buffer[outputIndex(parent, depthOffset)] + Real{1};
127 }
128 if (request.needsHeight()) {
129 heightAt(node) = Real{0};
130 }
131 if (request.needsDescendantCounts()) {
132 numDescendantsAt(node) = Real{0};
133 numLeafDescendantsAt(node) = isLeaf ? Real{1} : Real{0};
134 }
135 if (request.isLeaf)
136 context.buffer[outputIndex(node, isLeafOffset)] = isLeaf ? Real{1} : Real{0};
137 if (request.isRoot)
138 context.buffer[outputIndex(node, isRootOffset)] = isRoot ? Real{1} : Real{0};
139 if (request.numChildren)
140 context.buffer[outputIndex(node, numChildrenOffset)] = static_cast<Real>(numChildren);
141 if (request.numSiblings)
142 context.buffer[outputIndex(node, numSiblingsOffset)] =
143 isRoot ? Real{0} : static_cast<Real>(::mmcfilters::detail::CommittedTreeAccess::numChildren(context.tree, parent) - 1);
144 if (request.leafRatio)
145 context.buffer[outputIndex(node, leafRatioOffset)] = Real{0};
146 if (request.balance) {
147 minimumChildHeight[static_cast<std::size_t>(node)] = std::numeric_limits<Real>::infinity();
148 context.buffer[outputIndex(node, balanceOffset)] = Real{0};
149 }
150 if (request.avgChildHeight)
151 context.buffer[outputIndex(node, avgChildHeightOffset)] = Real{0};
152 },
153 [&](NodeId parent, NodeId child) {
154 if (request.needsDescendantCounts()) {
155 numDescendantsAt(parent) += numDescendantsAt(child) + Real{1};
156 numLeafDescendantsAt(parent) += numLeafDescendantsAt(child);
157 }
158 if (request.needsHeight()) {
159 const Real childHeight = heightAt(child);
160 heightAt(parent) = std::max(heightAt(parent), childHeight + Real{1});
161 if (request.balance) {
162 Real& minimumHeight = minimumChildHeight[static_cast<std::size_t>(parent)];
163 minimumHeight = std::min(minimumHeight, childHeight);
164 }
165 if (request.avgChildHeight) {
166 context.buffer[outputIndex(parent, avgChildHeightOffset)] += childHeight;
167 }
168 }
169 },
170 [&](NodeId node) {
171 const int numChildren = ::mmcfilters::detail::CommittedTreeAccess::numChildren(context.tree, node);
172 if (request.leafRatio) {
173 const Real descendantCount = numDescendantsAt(node);
174 context.buffer[outputIndex(node, leafRatioOffset)] =
175 descendantCount > Real{0}
176 ? ::mmcfilters::attributes::numeric::safeDivide(numLeafDescendantsAt(node), descendantCount + Real{1})
177 : Real{1};
178 }
179 if (numChildren != 0) {
180 if (request.balance) {
181 context.buffer[outputIndex(node, balanceOffset)] =
182 heightAt(node) - Real{1} - minimumChildHeight[static_cast<std::size_t>(node)];
183 }
184 if (request.avgChildHeight) {
185 context.buffer[outputIndex(node, avgChildHeightOffset)] = ::mmcfilters::attributes::numeric::safeDivide(
186 context.buffer[outputIndex(node, avgChildHeightOffset)], static_cast<Real>(numChildren));
187 }
188 }
189 });
190}
191
192} // namespace kernel
193
194template <std::floating_point Real>
195inline void validateTreeTopologyContext(const AttributeComputeContext<Real>& context) {
196 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
197 requireRequestedAttributeColumns(context);
198}
199} // namespace detail
200
228 public:
230 static constexpr std::string_view familyName = "tree-topology";
231
233 static constexpr AttributeComputerFamily family = AttributeComputerFamily::TreeTopology;
234
236 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
237
241 inline static constexpr std::array<Attribute, 11> producedAttributes{SubtreeHeight, DepthNode, IsLeafNode, IsRootNode,
242 NumChildrenNode, NumSiblingsNode, NumDescendantsNode, NumLeafDescendantsNode,
243 LeafRatioNode, BalanceNode, AvgChildHeightNode};
244
257 template <std::floating_point Real> static void compute(const AttributeComputeContext<Real>& context) {
258 const detail::TreeTopologyRequest request = detail::TreeTopologyRequest::from(context.requestedAttributes);
259 MMCFILTERS_CONTRACT_CHECKED_ONLY(detail::validateTreeTopologyContext(context));
260 detail::kernel::computeTreeTopology(context, request);
261 }
262
263 public:
273 template <std::floating_point Real> static void computeUnitRows(const UnitAttributeComputeContext<Real>& context) {
274 const MorphologicalTree& tree = context.tree;
275 std::span<const PixelId> unitPixels = context.unitPixels;
276 std::span<Real> buffer = context.buffer;
277 const AttributeNames& attrNames = context.attrNames;
278 std::span<const Attribute> requestedAttributes = context.requestedAttributes;
279
281
282 const bool computeHeight = requestsAttribute(requestedAttributes, SubtreeHeight);
283 const bool computeDepth = requestsAttribute(requestedAttributes, DepthNode);
284 const bool computeIsLeaf = requestsAttribute(requestedAttributes, IsLeafNode);
285 const bool computeIsRoot = requestsAttribute(requestedAttributes, IsRootNode);
286 const bool computeNumChildren = requestsAttribute(requestedAttributes, NumChildrenNode);
287 const bool computeNumSiblings = requestsAttribute(requestedAttributes, NumSiblingsNode);
288 const bool computeNumDescendants = requestsAttribute(requestedAttributes, NumDescendantsNode);
289 const bool computeNumLeafDescendants = requestsAttribute(requestedAttributes, NumLeafDescendantsNode);
290 const bool computeLeafRatio = requestsAttribute(requestedAttributes, LeafRatioNode);
291 const bool computeBalance = requestsAttribute(requestedAttributes, BalanceNode);
292 const bool computeAvgChildHeight = requestsAttribute(requestedAttributes, AvgChildHeightNode);
293
296 return;
297 }
298
300 if (computeHeight) {
301 buffer[attrNames.linearIndex(leafIndex, SubtreeHeight)] = Real{0};
302 }
303 if (computeDepth) {
304 buffer[attrNames.linearIndex(leafIndex, DepthNode)] = Real{0};
305 }
306 if (computeIsLeaf) {
307 buffer[attrNames.linearIndex(leafIndex, IsLeafNode)] = Real{1};
308 }
309 if (computeIsRoot) {
310 buffer[attrNames.linearIndex(leafIndex, IsRootNode)] = Real{1};
311 }
312 if (computeNumChildren) {
313 buffer[attrNames.linearIndex(leafIndex, NumChildrenNode)] = Real{0};
314 }
315 if (computeNumSiblings) {
316 buffer[attrNames.linearIndex(leafIndex, NumSiblingsNode)] = Real{0};
317 }
319 buffer[attrNames.linearIndex(leafIndex, NumDescendantsNode)] = Real{0};
320 }
322 buffer[attrNames.linearIndex(leafIndex, NumLeafDescendantsNode)] = Real{1};
323 }
324 if (computeLeafRatio) {
325 buffer[attrNames.linearIndex(leafIndex, LeafRatioNode)] = Real{1};
326 }
327 if (computeBalance) {
328 buffer[attrNames.linearIndex(leafIndex, BalanceNode)] = Real{0};
329 }
331 buffer[attrNames.linearIndex(leafIndex, AvgChildHeightNode)] = Real{0};
332 }
333 }
334 }
335};
336
337} // namespace mmcfilters::attributes::computers
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
#define MMCFILTERS_CONTRACT_CHECKED_ONLY(...)
Executes validation statements only when defensive checks are enabled.
Definition Contract.hpp:67
Layout object that maps scalar attributes to flat-buffer offsets.
Mutable connected-subset tree on a finite pixel domain.
Computes structural descriptors that depend only on the tree topology.
static void computeUnitRows(const UnitAttributeComputeContext< Real > &context)
Materializes topology descriptors for one-pixel unit supports.
static void compute(const AttributeComputeContext< Real > &context)
Computes the requested topology descriptors.
Owning result for one computed scalar attribute layout and buffer.
std::vector< Real > second
Flat per-node attribute buffer indexed through first.