mmcfilters
Public API documentation
Loading...
Searching...
No Matches
MorphologicalTreeFactory.hpp
1#pragma once
2
3#include "MorphologicalTree.hpp"
4#include "NativeHierarchy.hpp"
5#include "TreeOfShapesProducer.hpp"
6#include "TreeAltitudeAlgorithms.hpp"
7#include "ValuedMorphologicalTree.hpp"
8#include "detail/ComponentTreeProducerDetail.hpp"
9#include "detail/HigraHierarchyAdapterDetail.hpp"
10#include "detail/MorphologicalTreeConstructionTag.hpp"
11#include "detail/NativeHierarchyValidationDetail.hpp"
15#include "../utils/Image.hpp"
16#include "../utils/Contract.hpp"
17
18#include <cstddef>
19#include <cstdint>
20#include <optional>
21#include <span>
22#include <utility>
23#include <vector>
24
25namespace mmcfilters {
26
55 private:
61 template <AltitudeValue T> static void validateComponentTreeImage(const ImagePtr<T>& img, const char* context) {
62 MMCFILTERS_CONTRACT_REQUIRE(img != nullptr, throw std::invalid_argument(std::string(context) + " requires a non-null image."));
63 MMCFILTERS_CONTRACT_REQUIRE(img->getNumRows() > 0 && img->getNumColumns() > 0 && img->getSize() > 0,
64 throw std::invalid_argument(std::string(context) + " requires a non-empty 2D image."));
66 }
67
72 static void validateConnectedComponentTreeAdjacency(const RegularGridAdjacency2D& adjacency) {
73 const int numPixels = adjacency.getNumRows() * adjacency.getNumColumns();
74 std::vector<std::uint8_t> visited(static_cast<std::size_t>(numPixels), 0);
75 std::vector<PixelId> pending;
76 pending.reserve(static_cast<std::size_t>(numPixels));
77 pending.push_back(0);
78 visited[0] = 1;
79 int reached = 0;
80 while (!pending.empty()) {
81 const PixelId pixel = pending.back();
82 pending.pop_back();
83 ++reached;
84 for (PixelId neighbor : adjacency.getNeighborIndices(pixel)) {
85 if (visited[static_cast<std::size_t>(neighbor)] == 0) {
86 visited[static_cast<std::size_t>(neighbor)] = 1;
87 pending.push_back(neighbor);
88 }
89 }
90 }
91 if (reached != numPixels) {
92 throw std::invalid_argument("Component-tree adjacency must induce one connected image-domain graph.");
93 }
94 }
95
103 enum class MaterializedVersionPolicy { CanonicalNative, PreserveLinkedConstruction };
104
114 static constexpr detail::MorphologicalTreeConstructionTag tag() noexcept { return detail::MorphologicalTreeConstructionTag{}; }
115
121 static void validateTreeOfShapesImage(const ImageUInt8Ptr& img) {
122 if (!img) {
123 throw std::invalid_argument("MorphologicalTreeFactory::createTreeOfShapes requires a non-null image.");
124 }
125 if (img->getNumRows() <= 0 || img->getNumColumns() <= 0 || img->getSize() <= 0) {
126 throw std::invalid_argument("MorphologicalTreeFactory::createTreeOfShapes requires a non-empty 2D image.");
127 }
128 }
129
142 static void resolveTopographicImmersion(TopographicConvention& convention, int numRows, int numColumns) {
143 const auto* canonical = std::get_if<CanonicalComplementaryGridImmersion>(&convention.immersion);
144 if (canonical == nullptr) {
145 return;
146 }
147 constexpr double connectivity4Radius = 1.0;
148 constexpr double connectivity8Radius = 1.5;
149 const bool min4Max8 = canonical->pairing == ComplementaryPairing::Min4Max8;
153 }
154
163 template <AltitudeValue T>
165 materializeNativeHierarchy(detail::ValidatedNativeHierarchy<T>&& hierarchy, std::optional<NodeId> preservedExternalNodeIdOffset = std::nullopt,
166 MaterializedVersionPolicy versionPolicy = MaterializedVersionPolicy::CanonicalNative) {
167 auto storage = std::move(hierarchy).release();
168 const std::size_t numNodes = storage.parent.size();
169 MorphologicalTree topology(tag(), std::move(storage.parent), std::move(storage.smallestNodeMap), storage.root, storage.gridDomain2D,
170 std::move(storage.semantics), std::move(storage.topologyProof));
171 if (versionPolicy == MaterializedVersionPolicy::PreserveLinkedConstruction) {
172 // Account for initialization, non-root links, and finalization.
173 topology.mutationVersion_ = numNodes + 1;
174 }
175 if (preservedExternalNodeIdOffset) {
176 topology.preserveExternalNodeIdOffset(*preservedExternalNodeIdOffset);
177 }
178 return ValuedMorphologicalTree<T>(tag(), std::move(topology), std::move(storage.nodeAltitudes));
179 }
180
181 public:
195 template <AltitudeValue T> [[nodiscard]] static ValuedMorphologicalTree<T> createMaxTree(ImagePtr<T> img, double radius = 1.5) {
196 validateComponentTreeImage(img, "MorphologicalTreeFactory::createMaxTree");
197 RegularGridAdjacency2D adjacency(img->getNumRows(), img->getNumColumns(), radius);
198 MMCFILTERS_CONTRACT_REQUIRE(img->getSize() == 1 || radius >= 1.0,
199 throw std::invalid_argument("Component-tree adjacency must induce one connected image-domain graph."));
201 detail::kernel::ComponentTreeProducer<T>(detail::kernel::ComponentTreePolarity::MaxTree, std::move(adjacency)).build(img), std::nullopt,
202 MaterializedVersionPolicy::PreserveLinkedConstruction);
203 }
204
216 validateComponentTreeImage(img, "MorphologicalTreeFactory::createMaxTree");
217 MMCFILTERS_CONTRACT_REQUIRE(adjacency.getNumRows() == img->getNumRows() && adjacency.getNumColumns() == img->getNumColumns(),
218 throw std::invalid_argument("Component-tree adjacency domain must match the input image domain."));
219 MMCFILTERS_CONTRACT_CHECKED_ONLY(validateConnectedComponentTreeAdjacency(adjacency));
221 detail::kernel::ComponentTreeProducer<T>(detail::kernel::ComponentTreePolarity::MaxTree, std::move(adjacency)).build(img), std::nullopt,
222 MaterializedVersionPolicy::PreserveLinkedConstruction);
223 }
224
238 template <AltitudeValue T> [[nodiscard]] static ValuedMorphologicalTree<T> createMinTree(ImagePtr<T> img, double radius = 1.5) {
239 validateComponentTreeImage(img, "MorphologicalTreeFactory::createMinTree");
240 RegularGridAdjacency2D adjacency(img->getNumRows(), img->getNumColumns(), radius);
241 MMCFILTERS_CONTRACT_REQUIRE(img->getSize() == 1 || radius >= 1.0,
242 throw std::invalid_argument("Component-tree adjacency must induce one connected image-domain graph."));
244 detail::kernel::ComponentTreeProducer<T>(detail::kernel::ComponentTreePolarity::MinTree, std::move(adjacency)).build(img), std::nullopt,
245 MaterializedVersionPolicy::PreserveLinkedConstruction);
246 }
247
256 validateComponentTreeImage(img, "MorphologicalTreeFactory::createMinTree");
257 MMCFILTERS_CONTRACT_REQUIRE(adjacency.getNumRows() == img->getNumRows() && adjacency.getNumColumns() == img->getNumColumns(),
258 throw std::invalid_argument("Component-tree adjacency domain must match the input image domain."));
259 MMCFILTERS_CONTRACT_CHECKED_ONLY(validateConnectedComponentTreeAdjacency(adjacency));
261 detail::kernel::ComponentTreeProducer<T>(detail::kernel::ComponentTreePolarity::MinTree, std::move(adjacency)).build(img), std::nullopt,
262 MaterializedVersionPolicy::PreserveLinkedConstruction);
263 }
264
277 template <AltitudeValue T>
281 auto minTree = createMinTree(img, adjacency);
282 auto maxTree = createMaxTree(img, adjacency);
284 builder.build(img, std::move(minTree), std::move(maxTree));
286 std::move(builder).takeValidatedHierarchy(makeMorphologicalTreeSemantics(
287 MorphologicalTreeKind::UnrestrictedResidualTree, SharedAdjacencyContext{std::move(adjacency)})));
288 }
289
298 template <AltitudeValue T>
299 [[nodiscard]] static ValuedMorphologicalTree<T>
301 TreeAltitudeAlgorithms::validateFiniteImageAltitudes(img, "MorphologicalTreeFactory::createUnrestrictedResidualTree image");
302 RegularGridAdjacency2D adjacency(img->getNumRows(), img->getNumColumns(), radius);
303 return createUnrestrictedResidualTree(std::move(img), std::move(adjacency), options);
304 }
305
319 template <AltitudeValue T>
320 [[nodiscard]] static ValuedMorphologicalTree<T>
323 auto minTree = createMinTree(img, adjacency);
324 auto maxTree = createMaxTree(img, adjacency);
325 sdrt::SaturatedResidualTreeBuilder<T> builder(adjacency, infinityPixel, options);
326 builder.build(img, std::move(minTree), std::move(maxTree));
328 std::move(builder).takeValidatedHierarchy(makeMorphologicalTreeSemantics(
329 MorphologicalTreeKind::SaturatedResidualTree, SaturatedResidualContext{std::move(adjacency), infinityPixel})));
330 }
331
341 template <AltitudeValue T>
342 [[nodiscard]] static ValuedMorphologicalTree<T>
343 createSaturatedResidualTree(ImagePtr<T> img, PixelId infinityPixel, double radius = 1.5,
345 TreeAltitudeAlgorithms::validateFiniteImageAltitudes(img, "MorphologicalTreeFactory::createSaturatedResidualTree image");
346 RegularGridAdjacency2D adjacency(img->getNumRows(), img->getNumColumns(), radius);
347 return createSaturatedResidualTree(std::move(img), std::move(adjacency), infinityPixel, options);
348 }
349
372 template <class Altitude = std::uint8_t>
374 validateTreeOfShapesImage(img);
375 resolveTopographicImmersion(convention, img->getNumRows(), img->getNumColumns());
376
377 TreeOfShapesProducer producer(convention);
379 MorphologicalTreeSemantics semantics = makeMorphologicalTreeSemantics(MorphologicalTreeKind::TreeOfShapes, std::move(convention));
380 return materializeNativeHierarchy<Altitude>(std::move(result).takeValidatedHierarchy(std::move(semantics)));
381 }
382
390 return materializeNativeHierarchy<T>(detail::validateAndCopyNativeHierarchy<T>(std::move(hierarchy)));
391 }
392
408 template <AltitudeValue T>
409 [[nodiscard]] static ValuedMorphologicalTree<T> createFromNativeTopology(std::span<const NodeId> parent, std::span<const NodeId> smallestNodeMap,
410 std::span<const T> nodeAltitudes, NodeId root, MorphologicalTreeSemantics semantics) {
411 return createFromNativeHierarchy<T>(NativeHierarchyView<T>{parent, smallestNodeMap, nodeAltitudes, root, std::nullopt, std::move(semantics)});
412 }
413
429 template <AltitudeValue T>
430 [[nodiscard]] static ValuedMorphologicalTree<T> createFromNativeTopology(std::span<const NodeId> parent, std::span<const NodeId> smallestNodeMap,
431 std::span<const T> nodeAltitudes, NodeId root, int rows, int columns,
432 MorphologicalTreeSemantics semantics) {
434 NativeHierarchyView<T>{parent, smallestNodeMap, nodeAltitudes, root, GridDomain2D{rows, columns}, std::move(semantics)});
435 }
436
463 template <AltitudeValue T>
464 [[nodiscard]] static ValuedMorphologicalTree<T> createFromHigraParent(std::span<const NodeId> parent, std::span<const T> nodeAltitudes, int rows,
465 int columns, MorphologicalTreeKind kind,
466 std::optional<RegularGridAdjacency2D> adjacency = std::nullopt) {
467 auto imported = detail::adaptHigraHierarchy<T>(parent, nodeAltitudes, rows, columns, kind, std::move(adjacency));
468 return materializeNativeHierarchy<T>(std::move(imported.hierarchy), imported.internalNodeOffset, MaterializedVersionPolicy::PreserveLinkedConstruction);
469 }
470};
471
472} // namespace mmcfilters
#define MMCFILTERS_CONTRACT_REQUIRE(condition,...)
Evaluates a caller precondition and its failure action only in checked builds.
Definition Contract.hpp:53
#define MMCFILTERS_CONTRACT_CHECKED_ONLY(...)
Executes validation statements only when defensive checks are enabled.
Definition Contract.hpp:67
std::shared_ptr< ImageUInt8 > ImageUInt8Ptr
Shared pointer to an 8-bit unsigned image.
Definition Image.hpp:276
MorphologicalTreeSemantics makeMorphologicalTreeSemantics(MorphologicalTreeKind kind, MorphologicalTreeConstructionContext constructionContext=NoConstructionContext{})
Constructs semantics using the conventional altitude order of kind.
MorphologicalTreeKind
Declared construction family of one morphological tree.
Normative values used by self-dual residual-tree evolution.
Builder dedicated to the saturated self-dual residual tree.
Builder dedicated to the unrestricted self-dual residual tree.
Public construction facade for all high-level morphological trees.
static ValuedMorphologicalTree< T > createUnrestrictedResidualTree(ImagePtr< T > img, RegularGridAdjacency2D adjacency, sdrt::UnrestrictedResidualTreeOptions options={})
Builds the unrestricted residual tree.
static ValuedMorphologicalTree< T > createSaturatedResidualTree(ImagePtr< T > img, PixelId infinityPixel, double radius=1.5, sdrt::SaturatedResidualTreeOptions options={})
Builds the saturated residual tree with a radius adjacency.
static ValuedMorphologicalTree< T > createMaxTree(ImagePtr< T > img, double radius=1.5)
Builds a typed valuedTree max-tree from an image.
static ValuedMorphologicalTree< T > createUnrestrictedResidualTree(ImagePtr< T > img, double radius=1.5, sdrt::UnrestrictedResidualTreeOptions options={})
Builds the unrestricted residual tree with a radius adjacency.
static ValuedMorphologicalTree< T > createFromHigraParent(std::span< const NodeId > parent, std::span< const T > nodeAltitudes, int rows, int columns, MorphologicalTreeKind kind, std::optional< RegularGridAdjacency2D > adjacency=std::nullopt)
Imports a static Higra parent/altitude hierarchy.
static ValuedMorphologicalTree< T > createFromNativeHierarchy(NativeHierarchyView< T > hierarchy)
Materializes the common output contract of a native producer.
static ValuedMorphologicalTree< T > createSaturatedResidualTree(ImagePtr< T > img, RegularGridAdjacency2D adjacency, PixelId infinityPixel, sdrt::SaturatedResidualTreeOptions options={})
Builds the saturated residual tree.
static ValuedMorphologicalTree< Altitude > createTreeOfShapes(ImageUInt8Ptr img, TopographicConvention convention={})
Builds a valued tree of shapes from an 8-bit image.
static ValuedMorphologicalTree< T > createFromNativeTopology(std::span< const NodeId > parent, std::span< const NodeId > smallestNodeMap, std::span< const T > nodeAltitudes, NodeId root, int rows, int columns, MorphologicalTreeSemantics semantics)
Imports a native hierarchy with a regular 2D pixel layout.
static ValuedMorphologicalTree< T > createFromNativeTopology(std::span< const NodeId > parent, std::span< const NodeId > smallestNodeMap, std::span< const T > nodeAltitudes, NodeId root, MorphologicalTreeSemantics semantics)
Imports a valued connected-subset tree from native buffers.
static ValuedMorphologicalTree< T > createMinTree(ImagePtr< T > img, RegularGridAdjacency2D adjacency)
Builds a min-tree with an explicit regular-grid 2D adjacency.
static ValuedMorphologicalTree< T > createMinTree(ImagePtr< T > img, double radius=1.5)
Builds a typed valuedTree min-tree from an image.
static ValuedMorphologicalTree< T > createMaxTree(ImagePtr< T > img, RegularGridAdjacency2D adjacency)
Builds a max-tree with an explicit regular-grid 2D adjacency.
Mutable connected-subset tree on a finite pixel domain.
Immutable regular-grid 2D adjacency with allocation-free traversal.
int getNumColumns() const noexcept
Returns the number of columns in the attached grid domain.
int getNumRows() const noexcept
Returns the number of rows in the attached grid domain.
NeighborIndexRange getNeighborIndices(int row, int column) const
Returns valid neighbouring grid indices excluding the origin.
static void validateFiniteImageAltitudes(const ImagePtr< T > &image, const char *context)
Rejects non-finite floating-point pixels before using an image as altitude source.
Builds trees of shapes (ToS) using a union-find construction.
Builds the saturated residual tree from synchronized component trees.
Builds the unrestricted residual tree from synchronized component trees.
Ordered pair of complementary minimum and maximum adjacencies.
Selects the optimized complementary-grid immersion.
Owning result for one computed scalar attribute layout and buffer.
Shape metadata optionally attached to the pixel domain.
Immutable scientific interpretation attached to one morphological tree.
Records the shared adjacency and infinity pixel of a saturated residual construction.
Records one adjacency shared by both construction polarities.
Complete discrete convention retained by a tree-of-shapes result.
TreeOfShapesImmersion immersion
Selected topographic immersion.
Options that affect saturated residual-tree construction.
Options that affect unrestricted residual-tree construction.