Skip to content

Start internal support for dynamic component-tree algorithms#294

Open
wonderalexandre wants to merge 10 commits into
higra:masterfrom
wonderalexandre:component-tree-casf
Open

Start internal support for dynamic component-tree algorithms#294
wonderalexandre wants to merge 10 commits into
higra:masterfrom
wonderalexandre:component-tree-casf

Conversation

@wonderalexandre
Copy link
Copy Markdown

This PR follows the direction discussed in #293 and is being built incrementally.

Its goal is to introduce support for dynamic component-tree algorithms in Higra while keeping the low-level machinery internal until the design stabilizes.

The first commit adds an internal mutable component-tree backend under higra/detail/hierarchy, with unit tests covering structural invariants, mutations, traversals, and iterator invalidation.

Further commits in this PR will build on top of this backend.

Introduce a mutable internal backend for component-tree algorithms under higra/detail/hierarchy.

The new DynamicComponentTree separates internal-node storage from proper-part ownership, provides structural mutation operations, and exposes lazy traversal ranges with debug-only fail-fast checks.

This commit also adds unit tests covering construction, topology updates, proper-part moves, pruning, merging, traversals, and iterator invalidation.
Comment thread include/higra/detail/hierarchy/dynamic_component_tree.hpp
Comment thread include/higra/detail/hierarchy/dynamic_component_tree.hpp Outdated
Comment thread include/higra/detail/hierarchy/dynamic_component_tree.hpp Outdated
Move the type to hg::detail::hierarchy.

Remove altitude storage and altitude-dependent API from the tree.

Replace graph/image construction with parent/tree constructors.

Preserve stable node ids under pure topology updates.

Update dynamic component-tree tests to the new API.
@wonderalexandre
Copy link
Copy Markdown
Author

  • Namespace/internal API: DynamicComponentTree is now in hg::detail::hierarchy, and the old graph/image constructor path has been removed.

  • Topology vs altitude: altitude is now external to DynamicComponentTree; the tree no longer stores node altitude or exposes altitude accessors.

  • Stable ids: I added test coverage showing that surviving node ids remain stable under pure topology-only updates.

@wonderalexandre
Copy link
Copy Markdown
Author

Next small step in the series: incremental attribute computation for DynamicComponentTree, with on-demand recomputation after local edits. This will support the dual-tree update algorithm.

The commit also adds concrete computers for area and bounding-box-derived attributes (width, height, diagonal).

This detail-only backend updates the dual dynamic tree after subtree pruning.
@wonderalexandre
Copy link
Copy Markdown
Author

This commit contains the last internal backend piece needed for CASF.

The next step will be the public C++ CASF interface, which I currently expect to expose a small API centered on:

  • construction from the input graph, image, and attribute type,
  • one method to run the CASF and return the filtered image,
  • one export method for the current max-tree state in Higra's usual static representation,
  • one export method for the current min-tree state in Higra's usual static representation.
namespace hg {

enum class ComponentTreeCasfAttribute {
    area,
    bounding_box_width,
    bounding_box_height,
    bounding_box_diagonal
};

template<typename altitude_t, typename graph_t>
class ComponentTreeCasf {
public:
    ComponentTreeCasf(const graph_t &graph,
                      const array_1d<altitude_t> &image,
                      ComponentTreeCasfAttribute attribute = ComponentTreeCasfAttribute::area);

    array_1d<altitude_t> filter(const std::vector<double> &thresholds);

    auto exportMaxTree() const;

    auto exportMinTree() const;
};

} // namespace hg

The export methods are expected to return the tree, altitude array, and attribute array for the current max-tree and min-tree, respectively, using Higra's usual static representation.

@PerretB
Copy link
Copy Markdown
Member

PerretB commented Mar 17, 2026

Hi, I just have a few quick suggestions/questions (haven't read everything in detail).

  • can you rename ComponentTreeAdjustment to something more explicit like DualMinMaxTreeIncrementalFilter (might find something better :) )
  • I don't know if you did any test on this, but usually we take 16 bits as the upper limit for "small integers" optimizations. That might be something worth testing later.
  • I'm not quite sure why you don't allocate altitude and attribute for min/max trees inside your class instead of asking the user to allocate them? (assuming that the attribute type is double is a good idea, it simplifies things and optimizing this won't make a large difference in practice)

@wonderalexandre
Copy link
Copy Markdown
Author

Hi Benjamin, thank you for your suggestions. Below are my answers to your questions.

  • can you rename ComponentTreeAdjustment to something more explicit like DualMinMaxTreeIncrementalFilter (might find something better :) )

I agree that ComponentTreeAdjustment is probably too vague.

Since this class exposes pruneMaxTreeAndUpdateMinTree and pruneMinTreeAndUpdateMaxTree, it does more than a low-level adjustment routine and effectively performs incremental filtering steps on the dual min/max-tree pair.

So DualMinMaxTreeIncrementalFilter seems like a reasonable name here. I can rename it in the next revision.

  • I don't know if you did any test on this, but usually we take 16 bits as the upper limit for "small integers" optimizations. That might be something worth testing later.

I do not have a robust study yet for images beyond the 8-bit case, but I ran a small benchmark on 10 images with resolution 1080x1920, using 50 area thresholds from 50 to 2500.

For 8-bit, the dense backend looks like a small micro-optimization: it gives about 10% speedup on average over the sparse version.

For 16-bit, making it dense is clearly detrimental in this benchmark: the dense version is about 4.6x slower on average than the sparse one.

So these results do not suggest extending the dense path to 16-bit. If anything, they suggest that the dense backend is only a minor optimization for very small integer domains.

If reducing implementation complexity is preferable, I think it would also be reasonable to keep only the sparse bucket backend.

  • I'm not quite sure why you don't allocate altitude and attribute for min/max trees inside your class instead of asking the user to allocate them? (assuming that the attribute type is double is a good idea, it simplifies things and optimizing this won't make a large difference in practice)

At first I kept them external to preserve the separation between topology and attached data, and to keep the incremental attribute logic reusable.

But I agree that at the public API level it is better to own them inside the CASF class. In the current version (not committed yet), ComponentTreeCasf already does that, so the external buffers remain only an internal backend detail.

Add a public C++ ComponentTreeCasf interface on over of the internal dynamic component-tree backend.

The class supports area and bounding-box attributes, applies threshold sequences, reconstructs the filtered image, and exports the current min-tree and max-tree in Higra's static representation.

Add tests for export correctness, supported attributes, empty thresholds, determinism across independent instances, naive area and bounding-box references, candidate selection, and stress behaviour.
Add the CASF benchmark implementation and its synthetic benchmark cases.

CMake integration is left for a separate change.
@wonderalexandre
Copy link
Copy Markdown
Author

At this point, I have finalized the C++ backend, including:

  • internal dynamic component tree structure,
  • pruning and dual-tree update mechanisms,
  • incremental attribute maintenance,
  • CASF operator,
  • unit tests,
  • benchmark for CASF vs. naive.

The next step is to implement the public Python interface:

vertex_weights_out = connected_alternating_sequential_filter(
    graph,
    vertex_weights,
    ATTR_TYPE,
    thresholds
)

Introduce one incremental attribute computer per dynamic tree in the CASF pipeline. Add explicit structural update hooks for proper-part moves and node removal, and switch attribute refreshes to selective marked recomputation instead of broader node recalculation. This commit establishes the correctness and performance foundation required by the Python public API layer.
Add the public connected_alternating_sequential_filter entry point, keep the low-level binding internal, and cover the new API with Python regression tests.
@wonderalexandre
Copy link
Copy Markdown
Author

Small update: this PR now also includes the consolidation of the incremental CASF attribute-update contract in the internal backend, together with the first public Python entry point, hg.connected_alternating_sequential_filter(...), and its regression tests.

At this point the implementation is stable on the covered cases, and the incremental update design already gives good practical performance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants