MorphologicalAttributeFilters
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/MorphologicalTree.hpp"
8
9#include <algorithm>
10#include <array>
11#include <concepts>
12#include <cmath>
13#include <span>
14#include <string_view>
15#include <vector>
16
17namespace mmcfilters::attributes::computers {
18
19namespace detail {
20inline NodeId boundingBoxSlotOf(const MorphologicalTree&, NodeId nodeId) noexcept {
21 return nodeId;
22}
23
24struct BoundingBoxRequest {
25 bool width = false;
26 bool height = false;
27 bool rectangularity = false;
28 bool ratioWH = false;
29 bool colMin = false;
30 bool colMax = false;
31 bool rowMin = false;
32 bool rowMax = false;
33 bool diagonalLength = false;
34
35 [[nodiscard]] bool any() const noexcept {
36 return width || height || rectangularity || ratioWH || colMin ||
37 colMax || rowMin || rowMax || diagonalLength;
38 }
39
40 [[nodiscard]] bool needsAreaDependency() const noexcept {
41 return rectangularity;
42 }
43
44 [[nodiscard]] static BoundingBoxRequest from(std::span<const Attribute> requestedAttributes) {
45 return {
46 .width = contains(requestedAttributes, BOX_WIDTH),
47 .height = contains(requestedAttributes, BOX_HEIGHT),
48 .rectangularity = contains(requestedAttributes, RECTANGULARITY),
49 .ratioWH = contains(requestedAttributes, RATIO_WH),
50 .colMin = contains(requestedAttributes, BOX_COL_MIN),
51 .colMax = contains(requestedAttributes, BOX_COL_MAX),
52 .rowMin = contains(requestedAttributes, BOX_ROW_MIN),
53 .rowMax = contains(requestedAttributes, BOX_ROW_MAX),
54 .diagonalLength = contains(requestedAttributes, DIAGONAL_LENGTH)};
55 }
56
57private:
58 [[nodiscard]] static bool contains(std::span<const Attribute> requestedAttributes, Attribute attribute) {
59 return std::find(requestedAttributes.begin(), requestedAttributes.end(), attribute) != requestedAttributes.end();
60 }
61};
62}
63
88public:
90 static constexpr std::string_view familyName = "bounding-box";
91
93 static constexpr AttributeComputerFamily family = AttributeComputerFamily::BoundingBox;
94
96 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
97
101 inline static constexpr std::array<Attribute, 9> producedAttributes{
102 BOX_WIDTH,
103 BOX_HEIGHT,
104 DIAGONAL_LENGTH,
105 RECTANGULARITY,
106 RATIO_WH,
107 BOX_COL_MIN,
108 BOX_COL_MAX,
109 BOX_ROW_MIN,
110 BOX_ROW_MAX};
111
122 template <std::floating_point Real>
124 const MorphologicalTree& tree = context.tree;
125 std::span<Real> buffer = context.buffer;
126 const AttributeNames& attrNames = context.attrNames;
127 std::span<const Attribute> requestedAttributes = context.requestedAttributes;
128
130
131 auto indexOfWidth = [&](NodeId idx) { return attrNames.linearIndex(idx, BOX_WIDTH); };
132 auto indexOfHeight = [&](NodeId idx) { return attrNames.linearIndex(idx, BOX_HEIGHT); };
133 auto indexOfRectangularity = [&](NodeId idx) { return attrNames.linearIndex(idx, RECTANGULARITY); };
134 auto indexOfRatioWH = [&](NodeId idx) { return attrNames.linearIndex(idx, RATIO_WH); };
135 auto indexOfColMin = [&](NodeId idx) { return attrNames.linearIndex(idx, BOX_COL_MIN); };
136 auto indexOfColMax = [&](NodeId idx) { return attrNames.linearIndex(idx, BOX_COL_MAX); };
137 auto indexOfRowMin = [&](NodeId idx) { return attrNames.linearIndex(idx, BOX_ROW_MIN); };
138 auto indexOfRowMax = [&](NodeId idx) { return attrNames.linearIndex(idx, BOX_ROW_MAX); };
139 auto indexOfDiagonalLength = [&](NodeId idx) { return attrNames.linearIndex(idx, DIAGONAL_LENGTH); };
140
141 const detail::BoundingBoxRequest request = detail::BoundingBoxRequest::from(requestedAttributes);
142 if (!request.any()) {
143 return;
144 }
145
146 const DependencySourceT<Real>* dependencyArea = request.needsAreaDependency()
147 ? &context.dependencies.require(AREA)
148 : nullptr;
149 auto indexOfArea = [&](NodeId idx) { return dependencyArea->attrNames->linearIndex(idx, AREA); };
150
151 int n = tree.getNumInternalNodeSlots();
152 int numCols = tree.getNumColsOfImage();
153 int numRows = tree.getNumRowsOfImage();
154
155 std::vector<int> xmin(n, numCols);
156 std::vector<int> xmax(n, 0);
157 std::vector<int> ymin(n, numRows);
158 std::vector<int> ymax(n, 0);
159
160 ::mmcfilters::detail::traversePostOrder(
161 tree,
162 tree.getRoot(),
163 [&](NodeId nodeId) {
164 const NodeId idx = detail::boundingBoxSlotOf(tree, nodeId);
165 xmin[idx] = numCols;
166 xmax[idx] = 0;
167 ymin[idx] = numRows;
168 ymax[idx] = 0;
169
170 for (int p : tree.getProperParts(nodeId)) {
171 auto [y, x] = ImageUtils::to2D(p, numCols);
172 xmin[idx] = std::min(xmin[idx], x);
173 xmax[idx] = std::max(xmax[idx], x);
174 ymin[idx] = std::min(ymin[idx], y);
175 ymax[idx] = std::max(ymax[idx], y);
176 }
177 },
179 const NodeId pid = detail::boundingBoxSlotOf(tree, parentNodeId);
180 const NodeId cid = detail::boundingBoxSlotOf(tree, childNodeId);
181 xmin[pid] = std::min(xmin[pid], xmin[cid]);
182 xmax[pid] = std::max(xmax[pid], xmax[cid]);
183 ymin[pid] = std::min(ymin[pid], ymin[cid]);
184 ymax[pid] = std::max(ymax[pid], ymax[cid]);
185 },
186 [&](NodeId nodeId) {
187 const NodeId idx = detail::boundingBoxSlotOf(tree, nodeId);
188 if(request.width)
189 buffer[indexOfWidth(idx)] = static_cast<Real>(xmax[idx] - xmin[idx] + 1);
190 if(request.height)
191 buffer[indexOfHeight(idx)] = static_cast<Real>(ymax[idx] - ymin[idx] + 1);
192
193 if(request.rectangularity) {
195 Real width = static_cast<Real>(xmax[idx] - xmin[idx] + 1);
196 Real height = static_cast<Real>(ymax[idx] - ymin[idx] + 1);
197 Real denom = width * height;
199 ::mmcfilters::attributes::numeric::safeDivide(area, denom);
200 }
201 if(request.ratioWH) {
202 Real width = static_cast<Real>(xmax[idx] - xmin[idx] + 1);
203 Real height = static_cast<Real>(ymax[idx] - ymin[idx] + 1);
205 ::mmcfilters::attributes::numeric::safeDivide(std::max(width, height), std::min(width, height));
206 }
207 if(request.colMin)
208 buffer[indexOfColMin(idx)] = static_cast<Real>(xmin[idx]);
209 if(request.colMax)
210 buffer[indexOfColMax(idx)] = static_cast<Real>(xmax[idx]);
211 if(request.rowMin)
212 buffer[indexOfRowMin(idx)] = static_cast<Real>(ymin[idx]);
213 if(request.rowMax)
214 buffer[indexOfRowMax(idx)] = static_cast<Real>(ymax[idx]);
215 if(request.diagonalLength) {
216 Real width = static_cast<Real>(xmax[idx] - xmin[idx] + 1);
217 Real height = static_cast<Real>(ymax[idx] - ymin[idx] + 1);
218 buffer[indexOfDiagonalLength(idx)] =
219 ::mmcfilters::attributes::numeric::safeSqrt(width * width + height * height);
220 }
221 }
222 );
223 }
224
231 template <std::floating_point Real>
233 {
234 requireUnitAttributeBufferShape(context.tree, context.unitProperParts, context.buffer, context.attrNames);
235
236 const detail::BoundingBoxRequest request = detail::BoundingBoxRequest::from(context.requestedAttributes);
237 if (!request.any()) {
238 return;
239 }
240
241 const int numCols = context.tree.getNumColsOfImage();
242 for (NodeId leafIndex = 0; leafIndex < static_cast<NodeId>(context.unitProperParts.size()); ++leafIndex) {
243 const NodeId properPart = context.unitProperParts[static_cast<size_t>(leafIndex)];
244 const auto [row, col] = ImageUtils::to2D(properPart, numCols);
245 if (request.width) {
246 context.buffer[context.attrNames.linearIndex(leafIndex, BOX_WIDTH)] = Real{1};
247 }
248 if (request.height) {
249 context.buffer[context.attrNames.linearIndex(leafIndex, BOX_HEIGHT)] = Real{1};
250 }
251 if (request.rectangularity) {
252 context.buffer[context.attrNames.linearIndex(leafIndex, RECTANGULARITY)] = Real{1};
253 }
254 if (request.ratioWH) {
255 context.buffer[context.attrNames.linearIndex(leafIndex, RATIO_WH)] = Real{1};
256 }
257 if (request.colMin) {
258 context.buffer[context.attrNames.linearIndex(leafIndex, BOX_COL_MIN)] = static_cast<Real>(col);
259 }
260 if (request.colMax) {
261 context.buffer[context.attrNames.linearIndex(leafIndex, BOX_COL_MAX)] = static_cast<Real>(col);
262 }
263 if (request.rowMin) {
264 context.buffer[context.attrNames.linearIndex(leafIndex, BOX_ROW_MIN)] = static_cast<Real>(row);
265 }
266 if (request.rowMax) {
267 context.buffer[context.attrNames.linearIndex(leafIndex, BOX_ROW_MAX)] = static_cast<Real>(row);
268 }
269 if (request.diagonalLength) {
270 context.buffer[context.attrNames.linearIndex(leafIndex, DIAGONAL_LENGTH)] = std::sqrt(Real{2});
271 }
272 }
273 }
274
275};
276
277} // namespace mmcfilters::attributes::computers
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
Layout object that maps scalar attributes to flat-buffer offsets.
static std::pair< int, int > to2D(int index, int numCols) noexcept
Converts a row-major linear index to (row, col).
Definition Image.hpp:283
Mutable morphological tree built directly on proper parts and dense node ids.
int getNumInternalNodeSlots() const
Returns the size of the dense internal-node id domain.
NodeId getRoot() const
Returns the current hierarchy root.
int getNumColsOfImage() const
Returns the number of image columns in the attached 2D domain.
int getNumRowsOfImage() const
Returns the number of image rows in the attached 2D domain.
Computes descriptors derived from the axis-aligned bounding box of the node support.
static constexpr AttributeComputerFamily family
Stable family id used by the scheduler.
static void computeUnitRows(const UnitAttributeComputeContext< Real > &context)
Materializes bounding-box descriptors for one-pixel unit supports.
static constexpr std::string_view familyName
Family name used in dependency-plan diagnostics.
static void compute(const AttributeComputeContext< Real > &context)
Computes the requested bounding-box descriptors.
static constexpr AttributeComputerDomain domain
Execution domain required by the computer.
static constexpr std::array< Attribute, 9 > producedAttributes
Canonical list of bounding-box descriptors produced by this computer.
Owning result for one computed scalar attribute layout and buffer.