MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
Higra Interoperability

This guide documents the Higra-facing boundary of mmcfilters: importing a static hierarchy, preserving imported Higra node ids, exporting the current tree, and projecting node attributes to exported layouts.

For the underlying tree model, see Morphological Trees. For attribute buffer layout, see Attributes.

Two Higra Domains

The library exposes two distinct Higra-related domains:

Domain Created by Edit-stable Main use
Preserved imported Higra domain createFromHigraParent(...) No Original imported ids
Exported compact Higra domain exportHigraHierarchy() Snapshot only Current live tree export

NodeIdSpace::HIGRA refers only to the preserved imported domain. It does not mean "whatever `exportHigraHierarchy()` would produce now".

exportHigraHierarchy() always computes a new compact layout for the current live rooted tree. Use projectNodeValuesToExportedHigra(...) or Python project_node_values_to_exported_higra(...) when attributes must be aligned with that exported snapshot.

Compact Layout

The compact Higra layout used by import and export is:

[proper parts | internal nodes]

For an image domain with rows * cols proper parts:

  • proper-part leaf ids are [0, rows * cols);
  • internal ids are [rows * cols, parent.size());
  • every leaf points to an internal node;
  • every internal node points to another internal node or to itself;
  • exactly one internal node is self-parented and is the root.

When exporting, proper parts are emitted in row-major order. Internal nodes are assigned compact ids from the live rooted tree. For max-trees and min-trees, the export order follows the tree altitude polarity with a deterministic post-order tie-breaker. Trees of shapes and self-dual residual trees derive the export direction from their local altitude relation.

The exported altitude array has the same length as the exported parent array. Leaf/proper-part altitudes are filled with the altitude of their owner node.

Importing A Static Hierarchy

Use MorphologicalTreeFactory::createFromHigraParent(...) in C++:

#include <cstdint>
#include <span>
#include <vector>
#include <mmcfilters/trees/MorphologicalTreeFactory.hpp>
using namespace mmcfilters;
std::vector<NodeId> parent = /* compact [proper parts | internal nodes] */;
std::vector<std::uint8_t> altitude = /* same size as parent */;
auto tree = MorphologicalTreeFactory::createFromHigraParent(
std::span<const NodeId>(parent),
std::span<const std::uint8_t>(altitude),
MorphologicalTreeKind::MAX_TREE,
Two-dimensional adjacency relation with configurable radius and efficient iteration.
Owning result for one computed scalar attribute layout and buffer.

The altitude type is typed in C++:

std::vector<float> floatAltitude(parent.size(), 0.0f);
auto floatTree = MorphologicalTreeFactory::createFromHigraParent<float>(
std::span<const NodeId>(parent),
std::span<const float>(floatAltitude),
MorphologicalTreeKind::MIN_TREE,

Python exposes the canonical 8-bit path:

import mmcfilters
parent,
altitude,
rows,
cols,
mmcfilters.MorphologicalTreeKind.MAX_TREE,
radius=1.5,
)
static WeightedMorphologicalTree< T > createFromHigraParent(std::span< const NodeId > higraParent, std::span< const T > higraAltitude, int rows, int cols, MorphologicalTreeKind kind, std::optional< AdjacencyRelation > adjacency=std::nullopt)
Imports a static Higra parent/altitude hierarchy.

Python altitude inputs must be integer sequences in [0, 255] or 1D C-contiguous np.uint8 arrays. C++ accepts any type satisfying the public AltitudeValue contract.

Max-tree and min-tree imports require adjacency metadata. In Python, pass radius; in C++, pass an AdjacencyRelation. Tree-of-shapes imports can omit component-tree adjacency.

SELF_DUAL_RESIDUAL_TREE is not accepted by the Higra import path; SDRT construction has its own factory path.

Preserved Imported Ids

After import, the tree still uses the internal dense NodeId domain for normal tree operations. A preserved mapping from internal live nodes to the original Higra ids is available until the topology is edited:

NodeId internal = tree.topology().getRoot();
NodeId originalHigraId = tree.topology().getHigraNodeId(internal);
int higraDomainSize = tree.topology().getNumHigraNodes();
internal = tree.getRoot()
original_higra_id = tree.getHigraNodeId(internal)
higra_domain_size = tree.numHigraNodes

For trees imported from the compact layout above, the internal slot associated with Higra internal id h starts as:

node_id = h - rows * cols

Do not rely on that arithmetic after edits. Safe public code should use getHigraNodeId(...) while the preserved domain is still valid.

Any topology mutation invalidates the preserved imported Higra domain. This includes safe mutators such as pruneNode(...) and mergeNodeIntoParent(...) and staged edit commits. After invalidation:

  • getNumHigraNodes() fails;
  • NodeIdSpace::HIGRA attribute requests fail;
  • getHigraNodeId(node) returns InvalidNode.

Export still works after edits because it creates a new compact domain.

Attributes In Preserved Higra Space

Attribute computation always runs internally in NodeIdSpace::MORPHOLOGICAL_TREE. Projection to NodeIdSpace::HIGRA is an API-boundary step.

Use preserved Higra output space when a consumer needs rows indexed by the original imported ids:

auto computed = AttributeComputation::computeSingleAttribute(
tree,
AREA,
NodeIdSpace::HIGRA);
const std::vector<float>& values = computed.values();
Layout object that maps scalar attributes to flat-buffer offsets.
std::vector< Real > & values() noexcept
Returns the mutable flat attribute buffer.
AttributeNames & attributeNames() noexcept
Returns the mutable attribute layout.
area_in_imported_space = mmcfilters.Attribute.computeSingleAttribute(
tree,
mmcfilters.Attribute.AREA,
mmcfilters.NodeIdSpace.HIGRA,
)

Live internal-node rows receive the values computed in the internal MorphologicalTree node-id space. Proper-part/leaf rows in the preserved imported Higra domain receive the same unit-component values used by compact Higra export.

Exporting The Current Tree

Use exportHigraHierarchy() when a consumer needs the current live tree:

auto [exportedParent, exportedAltitude] = tree.exportHigraHierarchy();
exported_parent, exported_altitude = tree.exportHigraHierarchy()

The exported layout is a fresh snapshot. It is valid for image-built trees, imported trees, and edited trees as long as the current topology is one rooted live component and the altitude buffer covers the internal node slots.

Dead internal slots are not exported. Export size is:

num_total_proper_parts + num_live_internal_nodes

The exported parent and altitude arrays should be treated as a pair. If the tree is edited again, export again and reproject any attributes that must align with the new compact ids.

Projecting Attributes To Exported Layout

NodeIdSpace::HIGRA is not the right tool for exported snapshots. To align attributes with exportHigraHierarchy(), project a dense internal-node buffer:

auto [names, values] = AttributeComputation::computeAttributes(
tree,
std::vector<AttributeOrGroup>{AREA, MAX_DIST});
std::vector<float> exportedValues =
AttributeComputation::projectNodeValuesToExportedHigra(
tree,
values);

Python exposes the same operation on WeightedMorphologicalTree:

layout, values = mmcfilters.Attribute.computeAttributes(
tree,
[mmcfilters.Attribute.AREA, mmcfilters.Attribute.MAX_DIST],
)
exported_values = tree.project_node_values_to_exported_higra(
values,
[mmcfilters.Attribute.AREA, mmcfilters.Attribute.MAX_DIST],
)

For a single attribute:

area = mmcfilters.Attribute.computeSingleAttribute(
tree,
mmcfilters.Attribute.AREA,
)
exported_area = tree.project_node_values_to_exported_higra(
area,
mmcfilters.Attribute.AREA,
)

The projection output follows the same [proper parts | live internal nodes] layout as the exported hierarchy. Internal-node rows are copied from the node-indexed input. Proper-part rows are filled with unit-component values for the requested attributes.

Examples of unit proper-part values:

  • AREA: 1;
  • LEVEL and MEAN_LEVEL: the altitude of the proper-part owner;
  • VOLUME: one pixel at the owner altitude;
  • VARIANCE_LEVEL, GRAY_HEIGHT, and MAX_DIST: 0;
  • bounding-box attributes: the proper-part row/column coordinates.

Projection fails if:

  • the node-value buffer shape does not match the dense internal-node domain;
  • the attribute list does not match the number of columns;
  • an attribute has no registered unit-component projection;
  • the tree is currently inside an edit session;
  • a borrowed WeightedTreeView<T> became stale after topology mutation.

Round Trip Pattern

A common interoperability round trip is:

parent, altitude = tree.exportHigraHierarchy()
parent,
altitude,
image.shape[0],
image.shape[1],
mmcfilters.MorphologicalTreeKind.MAX_TREE,
radius=1.5,
)
area_exported = tree.project_node_values_to_exported_higra(
mmcfilters.Attribute.computeSingleAttribute(tree, mmcfilters.Attribute.AREA),
mmcfilters.Attribute.AREA,
)
area_imported_space = mmcfilters.Attribute.computeSingleAttribute(
roundtrip,
mmcfilters.Attribute.AREA,
mmcfilters.NodeIdSpace.HIGRA,
)
static WeightedMorphologicalTree< T > createMaxTree(ImagePtr< T > img, double radius=1.5)
Builds a typed weighted max-tree from an image.

area_exported and area_imported_space use the same compact id layout after the round trip. Both paths fill proper-part rows with unit-component values.

Choosing The API

Use preserved NodeIdSpace::HIGRA when:

  • the tree was imported from Higra;
  • the topology has not been edited;
  • the downstream consumer wants the original imported node ids;
  • proper-part rows should follow the same unit-component convention as export.

Use exported Higra projection when:

  • the tree was image-built;
  • the topology may have been edited;
  • the downstream consumer will use exportHigraHierarchy() output.

Use internal NodeIdSpace::MORPHOLOGICAL_TREE when:

  • the data stays inside mmcfilters;
  • filters, UAO, contours, or topology queries will consume it;
  • dead internal slots and live-node iteration are part of the workflow.

Related Guides

  • Morphological Trees: owner/view boundary, NodeId, proper parts, altitude, and edits.
  • Attributes: attribute layouts, output spaces, and unit export projections.
  • Python API Guide: Python construction and wrapper names.
  • Editing API: edit-session lifetime and derived-state invalidation.