48 static constexpr std::string_view
familyName =
"central-moments";
51 static constexpr AttributeComputerFamily
family = AttributeComputerFamily::CentralMoments;
54 static constexpr AttributeComputerDomain
domain = AttributeComputerDomain::Topology;
78 template <std::
floating_po
int Real>
88 template <std::
floating_po
int Real>
116 void add(
const RawMoments& other) {
132 auto indexOf = [&](
NodeId idx, Attribute attr) {
return attrNames.
linearIndex(idx, attr); };
134 ::mmcfilters::detail::traversePostOrder(
138 const NodeId node = detail::momentSlotOf(tree, nodeId);
139 raw[static_cast<std::size_t>(node)] = RawMoments{};
140 for (
int p : tree.getProperParts(nodeId)) {
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};
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;
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)]);
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}) {
171 const Real cx = moments.m10 / moments.m00;
172 const Real cy = moments.m01 / moments.m00;
175 const Real mu20 = moments.m20 - Real{2} * cx * moments.m10 + cx * cx * moments.m00;
176 buffer[indexOf(node, CENTRAL_MOMENT_20)] = mu20;
179 const Real mu02 = moments.m02 - Real{2} * cy * moments.m01 + cy * cy * moments.m00;
180 buffer[indexOf(node, CENTRAL_MOMENT_02)] = mu02;
183 const Real mu11 = moments.m11 - cx * moments.m01 - cy * moments.m10 + cx * cy * moments.m00;
184 buffer[indexOf(node, CENTRAL_MOMENT_11)] = mu11;
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;
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;
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;
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;
218 template <std::
floating_po
int Real>
261 static constexpr std::string_view familyName =
"hu-moments";
264 static constexpr AttributeComputerFamily family = AttributeComputerFamily::HuMoments;
267 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
272 inline static constexpr std::array<Attribute, 7> producedAttributes{
290 template <std::
floating_po
int Real>
301 template <std::
floating_po
int Real>
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); };
320 auto normMoment = [](Real area, Real moment,
int p,
int q) {
321 return ::mmcfilters::attributes::numeric::safeDivide(
323 std::pow(area,
static_cast<Real
>(p + q + 2) / Real{2}));
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();
334 ::mmcfilters::detail::traversePostOrder(
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}) {
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);
364 buffer[indexOf(idx, HU_MOMENT_1)] = eta20 + eta02;
366 buffer[indexOf(idx, HU_MOMENT_2)] = std::pow(eta20 - eta02, 2) + Real{4} * std::pow(eta11, 2);
368 buffer[indexOf(idx, HU_MOMENT_3)] = std::pow(eta30 - Real{3} * eta12, 2) + std::pow(Real{3} * eta21 - eta03, 2);
370 buffer[indexOf(idx, HU_MOMENT_4)] = std::pow(eta30 + eta12, 2) + std::pow(eta21 + eta03, 2);
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));
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);
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));
391 template <std::
floating_po
int Real>
442 static constexpr std::string_view familyName =
"moment-derived";
445 static constexpr AttributeComputerFamily family = AttributeComputerFamily::MomentDerived;
448 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
453 template <std::
floating_po
int Real>
455 return static_cast<Real>(1.0e6);
461 inline static constexpr std::array<Attribute, 7> producedAttributes{
479 template <std::
floating_po
int Real>
490 template <std::
floating_po
int Real>
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); };
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();
510 const DependencyResolver<Real> dependencies{dependencySources};
511 const auto& momentDependency = dependencies.requireAll({
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); };
522 ::mmcfilters::detail::traversePostOrder(
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)];
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;
537 const Real lambda2 = mu20 + mu02 - sqrtDiscriminant;
539 if (computeMajorAxis) {
540 buffer[indexOfMajorAxis(idx)] =
541 ::mmcfilters::attributes::numeric::safeSqrt(
542 ::mmcfilters::attributes::numeric::safeDivide(Real{2} * lambda1, area));
544 if (computeMinorAxis) {
545 buffer[indexOfMinorAxis(idx)] =
546 ::mmcfilters::attributes::numeric::safeSqrt(
547 ::mmcfilters::attributes::numeric::safeDivide(Real{2} * lambda2, area));
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>();
556 buffer[indexOfEccentricity(idx)] =
557 ::mmcfilters::attributes::numeric::clampUpper(
558 ::mmcfilters::attributes::numeric::safeDivide(lambda1, lambda2, maxFiniteEccentricity<Real>()),
559 maxFiniteEccentricity<Real>());
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);
568 if (computeAxisOrientation) {
570 if (mu20 != mu02 || mu11 != 0) {
571 Real radians = Real{0.5} * std::atan2(Real{2} * mu11, mu20 - mu02);
572 Real degrees = radians * (Real{180} / std::numbers::pi_v<Real>);
573 buffer[indexOfAxisOrientation(idx)] = std::fmod(std::abs(degrees), Real{360});
575 buffer[indexOfAxisOrientation(idx)] = Real{0};
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;
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};
591 buffer[indexOfCircularity(idx)] =
592 ::mmcfilters::attributes::numeric::safeDivide(lambda2, lambda1);
606 template <std::
floating_po
int Real>
624 (attribute == ECCENTRICITY || attribute == CIRCULARITY) ?
Real{1} :
Real{0};