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"
18namespace mmcfilters::attributes::computers {
23struct CentralMomentsRequest {
31 bool rawInertia =
false;
34 [[nodiscard]]
bool any() const noexcept {
return mu20 || mu02 || mu11 || mu30 || mu03 || mu21 || mu12 || rawInertia; }
41 [[nodiscard]]
static CentralMomentsRequest from(std::span<const Attribute> requestedAttributes) {
42 return {.mu20 = requestsAttribute(requestedAttributes, CentralMoment20),
43 .mu02 = requestsAttribute(requestedAttributes, CentralMoment02),
44 .mu11 = requestsAttribute(requestedAttributes, CentralMoment11),
45 .mu30 = requestsAttribute(requestedAttributes, CentralMoment30),
46 .mu03 = requestsAttribute(requestedAttributes, CentralMoment03),
47 .mu21 = requestsAttribute(requestedAttributes, CentralMoment21),
48 .mu12 = requestsAttribute(requestedAttributes, CentralMoment12),
49 .rawInertia = requestsAttribute(requestedAttributes, RawInertia)};
54struct HuMomentsRequest {
64 [[nodiscard]]
bool any() const noexcept {
return hu1 || hu2 || hu3 || hu4 || hu5 || hu6 || hu7; }
71 [[nodiscard]]
static HuMomentsRequest from(std::span<const Attribute> requestedAttributes) {
72 return {.hu1 = requestsAttribute(requestedAttributes, HuMoment1),
73 .hu2 = requestsAttribute(requestedAttributes, HuMoment2),
74 .hu3 = requestsAttribute(requestedAttributes, HuMoment3),
75 .hu4 = requestsAttribute(requestedAttributes, HuMoment4),
76 .hu5 = requestsAttribute(requestedAttributes, HuMoment5),
77 .hu6 = requestsAttribute(requestedAttributes, HuMoment6),
78 .hu7 = requestsAttribute(requestedAttributes, HuMoment7)};
83struct MomentDerivedRequest {
85 bool compactness =
false;
86 bool eccentricity =
false;
87 bool majorAxis =
false;
88 bool minorAxis =
false;
89 bool axisOrientation =
false;
90 bool axisOrientationSigned =
false;
91 bool circularity =
false;
94 [[nodiscard]]
bool any() const noexcept {
95 return inertia || compactness || eccentricity || majorAxis || minorAxis || axisOrientation || axisOrientationSigned || circularity;
103 [[nodiscard]]
static MomentDerivedRequest from(std::span<const Attribute> requestedAttributes) {
104 return {.inertia = requestsAttribute(requestedAttributes, Inertia),
105 .compactness = requestsAttribute(requestedAttributes, Compactness),
106 .eccentricity = requestsAttribute(requestedAttributes, Eccentricity),
107 .majorAxis = requestsAttribute(requestedAttributes, LengthMajorAxis),
108 .minorAxis = requestsAttribute(requestedAttributes, LengthMinorAxis),
109 .axisOrientation = requestsAttribute(requestedAttributes, AxisOrientation),
110 .axisOrientationSigned = requestsAttribute(requestedAttributes, AxisOrientationSigned),
111 .circularity = requestsAttribute(requestedAttributes, Circularity)};
115template <std::
floating_po
int Real>
inline constexpr Real maximumFiniteEccentricity() noexcept {
return static_cast<Real
>(1.0e6); }
124template <std::
floating_po
int Real>
125inline void computeCentralMoments(
const AttributeComputeContext<Real>& context,
const CentralMomentsRequest& request) {
126 if (!request.any()) {
142 void add(
const RawMoments& other) {
156 const int stride = context.attrNames.NUM_ATTRIBUTES;
157 const auto offsetOf = [&](Attribute attribute) {
return context.attrNames.indexMap.find(attribute)->second; };
158 const int mu20Offset = request.mu20 ? offsetOf(CentralMoment20) : 0;
159 const int mu02Offset = request.mu02 ? offsetOf(CentralMoment02) : 0;
160 const int mu11Offset = request.mu11 ? offsetOf(CentralMoment11) : 0;
161 const int mu30Offset = request.mu30 ? offsetOf(CentralMoment30) : 0;
162 const int mu03Offset = request.mu03 ? offsetOf(CentralMoment03) : 0;
163 const int mu21Offset = request.mu21 ? offsetOf(CentralMoment21) : 0;
164 const int mu12Offset = request.mu12 ? offsetOf(CentralMoment12) : 0;
165 const int rawInertiaOffset = request.rawInertia ? offsetOf(RawInertia) : 0;
166 const auto outputIndex = [&](
NodeId node,
int offset) {
return static_cast<std::size_t
>(node * stride + offset); };
168 const GridDomain2D& domain = ::mmcfilters::detail::TreeKernelAccess::gridDomain2D(context.tree);
169 std::vector<RawMoments> raw(
static_cast<std::size_t
>(context.tree.numInternalNodeSlots()));
170 ::mmcfilters::detail::TreeKernelAccess::traversePostOrder(
171 context.tree, context.tree.root(),
173 RawMoments& moments = raw[static_cast<std::size_t>(node)];
174 moments = RawMoments{};
175 for (PixelId pixel : ::mmcfilters::detail::TreeKernelAccess::properParts(context.tree, node)) {
176 const Real x =
static_cast<Real
>(pixel % domain.columns);
177 const Real y =
static_cast<Real
>(pixel / domain.columns);
178 const Real x2 = x * x;
179 const Real y2 = y * y;
180 moments.m00 += Real{1};
185 moments.m11 += x * y;
186 moments.m30 += x2 * x;
187 moments.m03 += y2 * y;
188 moments.m21 += x2 * y;
189 moments.m12 += x * y2;
192 [&](
NodeId parent,
NodeId child) { raw[
static_cast<std::size_t
>(parent)].add(raw[
static_cast<std::size_t
>(child)]); },
194 const RawMoments& moments = raw[
static_cast<std::size_t
>(node)];
195 if (moments.m00 <= Real{0}) {
198 const Real cx = moments.m10 / moments.m00;
199 const Real cy = moments.m01 / moments.m00;
200 const bool needsMu20 = request.mu20 || request.rawInertia;
201 const bool needsMu02 = request.mu02 || request.rawInertia;
202 const Real mu20 = needsMu20 ? moments.m20 - Real{2} * cx * moments.m10 + cx * cx * moments.m00 : Real{0};
203 const Real mu02 = needsMu02 ? moments.m02 - Real{2} * cy * moments.m01 + cy * cy * moments.m00 : Real{0};
205 context.buffer[outputIndex(node, mu20Offset)] = mu20;
207 context.buffer[outputIndex(node, mu02Offset)] = mu02;
209 context.buffer[outputIndex(node, mu11Offset)] = moments.m11 - cx * moments.m01 - cy * moments.m10 + cx * cy * moments.m00;
211 context.buffer[outputIndex(node, mu30Offset)] =
212 moments.m30 - Real{3} * cx * moments.m20 + Real{3} * cx * cx * moments.m10 - cx * cx * cx * moments.m00;
214 context.buffer[outputIndex(node, mu03Offset)] =
215 moments.m03 - Real{3} * cy * moments.m02 + Real{3} * cy * cy * moments.m01 - cy * cy * cy * moments.m00;
217 context.buffer[outputIndex(node, mu21Offset)] = moments.m21 - Real{2} * cx * moments.m11 - cy * moments.m20 +
218 Real{2} * cx * cy * moments.m10 + cx * cx * moments.m01 -
219 cx * cx * cy * moments.m00;
221 context.buffer[outputIndex(node, mu12Offset)] = moments.m12 - Real{2} * cy * moments.m11 - cx * moments.m02 +
222 Real{2} * cx * cy * moments.m01 + cy * cy * moments.m10 -
223 cx * cy * cy * moments.m00;
224 if (request.rawInertia)
225 context.buffer[outputIndex(node, rawInertiaOffset)] = mu20 + mu02;
236template <std::
floating_po
int Real>
237inline void computeHuMoments(
const AttributeComputeContext<Real>& context,
const HuMomentsRequest& request,
238 const DependencySourceT<Real>& momentDependency,
const DependencySourceT<Real>& areaDependency) {
239 if (!request.any()) {
243 const int stride = context.attrNames.NUM_ATTRIBUTES;
244 const auto offsetOf = [&](Attribute attribute) {
return context.attrNames.indexMap.find(attribute)->
second; };
245 const int hu1Offset = request.hu1 ? offsetOf(HuMoment1) : 0;
246 const int hu2Offset = request.hu2 ? offsetOf(HuMoment2) : 0;
247 const int hu3Offset = request.hu3 ? offsetOf(HuMoment3) : 0;
248 const int hu4Offset = request.hu4 ? offsetOf(HuMoment4) : 0;
249 const int hu5Offset = request.hu5 ? offsetOf(HuMoment5) : 0;
250 const int hu6Offset = request.hu6 ? offsetOf(HuMoment6) : 0;
251 const int hu7Offset = request.hu7 ? offsetOf(HuMoment7) : 0;
252 const auto outputIndex = [&](
NodeId node,
int offset) {
return static_cast<std::size_t
>(node * stride + offset); };
254 const int momentStride = momentDependency.attrNames->NUM_ATTRIBUTES;
255 const auto momentOffset = [&](Attribute attribute) {
return momentDependency.attrNames->indexMap.find(attribute)->second; };
256 const auto momentIndex = [&](
NodeId node, Attribute attribute) {
257 return static_cast<std::size_t
>(node * momentStride + momentOffset(attribute));
259 const int areaStride = areaDependency.attrNames->NUM_ATTRIBUTES;
260 const int areaOffset = areaDependency.attrNames->indexMap.find(Area)->second;
261 const auto areaIndex = [&](
NodeId node) {
return static_cast<std::size_t
>(node * areaStride + areaOffset); };
262 const auto normMoment = [](Real area, Real moment,
int p,
int q) {
263 return ::mmcfilters::attributes::numeric::safeDivide(moment, std::pow(area,
static_cast<Real
>(p + q + 2) / Real{2}));
266 ::mmcfilters::detail::TreeKernelAccess::traversePostOrder(
267 context.tree, context.tree.root(), [](NodeId) {}, [](NodeId, NodeId) {},
269 const Real area = areaDependency.buffer[areaIndex(node)];
270 if (area <= Real{0}) {
273 const Real eta20 = normMoment(area, momentDependency.buffer[momentIndex(node, CentralMoment20)], 2, 0);
274 const Real eta02 = normMoment(area, momentDependency.buffer[momentIndex(node, CentralMoment02)], 0, 2);
275 const Real eta11 = normMoment(area, momentDependency.buffer[momentIndex(node, CentralMoment11)], 1, 1);
276 const Real eta30 = normMoment(area, momentDependency.buffer[momentIndex(node, CentralMoment30)], 3, 0);
277 const Real eta03 = normMoment(area, momentDependency.buffer[momentIndex(node, CentralMoment03)], 0, 3);
278 const Real eta21 = normMoment(area, momentDependency.buffer[momentIndex(node, CentralMoment21)], 2, 1);
279 const Real eta12 = normMoment(area, momentDependency.buffer[momentIndex(node, CentralMoment12)], 1, 2);
282 context.buffer[outputIndex(node, hu1Offset)] = eta20 + eta02;
284 context.buffer[outputIndex(node, hu2Offset)] = std::pow(eta20 - eta02, 2) + Real{4} * std::pow(eta11, 2);
286 context.buffer[outputIndex(node, hu3Offset)] = std::pow(eta30 - Real{3} * eta12, 2) + std::pow(Real{3} * eta21 - eta03, 2);
288 context.buffer[outputIndex(node, hu4Offset)] = std::pow(eta30 + eta12, 2) + std::pow(eta21 + eta03, 2);
290 context.buffer[outputIndex(node, hu5Offset)] =
291 (eta30 - Real{3} * eta12) * (eta30 + eta12) * (std::pow(eta30 + eta12, 2) - Real{3} * std::pow(eta21 + eta03, 2)) +
292 (Real{3} * eta21 - eta03) * (eta21 + eta03) * (Real{3} * std::pow(eta30 + eta12, 2) - std::pow(eta21 + eta03, 2));
294 context.buffer[outputIndex(node, hu6Offset)] =
295 (eta20 - eta02) * (std::pow(eta30 + eta12, 2) - std::pow(eta21 + eta03, 2)) +
296 Real{4} * eta11 * (eta30 + eta12) * (eta21 + eta03);
298 context.buffer[outputIndex(node, hu7Offset)] =
299 (Real{3} * eta21 - eta03) * (eta30 + eta12) * (std::pow(eta30 + eta12, 2) - Real{3} * std::pow(eta21 + eta03, 2)) -
300 (eta30 - Real{3} * eta12) * (eta21 + eta03) * (Real{3} * std::pow(eta30 + eta12, 2) - std::pow(eta21 + eta03, 2));
311template <std::
floating_po
int Real>
312inline void computeMomentDerived(
const AttributeComputeContext<Real>& context,
const MomentDerivedRequest& request,
313 const DependencySourceT<Real>& momentDependency,
const DependencySourceT<Real>& areaDependency) {
314 if (!request.any()) {
318 const int stride = context.attrNames.NUM_ATTRIBUTES;
319 const auto offsetOf = [&](Attribute attribute) {
return context.attrNames.indexMap.find(attribute)->
second; };
320 const int inertiaOffset = request.inertia ? offsetOf(Inertia) : 0;
321 const int compactnessOffset = request.compactness ? offsetOf(Compactness) : 0;
322 const int eccentricityOffset = request.eccentricity ? offsetOf(Eccentricity) : 0;
323 const int majorAxisOffset = request.majorAxis ? offsetOf(LengthMajorAxis) : 0;
324 const int minorAxisOffset = request.minorAxis ? offsetOf(LengthMinorAxis) : 0;
325 const int orientationOffset = request.axisOrientation ? offsetOf(AxisOrientation) : 0;
326 const int signedOrientationOffset = request.axisOrientationSigned ? offsetOf(AxisOrientationSigned) : 0;
327 const int circularityOffset = request.circularity ? offsetOf(Circularity) : 0;
328 const auto outputIndex = [&](
NodeId node,
int offset) {
return static_cast<std::size_t
>(node * stride + offset); };
330 const int momentStride = momentDependency.attrNames->NUM_ATTRIBUTES;
331 const int mu20Offset = momentDependency.attrNames->indexMap.find(CentralMoment20)->second;
332 const int mu02Offset = momentDependency.attrNames->indexMap.find(CentralMoment02)->second;
333 const int mu11Offset = momentDependency.attrNames->indexMap.find(CentralMoment11)->second;
334 const auto momentIndex = [&](
NodeId node,
int offset) {
return static_cast<std::size_t
>(node * momentStride + offset); };
335 const int areaStride = areaDependency.attrNames->NUM_ATTRIBUTES;
336 const int areaOffset = areaDependency.attrNames->indexMap.find(Area)->second;
337 const auto areaIndex = [&](
NodeId node) {
return static_cast<std::size_t
>(node * areaStride + areaOffset); };
339 ::mmcfilters::detail::TreeKernelAccess::traversePostOrder(
340 context.tree, context.tree.root(), [](NodeId) {}, [](NodeId, NodeId) {},
342 const Real mu20 = momentDependency.buffer[momentIndex(node, mu20Offset)];
343 const Real mu02 = momentDependency.buffer[momentIndex(node, mu02Offset)];
344 const Real mu11 = momentDependency.buffer[momentIndex(node, mu11Offset)];
345 const Real area = areaDependency.buffer[areaIndex(node)];
346 const Real discriminant = std::pow(mu20 - mu02, 2) + Real{4} * std::pow(mu11, 2);
347 const Real sqrtDiscriminant = ::mmcfilters::attributes::numeric::safeSqrt(discriminant);
348 const Real lambda1 = mu20 + mu02 + sqrtDiscriminant;
349 const Real lambda2 = mu20 + mu02 - sqrtDiscriminant;
351 if (request.majorAxis)
352 context.buffer[outputIndex(node, majorAxisOffset)] =
353 ::mmcfilters::attributes::numeric::safeSqrt(::mmcfilters::attributes::numeric::safeDivide(Real{2} * lambda1, area));
354 if (request.minorAxis)
355 context.buffer[outputIndex(node, minorAxisOffset)] =
356 ::mmcfilters::attributes::numeric::safeSqrt(::mmcfilters::attributes::numeric::safeDivide(Real{2} * lambda2, area));
357 if (request.eccentricity) {
358 const Real eps = std::numeric_limits<Real>::epsilon();
359 if (lambda1 <= eps && std::abs(lambda2) <= eps) {
360 context.buffer[outputIndex(node, eccentricityOffset)] = Real{1};
361 }
else if (lambda2 <= eps) {
362 context.buffer[outputIndex(node, eccentricityOffset)] = maximumFiniteEccentricity<Real>();
364 context.buffer[outputIndex(node, eccentricityOffset)] = ::mmcfilters::attributes::numeric::clampUpper(
365 ::mmcfilters::attributes::numeric::safeDivide(lambda1, lambda2, maximumFiniteEccentricity<Real>()),
366 maximumFiniteEccentricity<Real>());
369 if (request.compactness)
370 context.buffer[outputIndex(node, compactnessOffset)] = (Real{1} / (Real{2} * std::numbers::pi_v<Real>)) *
371 ::mmcfilters::attributes::numeric::safeDivide(area, mu20 + mu02);
372 if (request.axisOrientation || request.axisOrientationSigned) {
373 const Real degrees = (mu20 != mu02 || mu11 != Real{0})
374 ? Real{0.5} * std::atan2(Real{2} * mu11, mu20 - mu02) * (Real{180} / std::numbers::pi_v<Real>)
376 if (request.axisOrientation)
377 context.buffer[outputIndex(node, orientationOffset)] = std::fmod(std::abs(degrees), Real{360});
378 if (request.axisOrientationSigned)
379 context.buffer[outputIndex(node, signedOrientationOffset)] = degrees;
381 if (request.inertia) {
382 const Real areaSquared = area * area;
383 context.buffer[outputIndex(node, inertiaOffset)] = ::mmcfilters::attributes::numeric::safeDivide(mu20, areaSquared) +
384 ::mmcfilters::attributes::numeric::safeDivide(mu02, areaSquared);
386 if (request.circularity) {
387 const Real eps = std::numeric_limits<Real>::epsilon();
388 if (lambda1 <= eps && std::abs(lambda2) <= eps) {
389 context.buffer[outputIndex(node, circularityOffset)] = Real{1};
390 }
else if (lambda1 <= eps || lambda2 <= eps) {
391 context.buffer[outputIndex(node, circularityOffset)] = Real{0};
393 context.buffer[outputIndex(node, circularityOffset)] = ::mmcfilters::attributes::numeric::safeDivide(lambda2, lambda1);
401template <std::
floating_po
int Real>
inline void validateCentralMomentsContext(
const AttributeComputeContext<Real>& context) {
402 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
403 requireRequestedAttributeColumns(context);
404 static_cast<void>(context.tree.requireGridDomain2D(
"CentralMomentsComputer"));
407template <std::
floating_po
int Real>
inline void validateMomentDependencyContext(
const AttributeComputeContext<Real>& context) {
408 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
409 requireRequestedAttributeColumns(context);
437 static constexpr std::string_view familyName =
"central-moments";
440 static constexpr AttributeComputerFamily family = AttributeComputerFamily::CentralMoments;
443 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
448 inline static constexpr std::array<Attribute, 8> producedAttributes{CentralMoment20, CentralMoment02, CentralMoment11, CentralMoment30,
449 CentralMoment03, CentralMoment21, CentralMoment12, RawInertia};
464 const detail::CentralMomentsRequest
request = detail::CentralMomentsRequest::from(
context.requestedAttributes);
479 computeUnitRowsEstablished(
context);
489 template <
class Computer,
class Context>
friend void mmcfilters::detail::kernel::computeUnitRows(
const Context&
context);
493 constexpr std::array<Attribute, 8>
zeroAttributes{CentralMoment20, CentralMoment02, CentralMoment11, CentralMoment30,
494 CentralMoment03, CentralMoment21, CentralMoment12, RawInertia};
522 static constexpr std::string_view familyName =
"hu-moments";
525 static constexpr AttributeComputerFamily family = AttributeComputerFamily::HuMoments;
528 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
533 inline static constexpr std::array<Attribute, 7> producedAttributes{HuMoment1, HuMoment2, HuMoment3, HuMoment4,
534 HuMoment5, HuMoment6, HuMoment7};
548 const detail::HuMomentsRequest
request = detail::HuMomentsRequest::from(
context.requestedAttributes);
556 if constexpr (contract::validationsEnabled) {
557 momentDependency = &
context.dependencies.requireAll({CentralMoment20, CentralMoment02, CentralMoment11, CentralMoment30,
558 CentralMoment03, CentralMoment21, CentralMoment12});
578 computeUnitRowsEstablished(
context);
588 template <
class Computer,
class Context>
friend void mmcfilters::detail::kernel::computeUnitRows(
const Context&
context);
592 constexpr std::array<Attribute, 7>
zeroAttributes{HuMoment1, HuMoment2, HuMoment3, HuMoment4, HuMoment5, HuMoment6, HuMoment7};
628 static constexpr std::string_view familyName =
"moment-derived";
631 static constexpr AttributeComputerFamily family = AttributeComputerFamily::MomentDerived;
634 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
642 return detail::maximumFiniteEccentricity<Real>();
648 inline static constexpr std::array<Attribute, 8> producedAttributes{Inertia, Compactness, Eccentricity, LengthMajorAxis,
649 LengthMinorAxis, AxisOrientation, Circularity, AxisOrientationSigned};
663 const detail::MomentDerivedRequest
request = detail::MomentDerivedRequest::from(
context.requestedAttributes);
671 if constexpr (contract::validationsEnabled) {
692 computeUnitRowsEstablished(
context);
702 template <
class Computer,
class Context>
friend void mmcfilters::detail::kernel::computeUnitRows(
const Context&
context);
706 constexpr std::array<Attribute, 8>
zeroAttributes{Compactness, Eccentricity, LengthMajorAxis, LengthMinorAxis,
707 AxisOrientation, Inertia, Circularity, AxisOrientationSigned};
714 (attribute == Eccentricity || attribute == Circularity) ? Real{1} : Real{0};
int NodeId
Node identifier type used throughout the project.
#define MMCFILTERS_CONTRACT_CHECKED_ONLY(...)
Executes validation statements only when defensive checks are enabled.
Computes geometric central moments up to third order.
static void compute(const AttributeComputeContext< Real > &context)
Computes the requested central moments.
static void computeUnitRows(const UnitAttributeComputeContext< Real > &context)
Materializes central moments for one-pixel unit supports.
Computes the seven Hu invariant moments.
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.
std::vector< Real > second
Flat per-node attribute buffer indexed through first.