3#include "AttributeComputerDomain.hpp"
4#include "AttributeComputerFamily.hpp"
5#include "../detail/AttributeKernelSupport.hpp"
6#include "../../trees/detail/TreeKernelAccess.hpp"
7#include "../../trees/TreeAltitudeAlgorithms.hpp"
8#include "../../utils/Altitude.hpp"
9#include "../../utils/Contract.hpp"
19namespace mmcfilters::attributes::computers {
28inline NodeId grayStatsSlotOf(
const MorphologicalTree&, NodeId nodeId)
noexcept {
return nodeId; }
31struct GrayLevelStatsRequest {
33 bool meanGrayLevel =
false;
35 bool grayLevelVariance =
false;
37 bool grayLevelHeight =
false;
44 [[nodiscard]]
bool any() const noexcept {
return meanGrayLevel || grayLevelVariance || grayLevelHeight; }
51 [[nodiscard]]
bool needsAggregateDependencies() const noexcept {
return meanGrayLevel || grayLevelVariance; }
59 [[nodiscard]]
static GrayLevelStatsRequest from(std::span<const Attribute> requestedAttributes) {
60 return {.meanGrayLevel = containsGrayStatsAttribute(requestedAttributes, MeanGrayLevel),
61 .grayLevelVariance = containsGrayStatsAttribute(requestedAttributes, GrayLevelVariance),
62 .grayLevelHeight = containsGrayStatsAttribute(requestedAttributes, GrayLevelHeight)};
73 [[nodiscard]]
static bool containsGrayStatsAttribute(std::span<const Attribute> requestedAttributes, Attribute attribute) {
74 return std::find(requestedAttributes.begin(), requestedAttributes.end(), attribute) != requestedAttributes.end();
100template <std::
floating_po
int Real, AltitudeValue T>
101void computeGrayLevelStats(
const AltitudeAttributeComputeContext<Real, T>& context,
const GrayLevelStatsRequest& request,
102 const DependencySourceT<Real>* volumeDependency,
const DependencySourceT<Real>* areaDependency) {
103 if (!request.any()) {
107 const bool needsAggregateDependencies = request.needsAggregateDependencies();
108 const int stride = context.attrNames.NUM_ATTRIBUTES;
109 const auto offsetOf = [&](Attribute attribute) {
return context.attrNames.indexMap.find(attribute)->second; };
110 const int meanOffset = request.meanGrayLevel ? offsetOf(MeanGrayLevel) : 0;
111 const int varianceOffset = request.grayLevelVariance ? offsetOf(GrayLevelVariance) : 0;
112 const int grayHeightOffset = request.grayLevelHeight ? offsetOf(GrayLevelHeight) : 0;
113 auto indexOfMean = [&](
NodeId node) {
return static_cast<std::size_t
>(node * stride + meanOffset); };
114 auto indexOfVariance = [&](
NodeId node) {
return static_cast<std::size_t
>(node * stride + varianceOffset); };
115 auto indexOfGrayHeight = [&](
NodeId node) {
return static_cast<std::size_t
>(node * stride + grayHeightOffset); };
116 const int volumeStride = volumeDependency !=
nullptr ? volumeDependency->attrNames->NUM_ATTRIBUTES : 0;
117 const int volumeOffset = volumeDependency !=
nullptr ? volumeDependency->attrNames->indexMap.find(Volume)->second : 0;
118 const int areaStride = areaDependency !=
nullptr ? areaDependency->attrNames->NUM_ATTRIBUTES : 0;
119 const int areaOffset = areaDependency !=
nullptr ? areaDependency->attrNames->indexMap.find(Area)->second : 0;
120 auto indexOfVolume = [&](
NodeId node) {
return static_cast<std::size_t
>(node * volumeStride + volumeOffset); };
121 auto indexOfArea = [&](
NodeId node) {
return static_cast<std::size_t
>(node * areaStride + areaOffset); };
123 std::vector<double> sumGrayLevelSquare;
124 if (request.grayLevelVariance) {
125 sumGrayLevelSquare.assign(context.tree.numInternalNodeSlots(), 0.0);
127 std::vector<Real> subtreeMinAltitude;
128 std::vector<Real> subtreeMaxAltitude;
129 if (request.grayLevelHeight) {
130 subtreeMinAltitude.assign(context.tree.numInternalNodeSlots(), Real{0});
131 subtreeMaxAltitude.assign(context.tree.numInternalNodeSlots(), Real{0});
134 ::mmcfilters::detail::TreeKernelAccess::traversePostOrder(
135 context.tree, context.tree.root(),
137 const NodeId node = detail::grayStatsSlotOf(context.tree, nodeId);
138 const T nodeAltitude = context.altitude[static_cast<std::size_t>(nodeId)];
139 const Real nodeAltitudeAsReal = static_cast<Real>(nodeAltitude);
140 if (request.grayLevelVariance) {
141 const double nodeAltitudeAsDouble = static_cast<double>(nodeAltitude);
142 sumGrayLevelSquare[static_cast<std::size_t>(node)] =
143 static_cast<double>(::mmcfilters::detail::TreeKernelAccess::properPartCardinality(context.tree, nodeId)) * nodeAltitudeAsDouble *
144 nodeAltitudeAsDouble;
146 if (request.grayLevelHeight) {
147 subtreeMinAltitude[node] = nodeAltitudeAsReal;
148 subtreeMaxAltitude[node] = nodeAltitudeAsReal;
152 const NodeId parent = detail::grayStatsSlotOf(context.tree, parentNodeId);
153 const NodeId child = detail::grayStatsSlotOf(context.tree, childNodeId);
154 if (request.grayLevelVariance)
155 sumGrayLevelSquare[parent] += sumGrayLevelSquare[child];
156 if (request.grayLevelHeight) {
157 subtreeMinAltitude[parent] = std::min(subtreeMinAltitude[parent], subtreeMinAltitude[child]);
158 subtreeMaxAltitude[parent] = std::max(subtreeMaxAltitude[parent], subtreeMaxAltitude[child]);
162 const NodeId node = detail::grayStatsSlotOf(context.tree, nodeId);
163 Real area = needsAggregateDependencies ? areaDependency->buffer[indexOfArea(node)] : Real{0};
164 if (request.meanGrayLevel)
165 context.buffer[indexOfMean(node)] =
166 ::mmcfilters::attributes::numeric::safeDivide(volumeDependency->buffer[indexOfVolume(node)], area);
167 if (request.grayLevelVariance) {
168 Real meanGrayLevel = ::mmcfilters::attributes::numeric::safeDivide(volumeDependency->buffer[indexOfVolume(node)], area);
169 double meanGrayLevelSquare =
170 ::mmcfilters::attributes::numeric::safeDivide(sumGrayLevelSquare[
static_cast<std::size_t
>(node)],
static_cast<double>(area));
171 Real var =
static_cast<Real
>(meanGrayLevelSquare - (
static_cast<double>(meanGrayLevel) *
static_cast<double>(meanGrayLevel)));
172 context.buffer[indexOfVariance(node)] = ::mmcfilters::attributes::numeric::clampNonNegative(var);
174 if (request.grayLevelHeight) {
175 const Real nodeAltitude =
static_cast<Real
>(context.altitude[
static_cast<std::size_t
>(nodeId)]);
176 context.buffer[indexOfGrayHeight(node)] =
177 std::max(std::abs(nodeAltitude - subtreeMinAltitude[node]), std::abs(subtreeMaxAltitude[node] - nodeAltitude));
184template <std::
floating_po
int Real>
185inline const DependencySourceT<Real>* findGrayDependency(std::span<
const DependencySourceT<Real>> sources, Attribute attribute) {
186 for (
const DependencySourceT<Real>& source : sources) {
187 if (source.attrNames->contains(attribute)) {
194template <std::
floating_po
int Real, AltitudeValue T>
195inline void validateGrayLevelStatsContext(
const AltitudeAttributeComputeContext<Real, T>& context,
const GrayLevelStatsRequest& request) {
196 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
198 const auto requireColumn = [&](
bool requested, Attribute attribute,
const char* name) {
199 if (requested && !context.attrNames.contains(attribute)) {
200 throw std::invalid_argument(std::string(name) +
" computation requires a matching output column.");
203 requireColumn(request.meanGrayLevel, MeanGrayLevel,
"MEAN_GRAY_LEVEL");
204 requireColumn(request.grayLevelVariance, GrayLevelVariance,
"GRAY_LEVEL_VARIANCE");
205 requireColumn(request.grayLevelHeight, GrayLevelHeight,
"GRAY_LEVEL_HEIGHT");
234 static constexpr std::string_view familyName =
"gray-level-stats";
237 static constexpr AttributeComputerFamily family = AttributeComputerFamily::GrayLevelStats;
240 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Altitude;
245 inline static constexpr std::array<Attribute, 3> producedAttributes{MeanGrayLevel, GrayLevelVariance, GrayLevelHeight};
259 const detail::GrayLevelStatsRequest
request = detail::GrayLevelStatsRequest::from(
context.requestedAttributes);
263 if (
request.needsAggregateDependencies()) {
264 if constexpr (contract::validationsEnabled) {
285 TreeAltitudeAlgorithms::validateNodeAltitudeBufferShape(
context.tree,
context.altitude);
286 computeUnitRowsEstablished(
context);
296 template <
class Computer,
class Context>
friend void mmcfilters::detail::kernel::computeUnitRows(
const Context&
context);
301 const detail::GrayLevelStatsRequest
request = detail::GrayLevelStatsRequest::from(
context.requestedAttributes);
309 ?
static_cast<Real
>(::mmcfilters::detail::kernel::unitAltitude(
context.tree,
context.altitude, pixel))
315 if (
request.grayLevelVariance) {
318 if (request.grayLevelHeight) {
319 context.buffer[context.attrNames.linearIndex(leafIndex, GrayLevelHeight)] = Real{0};
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.