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/TreeTraversalDetail.hpp"
7#include "../../trees/detail/CommittedTreeAccess.hpp"
8#include "../../trees/TreeAltitudeAlgorithms.hpp"
9#include "../../utils/Altitude.hpp"
10#include "../../utils/Contract.hpp"
11
12#include <algorithm>
13#include <array>
14#include <cmath>
15#include <span>
16#include <stdexcept>
17#include <string_view>
18#include <vector>
19
20namespace mmcfilters::attributes::computers {
21
22namespace detail {
29inline NodeId grayStatsSlotOf(const MorphologicalTree&, NodeId nodeId) noexcept { return nodeId; }
30
32struct GrayLevelStatsRequest {
34 bool meanGrayLevel = false;
36 bool grayLevelVariance = false;
38 bool grayLevelHeight = false;
39
45 [[nodiscard]] bool any() const noexcept { return meanGrayLevel || grayLevelVariance || grayLevelHeight; }
46
52 [[nodiscard]] bool needsAggregateDependencies() const noexcept { return meanGrayLevel || grayLevelVariance; }
53
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)};
64 }
65
66 private:
74 [[nodiscard]] static bool containsGrayStatsAttribute(std::span<const Attribute> requestedAttributes, Attribute attribute) {
75 return std::find(requestedAttributes.begin(), requestedAttributes.end(), attribute) != requestedAttributes.end();
76 }
77};
78
92namespace kernel {
93
101template <std::floating_point 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()) {
105 return;
106 }
107
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); };
123
124 std::vector<double> sumGrayLevelSquare;
125 if (request.grayLevelVariance) {
126 sumGrayLevelSquare.assign(context.tree.numInternalNodeSlots(), 0.0);
127 }
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});
133 }
134
135 ::mmcfilters::detail::kernel::traversePostOrder(
136 context.tree, context.tree.root(),
137 [&](NodeId nodeId) {
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;
146 }
147 if (request.grayLevelHeight) {
148 subtreeMinAltitude[node] = nodeAltitudeAsReal;
149 subtreeMaxAltitude[node] = nodeAltitudeAsReal;
150 }
151 },
152 [&](NodeId parentNodeId, NodeId childNodeId) {
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]);
160 }
161 },
162 [&](NodeId nodeId) {
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);
174 }
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));
179 }
180 });
181}
182
183} // namespace kernel
184
185template <std::floating_point 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)) {
189 return &source;
190 }
191 }
192 return nullptr;
193}
194
195template <std::floating_point Real, AltitudeValue T>
196inline void validateGrayLevelStatsContext(const AltitudeAttributeComputeContext<Real, T>& context, const GrayLevelStatsRequest& request) {
197 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
198 TreeAltitudeAlgorithms::validateNodeAltitudeBufferShape(context.tree, context.altitude);
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.");
202 }
203 };
204 requireColumn(request.meanGrayLevel, MeanGrayLevel, "MEAN_GRAY_LEVEL");
205 requireColumn(request.grayLevelVariance, GrayLevelVariance, "GRAY_LEVEL_VARIANCE");
206 requireColumn(request.grayLevelHeight, GrayLevelHeight, "GRAY_LEVEL_HEIGHT");
207}
208
209} // namespace detail
210
233 public:
235 static constexpr std::string_view familyName = "gray-level-stats";
236
238 static constexpr AttributeComputerFamily family = AttributeComputerFamily::GrayLevelStats;
239
241 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Altitude;
242
246 inline static constexpr std::array<Attribute, 3> producedAttributes{MeanGrayLevel, GrayLevelVariance, GrayLevelHeight};
247
259 template <std::floating_point Real, AltitudeValue T> static void compute(const AltitudeAttributeComputeContext<Real, T>& context) {
260 const detail::GrayLevelStatsRequest request = detail::GrayLevelStatsRequest::from(context.requestedAttributes);
261 MMCFILTERS_CONTRACT_CHECKED_ONLY(detail::validateGrayLevelStatsContext(context, request));
264 if (request.needsAggregateDependencies()) {
265 if constexpr (contract::validationsEnabled) {
266 volumeDependency = &context.dependencies.require(Volume);
267 areaDependency = &context.dependencies.require(Area);
268 } else {
269 volumeDependency = detail::findGrayDependency(context.dependencySources, Volume);
270 areaDependency = detail::findGrayDependency(context.dependencySources, Area);
271 }
272 }
273 detail::kernel::computeGrayLevelStats(context, request, volumeDependency, areaDependency);
274 }
275
284 template <std::floating_point Real, AltitudeValue T> static void computeUnitRows(const AltitudeUnitAttributeComputeContext<Real, T>& context) {
285 requireUnitAttributeBufferShape(context.tree, context.unitPixels, context.buffer, context.attrNames);
286 TreeAltitudeAlgorithms::validateNodeAltitudeBufferShape(context.tree, context.altitude);
287
288 const detail::GrayLevelStatsRequest request = detail::GrayLevelStatsRequest::from(context.requestedAttributes);
289 if (!request.any()) {
290 return;
291 }
292
293 for (NodeId leafIndex = 0; leafIndex < static_cast<NodeId>(context.unitPixels.size()); ++leafIndex) {
294 const PixelId pixel = context.unitPixels[static_cast<size_t>(leafIndex)];
295 const Real altitudeValue = request.meanGrayLevel
296 ? static_cast<Real>(::mmcfilters::detail::kernel::unitAltitude(context.tree, context.altitude, pixel))
297 : Real{0};
298
299 if (request.meanGrayLevel) {
300 context.buffer[context.attrNames.linearIndex(leafIndex, MeanGrayLevel)] = altitudeValue;
301 }
302 if (request.grayLevelVariance) {
303 context.buffer[context.attrNames.linearIndex(leafIndex, GrayLevelVariance)] = Real{0};
304 }
305 if (request.grayLevelHeight) {
306 context.buffer[context.attrNames.linearIndex(leafIndex, GrayLevelHeight)] = Real{0};
307 }
308 }
309 }
310};
311
312} // 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.