MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
MomentBasedAttributeComputer.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 <cmath>
13#include <limits>
14#include <numbers>
15#include <string_view>
16#include <vector>
17
18namespace mmcfilters::attributes::computers {
19
20namespace detail {
21inline NodeId momentSlotOf(const MorphologicalTree&, NodeId nodeId) noexcept {
22 return nodeId;
23}
24}
25
26
27
46public:
48 static constexpr std::string_view familyName = "central-moments";
49
51 static constexpr AttributeComputerFamily family = AttributeComputerFamily::CentralMoments;
52
54 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
55
59 inline static constexpr std::array<Attribute, 7> producedAttributes{
60 CENTRAL_MOMENT_20,
61 CENTRAL_MOMENT_02,
62 CENTRAL_MOMENT_11,
63 CENTRAL_MOMENT_30,
64 CENTRAL_MOMENT_03,
65 CENTRAL_MOMENT_21,
66 CENTRAL_MOMENT_12};
67
78 template <std::floating_point Real>
80 computeImpl(
81 context.tree,
82 context.buffer,
83 context.attrNames,
84 context.requestedAttributes);
85 }
86
87private:
88 template <std::floating_point Real>
89 static void computeImpl(const MorphologicalTree& tree, std::span<Real> buffer, const AttributeNames& attrNames, std::span<const Attribute> requested) {
91
92 bool computeMu20 = std::find(requested.begin(), requested.end(), CENTRAL_MOMENT_20) != requested.end();
93 bool computeMu02 = std::find(requested.begin(), requested.end(), CENTRAL_MOMENT_02) != requested.end();
94 bool computeMu11 = std::find(requested.begin(), requested.end(), CENTRAL_MOMENT_11) != requested.end();
95 bool computeMu30 = std::find(requested.begin(), requested.end(), CENTRAL_MOMENT_30) != requested.end();
96 bool computeMu03 = std::find(requested.begin(), requested.end(), CENTRAL_MOMENT_03) != requested.end();
97 bool computeMu21 = std::find(requested.begin(), requested.end(), CENTRAL_MOMENT_21) != requested.end();
98 bool computeMu12 = std::find(requested.begin(), requested.end(), CENTRAL_MOMENT_12) != requested.end();
101 return;
102 }
103
104 struct RawMoments {
105 Real m00 = Real{0};
106 Real m10 = Real{0};
107 Real m01 = Real{0};
108 Real m20 = Real{0};
109 Real m02 = Real{0};
110 Real m11 = Real{0};
111 Real m30 = Real{0};
112 Real m03 = Real{0};
113 Real m21 = Real{0};
114 Real m12 = Real{0};
115
116 void add(const RawMoments& other) {
117 m00 += other.m00;
118 m10 += other.m10;
119 m01 += other.m01;
120 m20 += other.m20;
121 m02 += other.m02;
122 m11 += other.m11;
123 m30 += other.m30;
124 m03 += other.m03;
125 m21 += other.m21;
126 m12 += other.m12;
127 }
128 };
129
130 const int numCols = tree.getNumColsOfImage();
131 std::vector<RawMoments> raw(static_cast<std::size_t>(tree.getNumInternalNodeSlots()));
132 auto indexOf = [&](NodeId idx, Attribute attr) { return attrNames.linearIndex(idx, attr); };
133
134 ::mmcfilters::detail::traversePostOrder(
135 tree,
136 tree.getRoot(),
137 [&](NodeId nodeId) {
138 const NodeId node = detail::momentSlotOf(tree, nodeId);
139 raw[static_cast<std::size_t>(node)] = RawMoments{};
140 for (int p : tree.getProperParts(nodeId)) {
141 auto [py, px] = ImageUtils::to2D(p, numCols);
142 const Real x = static_cast<Real>(px);
143 const Real y = static_cast<Real>(py);
144 const Real x2 = x * x;
145 const Real y2 = y * y;
146 RawMoments& moments = raw[static_cast<std::size_t>(node)];
147 moments.m00 += Real{1};
148 moments.m10 += x;
149 moments.m01 += y;
150 moments.m20 += x2;
151 moments.m02 += y2;
152 moments.m11 += x * y;
153 moments.m30 += x2 * x;
154 moments.m03 += y2 * y;
155 moments.m21 += x2 * y;
156 moments.m12 += x * y2;
157 }
158 },
159 [&](NodeId parentNodeId, NodeId childNodeId) {
160 const NodeId parent = detail::momentSlotOf(tree, parentNodeId);
161 const NodeId child = detail::momentSlotOf(tree, childNodeId);
162 raw[static_cast<std::size_t>(parent)].add(raw[static_cast<std::size_t>(child)]);
163 },
164 [&](NodeId nodeId) {
165 const NodeId node = detail::momentSlotOf(tree, nodeId);
166 const RawMoments& moments = raw[static_cast<std::size_t>(node)];
167 if (moments.m00 <= Real{0}) {
168 return;
169 }
170
171 const Real cx = moments.m10 / moments.m00;
172 const Real cy = moments.m01 / moments.m00;
173
174 if (computeMu20) {
175 const Real mu20 = moments.m20 - Real{2} * cx * moments.m10 + cx * cx * moments.m00;
176 buffer[indexOf(node, CENTRAL_MOMENT_20)] = mu20;
177 }
178 if (computeMu02) {
179 const Real mu02 = moments.m02 - Real{2} * cy * moments.m01 + cy * cy * moments.m00;
180 buffer[indexOf(node, CENTRAL_MOMENT_02)] = mu02;
181 }
182 if (computeMu11) {
183 const Real mu11 = moments.m11 - cx * moments.m01 - cy * moments.m10 + cx * cy * moments.m00;
184 buffer[indexOf(node, CENTRAL_MOMENT_11)] = mu11;
185 }
186 if (computeMu30) {
187 const Real mu30 = moments.m30 - Real{3} * cx * moments.m20 + Real{3} * cx * cx * moments.m10 - cx * cx * cx * moments.m00;
188 buffer[indexOf(node, CENTRAL_MOMENT_30)] = mu30;
189 }
190 if (computeMu03) {
191 const Real mu03 = moments.m03 - Real{3} * cy * moments.m02 + Real{3} * cy * cy * moments.m01 - cy * cy * cy * moments.m00;
192 buffer[indexOf(node, CENTRAL_MOMENT_03)] = mu03;
193 }
194 if (computeMu21) {
195 const Real mu21 =
196 moments.m21 - Real{2} * cx * moments.m11 - cy * moments.m20 +
197 Real{2} * cx * cy * moments.m10 + cx * cx * moments.m01 -
198 cx * cx * cy * moments.m00;
199 buffer[indexOf(node, CENTRAL_MOMENT_21)] = mu21;
200 }
201 if (computeMu12) {
202 const Real mu12 =
203 moments.m12 - Real{2} * cy * moments.m11 - cx * moments.m02 +
204 Real{2} * cx * cy * moments.m01 + cy * cy * moments.m10 -
205 cx * cy * cy * moments.m00;
206 buffer[indexOf(node, CENTRAL_MOMENT_12)] = mu12;
207 }
208 }
209 );
210 }
211
212public:
218 template <std::floating_point Real>
220 {
221 requireUnitAttributeBufferShape(context.tree, context.unitProperParts, context.buffer, context.attrNames);
222 constexpr std::array<Attribute, 7> zeroAttributes{
223 CENTRAL_MOMENT_20,
224 CENTRAL_MOMENT_02,
225 CENTRAL_MOMENT_11,
226 CENTRAL_MOMENT_30,
227 CENTRAL_MOMENT_03,
228 CENTRAL_MOMENT_21,
229 CENTRAL_MOMENT_12};
230 for (const Attribute attribute : zeroAttributes) {
231 if (!requestsAttribute(context.requestedAttributes, attribute)) {
232 continue;
233 }
234 for (NodeId leafIndex = 0; leafIndex < static_cast<NodeId>(context.unitProperParts.size()); ++leafIndex) {
235 context.buffer[context.attrNames.linearIndex(leafIndex, attribute)] = Real{0};
236 }
237 }
238 }
239
240};
241
242
243
244
259public:
261 static constexpr std::string_view familyName = "hu-moments";
262
264 static constexpr AttributeComputerFamily family = AttributeComputerFamily::HuMoments;
265
267 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
268
272 inline static constexpr std::array<Attribute, 7> producedAttributes{
273 HU_MOMENT_1,
274 HU_MOMENT_2,
275 HU_MOMENT_3,
276 HU_MOMENT_4,
277 HU_MOMENT_5,
278 HU_MOMENT_6,
279 HU_MOMENT_7};
280
290 template <std::floating_point Real>
292 computeImpl(
293 context.tree,
294 context.buffer,
295 context.attrNames,
296 context.requestedAttributes,
297 context.dependencySources);
298 }
299
300private:
301 template <std::floating_point Real>
302 static void computeImpl(const MorphologicalTree& tree, std::span<Real> buffer, const AttributeNames& attrNames, std::span<const Attribute> requested, std::span<const DependencySourceT<Real>> dependencySources) {
304
305 auto indexOf = [&](NodeId idx, Attribute attr) { return attrNames.linearIndex(idx, attr); };
307 const auto& muDependency = dependencies.requireAll({
308 CENTRAL_MOMENT_20,
309 CENTRAL_MOMENT_02,
310 CENTRAL_MOMENT_11,
311 CENTRAL_MOMENT_30,
312 CENTRAL_MOMENT_03,
313 CENTRAL_MOMENT_21,
314 CENTRAL_MOMENT_12,
315 });
316 const auto& areaDependency = dependencies.require(AREA);
317 auto indexOfMu = [&](NodeId idx, Attribute attr) { return muDependency.attrNames->linearIndex(idx, attr); };
318 auto indexOfArea = [&](NodeId idx) { return areaDependency.attrNames->linearIndex(idx, AREA); };
319
320 auto normMoment = [](Real area, Real moment, int p, int q) {
321 return ::mmcfilters::attributes::numeric::safeDivide(
322 moment,
323 std::pow(area, static_cast<Real>(p + q + 2) / Real{2}));
324 };
325
326 bool computeHu1 = std::find(requested.begin(), requested.end(), HU_MOMENT_1) != requested.end();
327 bool computeHu2 = std::find(requested.begin(), requested.end(), HU_MOMENT_2) != requested.end();
328 bool computeHu3 = std::find(requested.begin(), requested.end(), HU_MOMENT_3) != requested.end();
329 bool computeHu4 = std::find(requested.begin(), requested.end(), HU_MOMENT_4) != requested.end();
330 bool computeHu5 = std::find(requested.begin(), requested.end(), HU_MOMENT_5) != requested.end();
331 bool computeHu6 = std::find(requested.begin(), requested.end(), HU_MOMENT_6) != requested.end();
332 bool computeHu7 = std::find(requested.begin(), requested.end(), HU_MOMENT_7) != requested.end();
333
334 ::mmcfilters::detail::traversePostOrder(
335 tree,
336 tree.getRoot(),
337 [](NodeId) {},
338 [](NodeId, NodeId) {},
339 [&](NodeId idxGlobalId) {
340 const NodeId idx = detail::momentSlotOf(tree, idxGlobalId);
341 Real mu20 = muDependency.buffer[indexOfMu(idx, CENTRAL_MOMENT_20)];
342 Real mu02 = muDependency.buffer[indexOfMu(idx, CENTRAL_MOMENT_02)];
343 Real mu11 = muDependency.buffer[indexOfMu(idx, CENTRAL_MOMENT_11)];
344 Real mu30 = muDependency.buffer[indexOfMu(idx, CENTRAL_MOMENT_30)];
345 Real mu03 = muDependency.buffer[indexOfMu(idx, CENTRAL_MOMENT_03)];
346 Real mu21 = muDependency.buffer[indexOfMu(idx, CENTRAL_MOMENT_21)];
347 Real mu12 = muDependency.buffer[indexOfMu(idx, CENTRAL_MOMENT_12)];
348 Real area = areaDependency.buffer[indexOfArea(idx)];
349 if (area <= Real{0}) {
350 return;
351 }
352
353 // Normalise the central moments before evaluating the Hu invariants.
354 Real eta20 = normMoment(area, mu20, 2, 0);
355 Real eta02 = normMoment(area, mu02, 0, 2);
356 Real eta11 = normMoment(area, mu11, 1, 1);
357 Real eta30 = normMoment(area, mu30, 3, 0);
358 Real eta03 = normMoment(area, mu03, 0, 3);
359 Real eta21 = normMoment(area, mu21, 2, 1);
360 Real eta12 = normMoment(area, mu12, 1, 2);
361
362 // Evaluate the seven classical Hu combinations.
363 if(computeHu1)
364 buffer[indexOf(idx, HU_MOMENT_1)] = eta20 + eta02; // The first Hu moment corresponds to a normalised inertia term.
365 if(computeHu2)
366 buffer[indexOf(idx, HU_MOMENT_2)] = std::pow(eta20 - eta02, 2) + Real{4} * std::pow(eta11, 2);
367 if(computeHu3)
368 buffer[indexOf(idx, HU_MOMENT_3)] = std::pow(eta30 - Real{3} * eta12, 2) + std::pow(Real{3} * eta21 - eta03, 2);
369 if(computeHu4)
370 buffer[indexOf(idx, HU_MOMENT_4)] = std::pow(eta30 + eta12, 2) + std::pow(eta21 + eta03, 2);
371 if(computeHu5)
372 buffer[indexOf(idx, HU_MOMENT_5)] = (eta30 - Real{3} * eta12) * (eta30 + eta12) * (std::pow(eta30 + eta12, 2) - Real{3} * std::pow(eta21 + eta03, 2)) +
373 (Real{3} * eta21 - eta03) * (eta21 + eta03) * (Real{3} * std::pow(eta30 + eta12, 2) - std::pow(eta21 + eta03, 2));
374 if(computeHu6)
375 buffer[indexOf(idx, HU_MOMENT_6)] = (eta20 - eta02) * (std::pow(eta30 + eta12, 2) - std::pow(eta21 + eta03, 2)) +
376 Real{4} * eta11 * (eta30 + eta12) * (eta21 + eta03);
377 if(computeHu7)
378 buffer[indexOf(idx, HU_MOMENT_7)] = (Real{3} * eta21 - eta03) * (eta30 + eta12) * (std::pow(eta30 + eta12, 2) - Real{3} * std::pow(eta21 + eta03, 2)) -
379 (eta30 - Real{3} * eta12) * (eta21 + eta03) * (Real{3} * std::pow(eta30 + eta12, 2) - std::pow(eta21 + eta03, 2));
380 }
381 );
382 }
383
384public:
391 template <std::floating_point Real>
393 {
394 requireUnitAttributeBufferShape(context.tree, context.unitProperParts, context.buffer, context.attrNames);
395 constexpr std::array<Attribute, 7> zeroAttributes{
396 HU_MOMENT_1,
397 HU_MOMENT_2,
398 HU_MOMENT_3,
399 HU_MOMENT_4,
400 HU_MOMENT_5,
401 HU_MOMENT_6,
402 HU_MOMENT_7};
403 for (const Attribute attribute : zeroAttributes) {
404 if (!requestsAttribute(context.requestedAttributes, attribute)) {
405 continue;
406 }
407 for (NodeId leafIndex = 0; leafIndex < static_cast<NodeId>(context.unitProperParts.size()); ++leafIndex) {
408 context.buffer[context.attrNames.linearIndex(leafIndex, attribute)] = Real{0};
409 }
410 }
411 }
412
413};
414
415
416
417
440public:
442 static constexpr std::string_view familyName = "moment-derived";
443
445 static constexpr AttributeComputerFamily family = AttributeComputerFamily::MomentDerived;
446
448 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
449
453 template <std::floating_point Real>
455 return static_cast<Real>(1.0e6);
456 }
457
461 inline static constexpr std::array<Attribute, 7> producedAttributes{
462 INERTIA,
463 COMPACTNESS,
464 ECCENTRICITY,
465 LENGTH_MAJOR_AXIS,
466 LENGTH_MINOR_AXIS,
467 AXIS_ORIENTATION,
468 CIRCULARITY};
469
479 template <std::floating_point Real>
481 computeImpl(
482 context.tree,
483 context.buffer,
484 context.attrNames,
485 context.requestedAttributes,
486 context.dependencySources);
487 }
488
489private:
490 template <std::floating_point Real>
491 static void computeImpl(const MorphologicalTree& tree, std::span<Real> buffer, const AttributeNames& attrNames, std::span<const Attribute> requestedAttributes, std::span<const DependencySourceT<Real>> dependencySources) {
493
494 auto indexOfMajorAxis = [&](int idx) { return attrNames.linearIndex(idx, LENGTH_MAJOR_AXIS); };
495 auto indexOfMinorAxis = [&](int idx) { return attrNames.linearIndex(idx, LENGTH_MINOR_AXIS); };
496 auto indexOfEccentricity = [&](int idx) { return attrNames.linearIndex(idx, ECCENTRICITY); };
497 auto indexOfCompactness = [&](int idx) { return attrNames.linearIndex(idx, COMPACTNESS); };
498 auto indexOfAxisOrientation = [&](int idx) { return attrNames.linearIndex(idx, AXIS_ORIENTATION); };
499 auto indexOfInertia = [&](int idx) { return attrNames.linearIndex(idx, INERTIA); };
500 auto indexOfCircularity = [&](int idx) { return attrNames.linearIndex(idx, CIRCULARITY); };
501
502 bool computeMajorAxis = std::find(requestedAttributes.begin(), requestedAttributes.end(), LENGTH_MAJOR_AXIS) != requestedAttributes.end();
503 bool computeMinorAxis = std::find(requestedAttributes.begin(), requestedAttributes.end(), LENGTH_MINOR_AXIS) != requestedAttributes.end();
504 bool computeEccentricity = std::find(requestedAttributes.begin(), requestedAttributes.end(), ECCENTRICITY) != requestedAttributes.end();
505 bool computeCompactness = std::find(requestedAttributes.begin(), requestedAttributes.end(), COMPACTNESS) != requestedAttributes.end();
506 bool computeAxisOrientation = std::find(requestedAttributes.begin(), requestedAttributes.end(), AXIS_ORIENTATION) != requestedAttributes.end();
507 bool computeInertia = std::find(requestedAttributes.begin(), requestedAttributes.end(), INERTIA) != requestedAttributes.end();
508 bool computeCircularity = std::find(requestedAttributes.begin(), requestedAttributes.end(), CIRCULARITY) != requestedAttributes.end();
509
510 const DependencyResolver<Real> dependencies{dependencySources};
511 const auto& momentDependency = dependencies.requireAll({
512 CENTRAL_MOMENT_20,
513 CENTRAL_MOMENT_02,
514 CENTRAL_MOMENT_11,
515 });
516 const auto& areaDependency = dependencies.require(AREA);
517 auto indexMu20 = [&](int idx) { return momentDependency.attrNames->linearIndex(idx, CENTRAL_MOMENT_20); };
518 auto indexMu02 = [&](int idx) { return momentDependency.attrNames->linearIndex(idx, CENTRAL_MOMENT_02); };
519 auto indexMu11 = [&](int idx) { return momentDependency.attrNames->linearIndex(idx, CENTRAL_MOMENT_11); };
520 auto indexArea = [&](int idx) { return areaDependency.attrNames->linearIndex(idx, AREA); };
521
522 ::mmcfilters::detail::traversePostOrder(
523 tree,
524 tree.getRoot(),
525 [&](NodeId) {},
526 [&](NodeId, NodeId) {},
527 [&](NodeId idxGlobalId) {
528 const NodeId idx = detail::momentSlotOf(tree, idxGlobalId);
529 Real mu20 = momentDependency.buffer[indexMu20(idx)];
530 Real mu02 = momentDependency.buffer[indexMu02(idx)];
531 Real mu11 = momentDependency.buffer[indexMu11(idx)];
532 Real area = areaDependency.buffer[indexArea(idx)];
533
534 const Real discriminant = std::pow(mu20 - mu02, 2) + Real{4} * std::pow(mu11, 2);
535 const Real sqrtDiscriminant = ::mmcfilters::attributes::numeric::safeSqrt(discriminant);
536 const Real lambda1 = mu20 + mu02 + sqrtDiscriminant; // Largest eigenvalue of the inertia matrix.
537 const Real lambda2 = mu20 + mu02 - sqrtDiscriminant; // Smallest eigenvalue of the inertia matrix.
538
539 if (computeMajorAxis) {
540 buffer[indexOfMajorAxis(idx)] =
541 ::mmcfilters::attributes::numeric::safeSqrt(
542 ::mmcfilters::attributes::numeric::safeDivide(Real{2} * lambda1, area));
543 }
544 if (computeMinorAxis) {
545 buffer[indexOfMinorAxis(idx)] =
546 ::mmcfilters::attributes::numeric::safeSqrt(
547 ::mmcfilters::attributes::numeric::safeDivide(Real{2} * lambda2, area));
548 }
549 if (computeEccentricity) {
550 const Real eps = std::numeric_limits<Real>::epsilon();
551 if (lambda1 <= eps && std::abs(lambda2) <= eps) {
552 buffer[indexOfEccentricity(idx)] = Real{1};
553 } else if (lambda2 <= eps) {
554 buffer[indexOfEccentricity(idx)] = maxFiniteEccentricity<Real>();
555 } else {
556 buffer[indexOfEccentricity(idx)] =
557 ::mmcfilters::attributes::numeric::clampUpper(
558 ::mmcfilters::attributes::numeric::safeDivide(lambda1, lambda2, maxFiniteEccentricity<Real>()),
559 maxFiniteEccentricity<Real>());
560 }
561 }
562 if (computeCompactness) {
563 Real denom = mu20 + mu02;
564 buffer[indexOfCompactness(idx)] =
565 (Real{1} / (Real{2} * std::numbers::pi_v<Real>)) *
566 ::mmcfilters::attributes::numeric::safeDivide(area, denom);
567 }
568 if (computeAxisOrientation) {
569 // Guard the orientation computation against the fully isotropic case.
570 if (mu20 != mu02 || mu11 != 0) {
571 Real radians = Real{0.5} * std::atan2(Real{2} * mu11, mu20 - mu02); // Orientation in radians.
572 Real degrees = radians * (Real{180} / std::numbers::pi_v<Real>); // Converted to degrees for the public API.
573 buffer[indexOfAxisOrientation(idx)] = std::fmod(std::abs(degrees), Real{360}); // Orientation stored in [0, 360].
574 } else {
575 buffer[indexOfAxisOrientation(idx)] = Real{0}; // Degenerate isotropic support: the principal direction is undefined.
576 }
577 }
578 if (computeInertia) {
579 const Real areaSquared = area * area;
580 Real normMu20 = ::mmcfilters::attributes::numeric::safeDivide(mu20, areaSquared);
581 Real normMu02 = ::mmcfilters::attributes::numeric::safeDivide(mu02, areaSquared);
582 buffer[indexOfInertia(idx)] = normMu20 + normMu02;
583 }
584 if (computeCircularity) {
585 const Real eps = std::numeric_limits<Real>::epsilon();
586 if (lambda1 <= eps && std::abs(lambda2) <= eps) {
587 buffer[indexOfCircularity(idx)] = Real{1};
588 } else if (lambda1 <= eps || lambda2 <= eps) {
589 buffer[indexOfCircularity(idx)] = Real{0};
590 } else {
591 buffer[indexOfCircularity(idx)] =
592 ::mmcfilters::attributes::numeric::safeDivide(lambda2, lambda1);
593 }
594 }
595 }
596 );
597 }
598
599public:
606 template <std::floating_point Real>
608 {
609 requireUnitAttributeBufferShape(context.tree, context.unitProperParts, context.buffer, context.attrNames);
610 constexpr std::array<Attribute, 7> zeroAttributes{
611 COMPACTNESS,
612 ECCENTRICITY,
613 LENGTH_MAJOR_AXIS,
614 LENGTH_MINOR_AXIS,
615 AXIS_ORIENTATION,
616 INERTIA,
617 CIRCULARITY};
618 for (const Attribute attribute : zeroAttributes) {
619 if (!requestsAttribute(context.requestedAttributes, attribute)) {
620 continue;
621 }
622 for (NodeId leafIndex = 0; leafIndex < static_cast<NodeId>(context.unitProperParts.size()); ++leafIndex) {
623 context.buffer[context.attrNames.linearIndex(leafIndex, attribute)] =
624 (attribute == ECCENTRICITY || attribute == CIRCULARITY) ? Real{1} : Real{0};
625 }
626 }
627 }
628
629};
630
631
632
633
634
635} // namespace mmcfilters::attributes::computers
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
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).
static std::pair< int, int > to2D(int index, int numCols) noexcept
Converts a row-major linear index to (row, col).
Definition Image.hpp:283
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.
NodeId getRoot() const
Returns the current hierarchy root.
int getNumColsOfImage() const
Returns the number of image columns in the attached 2D domain.
Computes geometric central moments up to third order.
static void compute(const AttributeComputeContext< Real > &context)
Computes the requested central moments.
static constexpr AttributeComputerDomain domain
Execution domain required by the computer.
static constexpr AttributeComputerFamily family
Stable family id used by the scheduler.
static constexpr std::array< Attribute, 7 > producedAttributes
Canonical list of central moments produced together.
static constexpr std::string_view familyName
Family name used in dependency-plan diagnostics.
static void computeUnitRows(const UnitAttributeComputeContext< Real > &context)
Materializes central moments for one-pixel unit supports.
static void compute(const AttributeComputeContext< Real > &context)
Computes the requested Hu invariant moments.
static void computeUnitRows(const UnitAttributeComputeContext< Real > &context)
Materializes Hu moments for one-pixel unit supports.
Computes higher-level shape descriptors derived from second-order central moments.
static void computeUnitRows(const UnitAttributeComputeContext< Real > &context)
Materializes moment-derived descriptors for one-pixel unit supports.
static constexpr Real maxFiniteEccentricity() noexcept
Largest finite eccentricity emitted for degenerate one-dimensional supports.
static void compute(const AttributeComputeContext< Real > &context)
Computes the requested moment-derived descriptors.
Owning result for one computed scalar attribute layout and buffer.