mmcfilters
Public API documentation
Loading...
Searching...
No Matches
MaxDistExactComputer.hpp
1#pragma once
2
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"
12
13#include <algorithm>
14#include <array>
15#include <cmath>
16#include <concepts>
17#include <span>
18#include <stdexcept>
19#include <string_view>
20
22namespace mmcfilters::attributes::computers::detail {
23
25struct DistanceTransformAttributeRequest {
26 bool maxDist = false;
27 bool maxSquaredDist = false;
28 bool squaredSum = false;
29 bool squaredMean = false;
30 bool rms = 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;
40 bool median = false;
41 bool mode = false;
42 bool q25 = false;
43 bool q75 = false;
44 bool q90 = false;
45 bool entropy = 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;
55
57 [[nodiscard]] bool any() const noexcept { return maximum() || moments() || profile() || spatial() || geometry(); }
58
60 [[nodiscard]] bool maximum() const noexcept { return maxDist || maxSquaredDist; }
61
63 [[nodiscard]] bool moments() const noexcept {
64 return squaredSum || squaredMean || rms || squaredVariance || distanceSum || distanceMean || distanceVariance;
65 }
66
68 [[nodiscard]] bool profile() const noexcept { return median || mode || q25 || q75 || q90 || entropy || positiveArea || levelCount; }
69
71 [[nodiscard]] bool spatial() const noexcept {
72 return weightedCentroidRow || weightedCentroidColumn || weightedMu20 || weightedMu02 || weightedMu11 || weightedAxisOrientation || weightedEccentricity;
73 }
74
76 [[nodiscard]] bool localization() const noexcept { return centerRow || centerColumn; }
77
79 [[nodiscard]] bool plateau() const noexcept { return plateauArea || plateauCentroidRow || plateauCentroidColumn; }
80
82 [[nodiscard]] bool geometry() const noexcept { return localization() || plateau(); }
83 [[nodiscard]] bool needsCoordinates() const noexcept { return geometry() || spatial(); }
84
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();
89 };
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)};
119 }
120};
121
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.");
132 }
133}
134
136template <std::floating_point Real> class MaxSquaredDistanceAttributeReducer {
137 public:
138 MaxSquaredDistanceAttributeReducer(std::span<Real> buffer, const AttributeNames& attrNames, DistanceTransformAttributeRequest request)
139 : buffer_(buffer), attrNames_(attrNames), request_(request) {}
140
141 void beginNode(NodeId) { currentMaximum_ = 0; }
142
143 void consumeSample(PixelId, distance_transform::SquaredDistance squaredDistance) { currentMaximum_ = std::max(currentMaximum_, squaredDistance); }
144
145 void endNode(NodeId node) {
146 if (request_.maxDist) {
147 materialize(node, MaxDistExact, std::sqrt(static_cast<long double>(currentMaximum_)));
148 }
149 if (request_.maxSquaredDist) {
150 materialize(node, MaxSquaredDistExact, currentMaximum_);
151 }
152 }
153
154 private:
155 template <class Value> void materialize(NodeId node, Attribute attribute, Value value) {
156 buffer_[attrNames_.linearIndex(node, attribute)] = static_cast<Real>(value);
157 }
158
159 std::span<Real> buffer_;
160 const AttributeNames& attrNames_;
161 DistanceTransformAttributeRequest request_;
162 distance_transform::SquaredDistance currentMaximum_ = 0;
163};
164
166template <std::floating_point Real> class DistanceFieldExtremumAttributeReducer {
167 public:
168 DistanceFieldExtremumAttributeReducer(std::span<Real> buffer, const AttributeNames& attrNames, DistanceTransformAttributeRequest request, int numColumns)
169 : buffer_(buffer), attrNames_(attrNames), request_(request), numColumns_(numColumns) {}
170
171 void beginNode(NodeId) { extremum_ = {}; }
172
173 void consumeSample(PixelId pixel, distance_transform::SquaredDistance squaredDistance) {
174 distance_transform::updateDistanceFieldExtremum(extremum_, pixel, squaredDistance);
175 }
176
177 void endNode(NodeId node) {
178 requireLocation();
179 if (request_.maxDist) {
180 materialize(node, MaxDistExact, std::sqrt(static_cast<long double>(extremum_.squaredDistance)));
181 }
182 if (request_.maxSquaredDist) {
183 materialize(node, MaxSquaredDistExact, extremum_.squaredDistance);
184 }
185 if (request_.centerRow) {
186 materialize(node, MaxDistCenterRowExact, extremum_.pixel / numColumns_);
187 }
188 if (request_.centerColumn) {
189 materialize(node, MaxDistCenterColumnExact, extremum_.pixel % numColumns_);
190 }
191 }
192
193 private:
194 void requireLocation() const {
195 if (extremum_.pixel == InvalidPixel) {
196 throw std::logic_error("Exact distance-field localization produced an empty live-node support.");
197 }
198 }
199
200 template <class Value> void materialize(NodeId node, Attribute attribute, Value value) {
201 buffer_[attrNames_.linearIndex(node, attribute)] = static_cast<Real>(value);
202 }
203
204 std::span<Real> buffer_;
205 const AttributeNames& attrNames_;
206 DistanceTransformAttributeRequest request_;
207 int numColumns_ = 0;
208 distance_transform::DistanceFieldExtremum extremum_;
209};
210
212template <std::floating_point Real> class DistanceFieldPlateauAttributeReducer {
213 public:
214 DistanceFieldPlateauAttributeReducer(std::span<Real> buffer, const AttributeNames& attrNames, DistanceTransformAttributeRequest request, int numColumns)
215 : buffer_(buffer), attrNames_(attrNames), request_(request), numColumns_(numColumns) {}
216
217 void beginNode(NodeId) { plateau_ = {}; }
218
219 void consumeSample(PixelId pixel, distance_transform::SquaredDistance squaredDistance) {
220 distance_transform::updateDistanceFieldMaximumPlateau(plateau_, pixel, squaredDistance, numColumns_);
221 }
222
223 void endNode(NodeId node) {
224 requirePlateau();
225 if (request_.maxDist) {
226 materialize(node, MaxDistExact, std::sqrt(static_cast<long double>(plateau_.squaredDistance)));
227 }
228 if (request_.maxSquaredDist) {
229 materialize(node, MaxSquaredDistExact, plateau_.squaredDistance);
230 }
231 if (request_.centerRow) {
232 materialize(node, MaxDistCenterRowExact, plateau_.pixel / numColumns_);
233 }
234 if (request_.centerColumn) {
235 materialize(node, MaxDistCenterColumnExact, plateau_.pixel % numColumns_);
236 }
237 if (request_.plateauArea) {
238 materialize(node, MaxDistPlateauAreaExact, plateau_.count);
239 }
240 if (request_.plateauCentroidRow) {
241 materialize(node, MaxDistPlateauCentroidRowExact, plateau_.centroidRow());
242 }
243 if (request_.plateauCentroidColumn) {
244 materialize(node, MaxDistPlateauCentroidColumnExact, plateau_.centroidColumn());
245 }
246 }
247
248 private:
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.");
252 }
253 }
254
255 template <class Value> void materialize(NodeId node, Attribute attribute, Value value) {
256 buffer_[attrNames_.linearIndex(node, attribute)] = static_cast<Real>(value);
257 }
258
259 std::span<Real> buffer_;
260 const AttributeNames& attrNames_;
261 DistanceTransformAttributeRequest request_;
262 int numColumns_ = 0;
263 distance_transform::DistanceFieldMaximumPlateau plateau_;
264};
265
267template <std::floating_point Real, bool TrackLocation, bool TrackPlateau> class DistanceFieldSummaryAttributeReducer {
268 public:
269 DistanceFieldSummaryAttributeReducer(std::span<Real> buffer, const AttributeNames& attrNames, DistanceTransformAttributeRequest request, int numColumns)
270 : buffer_(buffer), attrNames_(attrNames), request_(request), numColumns_(numColumns) {}
271
272 void beginNode(NodeId) {
273 if (request_.moments()) {
274 moments_.clear();
275 }
276 if (request_.profile()) {
277 histogram_.clear();
278 }
279 if (request_.spatial()) {
280 spatialMoments_.clear();
281 }
282 maximum_ = 0;
283 if constexpr (TrackPlateau) {
284 plateau_ = {};
285 } else if constexpr (TrackLocation) {
286 extremum_ = {};
287 }
288 }
289
290 void consumeSample(PixelId pixel, distance_transform::SquaredDistance squaredDistance) {
291 if (request_.moments()) {
292 moments_.add<false>(squaredDistance);
293 }
294 if (request_.profile()) {
295 histogram_.add<false>(squaredDistance);
296 }
297 if (request_.spatial()) {
298 spatialMoments_.add<false>(pixel, squaredDistance, numColumns_);
299 }
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);
305 }
306 }
307
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.");
312 }
313 if (request_.maxDist) {
314 materialize(node, MaxDistExact, std::sqrt(static_cast<long double>(maximum_)));
315 }
316 if (request_.maxSquaredDist) {
317 materialize(node, MaxSquaredDistExact, static_cast<long double>(maximum_));
318 }
319 if (request_.squaredSum) {
320 materialize(node, DistSquaredSumExact, moments_.sum());
321 }
322 if (request_.squaredMean) {
323 materialize(node, DistSquaredMeanExact, moments_.mean());
324 }
325 if (request_.rms) {
326 materialize(node, DistRmsExact, moments_.rms());
327 }
328 if (request_.squaredVariance) {
329 materialize(node, DistSquaredVarianceExact, moments_.populationVariance());
330 }
331 if (request_.distanceSum) {
332 materialize(node, DistSumExact, moments_.distanceSum());
333 }
334 if (request_.distanceMean) {
335 materialize(node, DistMeanExact, moments_.distanceMean());
336 }
337 if (request_.distanceVariance) {
338 materialize(node, DistVarianceExact, moments_.distancePopulationVariance());
339 }
340 if (request_.median) {
341 materialize(node, DistMedianExact, histogram_.quantile(0.5L));
342 }
343 if (request_.mode) {
344 materialize(node, DistModeExact, histogram_.mode());
345 }
346 if (request_.q25) {
347 materialize(node, DistQ25Exact, histogram_.quantile(0.25L));
348 }
349 if (request_.q75) {
350 materialize(node, DistQ75Exact, histogram_.quantile(0.75L));
351 }
352 if (request_.q90) {
353 materialize(node, DistQ90Exact, histogram_.quantile(0.9L));
354 }
355 if (request_.entropy) {
356 materialize(node, DistEntropyExact, histogram_.entropyBits());
357 }
358 if (request_.positiveArea) {
359 materialize(node, DistPositiveAreaExact, histogram_.positiveArea());
360 }
361 if (request_.levelCount) {
362 materialize(node, DistLevelCountExact, histogram_.levelCount());
363 }
364 if (request_.weightedCentroidRow) {
365 materialize(node, DistWeightedCentroidRowExact, spatialMoments_.centroidRow());
366 }
367 if (request_.weightedCentroidColumn) {
368 materialize(node, DistWeightedCentroidColumnExact, spatialMoments_.centroidColumn());
369 }
370 if (request_.weightedMu20) {
371 materialize(node, DistWeightedCentralMoment20Exact, spatialMoments_.centralMoment20());
372 }
373 if (request_.weightedMu02) {
374 materialize(node, DistWeightedCentralMoment02Exact, spatialMoments_.centralMoment02());
375 }
376 if (request_.weightedMu11) {
377 materialize(node, DistWeightedCentralMoment11Exact, spatialMoments_.centralMoment11());
378 }
379 if (request_.weightedAxisOrientation) {
380 materialize(node, DistWeightedAxisOrientationExact, spatialMoments_.axisOrientationDegrees());
381 }
382 if (request_.weightedEccentricity) {
383 materialize(node, DistWeightedEccentricityExact, spatialMoments_.eccentricity());
384 }
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.");
388 }
389 if (request_.centerRow) {
390 materialize(node, MaxDistCenterRowExact, static_cast<long double>(plateau_.pixel / numColumns_));
391 }
392 if (request_.centerColumn) {
393 materialize(node, MaxDistCenterColumnExact, static_cast<long double>(plateau_.pixel % numColumns_));
394 }
395 if (request_.plateauArea) {
396 materialize(node, MaxDistPlateauAreaExact, static_cast<long double>(plateau_.count));
397 }
398 if (request_.plateauCentroidRow) {
399 materialize(node, MaxDistPlateauCentroidRowExact, plateau_.centroidRow());
400 }
401 if (request_.plateauCentroidColumn) {
402 materialize(node, MaxDistPlateauCentroidColumnExact, plateau_.centroidColumn());
403 }
404 } else if constexpr (TrackLocation) {
405 if (extremum_.pixel == InvalidPixel) {
406 throw std::logic_error("Exact distance-field summary produced an empty maximum location.");
407 }
408 if (request_.centerRow) {
409 materialize(node, MaxDistCenterRowExact, static_cast<long double>(extremum_.pixel / numColumns_));
410 }
411 if (request_.centerColumn) {
412 materialize(node, MaxDistCenterColumnExact, static_cast<long double>(extremum_.pixel % numColumns_));
413 }
414 }
415 }
416
417 private:
418 void materialize(NodeId node, Attribute attribute, long double value) { buffer_[attrNames_.linearIndex(node, attribute)] = static_cast<Real>(value); }
419
420 std::span<Real> buffer_;
421 const AttributeNames& attrNames_;
422 DistanceTransformAttributeRequest request_;
423 int numColumns_ = 0;
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_;
430};
431
432namespace kernel {
433
435template <std::floating_point Real>
436inline void computeDistanceTransformAttributes(const AttributeComputeContext<Real>& context, const DistanceTransformAttributeRequest& request, int numColumns) {
437 if (!request.any()) {
438 return;
439 }
440
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);
448 } else {
449 DistanceFieldSummaryAttributeReducer<Real, false, false> summaryReducer(context.buffer, context.attrNames, request, numColumns);
450 distance_transform::ExactNodeDistanceFieldProvider::reduce(context.tree, summaryReducer);
451 }
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);
461 }
462}
463
464} // namespace kernel
465
466} // namespace mmcfilters::attributes::computers::detail
469namespace mmcfilters::attributes::computers {
470
478 public:
480 static constexpr std::string_view familyName = "max-dist-exact";
481
483 static constexpr AttributeComputerFamily family = AttributeComputerFamily::MaxDistExact;
484
486 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
487
489 inline static constexpr std::array<Attribute, 29> producedAttributes{MaxDistExact,
490 MaxSquaredDistExact,
491 DistSquaredSumExact,
492 DistSquaredMeanExact,
493 DistRmsExact,
494 DistSquaredVarianceExact,
495 MaxDistCenterRowExact,
496 MaxDistCenterColumnExact,
497 MaxDistPlateauAreaExact,
498 MaxDistPlateauCentroidRowExact,
499 MaxDistPlateauCentroidColumnExact,
500 DistSumExact,
501 DistMeanExact,
502 DistVarianceExact,
503 DistMedianExact,
504 DistModeExact,
505 DistQ25Exact,
506 DistQ75Exact,
507 DistQ90Exact,
508 DistEntropyExact,
509 DistPositiveAreaExact,
510 DistLevelCountExact,
511 DistWeightedCentroidRowExact,
512 DistWeightedCentroidColumnExact,
513 DistWeightedCentralMoment20Exact,
514 DistWeightedCentralMoment02Exact,
515 DistWeightedCentralMoment11Exact,
516 DistWeightedAxisOrientationExact,
517 DistWeightedEccentricityExact};
518
520 static void requireSupportedTreeKind(const MorphologicalTree& tree) { detail::requireMaxDistExactCapabilities(tree); }
521
523 template <std::floating_point Real> static void compute(const AttributeComputeContext<Real>& context) {
524 const detail::DistanceTransformAttributeRequest request = detail::DistanceTransformAttributeRequest::from(context.requestedAttributes);
526 requireRequestedAttributeColumns(context); detail::requireMaxDistExactCapabilities(context.tree));
527 const int numColumns = request.needsCoordinates() ? context.tree.numColumns() : 0;
528 detail::kernel::computeDistanceTransformAttributes(context, request, numColumns);
529 }
530
532 template <std::floating_point Real> static void computeUnitRows(const UnitAttributeComputeContext<Real>& context) {
533 requireUnitAttributeBufferShape(context.tree, context.unitPixels, context.buffer, context.attrNames);
534 const int numColumns = context.tree.numColumns();
535 for (NodeId leafIndex = 0; leafIndex < static_cast<NodeId>(context.unitPixels.size()); ++leafIndex) {
536 const PixelId pixel = context.unitPixels[static_cast<std::size_t>(leafIndex)];
537 for (Attribute attribute : producedAttributes) {
538 if (!requestsAttribute(context.requestedAttributes, attribute)) {
539 continue;
540 }
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) {
547 value = Real{1};
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) {
553 value = Real{1};
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) {
559 value = Real{1};
560 }
561 context.buffer[context.attrNames.linearIndex(leafIndex, attribute)] = value;
562 }
563 }
564 }
565};
566
567} // namespace mmcfilters::attributes::computers
constexpr Mode mode
Contract policy selected when this translation unit was compiled.
Definition Contract.hpp:40
#define MMCFILTERS_CONTRACT_CHECKED_ONLY(...)
Executes validation statements only when defensive checks are enabled.
Definition Contract.hpp:67
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.