mmcfilters
Public API documentation
Loading...
Searching...
No Matches
FilledShapeAttributeComputer.hpp
1#pragma once
2
3#include "AttributeComputerDomain.hpp"
4#include "AttributeComputerFamily.hpp"
5#include "../detail/AttributeKernelSupport.hpp"
6#include "../../contours/ContourTraceComputation.hpp"
7
8#include <algorithm>
9#include <array>
10#include <cmath>
11#include <concepts>
12#include <limits>
13#include <numbers>
14#include <span>
15#include <string_view>
16
17namespace mmcfilters::attributes::computers {
18
19namespace detail {
20
22struct FilledShapeMoments {
23 long double area = 0;
24 long double column = 0;
25 long double row = 0;
26 long double column2 = 0;
27 long double row2 = 0;
28 long double columnRow = 0;
29
45 void addBoundary(ContourTraceView trace, const ContourBoundary& boundary, int columns,
46 int originColumn, int originRow, bool secondOrder) {
47 for (ContourEdge edge : trace.boundaryEdges(boundary)) {
48 if (edge.side != ContourSide::East && edge.side != ContourSide::West) {
49 continue;
50 }
51 const bool east = edge.side == ContourSide::East;
52 const long double k = static_cast<long double>(edge.pixel % columns - originColumn) + (east ? 1 : 0);
53 const long double y = edge.pixel / columns - originRow;
54 const long double signedWidth = east ? k : -k;
55 const long double columnSum = signedWidth * (k - 1) / 2;
56 area += signedWidth;
57 column += columnSum;
58 row += signedWidth * y;
59 if (secondOrder) {
60 column2 += columnSum * (2 * k - 1) / 3;
61 row2 += signedWidth * y * y;
62 columnRow += columnSum * y;
63 }
64 }
65 }
66};
67
68} // namespace detail
69
79 public:
80 static constexpr std::string_view familyName = "filled-shape";
81 static constexpr AttributeComputerFamily family = AttributeComputerFamily::FilledShape;
82 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
83
85 inline static constexpr std::array<Attribute, 13> producedAttributes{
86 FilledArea, FilledCentroidRow, FilledCentroidColumn, FilledLengthMajorAxis, FilledLengthMinorAxis,
87 FilledAxisOrientation, FilledEccentricity, FilledInertia, HoleAreaFraction, FilledCentroidDisplacementNormalized,
88 FilledCompactness, FilledCircularity, FilledAxisOrientationSigned};
89
91 template <std::floating_point Real> static void compute(const AttributeComputeContext<Real>& context) {
93 if (!context.requestedAttributes.empty()) {
94 computeTraces(context, ContourTraceComputation(context.tree));
95 }
96 }
97
102 template <std::floating_point Real, AltitudeValue T> static void compute(const AltitudeAttributeComputeContext<Real, T>& context) {
103 const AttributeComputeContext<Real> topologyContext{context.tree, context.buffer, context.attrNames, context.requestedAttributes};
105 if (!context.requestedAttributes.empty()) {
106 const ValuedMorphologicalTreeView<T> view(context.tree, context.altitude);
107 computeTraces(topologyContext, ContourTraceComputation(view));
108 }
109 }
110
118 template <std::floating_point Real> static void computeUnitRows(const UnitAttributeComputeContext<Real>& context) {
119 requireUnitAttributeBufferShape(context.tree, context.unitPixels, context.buffer, context.attrNames);
120 computeUnitRowsEstablished(context);
121 }
122
123 private:
130 template <class Computer, class Context> friend void mmcfilters::detail::kernel::computeUnitRows(const Context& context);
131
133 template <std::floating_point Real> static void computeUnitRowsEstablished(const UnitAttributeComputeContext<Real>& context) {
134 const int columns = context.tree.requireGridDomain2D("FilledShapeAttributeComputer").columns;
135 const auto offsets = outputOffsets(context.attrNames, context.requestedAttributes);
136 for (NodeId row = 0; row < static_cast<NodeId>(context.unitPixels.size()); ++row) {
137 const PixelId pixel = context.unitPixels[static_cast<std::size_t>(row)];
138 const std::array<long double, 13> values{1, static_cast<long double>(pixel / columns),
139 static_cast<long double>(pixel % columns), 0, 0, 0, 1, 0, 0, 0, 0, 1};
140 writeRow(context.buffer, context.attrNames.NUM_ATTRIBUTES, row, offsets, values);
141 }
142 }
143
144 private:
146 template <std::floating_point Real> static void validateContext(const AttributeComputeContext<Real>& context) {
147 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
148 requireRequestedAttributeColumns(context);
149 }
150
157 static std::array<int, 13> outputOffsets(const AttributeNames& names, std::span<const Attribute> requested) {
158 std::array<int, 13> offsets;
159 offsets.fill(-1);
160 for (Attribute attribute : requested) {
161 const auto it = std::find(producedAttributes.begin(), producedAttributes.end(), attribute);
162 if (it == producedAttributes.end()) {
163 throw std::invalid_argument("FilledShapeAttributeComputer received an attribute from another family.");
164 }
165 offsets[static_cast<std::size_t>(it - producedAttributes.begin())] = names.linearIndex(0, attribute);
166 }
167 return offsets;
168 }
169
178 template <std::floating_point Real>
179 static void writeRow(std::span<Real> buffer, int stride, NodeId node, const std::array<int, 13>& offsets,
180 const std::array<long double, 13>& values) {
181 const std::size_t row = static_cast<std::size_t>(node) * static_cast<std::size_t>(stride);
182 for (std::size_t i = 0; i < offsets.size(); ++i) {
183 if (offsets[i] >= 0) {
184 buffer[row + static_cast<std::size_t>(offsets[i])] = static_cast<Real>(values[i]);
185 }
186 }
187 }
188
194 template <std::floating_point Real>
195 static void computeTraces(const AttributeComputeContext<Real>& context, const ContourTraceComputation& traces) {
196 const auto offsets = outputOffsets(context.attrNames, context.requestedAttributes);
197 const bool secondOrder = offsets[10] >= 0 || offsets[11] >= 0 || offsets[12] >= 0 ||
198 std::any_of(offsets.begin() + 3, offsets.begin() + 8, [](int offset) { return offset >= 0; });
199 const bool compareSupport = offsets[8] >= 0 || offsets[9] >= 0;
200 const int columns = context.tree.numColumns();
201 traces.forEachTrace([&](NodeId node, ContourTraceView trace) {
202 const ContourBoundary external = trace.externalBoundary();
203 const PixelId origin = (*trace.boundaryEdges(external).begin()).pixel;
204 const int originColumn = origin % columns;
205 const int originRow = origin / columns;
206 detail::FilledShapeMoments filled;
207 filled.addBoundary(trace, external, columns, originColumn, originRow, secondOrder);
208 const long double area = filled.area;
209 const long double centroidColumn = filled.column / area;
210 const long double centroidRow = filled.row / area;
211 std::array<long double, 13> values{area, centroidRow + originRow, centroidColumn + originColumn, 0, 0, 0, 1, 0, 0, 0, 0, 1};
212 if (secondOrder) {
213 const long double mu20 = std::max(0.L, filled.column2 - filled.column * centroidColumn);
214 const long double mu02 = std::max(0.L, filled.row2 - filled.row * centroidRow);
215 const long double mu11 = filled.columnRow - filled.column * centroidRow;
216 const long double discriminant = std::hypot(mu20 - mu02, 2 * mu11);
217 const long double lambda1 = mu20 + mu02 + discriminant;
218 const long double lambda2 = std::max(0.L, mu20 + mu02 - discriminant);
219 values[3] = std::sqrt(2 * lambda1 / area);
220 values[4] = std::sqrt(2 * lambda2 / area);
221 if (mu20 != mu02 || mu11 != 0) {
222 values[12] = std::atan2(2 * mu11, mu20 - mu02) * 90 / std::numbers::pi_v<long double>;
223 values[5] = std::abs(values[12]);
224 }
225 const long double epsilon = std::numeric_limits<Real>::epsilon();
226 values[6] = lambda1 <= epsilon ? 1 : (lambda2 <= epsilon ? 1e6L : std::min(lambda1 / lambda2, 1e6L));
227 values[7] = (mu20 + mu02) / (area * area);
228 values[10] = mu20 + mu02 > epsilon ? area / (2 * std::numbers::pi_v<long double> * (mu20 + mu02)) : 0;
229 values[11] = lambda1 <= epsilon ? 1 : (lambda2 <= epsilon ? 0 : lambda2 / lambda1);
230 }
231 if (compareSupport) {
232 auto support = filled;
233 for (const ContourBoundary& boundary : trace.boundaries()) {
234 if (boundary.kind == ContourBoundaryKind::Internal) {
235 support.addBoundary(trace, boundary, columns, originColumn, originRow, false);
236 }
237 }
238 values[8] = std::clamp(1 - support.area / area, 0.L, 1.L);
239 values[9] = std::hypot(centroidColumn - support.column / support.area,
240 centroidRow - support.row / support.area) / std::sqrt(area);
241 }
242 writeRow(context.buffer, context.attrNames.NUM_ATTRIBUTES, node, offsets, values);
243 });
244 }
245};
246
247} // namespace mmcfilters::attributes::computers
int PixelId
Pixel identifier type used by source and active construction domains.
Definition Common.hpp:26
#define MMCFILTERS_CONTRACT_CHECKED_ONLY(...)
Executes validation statements only when defensive checks are enabled.
Definition Contract.hpp:67
Incremental ordered contour traces on the image domain.
Shape descriptors of the region enclosed by the external boundary.
static constexpr AttributeComputerFamily family
Scheduler family.
static constexpr AttributeComputerDomain domain
Support-based computation.
static constexpr std::array< Attribute, 13 > producedAttributes
Canonical output order of the family.
static void compute(const AltitudeAttributeComputeContext< Real, T > &context)
Computes descriptors using altitudes to resolve directional connectivity.
static constexpr std::string_view familyName
Name used in dependency diagnostics.
static void compute(const AttributeComputeContext< Real > &context)
Computes requested filled-region descriptors.
static void computeUnitRows(const UnitAttributeComputeContext< Real > &context)
Writes unit-support values in the supplied pixel order.
Owning result for one computed scalar attribute layout and buffer.