3#include "AttributeComputerDomain.hpp"
4#include "AttributeComputerFamily.hpp"
5#include "../detail/AttributeKernelSupport.hpp"
6#include "detail/distance_transform/DistanceFieldMoments.hpp"
7#include "detail/distance_transform/DistanceFieldHistogram.hpp"
8#include "detail/distance_transform/DistanceWeightedSpatialMoments.hpp"
9#include "detail/distance_transform/MorphologicalTreeDistanceTransform.hpp"
10#include "../../trees/MorphologicalTree.hpp"
11#include "../../utils/Common.hpp"
22namespace mmcfilters::attributes::computers::detail {
25struct DistanceTransformAttributeRequest {
27 bool maxSquaredDist =
false;
28 bool squaredSum =
false;
29 bool squaredMean =
false;
31 bool squaredVariance =
false;
32 bool centerRow =
false;
33 bool centerColumn =
false;
34 bool plateauArea =
false;
35 bool plateauCentroidRow =
false;
36 bool plateauCentroidColumn =
false;
37 bool distanceSum =
false;
38 bool distanceMean =
false;
39 bool distanceVariance =
false;
46 bool positiveArea =
false;
47 bool levelCount =
false;
48 bool weightedCentroidRow =
false;
49 bool weightedCentroidColumn =
false;
50 bool weightedMu20 =
false;
51 bool weightedMu02 =
false;
52 bool weightedMu11 =
false;
53 bool weightedAxisOrientation =
false;
54 bool weightedEccentricity =
false;
57 [[nodiscard]]
bool any() const noexcept {
return maximum() || moments() || profile() || spatial() || geometry(); }
60 [[nodiscard]]
bool maximum() const noexcept {
return maxDist || maxSquaredDist; }
63 [[nodiscard]]
bool moments() const noexcept {
64 return squaredSum || squaredMean || rms || squaredVariance || distanceSum || distanceMean || distanceVariance;
68 [[nodiscard]]
bool profile() const noexcept {
return median ||
mode || q25 || q75 || q90 || entropy || positiveArea || levelCount; }
71 [[nodiscard]]
bool spatial() const noexcept {
72 return weightedCentroidRow || weightedCentroidColumn || weightedMu20 || weightedMu02 || weightedMu11 || weightedAxisOrientation || weightedEccentricity;
76 [[nodiscard]]
bool localization() const noexcept {
return centerRow || centerColumn; }
79 [[nodiscard]]
bool plateau() const noexcept {
return plateauArea || plateauCentroidRow || plateauCentroidColumn; }
82 [[nodiscard]]
bool geometry() const noexcept {
return localization() || plateau(); }
83 [[nodiscard]]
bool needsCoordinates() const noexcept {
return geometry() || spatial(); }
86 [[nodiscard]]
static DistanceTransformAttributeRequest from(std::span<const Attribute> requestedAttributes) {
87 const auto contains = [requestedAttributes](Attribute attribute) {
88 return std::find(requestedAttributes.begin(), requestedAttributes.end(), attribute) != requestedAttributes.end();
90 return {.maxDist = contains(MaxDistExact),
91 .maxSquaredDist = contains(MaxSquaredDistExact),
92 .squaredSum = contains(DistSquaredSumExact),
93 .squaredMean = contains(DistSquaredMeanExact),
94 .rms = contains(DistRmsExact),
95 .squaredVariance = contains(DistSquaredVarianceExact),
96 .centerRow = contains(MaxDistCenterRowExact),
97 .centerColumn = contains(MaxDistCenterColumnExact),
98 .plateauArea = contains(MaxDistPlateauAreaExact),
99 .plateauCentroidRow = contains(MaxDistPlateauCentroidRowExact),
100 .plateauCentroidColumn = contains(MaxDistPlateauCentroidColumnExact),
101 .distanceSum = contains(DistSumExact),
102 .distanceMean = contains(DistMeanExact),
103 .distanceVariance = contains(DistVarianceExact),
104 .median = contains(DistMedianExact),
105 .mode = contains(DistModeExact),
106 .q25 = contains(DistQ25Exact),
107 .q75 = contains(DistQ75Exact),
108 .q90 = contains(DistQ90Exact),
109 .entropy = contains(DistEntropyExact),
110 .positiveArea = contains(DistPositiveAreaExact),
111 .levelCount = contains(DistLevelCountExact),
112 .weightedCentroidRow = contains(DistWeightedCentroidRowExact),
113 .weightedCentroidColumn = contains(DistWeightedCentroidColumnExact),
114 .weightedMu20 = contains(DistWeightedCentralMoment20Exact),
115 .weightedMu02 = contains(DistWeightedCentralMoment02Exact),
116 .weightedMu11 = contains(DistWeightedCentralMoment11Exact),
117 .weightedAxisOrientation = contains(DistWeightedAxisOrientationExact),
118 .weightedEccentricity = contains(DistWeightedEccentricityExact)};
129inline void requireMaxDistExactCapabilities(
const MorphologicalTree& tree) {
130 if (!tree.hasGridDomain2D()) {
131 throw std::invalid_argument(
"Exact distance-field attributes require a regular 2D pixel domain.");
136template <std::
floating_po
int Real>
class MaxSquaredDistanceAttributeReducer {
138 MaxSquaredDistanceAttributeReducer(std::span<Real> buffer,
const AttributeNames& attrNames, DistanceTransformAttributeRequest request)
139 : buffer_(buffer), attrNames_(attrNames), request_(request) {}
141 void beginNode(NodeId) { currentMaximum_ = 0; }
143 void consumeSample(PixelId, distance_transform::SquaredDistance squaredDistance) { currentMaximum_ = std::max(currentMaximum_, squaredDistance); }
145 void endNode(NodeId node) {
146 if (request_.maxDist) {
147 materialize(node, MaxDistExact, std::sqrt(
static_cast<long double>(currentMaximum_)));
149 if (request_.maxSquaredDist) {
150 materialize(node, MaxSquaredDistExact, currentMaximum_);
155 template <
class Value>
void materialize(NodeId node, Attribute attribute, Value value) {
156 buffer_[attrNames_.linearIndex(node, attribute)] =
static_cast<Real
>(value);
159 std::span<Real> buffer_;
160 const AttributeNames& attrNames_;
161 DistanceTransformAttributeRequest request_;
162 distance_transform::SquaredDistance currentMaximum_ = 0;
166template <std::
floating_po
int Real>
class DistanceFieldExtremumAttributeReducer {
168 DistanceFieldExtremumAttributeReducer(std::span<Real> buffer,
const AttributeNames& attrNames, DistanceTransformAttributeRequest request,
int numColumns)
169 : buffer_(buffer), attrNames_(attrNames), request_(request), numColumns_(numColumns) {}
171 void beginNode(NodeId) { extremum_ = {}; }
173 void consumeSample(PixelId pixel, distance_transform::SquaredDistance squaredDistance) {
174 distance_transform::updateDistanceFieldExtremum(extremum_, pixel, squaredDistance);
177 void endNode(NodeId node) {
179 if (request_.maxDist) {
180 materialize(node, MaxDistExact, std::sqrt(
static_cast<long double>(extremum_.squaredDistance)));
182 if (request_.maxSquaredDist) {
183 materialize(node, MaxSquaredDistExact, extremum_.squaredDistance);
185 if (request_.centerRow) {
186 materialize(node, MaxDistCenterRowExact, extremum_.pixel / numColumns_);
188 if (request_.centerColumn) {
189 materialize(node, MaxDistCenterColumnExact, extremum_.pixel % numColumns_);
194 void requireLocation()
const {
195 if (extremum_.pixel == InvalidPixel) {
196 throw std::logic_error(
"Exact distance-field localization produced an empty live-node support.");
200 template <
class Value>
void materialize(NodeId node, Attribute attribute, Value value) {
201 buffer_[attrNames_.linearIndex(node, attribute)] =
static_cast<Real
>(value);
204 std::span<Real> buffer_;
205 const AttributeNames& attrNames_;
206 DistanceTransformAttributeRequest request_;
208 distance_transform::DistanceFieldExtremum extremum_;
212template <std::
floating_po
int Real>
class DistanceFieldPlateauAttributeReducer {
214 DistanceFieldPlateauAttributeReducer(std::span<Real> buffer,
const AttributeNames& attrNames, DistanceTransformAttributeRequest request,
int numColumns)
215 : buffer_(buffer), attrNames_(attrNames), request_(request), numColumns_(numColumns) {}
217 void beginNode(NodeId) { plateau_ = {}; }
219 void consumeSample(PixelId pixel, distance_transform::SquaredDistance squaredDistance) {
220 distance_transform::updateDistanceFieldMaximumPlateau(plateau_, pixel, squaredDistance, numColumns_);
223 void endNode(NodeId node) {
225 if (request_.maxDist) {
226 materialize(node, MaxDistExact, std::sqrt(
static_cast<long double>(plateau_.squaredDistance)));
228 if (request_.maxSquaredDist) {
229 materialize(node, MaxSquaredDistExact, plateau_.squaredDistance);
231 if (request_.centerRow) {
232 materialize(node, MaxDistCenterRowExact, plateau_.pixel / numColumns_);
234 if (request_.centerColumn) {
235 materialize(node, MaxDistCenterColumnExact, plateau_.pixel % numColumns_);
237 if (request_.plateauArea) {
238 materialize(node, MaxDistPlateauAreaExact, plateau_.count);
240 if (request_.plateauCentroidRow) {
241 materialize(node, MaxDistPlateauCentroidRowExact, plateau_.centroidRow());
243 if (request_.plateauCentroidColumn) {
244 materialize(node, MaxDistPlateauCentroidColumnExact, plateau_.centroidColumn());
249 void requirePlateau()
const {
250 if (plateau_.pixel == InvalidPixel || plateau_.count == 0) {
251 throw std::logic_error(
"Exact distance-field plateau reduction produced an empty live-node support.");
255 template <
class Value>
void materialize(NodeId node, Attribute attribute, Value value) {
256 buffer_[attrNames_.linearIndex(node, attribute)] =
static_cast<Real
>(value);
259 std::span<Real> buffer_;
260 const AttributeNames& attrNames_;
261 DistanceTransformAttributeRequest request_;
263 distance_transform::DistanceFieldMaximumPlateau plateau_;
267template <std::
floating_po
int Real,
bool TrackLocation,
bool TrackPlateau>
class DistanceFieldSummaryAttributeReducer {
269 DistanceFieldSummaryAttributeReducer(std::span<Real> buffer,
const AttributeNames& attrNames, DistanceTransformAttributeRequest request,
int numColumns)
270 : buffer_(buffer), attrNames_(attrNames), request_(request), numColumns_(numColumns) {}
272 void beginNode(NodeId) {
273 if (request_.moments()) {
276 if (request_.profile()) {
279 if (request_.spatial()) {
280 spatialMoments_.clear();
283 if constexpr (TrackPlateau) {
285 }
else if constexpr (TrackLocation) {
290 void consumeSample(PixelId pixel, distance_transform::SquaredDistance squaredDistance) {
291 if (request_.moments()) {
292 moments_.add<
false>(squaredDistance);
294 if (request_.profile()) {
295 histogram_.add<
false>(squaredDistance);
297 if (request_.spatial()) {
298 spatialMoments_.add<
false>(pixel, squaredDistance, numColumns_);
300 maximum_ = std::max(maximum_, squaredDistance);
301 if constexpr (TrackPlateau) {
302 distance_transform::updateDistanceFieldMaximumPlateau(plateau_, pixel, squaredDistance, numColumns_);
303 }
else if constexpr (TrackLocation) {
304 distance_transform::updateDistanceFieldExtremum(extremum_, pixel, squaredDistance);
308 void endNode(NodeId node) {
309 const std::uint64_t sampleCount = request_.moments() ? moments_.count() : (request_.profile() ? histogram_.count() : spatialMoments_.count());
310 if (sampleCount == 0) {
311 throw std::logic_error(
"Exact distance-field reduction produced an empty live-node support.");
313 if (request_.maxDist) {
314 materialize(node, MaxDistExact, std::sqrt(
static_cast<long double>(maximum_)));
316 if (request_.maxSquaredDist) {
317 materialize(node, MaxSquaredDistExact,
static_cast<long double>(maximum_));
319 if (request_.squaredSum) {
320 materialize(node, DistSquaredSumExact, moments_.sum());
322 if (request_.squaredMean) {
323 materialize(node, DistSquaredMeanExact, moments_.mean());
326 materialize(node, DistRmsExact, moments_.rms());
328 if (request_.squaredVariance) {
329 materialize(node, DistSquaredVarianceExact, moments_.populationVariance());
331 if (request_.distanceSum) {
332 materialize(node, DistSumExact, moments_.distanceSum());
334 if (request_.distanceMean) {
335 materialize(node, DistMeanExact, moments_.distanceMean());
337 if (request_.distanceVariance) {
338 materialize(node, DistVarianceExact, moments_.distancePopulationVariance());
340 if (request_.median) {
341 materialize(node, DistMedianExact, histogram_.quantile(0.5L));
344 materialize(node, DistModeExact, histogram_.mode());
347 materialize(node, DistQ25Exact, histogram_.quantile(0.25L));
350 materialize(node, DistQ75Exact, histogram_.quantile(0.75L));
353 materialize(node, DistQ90Exact, histogram_.quantile(0.9L));
355 if (request_.entropy) {
356 materialize(node, DistEntropyExact, histogram_.entropyBits());
358 if (request_.positiveArea) {
359 materialize(node, DistPositiveAreaExact, histogram_.positiveArea());
361 if (request_.levelCount) {
362 materialize(node, DistLevelCountExact, histogram_.levelCount());
364 if (request_.weightedCentroidRow) {
365 materialize(node, DistWeightedCentroidRowExact, spatialMoments_.centroidRow());
367 if (request_.weightedCentroidColumn) {
368 materialize(node, DistWeightedCentroidColumnExact, spatialMoments_.centroidColumn());
370 if (request_.weightedMu20) {
371 materialize(node, DistWeightedCentralMoment20Exact, spatialMoments_.centralMoment20());
373 if (request_.weightedMu02) {
374 materialize(node, DistWeightedCentralMoment02Exact, spatialMoments_.centralMoment02());
376 if (request_.weightedMu11) {
377 materialize(node, DistWeightedCentralMoment11Exact, spatialMoments_.centralMoment11());
379 if (request_.weightedAxisOrientation) {
380 materialize(node, DistWeightedAxisOrientationExact, spatialMoments_.axisOrientationDegrees());
382 if (request_.weightedEccentricity) {
383 materialize(node, DistWeightedEccentricityExact, spatialMoments_.eccentricity());
385 if constexpr (TrackPlateau) {
386 if (plateau_.pixel == InvalidPixel || plateau_.count == 0) {
387 throw std::logic_error(
"Exact distance-field summary produced an empty maximum plateau.");
389 if (request_.centerRow) {
390 materialize(node, MaxDistCenterRowExact,
static_cast<long double>(plateau_.pixel / numColumns_));
392 if (request_.centerColumn) {
393 materialize(node, MaxDistCenterColumnExact,
static_cast<long double>(plateau_.pixel % numColumns_));
395 if (request_.plateauArea) {
396 materialize(node, MaxDistPlateauAreaExact,
static_cast<long double>(plateau_.count));
398 if (request_.plateauCentroidRow) {
399 materialize(node, MaxDistPlateauCentroidRowExact, plateau_.centroidRow());
401 if (request_.plateauCentroidColumn) {
402 materialize(node, MaxDistPlateauCentroidColumnExact, plateau_.centroidColumn());
404 }
else if constexpr (TrackLocation) {
405 if (extremum_.pixel == InvalidPixel) {
406 throw std::logic_error(
"Exact distance-field summary produced an empty maximum location.");
408 if (request_.centerRow) {
409 materialize(node, MaxDistCenterRowExact,
static_cast<long double>(extremum_.pixel / numColumns_));
411 if (request_.centerColumn) {
412 materialize(node, MaxDistCenterColumnExact,
static_cast<long double>(extremum_.pixel % numColumns_));
418 void materialize(NodeId node, Attribute attribute,
long double value) { buffer_[attrNames_.linearIndex(node, attribute)] =
static_cast<Real
>(value); }
420 std::span<Real> buffer_;
421 const AttributeNames& attrNames_;
422 DistanceTransformAttributeRequest request_;
424 distance_transform::DistanceFieldMoments moments_;
425 distance_transform::DistanceFieldHistogram histogram_;
426 distance_transform::DistanceWeightedSpatialMoments spatialMoments_;
427 distance_transform::SquaredDistance maximum_ = 0;
428 distance_transform::DistanceFieldExtremum extremum_;
429 distance_transform::DistanceFieldMaximumPlateau plateau_;
435template <std::
floating_po
int Real>
436inline void computeDistanceTransformAttributes(
const AttributeComputeContext<Real>& context,
const DistanceTransformAttributeRequest& request,
int numColumns) {
437 if (!request.any()) {
441 if (request.moments() || request.profile() || request.spatial()) {
442 if (request.plateau()) {
443 DistanceFieldSummaryAttributeReducer<Real, false, true> summaryReducer(context.buffer, context.attrNames, request, numColumns);
444 distance_transform::ExactNodeDistanceFieldProvider::reduce(context.tree, summaryReducer);
445 }
else if (request.localization()) {
446 DistanceFieldSummaryAttributeReducer<Real, true, false> summaryReducer(context.buffer, context.attrNames, request, numColumns);
447 distance_transform::ExactNodeDistanceFieldProvider::reduce(context.tree, summaryReducer);
449 DistanceFieldSummaryAttributeReducer<Real, false, false> summaryReducer(context.buffer, context.attrNames, request, numColumns);
450 distance_transform::ExactNodeDistanceFieldProvider::reduce(context.tree, summaryReducer);
452 }
else if (request.plateau()) {
453 DistanceFieldPlateauAttributeReducer<Real> plateauReducer(context.buffer, context.attrNames, request, numColumns);
454 distance_transform::ExactNodeDistanceFieldProvider::reduce(context.tree, plateauReducer);
455 }
else if (request.localization()) {
456 DistanceFieldExtremumAttributeReducer<Real> extremumReducer(context.buffer, context.attrNames, request, numColumns);
457 distance_transform::ExactNodeDistanceFieldProvider::reduce(context.tree, extremumReducer);
458 }
else if (request.maximum()) {
459 MaxSquaredDistanceAttributeReducer<Real> maximumReducer(context.buffer, context.attrNames, request);
460 distance_transform::ExactNodeDistanceFieldProvider::reduce(context.tree, maximumReducer);
469namespace mmcfilters::attributes::computers {
480 static constexpr std::string_view
familyName =
"max-dist-exact";
483 static constexpr AttributeComputerFamily
family = AttributeComputerFamily::MaxDistExact;
486 static constexpr AttributeComputerDomain
domain = AttributeComputerDomain::Topology;
492 DistSquaredMeanExact,
494 DistSquaredVarianceExact,
495 MaxDistCenterRowExact,
496 MaxDistCenterColumnExact,
497 MaxDistPlateauAreaExact,
498 MaxDistPlateauCentroidRowExact,
499 MaxDistPlateauCentroidColumnExact,
509 DistPositiveAreaExact,
511 DistWeightedCentroidRowExact,
512 DistWeightedCentroidColumnExact,
513 DistWeightedCentralMoment20Exact,
514 DistWeightedCentralMoment02Exact,
515 DistWeightedCentralMoment11Exact,
516 DistWeightedAxisOrientationExact,
517 DistWeightedEccentricityExact};
524 const detail::DistanceTransformAttributeRequest
request = detail::DistanceTransformAttributeRequest::from(
context.requestedAttributes);
527 const int numColumns =
request.needsCoordinates() ?
context.tree.numColumns() : 0;
528 detail::kernel::computeDistanceTransformAttributes(
context,
request, numColumns);
534 const int numColumns =
context.tree.numColumns();
541 Real value = Real{0};
542 if (attribute == MaxDistCenterRowExact) {
543 value =
static_cast<Real
>(pixel / numColumns);
544 }
else if (attribute == MaxDistCenterColumnExact) {
545 value =
static_cast<Real
>(pixel % numColumns);
546 }
else if (attribute == MaxDistPlateauAreaExact) {
548 }
else if (attribute == MaxDistPlateauCentroidRowExact) {
549 value =
static_cast<Real
>(pixel / numColumns);
550 }
else if (attribute == MaxDistPlateauCentroidColumnExact) {
551 value =
static_cast<Real
>(pixel % numColumns);
552 }
else if (attribute == DistLevelCountExact) {
554 }
else if (attribute == DistWeightedCentroidRowExact) {
555 value =
static_cast<Real
>(pixel / numColumns);
556 }
else if (attribute == DistWeightedCentroidColumnExact) {
557 value =
static_cast<Real
>(pixel % numColumns);
558 }
else if (attribute == DistWeightedEccentricityExact) {
constexpr Mode mode
Contract policy selected when this translation unit was compiled.
#define MMCFILTERS_CONTRACT_CHECKED_ONLY(...)
Executes validation statements only when defensive checks are enabled.
Mutable connected-subset tree on a finite pixel domain.
Topology-only scalar computer for attributes derived from the node EDT.
static void computeUnitRows(const UnitAttributeComputeContext< Real > &context)
Materializes zero for one-pixel unit supports.
static void requireSupportedTreeKind(const MorphologicalTree &tree)
Validates the topology-only geometric capability contract.
static constexpr AttributeComputerDomain domain
Distance-transform attributes require topology/support, not altitudes.
static constexpr AttributeComputerFamily family
Stable family id used by the scheduler.
static constexpr std::string_view familyName
Family name used in dependency-plan diagnostics.
static void compute(const AttributeComputeContext< Real > &context)
Computes requested distance-transform projections in one pass.
static constexpr std::array< Attribute, 29 > producedAttributes
Canonical list of scalar descriptors materialized by this computer.
Owning result for one computed scalar attribute layout and buffer.