mmcfilters
Public API documentation
Loading...
Searching...
No Matches
GrayLevelStatsComputer.hpp
1#pragma once
2
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"
10
11#include <algorithm>
12#include <array>
13#include <cmath>
14#include <span>
15#include <stdexcept>
16#include <string_view>
17#include <vector>
18
19namespace mmcfilters::attributes::computers {
20
21namespace detail {
28inline NodeId grayStatsSlotOf(const MorphologicalTree&, NodeId nodeId) noexcept { return nodeId; }
29
31struct GrayLevelStatsRequest {
33 bool meanGrayLevel = false;
35 bool grayLevelVariance = false;
37 bool grayLevelHeight = false;
38
44 [[nodiscard]] bool any() const noexcept { return meanGrayLevel || grayLevelVariance || grayLevelHeight; }
45
51 [[nodiscard]] bool needsAggregateDependencies() const noexcept { return meanGrayLevel || grayLevelVariance; }
52
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)};
63 }
64
65 private:
73 [[nodiscard]] static bool containsGrayStatsAttribute(std::span<const Attribute> requestedAttributes, Attribute attribute) {
74 return std::find(requestedAttributes.begin(), requestedAttributes.end(), attribute) != requestedAttributes.end();
75 }
76};
77
91namespace kernel {
92
100template <std::floating_point 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()) {
104 return;
105 }
106
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); };
122
123 std::vector<double> sumGrayLevelSquare;
124 if (request.grayLevelVariance) {
125 sumGrayLevelSquare.assign(context.tree.numInternalNodeSlots(), 0.0);
126 }
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});
132 }
133
134 ::mmcfilters::detail::TreeKernelAccess::traversePostOrder(
135 context.tree, context.tree.root(),
136 [&](NodeId nodeId) {
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;
145 }
146 if (request.grayLevelHeight) {
147 subtreeMinAltitude[node] = nodeAltitudeAsReal;
148 subtreeMaxAltitude[node] = nodeAltitudeAsReal;
149 }
150 },
151 [&](NodeId parentNodeId, NodeId childNodeId) {
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]);
159 }
160 },
161 [&](NodeId nodeId) {
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);
173 }
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));
178 }
179 });
180}
181
182} // namespace kernel
183
184template <std::floating_point 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)) {
188 return &source;
189 }
190 }
191 return nullptr;
192}
193
194template <std::floating_point Real, AltitudeValue T>
195inline void validateGrayLevelStatsContext(const AltitudeAttributeComputeContext<Real, T>& context, const GrayLevelStatsRequest& request) {
196 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
197 TreeAltitudeAlgorithms::validateNodeAltitudeBufferShape(context.tree, context.altitude);
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.");
201 }
202 };
203 requireColumn(request.meanGrayLevel, MeanGrayLevel, "MEAN_GRAY_LEVEL");
204 requireColumn(request.grayLevelVariance, GrayLevelVariance, "GRAY_LEVEL_VARIANCE");
205 requireColumn(request.grayLevelHeight, GrayLevelHeight, "GRAY_LEVEL_HEIGHT");
206}
207
208} // namespace detail
209
232 public:
234 static constexpr std::string_view familyName = "gray-level-stats";
235
237 static constexpr AttributeComputerFamily family = AttributeComputerFamily::GrayLevelStats;
238
240 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Altitude;
241
245 inline static constexpr std::array<Attribute, 3> producedAttributes{MeanGrayLevel, GrayLevelVariance, GrayLevelHeight};
246
258 template <std::floating_point Real, AltitudeValue T> static void compute(const AltitudeAttributeComputeContext<Real, T>& context) {
259 const detail::GrayLevelStatsRequest request = detail::GrayLevelStatsRequest::from(context.requestedAttributes);
260 MMCFILTERS_CONTRACT_CHECKED_ONLY(detail::validateGrayLevelStatsContext(context, request));
263 if (request.needsAggregateDependencies()) {
264 if constexpr (contract::validationsEnabled) {
265 volumeDependency = &context.dependencies.require(Volume);
266 areaDependency = &context.dependencies.require(Area);
267 } else {
268 volumeDependency = detail::findGrayDependency(context.dependencySources, Volume);
269 areaDependency = detail::findGrayDependency(context.dependencySources, Area);
270 }
271 }
272 detail::kernel::computeGrayLevelStats(context, request, volumeDependency, areaDependency);
273 }
274
283 template <std::floating_point Real, AltitudeValue T> static void computeUnitRows(const AltitudeUnitAttributeComputeContext<Real, T>& context) {
284 requireUnitAttributeBufferShape(context.tree, context.unitPixels, context.buffer, context.attrNames);
285 TreeAltitudeAlgorithms::validateNodeAltitudeBufferShape(context.tree, context.altitude);
286 computeUnitRowsEstablished(context);
287 }
288
289 private:
296 template <class Computer, class Context> friend void mmcfilters::detail::kernel::computeUnitRows(const Context& context);
297
299 template <std::floating_point Real, AltitudeValue T> static void computeUnitRowsEstablished(const AltitudeUnitAttributeComputeContext<Real, T>& context) {
300
301 const detail::GrayLevelStatsRequest request = detail::GrayLevelStatsRequest::from(context.requestedAttributes);
302 if (!request.any()) {
303 return;
304 }
305
306 for (NodeId leafIndex = 0; leafIndex < static_cast<NodeId>(context.unitPixels.size()); ++leafIndex) {
307 const PixelId pixel = context.unitPixels[static_cast<size_t>(leafIndex)];
308 const Real altitudeValue = request.meanGrayLevel
309 ? static_cast<Real>(::mmcfilters::detail::kernel::unitAltitude(context.tree, context.altitude, pixel))
310 : Real{0};
311
312 if (request.meanGrayLevel) {
313 context.buffer[context.attrNames.linearIndex(leafIndex, MeanGrayLevel)] = altitudeValue;
314 }
315 if (request.grayLevelVariance) {
316 context.buffer[context.attrNames.linearIndex(leafIndex, GrayLevelVariance)] = Real{0};
317 }
318 if (request.grayLevelHeight) {
319 context.buffer[context.attrNames.linearIndex(leafIndex, GrayLevelHeight)] = Real{0};
320 }
321 }
322 }
323};
324
325} // namespace mmcfilters::attributes::computers
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
#define MMCFILTERS_CONTRACT_CHECKED_ONLY(...)
Executes validation statements only when defensive checks are enabled.
Definition Contract.hpp:67
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.