|
MorphologicalAttributeFilters
Public API documentation
|
This guide documents the attribute-computer architecture and the extension path for adding or changing descriptors. For public usage, see Attributes; for the descriptor table, see Attribute Catalog.
In this subsystem, incremental means bottom-up and local-event-oriented computation over the current tree. It does not mean that every public attribute buffer stays live after arbitrary topology edits.
Ordinary application code should include mmcfilters/attributes/Attributes.hpp and call AttributeComputation. Concrete computers are advanced extension components, not alternate public orchestration APIs.
The C++ library is header-only, so installed packages include some detail headers as transitive implementation dependencies. Those headers are shipped so public headers compile downstream; they are not compatibility-contract headers.
An attribute computer owns one coherent descriptor family. Every computer must provide:
inline static constexpr familyName for diagnostics;inline static constexpr family for scheduler grouping;inline static constexpr domain for execution routing;inline static constexpr producedAttributes as the canonical descriptor list;static compute(context) for internal-node rows;static computeUnitRows(unitContext) for compact exported-Higra unit rows.Computers are stateless static kernels. The produced-attribute list has a single source of truth: the computer class. runtimeProducedAttributes<Computer>() materializes it only for call sites that need runtime storage.
Unit-row support is mandatory. If a descriptor has a degenerate one-pixel meaning, the computer defines that value explicitly. Otherwise it still defines the exported unit-row convention.
AttributeRegistry.hpp stores public descriptor metadata:
Group membership is metadata. Public requests may mix scalar attributes and groups; the pipeline expands groups, deduplicates scalars, and returns only the requested public descriptors.
AttributeComputerRegistry.hpp defines the computer protocol and RegisteredAttributeComputers. Produced descriptors are declared only by Computer::producedAttributes, and scheduler grouping is declared only by Computer::family.
At a high level, a request follows this path:
AttributeFamilyScheduler adds hidden dependencies, groups descriptors by family, and preserves dependency order. The central executors are:
executeAttributeComputationPlan(...) for altitude-aware requests;executeTopologyAttributeComputationPlan(...) for topology/support requests.The internal orchestration path is detail::AttributePipeline; topology-only families are delegated to TopologyAttributeBackend. New code should extend this path instead of adding another top-level execution pipeline.
Dependencies are ordinary attribute results consumed by another computer. They are passed as DependencySourceT<Real>, a non-owning pair of AttributeNames and const Real*. Dependency buffers are reusable only when they contain the requested descriptors and use NodeIdSpace::MORPHOLOGICAL_TREE.
Several computers use bottom-up accumulation: preprocess the current node, merge children into the parent, then finalize the current node. Delta-augmented public calls compute the base attribute first, then materialize ancestor/descendant offsets from a typed altitude step, radius, and padding policy.
The context types in AttributeKernelSupport.hpp are the adapter boundary:
AttributeComputeContext<Real>;AltitudeAttributeComputeContext<Real, T>;UnitAttributeComputeContext<Real>;AltitudeUnitAttributeComputeContext<Real, T>.TopologyAttributeComputer and AltitudeAttributeComputer enforce the standard computer protocol. A new family should not add public family-specific method names. Private helpers and detail kernels may keep narrower signatures when that makes implementation or testing clearer.
Local-event bucket types such as detail::BitquadFamilyCounts and detail::ContourSideCounts are implementation storage, not public contracts.
Computers use AttributeNumericPolicy.hpp for degenerate divisions, square roots, non-negative clamping, finite fallbacks, and ratio bounds. Attribute buffers should not expose accidental NaN or infinite values for ordinary finite inputs.
Start by deciding whether the descriptor belongs to an existing family or requires a new family. Prefer an existing family when traversal, dependencies, or intermediate state are shared.
Common metadata steps:
AttributeTypes.hpp and one matching row in AttributeRegistry.hpp.For a descriptor in an existing family:
producedAttributes.compute(context).DependencyResolver<Real> for semantic dependencies and AttributeNumericPolicy.hpp for finite fallbacks.AttributeFamilyScheduler.hpp only when another materialized attribute is consumed.computeUnitRows(unitContext).For a new family:
AttributeComputerFamily value.mmcfilters/attributes/computers/.familyName, family, domain, and producedAttributes.compute(context) and computeUnitRows(unitContext).RegisteredAttributeComputers.AttributePipeline.hpp or TopologyAttributeBackend.hpp.Update Python bindings, notebooks, or examples only when the public surface changes.
Useful checks while changing this subsystem are:
Run Python tests when bindings or the Python facade change.