3#include "AttributeComputerDomain.hpp"
4#include "AttributeComputerFamily.hpp"
5#include "../detail/AttributeKernelSupport.hpp"
6#include "../../trees/detail/TreeTraversalDetail.hpp"
7#include "../../trees/detail/CommittedTreeAccess.hpp"
8#include "../../trees/TreeAltitudeAlgorithms.hpp"
9#include "../../utils/Altitude.hpp"
10#include "../../utils/Contract.hpp"
20namespace mmcfilters::attributes::computers {
29inline NodeId grayStatsSlotOf(
const MorphologicalTree&, NodeId nodeId)
noexcept {
return nodeId; }
32struct GrayLevelStatsRequest {
34 bool meanGrayLevel =
false;
36 bool grayLevelVariance =
false;
38 bool grayLevelHeight =
false;
45 [[nodiscard]]
bool any() const noexcept {
return meanGrayLevel || grayLevelVariance || grayLevelHeight; }
52 [[nodiscard]]
bool needsAggregateDependencies() const noexcept {
return meanGrayLevel || grayLevelVariance; }
60 [[nodiscard]]
static GrayLevelStatsRequest from(std::span<const Attribute> requestedAttributes) {
61 return {.meanGrayLevel = containsGrayStatsAttribute(requestedAttributes, MeanGrayLevel),
62 .grayLevelVariance = containsGrayStatsAttribute(requestedAttributes, GrayLevelVariance),
63 .grayLevelHeight = containsGrayStatsAttribute(requestedAttributes, GrayLevelHeight)};
74 [[nodiscard]]
static bool containsGrayStatsAttribute(std::span<const Attribute> requestedAttributes, Attribute attribute) {
75 return std::find(requestedAttributes.begin(), requestedAttributes.end(), attribute) != requestedAttributes.end();
101template <std::
floating_po
int Real, AltitudeValue T>
102void computeGrayLevelStats(
const AltitudeAttributeComputeContext<Real, T>& context,
const GrayLevelStatsRequest& request,
103 const DependencySourceT<Real>* volumeDependency,
const DependencySourceT<Real>* areaDependency) {
104 if (!request.any()) {
108 const bool needsAggregateDependencies = request.needsAggregateDependencies();
109 const int stride = context.attrNames.NUM_ATTRIBUTES;
110 const auto offsetOf = [&](Attribute attribute) {
return context.attrNames.indexMap.find(attribute)->second; };
111 const int meanOffset = request.meanGrayLevel ? offsetOf(MeanGrayLevel) : 0;
112 const int varianceOffset = request.grayLevelVariance ? offsetOf(GrayLevelVariance) : 0;
113 const int grayHeightOffset = request.grayLevelHeight ? offsetOf(GrayLevelHeight) : 0;
114 auto indexOfMean = [&](
NodeId node) {
return static_cast<std::size_t
>(node * stride + meanOffset); };
115 auto indexOfVariance = [&](
NodeId node) {
return static_cast<std::size_t
>(node * stride + varianceOffset); };
116 auto indexOfGrayHeight = [&](
NodeId node) {
return static_cast<std::size_t
>(node * stride + grayHeightOffset); };
117 const int volumeStride = volumeDependency !=
nullptr ? volumeDependency->attrNames->NUM_ATTRIBUTES : 0;
118 const int volumeOffset = volumeDependency !=
nullptr ? volumeDependency->attrNames->indexMap.find(Volume)->second : 0;
119 const int areaStride = areaDependency !=
nullptr ? areaDependency->attrNames->NUM_ATTRIBUTES : 0;
120 const int areaOffset = areaDependency !=
nullptr ? areaDependency->attrNames->indexMap.find(Area)->second : 0;
121 auto indexOfVolume = [&](
NodeId node) {
return static_cast<std::size_t
>(node * volumeStride + volumeOffset); };
122 auto indexOfArea = [&](
NodeId node) {
return static_cast<std::size_t
>(node * areaStride + areaOffset); };
124 std::vector<double> sumGrayLevelSquare;
125 if (request.grayLevelVariance) {
126 sumGrayLevelSquare.assign(context.tree.numInternalNodeSlots(), 0.0);
128 std::vector<Real> subtreeMinAltitude;
129 std::vector<Real> subtreeMaxAltitude;
130 if (request.grayLevelHeight) {
131 subtreeMinAltitude.assign(context.tree.numInternalNodeSlots(), Real{0});
132 subtreeMaxAltitude.assign(context.tree.numInternalNodeSlots(), Real{0});
135 ::mmcfilters::detail::kernel::traversePostOrder(
136 context.tree, context.tree.root(),
138 const NodeId node = detail::grayStatsSlotOf(context.tree, nodeId);
139 const T nodeAltitude = context.altitude[static_cast<std::size_t>(nodeId)];
140 const Real nodeAltitudeAsReal = static_cast<Real>(nodeAltitude);
141 if (request.grayLevelVariance) {
142 const double nodeAltitudeAsDouble = static_cast<double>(nodeAltitude);
143 sumGrayLevelSquare[static_cast<std::size_t>(node)] =
144 static_cast<double>(::mmcfilters::detail::CommittedTreeAccess::properPartCardinality(context.tree, nodeId)) * nodeAltitudeAsDouble *
145 nodeAltitudeAsDouble;
147 if (request.grayLevelHeight) {
148 subtreeMinAltitude[node] = nodeAltitudeAsReal;
149 subtreeMaxAltitude[node] = nodeAltitudeAsReal;
153 const NodeId parent = detail::grayStatsSlotOf(context.tree, parentNodeId);
154 const NodeId child = detail::grayStatsSlotOf(context.tree, childNodeId);
155 if (request.grayLevelVariance)
156 sumGrayLevelSquare[parent] += sumGrayLevelSquare[child];
157 if (request.grayLevelHeight) {
158 subtreeMinAltitude[parent] = std::min(subtreeMinAltitude[parent], subtreeMinAltitude[child]);
159 subtreeMaxAltitude[parent] = std::max(subtreeMaxAltitude[parent], subtreeMaxAltitude[child]);
163 const NodeId node = detail::grayStatsSlotOf(context.tree, nodeId);
164 Real area = needsAggregateDependencies ? areaDependency->buffer[indexOfArea(node)] : Real{0};
165 if (request.meanGrayLevel)
166 context.buffer[indexOfMean(node)] =
167 ::mmcfilters::attributes::numeric::safeDivide(volumeDependency->buffer[indexOfVolume(node)], area);
168 if (request.grayLevelVariance) {
169 Real meanGrayLevel = ::mmcfilters::attributes::numeric::safeDivide(volumeDependency->buffer[indexOfVolume(node)], area);
170 double meanGrayLevelSquare =
171 ::mmcfilters::attributes::numeric::safeDivide(sumGrayLevelSquare[
static_cast<std::size_t
>(node)],
static_cast<double>(area));
172 Real var =
static_cast<Real
>(meanGrayLevelSquare - (
static_cast<double>(meanGrayLevel) *
static_cast<double>(meanGrayLevel)));
173 context.buffer[indexOfVariance(node)] = ::mmcfilters::attributes::numeric::clampNonNegative(var);
175 if (request.grayLevelHeight) {
176 const Real nodeAltitude =
static_cast<Real
>(context.altitude[
static_cast<std::size_t
>(nodeId)]);
177 context.buffer[indexOfGrayHeight(node)] =
178 std::max(std::abs(nodeAltitude - subtreeMinAltitude[node]), std::abs(subtreeMaxAltitude[node] - nodeAltitude));
185template <std::
floating_po
int Real>
186inline const DependencySourceT<Real>* findGrayDependency(std::span<
const DependencySourceT<Real>> sources, Attribute attribute) {
187 for (
const DependencySourceT<Real>& source : sources) {
188 if (source.attrNames->contains(attribute)) {
195template <std::
floating_po
int Real, AltitudeValue T>
196inline void validateGrayLevelStatsContext(
const AltitudeAttributeComputeContext<Real, T>& context,
const GrayLevelStatsRequest& request) {
197 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
199 const auto requireColumn = [&](
bool requested, Attribute attribute,
const char* name) {
200 if (requested && !context.attrNames.contains(attribute)) {
201 throw std::invalid_argument(std::string(name) +
" computation requires a matching output column.");
204 requireColumn(request.meanGrayLevel, MeanGrayLevel,
"MEAN_GRAY_LEVEL");
205 requireColumn(request.grayLevelVariance, GrayLevelVariance,
"GRAY_LEVEL_VARIANCE");
206 requireColumn(request.grayLevelHeight, GrayLevelHeight,
"GRAY_LEVEL_HEIGHT");
235 static constexpr std::string_view familyName =
"gray-level-stats";
238 static constexpr AttributeComputerFamily family = AttributeComputerFamily::GrayLevelStats;
241 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Altitude;
246 inline static constexpr std::array<Attribute, 3> producedAttributes{MeanGrayLevel, GrayLevelVariance, GrayLevelHeight};
260 const detail::GrayLevelStatsRequest
request = detail::GrayLevelStatsRequest::from(
context.requestedAttributes);
264 if (
request.needsAggregateDependencies()) {
265 if constexpr (contract::validationsEnabled) {
286 TreeAltitudeAlgorithms::validateNodeAltitudeBufferShape(
context.tree,
context.altitude);
288 const detail::GrayLevelStatsRequest
request = detail::GrayLevelStatsRequest::from(
context.requestedAttributes);
296 ?
static_cast<Real
>(::mmcfilters::detail::kernel::unitAltitude(
context.tree,
context.altitude, pixel))
302 if (
request.grayLevelVariance) {
int NodeId
Node identifier type used throughout the project.
#define MMCFILTERS_CONTRACT_CHECKED_ONLY(...)
Executes validation statements only when defensive checks are enabled.
static void validateNodeAltitudeBufferShape(const MorphologicalTree &tree, std::span< const T > altitude)
Validates that an altitude buffer covers the dense internal-node domain.
Computes grey-level statistics derived from subtree aggregation.
static void compute(const AltitudeAttributeComputeContext< Real, T > &context)
Computes the requested grey-level statistics.
static void computeUnitRows(const AltitudeUnitAttributeComputeContext< Real, T > &context)
Materializes grey-level statistics for one-pixel unit supports.
Owning result for one computed scalar attribute layout and buffer.