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/TreeKernelAccess.hpp"
7#include "../../trees/MorphologicalTree.hpp"
8#include "../../utils/Contract.hpp"
9
10#include <algorithm>
11#include <array>
12#include <concepts>
13#include <limits>
14#include <string_view>
15#include <vector>
16
17namespace mmcfilters::attributes::computers {
18
19namespace detail {
20
22struct TreeTopologyRequest {
23 bool height = false;
24 bool depth = false;
25 bool isLeaf = false;
26 bool isRoot = false;
27 bool numChildren = false;
28 bool numSiblings = false;
29 bool numDescendants = false;
30 bool numLeafDescendants = false;
31 bool leafRatio = false;
32 bool balance = false;
33 bool avgChildHeight = false;
34
36 [[nodiscard]] bool any() const noexcept {
37 return height || depth || isLeaf || isRoot || numChildren || numSiblings || numDescendants || numLeafDescendants || leafRatio || balance ||
38 avgChildHeight;
39 }
40
42 [[nodiscard]] bool needsHeight() const noexcept { return height || balance || avgChildHeight; }
44 [[nodiscard]] bool needsDescendantCounts() const noexcept { return numDescendants || numLeafDescendants || leafRatio; }
45
51 [[nodiscard]] static TreeTopologyRequest from(std::span<const Attribute> requestedAttributes) {
52 return {.height = requestsAttribute(requestedAttributes, SubtreeHeight),
53 .depth = requestsAttribute(requestedAttributes, DepthNode),
54 .isLeaf = requestsAttribute(requestedAttributes, IsLeafNode),
55 .isRoot = requestsAttribute(requestedAttributes, IsRootNode),
56 .numChildren = requestsAttribute(requestedAttributes, NumChildrenNode),
57 .numSiblings = requestsAttribute(requestedAttributes, NumSiblingsNode),
58 .numDescendants = requestsAttribute(requestedAttributes, NumDescendantsNode),
59 .numLeafDescendants = requestsAttribute(requestedAttributes, NumLeafDescendantsNode),
60 .leafRatio = requestsAttribute(requestedAttributes, LeafRatioNode),
61 .balance = requestsAttribute(requestedAttributes, BalanceNode),
62 .avgChildHeight = requestsAttribute(requestedAttributes, AvgChildHeightNode)};
63 }
64};
65
66namespace kernel {
67
73template <std::floating_point Real>
74inline void computeTreeTopology(const AttributeComputeContext<Real>& context, const TreeTopologyRequest& request) {
75 if (!request.any()) {
76 return;
77 }
78
79 const int stride = context.attrNames.NUM_ATTRIBUTES;
80 const auto offsetOf = [&](Attribute attribute) { return context.attrNames.indexMap.find(attribute)->second; };
81 const int heightOffset = request.height ? offsetOf(SubtreeHeight) : 0;
82 const int depthOffset = request.depth ? offsetOf(DepthNode) : 0;
83 const int isLeafOffset = request.isLeaf ? offsetOf(IsLeafNode) : 0;
84 const int isRootOffset = request.isRoot ? offsetOf(IsRootNode) : 0;
85 const int numChildrenOffset = request.numChildren ? offsetOf(NumChildrenNode) : 0;
86 const int numSiblingsOffset = request.numSiblings ? offsetOf(NumSiblingsNode) : 0;
87 const int numDescendantsOffset = request.numDescendants ? offsetOf(NumDescendantsNode) : 0;
88 const int numLeafDescendantsOffset = request.numLeafDescendants ? offsetOf(NumLeafDescendantsNode) : 0;
89 const int leafRatioOffset = request.leafRatio ? offsetOf(LeafRatioNode) : 0;
90 const int balanceOffset = request.balance ? offsetOf(BalanceNode) : 0;
91 const int avgChildHeightOffset = request.avgChildHeight ? offsetOf(AvgChildHeightNode) : 0;
92 const auto outputIndex = [&](NodeId node, int offset) { return static_cast<std::size_t>(node * stride + offset); };
93
94 const int numNodeSlots = context.tree.numInternalNodeSlots();
95 std::vector<Real> heightStorage(request.needsHeight() && !request.height ? static_cast<std::size_t>(numNodeSlots) : 0, Real{0});
96 std::vector<Real> numDescendantStorage(request.needsDescendantCounts() && !request.numDescendants ? static_cast<std::size_t>(numNodeSlots) : 0,
97 Real{0});
98 std::vector<Real> numLeafDescendantStorage(
99 request.needsDescendantCounts() && !request.numLeafDescendants ? static_cast<std::size_t>(numNodeSlots) : 0, Real{0});
100 std::vector<Real> minimumChildHeight(request.balance ? static_cast<std::size_t>(numNodeSlots) : 0, Real{0});
101
102 const auto heightAt = [&](NodeId node) -> Real& {
103 return request.height ? context.buffer[outputIndex(node, heightOffset)] : heightStorage[static_cast<std::size_t>(node)];
104 };
105 const auto numDescendantsAt = [&](NodeId node) -> Real& {
106 return request.numDescendants ? context.buffer[outputIndex(node, numDescendantsOffset)]
107 : numDescendantStorage[static_cast<std::size_t>(node)];
108 };
109 const auto numLeafDescendantsAt = [&](NodeId node) -> Real& {
110 return request.numLeafDescendants ? context.buffer[outputIndex(node, numLeafDescendantsOffset)]
111 : numLeafDescendantStorage[static_cast<std::size_t>(node)];
112 };
113
114 const NodeId root = context.tree.root();
115 ::mmcfilters::detail::TreeKernelAccess::traversePostOrder(
116 context.tree, root,
117 [&](NodeId node) {
118 const bool isRoot = node == root;
119 const NodeId parent = isRoot ? InvalidNode : ::mmcfilters::detail::TreeKernelAccess::nodeParent(context.tree, node);
120 const int numChildren = ::mmcfilters::detail::TreeKernelAccess::numChildren(context.tree, node);
121 const bool isLeaf = numChildren == 0;
122
123 if (request.depth) {
124 context.buffer[outputIndex(node, depthOffset)] =
125 isRoot ? Real{0} : context.buffer[outputIndex(parent, depthOffset)] + Real{1};
126 }
127 if (request.needsHeight()) {
128 heightAt(node) = Real{0};
129 }
130 if (request.needsDescendantCounts()) {
131 numDescendantsAt(node) = Real{0};
132 numLeafDescendantsAt(node) = isLeaf ? Real{1} : Real{0};
133 }
134 if (request.isLeaf)
135 context.buffer[outputIndex(node, isLeafOffset)] = isLeaf ? Real{1} : Real{0};
136 if (request.isRoot)
137 context.buffer[outputIndex(node, isRootOffset)] = isRoot ? Real{1} : Real{0};
138 if (request.numChildren)
139 context.buffer[outputIndex(node, numChildrenOffset)] = static_cast<Real>(numChildren);
140 if (request.numSiblings)
141 context.buffer[outputIndex(node, numSiblingsOffset)] =
142 isRoot ? Real{0} : static_cast<Real>(::mmcfilters::detail::TreeKernelAccess::numChildren(context.tree, parent) - 1);
143 if (request.leafRatio)
144 context.buffer[outputIndex(node, leafRatioOffset)] = Real{0};
145 if (request.balance) {
146 minimumChildHeight[static_cast<std::size_t>(node)] = std::numeric_limits<Real>::infinity();
147 context.buffer[outputIndex(node, balanceOffset)] = Real{0};
148 }
149 if (request.avgChildHeight)
150 context.buffer[outputIndex(node, avgChildHeightOffset)] = Real{0};
151 },
152 [&](NodeId parent, NodeId child) {
153 if (request.needsDescendantCounts()) {
154 numDescendantsAt(parent) += numDescendantsAt(child) + Real{1};
155 numLeafDescendantsAt(parent) += numLeafDescendantsAt(child);
156 }
157 if (request.needsHeight()) {
158 const Real childHeight = heightAt(child);
159 heightAt(parent) = std::max(heightAt(parent), childHeight + Real{1});
160 if (request.balance) {
161 Real& minimumHeight = minimumChildHeight[static_cast<std::size_t>(parent)];
162 minimumHeight = std::min(minimumHeight, childHeight);
163 }
164 if (request.avgChildHeight) {
165 context.buffer[outputIndex(parent, avgChildHeightOffset)] += childHeight;
166 }
167 }
168 },
169 [&](NodeId node) {
170 const int numChildren = ::mmcfilters::detail::TreeKernelAccess::numChildren(context.tree, node);
171 if (request.leafRatio) {
172 const Real descendantCount = numDescendantsAt(node);
173 context.buffer[outputIndex(node, leafRatioOffset)] =
174 descendantCount > Real{0}
175 ? ::mmcfilters::attributes::numeric::safeDivide(numLeafDescendantsAt(node), descendantCount + Real{1})
176 : Real{1};
177 }
178 if (numChildren != 0) {
179 if (request.balance) {
180 context.buffer[outputIndex(node, balanceOffset)] =
181 heightAt(node) - Real{1} - minimumChildHeight[static_cast<std::size_t>(node)];
182 }
183 if (request.avgChildHeight) {
184 context.buffer[outputIndex(node, avgChildHeightOffset)] = ::mmcfilters::attributes::numeric::safeDivide(
185 context.buffer[outputIndex(node, avgChildHeightOffset)], static_cast<Real>(numChildren));
186 }
187 }
188 });
189}
190
191} // namespace kernel
192
193template <std::floating_point Real>
194inline void validateTreeTopologyContext(const AttributeComputeContext<Real>& context) {
195 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
196 requireRequestedAttributeColumns(context);
197}
198} // namespace detail
199
227 public:
229 static constexpr std::string_view familyName = "tree-topology";
230
232 static constexpr AttributeComputerFamily family = AttributeComputerFamily::TreeTopology;
233
235 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
236
240 inline static constexpr std::array<Attribute, 11> producedAttributes{SubtreeHeight, DepthNode, IsLeafNode, IsRootNode,
241 NumChildrenNode, NumSiblingsNode, NumDescendantsNode, NumLeafDescendantsNode,
242 LeafRatioNode, BalanceNode, AvgChildHeightNode};
243
256 template <std::floating_point Real> static void compute(const AttributeComputeContext<Real>& context) {
257 const detail::TreeTopologyRequest request = detail::TreeTopologyRequest::from(context.requestedAttributes);
258 MMCFILTERS_CONTRACT_CHECKED_ONLY(detail::validateTreeTopologyContext(context));
259 detail::kernel::computeTreeTopology(context, request);
260 }
261
262 public:
272 template <std::floating_point Real> static void computeUnitRows(const UnitAttributeComputeContext<Real>& context) {
273 requireUnitAttributeBufferShape(context.tree, context.unitPixels, context.buffer, context.attrNames);
274 computeUnitRowsEstablished(context);
275 }
276
277 private:
284 template <class Computer, class Context> friend void mmcfilters::detail::kernel::computeUnitRows(const Context& context);
285
287 template <std::floating_point Real> static void computeUnitRowsEstablished(const UnitAttributeComputeContext<Real>& context) {
288 std::span<const PixelId> unitPixels = context.unitPixels;
289 std::span<Real> buffer = context.buffer;
290 const AttributeNames& attrNames = context.attrNames;
291 std::span<const Attribute> requestedAttributes = context.requestedAttributes;
292
293 const bool computeHeight = requestsAttribute(requestedAttributes, SubtreeHeight);
294 const bool computeDepth = requestsAttribute(requestedAttributes, DepthNode);
295 const bool computeIsLeaf = requestsAttribute(requestedAttributes, IsLeafNode);
296 const bool computeIsRoot = requestsAttribute(requestedAttributes, IsRootNode);
297 const bool computeNumChildren = requestsAttribute(requestedAttributes, NumChildrenNode);
298 const bool computeNumSiblings = requestsAttribute(requestedAttributes, NumSiblingsNode);
299 const bool computeNumDescendants = requestsAttribute(requestedAttributes, NumDescendantsNode);
300 const bool computeNumLeafDescendants = requestsAttribute(requestedAttributes, NumLeafDescendantsNode);
301 const bool computeLeafRatio = requestsAttribute(requestedAttributes, LeafRatioNode);
302 const bool computeBalance = requestsAttribute(requestedAttributes, BalanceNode);
303 const bool computeAvgChildHeight = requestsAttribute(requestedAttributes, AvgChildHeightNode);
304
307 return;
308 }
309
311 if (computeHeight) {
312 buffer[attrNames.linearIndex(leafIndex, SubtreeHeight)] = Real{0};
313 }
314 if (computeDepth) {
315 buffer[attrNames.linearIndex(leafIndex, DepthNode)] = Real{0};
316 }
317 if (computeIsLeaf) {
318 buffer[attrNames.linearIndex(leafIndex, IsLeafNode)] = Real{1};
319 }
320 if (computeIsRoot) {
321 buffer[attrNames.linearIndex(leafIndex, IsRootNode)] = Real{1};
322 }
323 if (computeNumChildren) {
324 buffer[attrNames.linearIndex(leafIndex, NumChildrenNode)] = Real{0};
325 }
326 if (computeNumSiblings) {
327 buffer[attrNames.linearIndex(leafIndex, NumSiblingsNode)] = Real{0};
328 }
329 if (computeNumDescendants) {
330 buffer[attrNames.linearIndex(leafIndex, NumDescendantsNode)] = Real{0};
331 }
332 if (computeNumLeafDescendants) {
333 buffer[attrNames.linearIndex(leafIndex, NumLeafDescendantsNode)] = Real{1};
334 }
335 if (computeLeafRatio) {
336 buffer[attrNames.linearIndex(leafIndex, LeafRatioNode)] = Real{1};
337 }
338 if (computeBalance) {
339 buffer[attrNames.linearIndex(leafIndex, BalanceNode)] = Real{0};
340 }
341 if (computeAvgChildHeight) {
342 buffer[attrNames.linearIndex(leafIndex, AvgChildHeightNode)] = Real{0};
343 }
344 }
345 }
346};
347
348} // 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.
int linearIndex(int nodeIndex, Attribute attr) const
Returns the linear index in the flat buffer for (node, attr).
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.