80 static constexpr std::string_view
familyName =
"filled-shape";
81 static constexpr AttributeComputerFamily
family = AttributeComputerFamily::FilledShape;
82 static constexpr AttributeComputerDomain
domain = AttributeComputerDomain::Topology;
86 FilledArea, FilledCentroidRow, FilledCentroidColumn, FilledLengthMajorAxis, FilledLengthMinorAxis,
87 FilledAxisOrientation, FilledEccentricity, FilledInertia, HoleAreaFraction, FilledCentroidDisplacementNormalized,
88 FilledCompactness, FilledCircularity, FilledAxisOrientationSigned};
93 if (!
context.requestedAttributes.empty()) {
105 if (!
context.requestedAttributes.empty()) {
120 computeUnitRowsEstablished(
context);
130 template <
class Computer,
class Context>
friend void mmcfilters::detail::kernel::computeUnitRows(
const Context&
context);
134 const int columns =
context.tree.requireGridDomain2D(
"FilledShapeAttributeComputer").columns;
135 const auto offsets = outputOffsets(
context.attrNames,
context.requestedAttributes);
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);
146 template <std::
floating_po
int Real>
static void validateContext(
const AttributeComputeContext<Real>& context) {
147 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
148 requireRequestedAttributeColumns(context);
157 static std::array<int, 13> outputOffsets(
const AttributeNames& names, std::span<const Attribute> requested) {
158 std::array<int, 13> offsets;
160 for (Attribute attribute : requested) {
163 throw std::invalid_argument(
"FilledShapeAttributeComputer received an attribute from another family.");
165 offsets[
static_cast<std::size_t
>(it -
producedAttributes.begin())] = names.linearIndex(0, attribute);
178 template <std::
floating_po
int 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]);
194 template <std::
floating_po
int 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};
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]);
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);
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);
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);
242 writeRow(context.buffer, context.attrNames.NUM_ATTRIBUTES, node, offsets, values);