mmcfilters
Public API documentation
Loading...
Searching...
No Matches
HierarchySaliencyMapProjection.hpp
1#pragma once
2
3#include "HierarchySaliencyMap.hpp"
4#include "../../utils/Image.hpp"
5#include "../MorphologicalTree.hpp"
6
7#include <cstddef>
8#include <cstdint>
9#include <numeric>
10#include <span>
11#include <stdexcept>
12#include <string>
13#include <vector>
14
15namespace mmcfilters {
16
25 int numRows = 0;
27 int numColumns = 0;
29 double adjacencyRadius = 0.0;
31 std::vector<NodeId> sources;
33 std::vector<NodeId> targets;
34
40 [[nodiscard]] std::size_t size() const noexcept { return sources.size(); }
41
47 [[nodiscard]] bool empty() const noexcept { return sources.empty(); }
48};
49
53enum class EdgeToPixelReducer {
55 Max,
57 Mean
58};
59
69 int numRows = 0;
71 int numColumns = 0;
73 double adjacencyRadius = 0.0;
75 std::vector<NodeId> sources;
77 std::vector<NodeId> targets;
79 std::vector<NodeId> nodes;
80
86 [[nodiscard]] std::size_t size() const noexcept { return nodes.size(); }
87
93 [[nodiscard]] bool empty() const noexcept { return nodes.empty(); }
94};
95
108 int numRows = 0;
110 int numColumns = 0;
114 double adjacencyRadius = 0.0;
116 std::vector<std::size_t> offsets;
118 std::vector<NodeId> sources;
120 std::vector<NodeId> targets;
121
127 [[nodiscard]] std::size_t size() const noexcept { return sources.size(); }
128
134 [[nodiscard]] bool empty() const noexcept { return sources.empty(); }
135
142 [[nodiscard]] std::size_t nodeBegin(NodeId nodeId) const { return offsets.at(static_cast<std::size_t>(nodeId)); }
143
150 [[nodiscard]] std::size_t nodeEnd(NodeId nodeId) const { return offsets.at(static_cast<std::size_t>(nodeId) + 1); }
151
158 [[nodiscard]] std::size_t nodeSize(NodeId nodeId) const { return nodeEnd(nodeId) - nodeBegin(nodeId); }
159};
160
169 private:
176 template <class Value> static void validateEdgeMap(const EdgeSaliencyMap<Value>& edgeMap, const char* context) {
177 if (edgeMap.sources.size() != edgeMap.targets.size() || edgeMap.sources.size() != edgeMap.values.size()) {
178 throw std::invalid_argument(std::string(context) + " requires sources, targets, and values to have the same length.");
179 }
180 if (edgeMap.numRows <= 0 || edgeMap.numColumns <= 0) {
181 throw std::invalid_argument(std::string(context) + " requires a non-empty image domain.");
182 }
183 }
184
191 static void validateIncrementalContours(const IncrementalNodeContourMap& contours, const char* context) {
192 if (contours.numRows <= 0 || contours.numColumns <= 0) {
193 throw std::invalid_argument(std::string(context) + " requires a non-empty image domain.");
194 }
195 if (contours.numNodeSlots < 0) {
196 throw std::invalid_argument(std::string(context) + " requires a non-negative node-slot count.");
197 }
198 if (contours.sources.size() != contours.targets.size()) {
199 throw std::invalid_argument(std::string(context) + " requires sources and targets to have the same length.");
200 }
201 if (contours.offsets.size() != static_cast<std::size_t>(contours.numNodeSlots) + 1) {
202 throw std::invalid_argument(std::string(context) + " requires offsets to have numNodeSlots + 1 entries.");
203 }
204 if (contours.offsets.empty() || contours.offsets.front() != 0 || contours.offsets.back() != contours.sources.size()) {
205 throw std::invalid_argument(std::string(context) + " requires offsets to span exactly the contour edge arrays.");
206 }
207 for (std::size_t i = 1; i < contours.offsets.size(); ++i) {
208 if (contours.offsets[i] < contours.offsets[i - 1] || contours.offsets[i] > contours.sources.size()) {
209 throw std::invalid_argument(std::string(context) + " requires monotonically increasing offsets inside the edge-array bounds.");
210 }
211 }
212 }
213
220 template <class Value> static void validateEdgeEndpoints(const EdgeSaliencyMap<Value>& edgeMap, const char* context) {
221 const auto pixelCount = static_cast<std::size_t>(edgeMap.numRows) * static_cast<std::size_t>(edgeMap.numColumns);
222 for (std::size_t i = 0; i < edgeMap.size(); ++i) {
223 const NodeId source = edgeMap.sources[i];
224 const NodeId target = edgeMap.targets[i];
225 if (source < 0 || static_cast<std::size_t>(source) >= pixelCount || target < 0 || static_cast<std::size_t>(target) >= pixelCount) {
226 throw std::invalid_argument(std::string(context) + " requires sources and targets inside the image domain.");
227 }
228 }
229 }
230
238 static void validateTreeAndAdjacency(const MorphologicalTree& tree, const RegularGridAdjacency2D& adjacency, const char* context) {
239 detail::requireCommittedRootedHierarchy(tree, context);
240 if (tree.numRows() <= 0 || tree.numColumns() <= 0 || tree.numPixels() <= 0) {
241 throw std::invalid_argument(std::string(context) + " requires a non-empty image/pixel domain.");
242 }
243 if (adjacency.getNumRows() != tree.numRows() || adjacency.getNumColumns() != tree.numColumns()) {
244 throw std::invalid_argument(std::string(context) + " adjacency domain must match the tree image domain.");
245 }
246 }
247
255 static IncrementalNodeContourMap groupNodeContourEdges(NodeContourEdgeMap&& flat, int numNodeSlots) {
257 contours.numRows = flat.numRows;
258 contours.numColumns = flat.numColumns;
259 contours.numNodeSlots = numNodeSlots;
260 contours.adjacencyRadius = flat.adjacencyRadius;
261 contours.offsets.assign(static_cast<std::size_t>(contours.numNodeSlots) + 1, 0);
262
263 for (NodeId nodeId : flat.nodes) {
264 ++contours.offsets[static_cast<std::size_t>(nodeId) + 1];
265 }
266 std::partial_sum(contours.offsets.begin(), contours.offsets.end(), contours.offsets.begin());
267
268 contours.sources.resize(flat.size());
269 contours.targets.resize(flat.size());
270 std::vector<std::size_t> cursor = contours.offsets;
271 for (std::size_t i = 0; i < flat.size(); ++i) {
272 const NodeId nodeId = flat.nodes[i];
273 const std::size_t dst = cursor[static_cast<std::size_t>(nodeId)]++;
274 contours.sources[dst] = flat.sources[i];
275 contours.targets[dst] = flat.targets[i];
276 }
277
278 return contours;
279 }
280
281 public:
295 template <class Value>
296 [[nodiscard]] static ImagePtr<double> edgeMapToPixelImage(const EdgeSaliencyMap<Value>& edgeMap, EdgeToPixelReducer reducer = EdgeToPixelReducer::Max) {
297 constexpr const char* context = "HierarchySaliencyMapProjection::edgeMapToPixelImage";
298 validateEdgeMap(edgeMap, context);
299 validateEdgeEndpoints(edgeMap, context);
300
301 ImagePtr<double> image = Image<double>::create(edgeMap.numRows, edgeMap.numColumns, 0.0);
302 double* pixels = image->rawData();
303 const auto pixelCount = static_cast<std::size_t>(edgeMap.numRows) * static_cast<std::size_t>(edgeMap.numColumns);
304
305 switch (reducer) {
306 case EdgeToPixelReducer::Max: {
307 std::vector<uint8_t> initialized(pixelCount, false);
308 for (std::size_t i = 0; i < edgeMap.size(); ++i) {
309 const double value = static_cast<double>(edgeMap.values[i]);
310 const auto source = static_cast<std::size_t>(edgeMap.sources[i]);
311 const auto target = static_cast<std::size_t>(edgeMap.targets[i]);
312 if (!initialized[source] || pixels[source] < value) {
313 pixels[source] = value;
314 initialized[source] = true;
315 }
316 if (!initialized[target] || pixels[target] < value) {
317 pixels[target] = value;
318 initialized[target] = true;
319 }
320 }
321 return image;
322 }
323 case EdgeToPixelReducer::Mean: {
324 std::vector<std::size_t> count(pixelCount, 0);
325 for (std::size_t i = 0; i < edgeMap.size(); ++i) {
326 const double value = static_cast<double>(edgeMap.values[i]);
327 const auto source = static_cast<std::size_t>(edgeMap.sources[i]);
328 const auto target = static_cast<std::size_t>(edgeMap.targets[i]);
329 pixels[source] += value;
330 pixels[target] += value;
331 ++count[source];
332 ++count[target];
333 }
334 for (std::size_t i = 0; i < pixelCount; ++i) {
335 if (count[i] != 0) {
336 pixels[i] /= static_cast<double>(count[i]);
337 }
338 }
339 return image;
340 }
341 default:
342 throw std::invalid_argument(std::string(context) + " received an unknown edge-to-pixel reducer.");
343 }
344 }
345
357 template <class Value, class Threshold> [[nodiscard]] static EdgeContourMap thresholdCut(const EdgeSaliencyMap<Value>& edgeMap, Threshold threshold) {
358 constexpr const char* context = "HierarchySaliencyMapProjection::thresholdCut";
359 validateEdgeMap(edgeMap, context);
360
361 EdgeContourMap contours;
362 contours.numRows = edgeMap.numRows;
363 contours.numColumns = edgeMap.numColumns;
364 contours.adjacencyRadius = edgeMap.adjacencyRadius;
365 contours.sources.reserve(edgeMap.size());
366 contours.targets.reserve(edgeMap.size());
367
368 for (std::size_t i = 0; i < edgeMap.size(); ++i) {
369 if (edgeMap.values[i] >= threshold) {
370 contours.sources.push_back(edgeMap.sources[i]);
371 contours.targets.push_back(edgeMap.targets[i]);
372 }
373 }
374 return contours;
375 }
376
389 constexpr const char* context = "HierarchySaliencyMapProjection::nodeContourEdges";
390 validateTreeAndAdjacency(tree, adjacency, context);
391
392 NodeContourEdgeMap contours;
393 contours.numRows = tree.numRows();
394 contours.numColumns = tree.numColumns();
395 contours.adjacencyRadius = adjacency.getRadius();
396
397 const int numPixels = tree.numPixels();
398 for (NodeId source = 0; source < numPixels; ++source) {
399 const NodeId sourceSmallestNode = detail::TreeKernelAccess::smallestNodeMap(tree, source);
400
401 for (int target : detail::CommittedGridAccess::forwardNeighbors(adjacency, source)) {
402 const NodeId targetSmallestNode = detail::TreeKernelAccess::smallestNodeMap(tree, target);
404 continue;
405 }
406
407 const NodeId lca = detail::TreeKernelAccess::lowestCommonAncestor(tree, sourceSmallestNode, targetSmallestNode);
408
409 contours.sources.push_back(source);
410 contours.targets.push_back(target);
411 contours.nodes.push_back(lca);
412 }
413 }
414 return contours;
415 }
416
424 const RegularGridAdjacency2D* adjacency = tree.constructionAdjacency();
425 if (adjacency == nullptr) {
426 throw std::invalid_argument(
427 "HierarchySaliencyMapProjection::nodeContourEdges requires an attached adjacency relation; pass an explicit adjacency relation instead.");
428 }
429 return nodeContourEdges(tree, *adjacency);
430 }
431
439 template <AltitudeValue T>
441 return nodeContourEdges(tree.topology(), adjacency);
442 }
443
450 template <AltitudeValue T> [[nodiscard]] static NodeContourEdgeMap nodeContourEdges(const ValuedMorphologicalTree<T>& tree) {
451 return nodeContourEdges(tree.topology());
452 }
453
466 return groupNodeContourEdges(nodeContourEdges(tree, adjacency), tree.numInternalNodeSlots());
467 }
468
476 return groupNodeContourEdges(nodeContourEdges(tree), tree.numInternalNodeSlots());
477 }
478
486 template <AltitudeValue T>
488 const RegularGridAdjacency2D& adjacency) {
489 return computeIncrementalNodeContours(tree.topology(), adjacency);
490 }
491
499 return computeIncrementalNodeContours(tree.topology());
500 }
501
515 template <class Value>
517 constexpr const char* context = "HierarchySaliencyMapProjection::projectNodeValuation";
518 validateIncrementalContours(contours, context);
519 if (nodeValuation.size() != static_cast<std::size_t>(contours.numNodeSlots)) {
520 throw std::invalid_argument(std::string(context) + " requires one valuation value per dense node slot.");
521 }
522
524 edgeMap.numRows = contours.numRows;
525 edgeMap.numColumns = contours.numColumns;
526 edgeMap.adjacencyRadius = contours.adjacencyRadius;
527 edgeMap.sources.reserve(contours.size());
528 edgeMap.targets.reserve(contours.size());
529 edgeMap.values.reserve(contours.size());
530
531 for (NodeId nodeId = 0; nodeId < contours.numNodeSlots; ++nodeId) {
532 const Value value = nodeValuation[static_cast<std::size_t>(nodeId)];
533 for (std::size_t i = contours.offsets[static_cast<std::size_t>(nodeId)]; i < contours.offsets[static_cast<std::size_t>(nodeId) + 1]; ++i) {
534 edgeMap.sources.push_back(contours.sources[i]);
535 edgeMap.targets.push_back(contours.targets[i]);
536 edgeMap.values.push_back(value);
537 }
538 }
539 return edgeMap;
540 }
541
556 template <class Value, class Threshold>
559 constexpr const char* context = "HierarchySaliencyMapProjection::thresholdByNodeValuation";
560 validateIncrementalContours(contours, context);
561 if (nodeValuation.size() != static_cast<std::size_t>(contours.numNodeSlots)) {
562 throw std::invalid_argument(std::string(context) + " requires one valuation value per dense node slot.");
563 }
564
566 cut.numRows = contours.numRows;
567 cut.numColumns = contours.numColumns;
568 cut.adjacencyRadius = contours.adjacencyRadius;
569 cut.sources.reserve(contours.size());
570 cut.targets.reserve(contours.size());
571
572 for (NodeId nodeId = 0; nodeId < contours.numNodeSlots; ++nodeId) {
573 if (nodeValuation[static_cast<std::size_t>(nodeId)] >= threshold) {
574 for (std::size_t i = contours.offsets[static_cast<std::size_t>(nodeId)]; i < contours.offsets[static_cast<std::size_t>(nodeId) + 1]; ++i) {
575 cut.sources.push_back(contours.sources[i]);
576 cut.targets.push_back(contours.targets[i]);
577 }
578 }
579 }
580 return cut;
581 }
582};
583
584} // namespace mmcfilters
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
Derived projections and contour materializations of saliency maps.
static ImagePtr< double > edgeMapToPixelImage(const EdgeSaliencyMap< Value > &edgeMap, EdgeToPixelReducer reducer=EdgeToPixelReducer::Max)
Rasterizes an edge-indexed map into a pixel image for display.
static IncrementalNodeContourMap computeIncrementalNodeContours(const ValuedMorphologicalTree< T > &tree, const RegularGridAdjacency2D &adjacency)
Computes valued-tree incremental contours with explicit adjacency.
static IncrementalNodeContourMap computeIncrementalNodeContours(const MorphologicalTree &tree)
Computes per-node incremental contour edges using stored adjacency.
static EdgeContourMap thresholdCut(const EdgeSaliencyMap< Value > &edgeMap, Threshold threshold)
Thresholds an edge saliency map into an edge contour set.
static EdgeContourMap thresholdByNodeValuation(const IncrementalNodeContourMap &contours, std::span< const Value > nodeValuation, Threshold threshold)
Thresholds incremental contour edges by dense node valuation.
static IncrementalNodeContourMap computeIncrementalNodeContours(const MorphologicalTree &tree, const RegularGridAdjacency2D &adjacency)
Computes per-node incremental contour edges with explicit adjacency.
static EdgeSaliencyMap< Value > projectNodeValuation(const IncrementalNodeContourMap &contours, std::span< const Value > nodeValuation)
Projects dense node valuation onto transition contour edges.
static NodeContourEdgeMap nodeContourEdges(const ValuedMorphologicalTree< T > &tree)
Projects valued-tree stored-adjacency edges onto hierarchy nodes.
static IncrementalNodeContourMap computeIncrementalNodeContours(const ValuedMorphologicalTree< T > &tree)
Computes valued-tree incremental contours using stored adjacency.
static NodeContourEdgeMap nodeContourEdges(const ValuedMorphologicalTree< T > &tree, const RegularGridAdjacency2D &adjacency)
Projects valued-tree adjacency edges onto their hierarchy node.
static NodeContourEdgeMap nodeContourEdges(const MorphologicalTree &tree, const RegularGridAdjacency2D &adjacency)
Projects transition adjacency edges onto their hierarchy node.
static NodeContourEdgeMap nodeContourEdges(const MorphologicalTree &tree)
Projects stored-adjacency transition edges onto hierarchy nodes.
static Ptr create(int rows, int columns)
Creates an owned image with uninitialised pixel values.
Definition Image.hpp:89
Mutable connected-subset tree on a finite pixel domain.
int numRows() const
Returns the number of rows in the regular 2D pixel domain.
int numInternalNodeSlots() const
Returns the size of the dense internal-node id domain.
int numPixels() const
Returns the cardinality of the pixel domain.
const RegularGridAdjacency2D * constructionAdjacency() const noexcept
Returns the adjacency retained by a shared construction context.
int numColumns() const
Returns the number of columns in the regular 2D pixel domain.
Immutable regular-grid 2D adjacency with allocation-free traversal.
double getRadius() const noexcept
Returns the configured or bounding Euclidean radius.
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.
Owning result for one computed scalar attribute layout and buffer.
std::vector< Real > & values() noexcept
Returns the mutable flat attribute buffer.
Edge-indexed contour set induced by thresholding a saliency map.
std::vector< NodeId > sources
Source pixel identifier of each selected edge.
int numColumns
Number of columns in the image grid.
int numRows
Number of rows in the image grid.
std::vector< NodeId > targets
Target pixel identifier of each selected edge.
std::size_t size() const noexcept
Returns the number of selected edges.
bool empty() const noexcept
Returns whether no contour edge is selected.
double adjacencyRadius
Radius of the adjacency used to enumerate the edges.
Per-node incremental contour edges induced by a hierarchy.
std::vector< NodeId > sources
Source pixel ids grouped by boundary-owning node.
int numNodeSlots
Number of slots in the dense node-id domain.
std::size_t size() const noexcept
Returns the total number of stored contour edges.
std::vector< NodeId > targets
Target pixel ids parallel to sources.
std::size_t nodeEnd(NodeId nodeId) const
Returns the exclusive edge offset for nodeId.
std::vector< std::size_t > offsets
CSR-style boundaries of the edge slice owned by each node.
std::size_t nodeBegin(NodeId nodeId) const
Returns the inclusive edge offset for nodeId.
double adjacencyRadius
Radius of the adjacency used to enumerate the edges.
std::size_t nodeSize(NodeId nodeId) const
Returns the number of contour edges owned by nodeId.
bool empty() const noexcept
Returns whether no contour edge is stored.
int numColumns
Number of columns in the image grid.
Edge-indexed contour set projected onto hierarchy nodes.
double adjacencyRadius
Radius of the adjacency used to enumerate the edges.
std::vector< NodeId > sources
Source pixel identifier of each contour edge.
std::size_t size() const noexcept
Returns the number of projected contour edges.
int numColumns
Number of columns in the image grid.
std::vector< NodeId > targets
Target pixel identifier of each contour edge.
std::vector< NodeId > nodes
Boundary-owning hierarchy node parallel to each edge.
bool empty() const noexcept
Returns whether the projection has no contour edges.
int numRows
Number of rows in the image grid.