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"
19namespace mmcfilters::attributes::computers {
28inline NodeId boundingBoxSlotOf(
const MorphologicalTree&, NodeId nodeId)
noexcept {
return nodeId; }
31struct BoundingBoxRequest {
37 bool rectangularity =
false;
41 bool columnMin =
false;
43 bool columnMax =
false;
49 bool diagonalLength =
false;
56 [[nodiscard]]
bool any() const noexcept {
return width || height || rectangularity || ratioWH || columnMin || columnMax || rowMin || rowMax || diagonalLength; }
63 [[nodiscard]]
bool needsAreaDependency() const noexcept {
return rectangularity; }
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)};
91 [[nodiscard]]
static bool contains(std::span<const Attribute> requestedAttributes, Attribute attribute) {
92 return std::find(requestedAttributes.begin(), requestedAttributes.end(), attribute) != requestedAttributes.end();
104template <std::
floating_po
int Real>
105inline void computeBoundingBox(
const AttributeComputeContext<Real>& context,
const BoundingBoxRequest& request,
106 const DependencySourceT<Real>* areaDependency) {
107 if (!request.any()) {
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); };
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); };
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);
135 ::mmcfilters::detail::TreeKernelAccess::traversePostOrder(
136 context.tree, context.tree.root(),
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);
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)]);
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);
159 context.buffer[outputIndex(node, widthOffset)] = width;
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);
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)]);
173 context.buffer[outputIndex(node, rowMinOffset)] =
static_cast<Real
>(rowMin[
static_cast<std::size_t
>(node)]);
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);
183template <std::
floating_po
int 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"));
217 static constexpr std::string_view familyName =
"bounding-box";
220 static constexpr AttributeComputerFamily family = AttributeComputerFamily::BoundingBox;
223 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Topology;
228 inline static constexpr std::array<Attribute, 9> producedAttributes{BoundingBoxWidth, BoundingBoxHeight, DiagonalLength, Rectangularity, RatioWh,
229 BoxColumnMin, BoxColumnMax, BoxRowMin, BoxRowMax};
244 const detail::BoundingBoxRequest
request = detail::BoundingBoxRequest::from(
context.requestedAttributes);
248 if (
request.needsAreaDependency()) {
249 if constexpr (contract::validationsEnabled) {
268 computeUnitRowsEstablished(
context);
278 template <
class Computer,
class Context>
friend void mmcfilters::detail::kernel::computeUnitRows(
const Context&
context);
283 const detail::BoundingBoxRequest
request = detail::BoundingBoxRequest::from(
context.requestedAttributes);
288 const int numColumns =
context.tree.numColumns();
291 const auto [row, column] = ImageUtils::to2D(pixel, numColumns);
295 if (request.height) {
296 context.buffer[context.attrNames.linearIndex(leafIndex, BoundingBoxHeight)] = Real{1};
298 if (request.rectangularity) {
299 context.buffer[context.attrNames.linearIndex(leafIndex, Rectangularity)] = Real{1};
301 if (request.ratioWH) {
302 context.buffer[context.attrNames.linearIndex(leafIndex, RatioWh)] = Real{1};
304 if (request.columnMin) {
305 context.buffer[context.attrNames.linearIndex(leafIndex, BoxColumnMin)] =
static_cast<Real
>(column);
307 if (request.columnMax) {
308 context.buffer[context.attrNames.linearIndex(leafIndex, BoxColumnMax)] =
static_cast<Real
>(column);
310 if (request.rowMin) {
311 context.buffer[context.attrNames.linearIndex(leafIndex, BoxRowMin)] =
static_cast<Real
>(row);
313 if (request.rowMax) {
314 context.buffer[context.attrNames.linearIndex(leafIndex, BoxRowMax)] =
static_cast<Real
>(row);
316 if (request.diagonalLength) {
317 context.buffer[context.attrNames.linearIndex(leafIndex, DiagonalLength)] = std::sqrt(Real{2});
int NodeId
Node identifier type used throughout the project.
#define MMCFILTERS_CONTRACT_CHECKED_ONLY(...)
Executes validation statements only when defensive checks are enabled.
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.