MorphologicalAttributeFilters
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/TreeTraversalDetail.hpp"
7#include "../../trees/MorphologicalTree.hpp"
8
9#include <algorithm>
10#include <array>
11#include <concepts>
12#include <limits>
13#include <string_view>
14#include <vector>
15
16
17namespace mmcfilters::attributes::computers {
18
19namespace detail {
20inline NodeId topologySlotOf(const MorphologicalTree&, NodeId nodeId) noexcept {
21 return nodeId;
22}
23}
24
52public:
54 static constexpr std::string_view familyName = "tree-topology";
55
57 static constexpr AttributeComputerFamily family = AttributeComputerFamily::TreeTopology;
58
60 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
61
65 inline static constexpr std::array<Attribute, 11> producedAttributes{
66 HEIGHT_NODE,
67 DEPTH_NODE,
68 IS_LEAF_NODE,
69 IS_ROOT_NODE,
70 NUM_CHILDREN_NODE,
71 NUM_SIBLINGS_NODE,
72 NUM_DESCENDANTS_NODE,
73 NUM_LEAF_DESCENDANTS_NODE,
74 LEAF_RATIO_NODE,
75 BALANCE_NODE,
76 AVG_CHILD_HEIGHT_NODE};
77
88 template <std::floating_point Real>
90 computeImpl(
91 context.tree,
92 context.buffer,
93 context.attrNames,
94 context.requestedAttributes);
95 }
96
97private:
98 template <std::floating_point Real>
99 static void computeImpl(const MorphologicalTree& tree, std::span<Real> buffer, const AttributeNames& attrNames, std::span<const Attribute> requestedAttributes) {
101
102 bool computeHeight = std::find(requestedAttributes.begin(), requestedAttributes.end(), HEIGHT_NODE) != requestedAttributes.end();
103 bool computeDepth = std::find(requestedAttributes.begin(), requestedAttributes.end(), DEPTH_NODE) != requestedAttributes.end();
104 bool computeIsLeaf = std::find(requestedAttributes.begin(), requestedAttributes.end(), IS_LEAF_NODE) != requestedAttributes.end();
105 bool computeIsRoot = std::find(requestedAttributes.begin(), requestedAttributes.end(), IS_ROOT_NODE) != requestedAttributes.end();
106 bool computeNumChildren = std::find(requestedAttributes.begin(), requestedAttributes.end(), NUM_CHILDREN_NODE) != requestedAttributes.end();
107 bool computeNumSiblings = std::find(requestedAttributes.begin(), requestedAttributes.end(), NUM_SIBLINGS_NODE) != requestedAttributes.end();
108 bool computeNumDescendants = std::find(requestedAttributes.begin(), requestedAttributes.end(), NUM_DESCENDANTS_NODE) != requestedAttributes.end();
109 bool computeNumLeafDescendants = std::find(requestedAttributes.begin(), requestedAttributes.end(), NUM_LEAF_DESCENDANTS_NODE) != requestedAttributes.end();
110 bool computeLeafRatio = std::find(requestedAttributes.begin(), requestedAttributes.end(), LEAF_RATIO_NODE) != requestedAttributes.end();
111 bool computeBalance = std::find(requestedAttributes.begin(), requestedAttributes.end(), BALANCE_NODE) != requestedAttributes.end();
112 bool computeAvgChildHeight = std::find(requestedAttributes.begin(), requestedAttributes.end(), AVG_CHILD_HEIGHT_NODE) != requestedAttributes.end();
113
114 const int numNodeSlots = tree.getNumInternalNodeSlots();
115
116 // Reuse the public output buffer when an intermediate quantity has been
117 // requested explicitly; otherwise, fall back to temporary storage so
118 // the derived descriptors can still be computed internally.
119 std::vector<Real> heightStorage(computeHeight ? 0 : numNodeSlots, Real{0});
121 auto indexOfHeight = [&](NodeId idx) {
122 return computeHeight ? attrNames.linearIndex(idx, HEIGHT_NODE) : idx;
123 };
124
125 std::vector<Real> numDescStorage(computeNumDescendants ? 0 : numNodeSlots, Real{0});
127 auto indexOfNumDescendants = [&](NodeId idx) {
128 return computeNumDescendants ? attrNames.linearIndex(idx, NUM_DESCENDANTS_NODE) : idx;
129 };
130
134 return computeNumLeafDescendants ? attrNames.linearIndex(idx, NUM_LEAF_DESCENDANTS_NODE) : idx;
135 };
136
137 std::vector<Real> depthStorage(computeDepth ? 0 : numNodeSlots, Real{0});
138 Real* bufferDepth = computeDepth ? buffer.data() : depthStorage.data();
139 auto indexOfDepth = [&](NodeId idx) {
140 return computeDepth ? attrNames.linearIndex(idx, DEPTH_NODE) : idx;
141 };
142
143 std::vector<Real> minChildHeightStorage(computeBalance ? numNodeSlots : 0, Real{0});
144
145 ::mmcfilters::detail::traversePostOrder(tree,
146 tree.getRoot(),
147 [&](NodeId nodeId) {
148 const NodeId node = detail::topologySlotOf(tree, nodeId);
149 const bool isRoot = tree.isRoot(nodeId);
150 const NodeId parentNodeId = tree.getNodeParent(nodeId);
151 const NodeId parent = isRoot ? InvalidNode : detail::topologySlotOf(tree, parentNodeId);
152 const int numChildren = tree.getNumChildren(nodeId);
153 const bool isLeaf = tree.isLeaf(nodeId);
154
155 Real parentDepth = (parent != InvalidNode) ? bufferDepth[indexOfDepth(parent)] : Real{0};
156 bufferDepth[indexOfDepth(node)] = parent != InvalidNode ? parentDepth + Real{1} : Real{0};
157
158 bufferHeight[indexOfHeight(node)] = Real{0};
159 bufferNumDesc[indexOfNumDescendants(node)] = Real{0};
160 bufferNumLeafDesc[indexOfNumLeafDescendants(node)] = isLeaf ? Real{1} : Real{0};
161
162 if (computeHeight)
163 buffer[attrNames.linearIndex(node, HEIGHT_NODE)] = Real{0};
164 if (computeIsLeaf)
165 buffer[attrNames.linearIndex(node, IS_LEAF_NODE)] = isLeaf ? Real{1} : Real{0};
166 if (computeIsRoot)
167 buffer[attrNames.linearIndex(node, IS_ROOT_NODE)] = isRoot ? Real{1} : Real{0};
168 if (computeNumChildren)
169 buffer[attrNames.linearIndex(node, NUM_CHILDREN_NODE)] = static_cast<Real>(numChildren);
170 if (computeNumSiblings)
171 buffer[attrNames.linearIndex(node, NUM_SIBLINGS_NODE)] = isRoot ? Real{0} : static_cast<Real>(tree.getNumChildren(parentNodeId) - 1);
172 if (computeLeafRatio)
173 buffer[attrNames.linearIndex(node, LEAF_RATIO_NODE)] = Real{0};
174 if (computeBalance) {
175 minChildHeightStorage[static_cast<std::size_t>(node)] = std::numeric_limits<Real>::infinity();
176 buffer[attrNames.linearIndex(node, BALANCE_NODE)] = Real{0};
177 }
178 if (computeAvgChildHeight)
179 buffer[attrNames.linearIndex(node, AVG_CHILD_HEIGHT_NODE)] = Real{0};
180 },
181 [&](NodeId parentNodeId, NodeId childNodeId) {
182 const NodeId parent = detail::topologySlotOf(tree, parentNodeId);
183 const NodeId child = detail::topologySlotOf(tree, childNodeId);
184
185 bufferNumDesc[indexOfNumDescendants(parent)] += bufferNumDesc[indexOfNumDescendants(child)] + Real{1};
186 bufferNumLeafDesc[indexOfNumLeafDescendants(parent)] += bufferNumLeafDesc[indexOfNumLeafDescendants(child)];
187
188 Real childHeight = bufferHeight[indexOfHeight(child)];
189 Real& parentHeight = bufferHeight[indexOfHeight(parent)];
190 parentHeight = std::max(parentHeight, childHeight + Real{1});
191 const int numChildren = tree.getNumChildren(parentNodeId);
192
193 if (computeBalance) {
194 Real& minChildHeight = minChildHeightStorage[static_cast<std::size_t>(parent)];
195 minChildHeight = std::min(minChildHeight, childHeight);
196 }
197
198 if (computeAvgChildHeight) {
199 Real& sumH = buffer[attrNames.linearIndex(parent, AVG_CHILD_HEIGHT_NODE)];
200 if (numChildren == 1)
201 sumH = childHeight;
202 else
203 sumH += childHeight;
204 }
205 },
206 [&](NodeId idxGlobalId) {
207 const NodeId idx = detail::topologySlotOf(tree, idxGlobalId);
208
209 if (computeLeafRatio) {
210 Real desc = bufferNumDesc[indexOfNumDescendants(idx)];
211 Real leafCount = bufferNumLeafDesc[indexOfNumLeafDescendants(idx)];
212 buffer[attrNames.linearIndex(idx, LEAF_RATIO_NODE)] =
213 desc > Real{0}
214 ? ::mmcfilters::attributes::numeric::safeDivide(leafCount, desc + Real{1})
215 : Real{1};
216 }
217
218 if (!tree.isLeaf(idxGlobalId)) {
219 if (computeBalance) {
220 const Real maxChildHeight = bufferHeight[indexOfHeight(idx)] - Real{1};
221 const Real minChildHeight = minChildHeightStorage[static_cast<std::size_t>(idx)];
222 buffer[attrNames.linearIndex(idx, BALANCE_NODE)] = maxChildHeight - minChildHeight;
223 }
224
225 if (computeAvgChildHeight) {
226 buffer[attrNames.linearIndex(idx, AVG_CHILD_HEIGHT_NODE)] =
227 ::mmcfilters::attributes::numeric::safeDivide(
228 buffer[attrNames.linearIndex(idx, AVG_CHILD_HEIGHT_NODE)],
229 static_cast<Real>(tree.getNumChildren(idxGlobalId)));
230 }
231 }
232 }
233 );
234 }
235
236public:
244 template <std::floating_point Real>
246 {
247 const MorphologicalTree& tree = context.tree;
248 std::span<const NodeId> unitProperParts = context.unitProperParts;
249 std::span<Real> buffer = context.buffer;
250 const AttributeNames& attrNames = context.attrNames;
251 std::span<const Attribute> requestedAttributes = context.requestedAttributes;
252
254
255 const bool computeHeight = requestsAttribute(requestedAttributes, HEIGHT_NODE);
256 const bool computeDepth = requestsAttribute(requestedAttributes, DEPTH_NODE);
257 const bool computeIsLeaf = requestsAttribute(requestedAttributes, IS_LEAF_NODE);
258 const bool computeIsRoot = requestsAttribute(requestedAttributes, IS_ROOT_NODE);
259 const bool computeNumChildren = requestsAttribute(requestedAttributes, NUM_CHILDREN_NODE);
260 const bool computeNumSiblings = requestsAttribute(requestedAttributes, NUM_SIBLINGS_NODE);
261 const bool computeNumDescendants = requestsAttribute(requestedAttributes, NUM_DESCENDANTS_NODE);
262 const bool computeNumLeafDescendants = requestsAttribute(requestedAttributes, NUM_LEAF_DESCENDANTS_NODE);
263 const bool computeLeafRatio = requestsAttribute(requestedAttributes, LEAF_RATIO_NODE);
264 const bool computeBalance = requestsAttribute(requestedAttributes, BALANCE_NODE);
265 const bool computeAvgChildHeight = requestsAttribute(requestedAttributes, AVG_CHILD_HEIGHT_NODE);
266
271 return;
272 }
273
275 if (computeHeight) {
276 buffer[attrNames.linearIndex(leafIndex, HEIGHT_NODE)] = Real{0};
277 }
278 if (computeDepth) {
279 buffer[attrNames.linearIndex(leafIndex, DEPTH_NODE)] = Real{0};
280 }
281 if (computeIsLeaf) {
282 buffer[attrNames.linearIndex(leafIndex, IS_LEAF_NODE)] = Real{1};
283 }
284 if (computeIsRoot) {
285 buffer[attrNames.linearIndex(leafIndex, IS_ROOT_NODE)] = Real{1};
286 }
287 if (computeNumChildren) {
288 buffer[attrNames.linearIndex(leafIndex, NUM_CHILDREN_NODE)] = Real{0};
289 }
290 if (computeNumSiblings) {
291 buffer[attrNames.linearIndex(leafIndex, NUM_SIBLINGS_NODE)] = Real{0};
292 }
294 buffer[attrNames.linearIndex(leafIndex, NUM_DESCENDANTS_NODE)] = Real{0};
295 }
297 buffer[attrNames.linearIndex(leafIndex, NUM_LEAF_DESCENDANTS_NODE)] = Real{1};
298 }
299 if (computeLeafRatio) {
300 buffer[attrNames.linearIndex(leafIndex, LEAF_RATIO_NODE)] = Real{1};
301 }
302 if (computeBalance) {
303 buffer[attrNames.linearIndex(leafIndex, BALANCE_NODE)] = Real{0};
304 }
306 buffer[attrNames.linearIndex(leafIndex, AVG_CHILD_HEIGHT_NODE)] = Real{0};
307 }
308 }
309 }
310
311};
312
313} // namespace mmcfilters::attributes::computers
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
Definition Common.hpp:25
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).
Mutable morphological tree built directly on proper parts and dense node ids.
bool isLeaf(NodeId nodeId) const
Tests whether nodeId has no direct children.
int getNumInternalNodeSlots() const
Returns the size of the dense internal-node id domain.
NodeId getRoot() const
Returns the current hierarchy root.
int getNumChildren(NodeId nodeId) const
Returns the number of direct children of nodeId.
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.
static constexpr std::string_view familyName
Family name used in dependency-plan diagnostics.
static constexpr std::array< Attribute, 11 > producedAttributes
Canonical list of topology-derived descriptors produced by this computer.
static constexpr AttributeComputerFamily family
Stable family id used by the scheduler.
static constexpr AttributeComputerDomain domain
Execution domain required by the computer.
Owning result for one computed scalar attribute layout and buffer.