mmcfilters
Public API documentation
Loading...
Searching...
No Matches
BoundingBoxComputer.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/MorphologicalTree.hpp"
9#include "../../utils/Contract.hpp"
10#include "../../utils/Image.hpp"
11
12#include <algorithm>
13#include <array>
14#include <concepts>
15#include <cmath>
16#include <span>
17#include <string_view>
18#include <vector>
19
20namespace mmcfilters::attributes::computers {
21
22namespace detail {
29inline NodeId boundingBoxSlotOf(const MorphologicalTree&, NodeId nodeId) noexcept { return nodeId; }
30
32struct BoundingBoxRequest {
34 bool width = false;
36 bool height = false;
38 bool rectangularity = false;
40 bool ratioWH = false;
42 bool columnMin = false;
44 bool columnMax = false;
46 bool rowMin = false;
48 bool rowMax = false;
50 bool diagonalLength = false;
51
57 [[nodiscard]] bool any() const noexcept { return width || height || rectangularity || ratioWH || columnMin || columnMax || rowMin || rowMax || diagonalLength; }
58
64 [[nodiscard]] bool needsAreaDependency() const noexcept { return rectangularity; }
65
72 [[nodiscard]] static BoundingBoxRequest from(std::span<const Attribute> requestedAttributes) {
73 return {.width = contains(requestedAttributes, BoxWidth),
74 .height = contains(requestedAttributes, BoundingBoxHeight),
75 .rectangularity = contains(requestedAttributes, Rectangularity),
76 .ratioWH = contains(requestedAttributes, RatioWh),
77 .columnMin = contains(requestedAttributes, BoxColumnMin),
78 .columnMax = contains(requestedAttributes, BoxColumnMax),
79 .rowMin = contains(requestedAttributes, BoxRowMin),
80 .rowMax = contains(requestedAttributes, BoxRowMax),
81 .diagonalLength = contains(requestedAttributes, DiagonalLength)};
82 }
83
84 private:
92 [[nodiscard]] static bool contains(std::span<const Attribute> requestedAttributes, Attribute attribute) {
93 return std::find(requestedAttributes.begin(), requestedAttributes.end(), attribute) != requestedAttributes.end();
94 }
95};
96
97namespace kernel {
98
105template <std::floating_point Real>
106inline void computeBoundingBox(const AttributeComputeContext<Real>& context, const BoundingBoxRequest& request,
107 const DependencySourceT<Real>* areaDependency) {
108 if (!request.any()) {
109 return;
110 }
111
112 const int stride = context.attrNames.NUM_ATTRIBUTES;
113 const auto offsetOf = [&](Attribute attribute) { return context.attrNames.indexMap.find(attribute)->second; };
114 const int widthOffset = request.width ? offsetOf(BoxWidth) : 0;
115 const int heightOffset = request.height ? offsetOf(BoundingBoxHeight) : 0;
116 const int rectangularityOffset = request.rectangularity ? offsetOf(Rectangularity) : 0;
117 const int ratioOffset = request.ratioWH ? offsetOf(RatioWh) : 0;
118 const int columnMinOffset = request.columnMin ? offsetOf(BoxColumnMin) : 0;
119 const int columnMaxOffset = request.columnMax ? offsetOf(BoxColumnMax) : 0;
120 const int rowMinOffset = request.rowMin ? offsetOf(BoxRowMin) : 0;
121 const int rowMaxOffset = request.rowMax ? offsetOf(BoxRowMax) : 0;
122 const int diagonalOffset = request.diagonalLength ? offsetOf(DiagonalLength) : 0;
123 auto outputIndex = [&](NodeId node, int offset) { return static_cast<std::size_t>(node * stride + offset); };
124
125 const int areaStride = areaDependency != nullptr ? areaDependency->attrNames->NUM_ATTRIBUTES : 0;
126 const int areaOffset = areaDependency != nullptr ? areaDependency->attrNames->indexMap.find(Area)->second : 0;
127 auto areaIndex = [&](NodeId node) { return static_cast<std::size_t>(node * areaStride + areaOffset); };
128
129 const int numNodes = context.tree.numInternalNodeSlots();
130 const GridDomain2D& domain = ::mmcfilters::detail::CommittedTreeAccess::gridDomain2D(context.tree);
131 std::vector<int> columnMin(static_cast<std::size_t>(numNodes), domain.columns);
132 std::vector<int> columnMax(static_cast<std::size_t>(numNodes), 0);
133 std::vector<int> rowMin(static_cast<std::size_t>(numNodes), domain.rows);
134 std::vector<int> rowMax(static_cast<std::size_t>(numNodes), 0);
135
136 ::mmcfilters::detail::kernel::traversePostOrder(
137 context.tree, context.tree.root(),
138 [&](NodeId node) {
139 for (PixelId pixel : ::mmcfilters::detail::CommittedTreeAccess::properParts(context.tree, node)) {
140 const int row = pixel / domain.columns;
141 const int column = pixel % domain.columns;
142 columnMin[static_cast<std::size_t>(node)] = std::min(columnMin[static_cast<std::size_t>(node)], column);
143 columnMax[static_cast<std::size_t>(node)] = std::max(columnMax[static_cast<std::size_t>(node)], column);
144 rowMin[static_cast<std::size_t>(node)] = std::min(rowMin[static_cast<std::size_t>(node)], row);
145 rowMax[static_cast<std::size_t>(node)] = std::max(rowMax[static_cast<std::size_t>(node)], row);
146 }
147 },
148 [&](NodeId parent, NodeId child) {
149 columnMin[static_cast<std::size_t>(parent)] =
150 std::min(columnMin[static_cast<std::size_t>(parent)], columnMin[static_cast<std::size_t>(child)]);
151 columnMax[static_cast<std::size_t>(parent)] =
152 std::max(columnMax[static_cast<std::size_t>(parent)], columnMax[static_cast<std::size_t>(child)]);
153 rowMin[static_cast<std::size_t>(parent)] = std::min(rowMin[static_cast<std::size_t>(parent)], rowMin[static_cast<std::size_t>(child)]);
154 rowMax[static_cast<std::size_t>(parent)] = std::max(rowMax[static_cast<std::size_t>(parent)], rowMax[static_cast<std::size_t>(child)]);
155 },
156 [&](NodeId node) {
157 const Real width = static_cast<Real>(columnMax[static_cast<std::size_t>(node)] - columnMin[static_cast<std::size_t>(node)] + 1);
158 const Real height = static_cast<Real>(rowMax[static_cast<std::size_t>(node)] - rowMin[static_cast<std::size_t>(node)] + 1);
159 if (request.width)
160 context.buffer[outputIndex(node, widthOffset)] = width;
161 if (request.height)
162 context.buffer[outputIndex(node, heightOffset)] = height;
163 if (request.rectangularity)
164 context.buffer[outputIndex(node, rectangularityOffset)] =
165 ::mmcfilters::attributes::numeric::safeDivide(areaDependency->buffer[areaIndex(node)], width * height);
166 if (request.ratioWH)
167 context.buffer[outputIndex(node, ratioOffset)] =
168 ::mmcfilters::attributes::numeric::safeDivide(std::max(width, height), std::min(width, height));
169 if (request.columnMin)
170 context.buffer[outputIndex(node, columnMinOffset)] = static_cast<Real>(columnMin[static_cast<std::size_t>(node)]);
171 if (request.columnMax)
172 context.buffer[outputIndex(node, columnMaxOffset)] = static_cast<Real>(columnMax[static_cast<std::size_t>(node)]);
173 if (request.rowMin)
174 context.buffer[outputIndex(node, rowMinOffset)] = static_cast<Real>(rowMin[static_cast<std::size_t>(node)]);
175 if (request.rowMax)
176 context.buffer[outputIndex(node, rowMaxOffset)] = static_cast<Real>(rowMax[static_cast<std::size_t>(node)]);
177 if (request.diagonalLength)
178 context.buffer[outputIndex(node, diagonalOffset)] = ::mmcfilters::attributes::numeric::safeSqrt(width * width + height * height);
179 });
180}
181
182} // namespace kernel
183
184template <std::floating_point Real>
185inline void validateBoundingBoxContext(const AttributeComputeContext<Real>& context) {
186 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
187 requireRequestedAttributeColumns(context);
188 static_cast<void>(context.tree.requireGridDomain2D("BoundingBoxComputer"));
189}
190} // namespace detail
191
216 public:
218 static constexpr std::string_view familyName = "bounding-box";
219
221 static constexpr AttributeComputerFamily family = AttributeComputerFamily::BoundingBox;
222
224 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
225
229 inline static constexpr std::array<Attribute, 9> producedAttributes{BoxWidth, BoundingBoxHeight, DiagonalLength, Rectangularity, RatioWh,
230 BoxColumnMin, BoxColumnMax, BoxRowMin, BoxRowMax};
231
244 template <std::floating_point Real> static void compute(const AttributeComputeContext<Real>& context) {
245 const detail::BoundingBoxRequest request = detail::BoundingBoxRequest::from(context.requestedAttributes);
246 MMCFILTERS_CONTRACT_CHECKED_ONLY(detail::validateBoundingBoxContext(context));
247
249 if (request.needsAreaDependency()) {
250 if constexpr (contract::validationsEnabled) {
251 areaDependency = &context.dependencies.require(Area);
252 } else {
254 }
255 }
256 detail::kernel::computeBoundingBox(context, request, areaDependency);
257 }
258
267 template <std::floating_point Real> static void computeUnitRows(const UnitAttributeComputeContext<Real>& context) {
268 requireUnitAttributeBufferShape(context.tree, context.unitPixels, context.buffer, context.attrNames);
269
270 const detail::BoundingBoxRequest request = detail::BoundingBoxRequest::from(context.requestedAttributes);
271 if (!request.any()) {
272 return;
273 }
274
275 const int numColumns = context.tree.numColumns();
276 for (NodeId leafIndex = 0; leafIndex < static_cast<NodeId>(context.unitPixels.size()); ++leafIndex) {
277 const PixelId pixel = context.unitPixels[static_cast<size_t>(leafIndex)];
278 const auto [row, column] = ImageUtils::to2D(pixel, numColumns);
279 if (request.width) {
280 context.buffer[context.attrNames.linearIndex(leafIndex, BoxWidth)] = Real{1};
281 }
282 if (request.height) {
283 context.buffer[context.attrNames.linearIndex(leafIndex, BoundingBoxHeight)] = Real{1};
284 }
285 if (request.rectangularity) {
286 context.buffer[context.attrNames.linearIndex(leafIndex, Rectangularity)] = Real{1};
287 }
288 if (request.ratioWH) {
289 context.buffer[context.attrNames.linearIndex(leafIndex, RatioWh)] = Real{1};
290 }
291 if (request.columnMin) {
292 context.buffer[context.attrNames.linearIndex(leafIndex, BoxColumnMin)] = static_cast<Real>(column);
293 }
294 if (request.columnMax) {
295 context.buffer[context.attrNames.linearIndex(leafIndex, BoxColumnMax)] = static_cast<Real>(column);
296 }
297 if (request.rowMin) {
298 context.buffer[context.attrNames.linearIndex(leafIndex, BoxRowMin)] = static_cast<Real>(row);
299 }
300 if (request.rowMax) {
301 context.buffer[context.attrNames.linearIndex(leafIndex, BoxRowMax)] = static_cast<Real>(row);
302 }
303 if (request.diagonalLength) {
304 context.buffer[context.attrNames.linearIndex(leafIndex, DiagonalLength)] = std::sqrt(Real{2});
305 }
306 }
307 }
308};
309
310} // 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
Computes descriptors derived from the axis-aligned bounding box of the node support.
static void computeUnitRows(const UnitAttributeComputeContext< Real > &context)
Materializes bounding-box descriptors for one-pixel unit supports.
static void compute(const AttributeComputeContext< Real > &context)
Computes the requested bounding-box descriptors.
Owning result for one computed scalar attribute layout and buffer.
std::vector< Real > second
Flat per-node attribute buffer indexed through first.