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/TreeKernelAccess.hpp"
7#include "../../trees/MorphologicalTree.hpp"
8#include "../../utils/Contract.hpp"
9#include "../../utils/Image.hpp"
10
11#include <algorithm>
12#include <array>
13#include <concepts>
14#include <cmath>
15#include <span>
16#include <string_view>
17#include <vector>
18
19namespace mmcfilters::attributes::computers {
20
21namespace detail {
28inline NodeId boundingBoxSlotOf(const MorphologicalTree&, NodeId nodeId) noexcept { return nodeId; }
29
31struct BoundingBoxRequest {
33 bool width = false;
35 bool height = false;
37 bool rectangularity = false;
39 bool ratioWH = false;
41 bool columnMin = false;
43 bool columnMax = false;
45 bool rowMin = false;
47 bool rowMax = false;
49 bool diagonalLength = false;
50
56 [[nodiscard]] bool any() const noexcept { return width || height || rectangularity || ratioWH || columnMin || columnMax || rowMin || rowMax || diagonalLength; }
57
63 [[nodiscard]] bool needsAreaDependency() const noexcept { return rectangularity; }
64
71 [[nodiscard]] static BoundingBoxRequest from(std::span<const Attribute> requestedAttributes) {
72 return {.width = contains(requestedAttributes, BoundingBoxWidth),
73 .height = contains(requestedAttributes, BoundingBoxHeight),
74 .rectangularity = contains(requestedAttributes, Rectangularity),
75 .ratioWH = contains(requestedAttributes, RatioWh),
76 .columnMin = contains(requestedAttributes, BoxColumnMin),
77 .columnMax = contains(requestedAttributes, BoxColumnMax),
78 .rowMin = contains(requestedAttributes, BoxRowMin),
79 .rowMax = contains(requestedAttributes, BoxRowMax),
80 .diagonalLength = contains(requestedAttributes, DiagonalLength)};
81 }
82
83 private:
91 [[nodiscard]] static bool contains(std::span<const Attribute> requestedAttributes, Attribute attribute) {
92 return std::find(requestedAttributes.begin(), requestedAttributes.end(), attribute) != requestedAttributes.end();
93 }
94};
95
96namespace kernel {
97
104template <std::floating_point Real>
105inline void computeBoundingBox(const AttributeComputeContext<Real>& context, const BoundingBoxRequest& request,
106 const DependencySourceT<Real>* areaDependency) {
107 if (!request.any()) {
108 return;
109 }
110
111 const int stride = context.attrNames.NUM_ATTRIBUTES;
112 const auto offsetOf = [&](Attribute attribute) { return context.attrNames.indexMap.find(attribute)->second; };
113 const int widthOffset = request.width ? offsetOf(BoundingBoxWidth) : 0;
114 const int heightOffset = request.height ? offsetOf(BoundingBoxHeight) : 0;
115 const int rectangularityOffset = request.rectangularity ? offsetOf(Rectangularity) : 0;
116 const int ratioOffset = request.ratioWH ? offsetOf(RatioWh) : 0;
117 const int columnMinOffset = request.columnMin ? offsetOf(BoxColumnMin) : 0;
118 const int columnMaxOffset = request.columnMax ? offsetOf(BoxColumnMax) : 0;
119 const int rowMinOffset = request.rowMin ? offsetOf(BoxRowMin) : 0;
120 const int rowMaxOffset = request.rowMax ? offsetOf(BoxRowMax) : 0;
121 const int diagonalOffset = request.diagonalLength ? offsetOf(DiagonalLength) : 0;
122 auto outputIndex = [&](NodeId node, int offset) { return static_cast<std::size_t>(node * stride + offset); };
123
124 const int areaStride = areaDependency != nullptr ? areaDependency->attrNames->NUM_ATTRIBUTES : 0;
125 const int areaOffset = areaDependency != nullptr ? areaDependency->attrNames->indexMap.find(Area)->second : 0;
126 auto areaIndex = [&](NodeId node) { return static_cast<std::size_t>(node * areaStride + areaOffset); };
127
128 const int numNodes = context.tree.numInternalNodeSlots();
129 const GridDomain2D& domain = ::mmcfilters::detail::TreeKernelAccess::gridDomain2D(context.tree);
130 std::vector<int> columnMin(static_cast<std::size_t>(numNodes), domain.columns);
131 std::vector<int> columnMax(static_cast<std::size_t>(numNodes), 0);
132 std::vector<int> rowMin(static_cast<std::size_t>(numNodes), domain.rows);
133 std::vector<int> rowMax(static_cast<std::size_t>(numNodes), 0);
134
135 ::mmcfilters::detail::TreeKernelAccess::traversePostOrder(
136 context.tree, context.tree.root(),
137 [&](NodeId node) {
138 for (PixelId pixel : ::mmcfilters::detail::TreeKernelAccess::properParts(context.tree, node)) {
139 const int row = pixel / domain.columns;
140 const int column = pixel % domain.columns;
141 columnMin[static_cast<std::size_t>(node)] = std::min(columnMin[static_cast<std::size_t>(node)], column);
142 columnMax[static_cast<std::size_t>(node)] = std::max(columnMax[static_cast<std::size_t>(node)], column);
143 rowMin[static_cast<std::size_t>(node)] = std::min(rowMin[static_cast<std::size_t>(node)], row);
144 rowMax[static_cast<std::size_t>(node)] = std::max(rowMax[static_cast<std::size_t>(node)], row);
145 }
146 },
147 [&](NodeId parent, NodeId child) {
148 columnMin[static_cast<std::size_t>(parent)] =
149 std::min(columnMin[static_cast<std::size_t>(parent)], columnMin[static_cast<std::size_t>(child)]);
150 columnMax[static_cast<std::size_t>(parent)] =
151 std::max(columnMax[static_cast<std::size_t>(parent)], columnMax[static_cast<std::size_t>(child)]);
152 rowMin[static_cast<std::size_t>(parent)] = std::min(rowMin[static_cast<std::size_t>(parent)], rowMin[static_cast<std::size_t>(child)]);
153 rowMax[static_cast<std::size_t>(parent)] = std::max(rowMax[static_cast<std::size_t>(parent)], rowMax[static_cast<std::size_t>(child)]);
154 },
155 [&](NodeId node) {
156 const Real width = static_cast<Real>(columnMax[static_cast<std::size_t>(node)] - columnMin[static_cast<std::size_t>(node)] + 1);
157 const Real height = static_cast<Real>(rowMax[static_cast<std::size_t>(node)] - rowMin[static_cast<std::size_t>(node)] + 1);
158 if (request.width)
159 context.buffer[outputIndex(node, widthOffset)] = width;
160 if (request.height)
161 context.buffer[outputIndex(node, heightOffset)] = height;
162 if (request.rectangularity)
163 context.buffer[outputIndex(node, rectangularityOffset)] =
164 ::mmcfilters::attributes::numeric::safeDivide(areaDependency->buffer[areaIndex(node)], width * height);
165 if (request.ratioWH)
166 context.buffer[outputIndex(node, ratioOffset)] =
167 ::mmcfilters::attributes::numeric::safeDivide(std::max(width, height), std::min(width, height));
168 if (request.columnMin)
169 context.buffer[outputIndex(node, columnMinOffset)] = static_cast<Real>(columnMin[static_cast<std::size_t>(node)]);
170 if (request.columnMax)
171 context.buffer[outputIndex(node, columnMaxOffset)] = static_cast<Real>(columnMax[static_cast<std::size_t>(node)]);
172 if (request.rowMin)
173 context.buffer[outputIndex(node, rowMinOffset)] = static_cast<Real>(rowMin[static_cast<std::size_t>(node)]);
174 if (request.rowMax)
175 context.buffer[outputIndex(node, rowMaxOffset)] = static_cast<Real>(rowMax[static_cast<std::size_t>(node)]);
176 if (request.diagonalLength)
177 context.buffer[outputIndex(node, diagonalOffset)] = ::mmcfilters::attributes::numeric::safeSqrt(width * width + height * height);
178 });
179}
180
181} // namespace kernel
182
183template <std::floating_point Real>
184inline void validateBoundingBoxContext(const AttributeComputeContext<Real>& context) {
185 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
186 requireRequestedAttributeColumns(context);
187 static_cast<void>(context.tree.requireGridDomain2D("BoundingBoxComputer"));
188}
189} // namespace detail
190
215 public:
217 static constexpr std::string_view familyName = "bounding-box";
218
220 static constexpr AttributeComputerFamily family = AttributeComputerFamily::BoundingBox;
221
223 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
224
228 inline static constexpr std::array<Attribute, 9> producedAttributes{BoundingBoxWidth, BoundingBoxHeight, DiagonalLength, Rectangularity, RatioWh,
229 BoxColumnMin, BoxColumnMax, BoxRowMin, BoxRowMax};
230
243 template <std::floating_point Real> static void compute(const AttributeComputeContext<Real>& context) {
244 const detail::BoundingBoxRequest request = detail::BoundingBoxRequest::from(context.requestedAttributes);
245 MMCFILTERS_CONTRACT_CHECKED_ONLY(detail::validateBoundingBoxContext(context));
246
248 if (request.needsAreaDependency()) {
249 if constexpr (contract::validationsEnabled) {
250 areaDependency = &context.dependencies.require(Area);
251 } else {
253 }
254 }
255 detail::kernel::computeBoundingBox(context, request, areaDependency);
256 }
257
266 template <std::floating_point Real> static void computeUnitRows(const UnitAttributeComputeContext<Real>& context) {
267 requireUnitAttributeBufferShape(context.tree, context.unitPixels, context.buffer, context.attrNames);
268 computeUnitRowsEstablished(context);
269 }
270
271 private:
278 template <class Computer, class Context> friend void mmcfilters::detail::kernel::computeUnitRows(const Context& context);
279
281 template <std::floating_point Real> static void computeUnitRowsEstablished(const UnitAttributeComputeContext<Real>& context) {
282
283 const detail::BoundingBoxRequest request = detail::BoundingBoxRequest::from(context.requestedAttributes);
284 if (!request.any()) {
285 return;
286 }
287
288 const int numColumns = context.tree.numColumns();
289 for (NodeId leafIndex = 0; leafIndex < static_cast<NodeId>(context.unitPixels.size()); ++leafIndex) {
290 const PixelId pixel = context.unitPixels[static_cast<size_t>(leafIndex)];
291 const auto [row, column] = ImageUtils::to2D(pixel, numColumns);
292 if (request.width) {
293 context.buffer[context.attrNames.linearIndex(leafIndex, BoundingBoxWidth)] = Real{1};
294 }
295 if (request.height) {
296 context.buffer[context.attrNames.linearIndex(leafIndex, BoundingBoxHeight)] = Real{1};
297 }
298 if (request.rectangularity) {
299 context.buffer[context.attrNames.linearIndex(leafIndex, Rectangularity)] = Real{1};
300 }
301 if (request.ratioWH) {
302 context.buffer[context.attrNames.linearIndex(leafIndex, RatioWh)] = Real{1};
303 }
304 if (request.columnMin) {
305 context.buffer[context.attrNames.linearIndex(leafIndex, BoxColumnMin)] = static_cast<Real>(column);
306 }
307 if (request.columnMax) {
308 context.buffer[context.attrNames.linearIndex(leafIndex, BoxColumnMax)] = static_cast<Real>(column);
309 }
310 if (request.rowMin) {
311 context.buffer[context.attrNames.linearIndex(leafIndex, BoxRowMin)] = static_cast<Real>(row);
312 }
313 if (request.rowMax) {
314 context.buffer[context.attrNames.linearIndex(leafIndex, BoxRowMax)] = static_cast<Real>(row);
315 }
316 if (request.diagonalLength) {
317 context.buffer[context.attrNames.linearIndex(leafIndex, DiagonalLength)] = std::sqrt(Real{2});
318 }
319 }
320 }
321};
322
323} // 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.