474 using AdjacencyUC = detail::AdjacencyUC;
476 using DiagonalConnection = detail::DiagonalConnection;
478 using PriorityQueueToS = detail::PriorityQueueToS;
483 std::vector<ToSFloodDepth> treeLevel;
485 std::vector<ToSGrayLevel> grayLevel;
487 std::vector<PixelId> order;
489 AdjacencyUC adjacency;
493 struct CanonicalFloodTree {
495 std::vector<ToSFloodDepth> treeLevel;
497 std::vector<ToSGrayLevel> grayLevel;
499 std::vector<PixelId> order;
501 std::vector<PixelId> parent;
507 detail::TopographicImmersionMode immersionMode_ = detail::TopographicImmersionMode::SelfDualSpan;
517 throw std::invalid_argument(
"Tree-of-shapes infinity pixel must be non-negative.");
519 if (std::holds_alternative<SelfDualSpanImmersion>(
convention.immersion)) {
520 return detail::TopographicImmersionMode::SelfDualSpan;
522 const auto&
adjacencies = std::get<ComplementaryGridImmersion>(
convention.immersion).complementaryAdjacencies;
524 return detail::TopographicImmersionMode::Min4Max8;
527 return detail::TopographicImmersionMode::Min8Max4;
529 throw std::invalid_argument(
"Complementary-grid immersion requires minimum/maximum adjacencies in the canonical 4/8 or 8/4 pairing.");
535 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-null image.");
537 if (
const auto* immersion = std::get_if<ComplementaryGridImmersion>(&convention_.
immersion)) {
538 const auto&
adjacencies = immersion->complementaryAdjacencies;
539 const int rows =
image->getNumRows();
540 const int columns =
image->getNumColumns();
541 if (
adjacencies.minAdjacency.getNumRows() != rows ||
adjacencies.minAdjacency.getNumColumns() != columns ||
543 throw std::invalid_argument(
"Tree-of-shapes complementary adjacencies must match the source image domain.");
546 static_cast<void>(validatedInfinityPixel(interpolatedNumRows(
image->getNumRows()), interpolatedNumColumns(
image->getNumColumns())));
554 inline bool usesConnectivityMap()
const noexcept {
555 return immersionMode_ != detail::TopographicImmersionMode::SelfDualSpan;
563 inline bool usesHighDiagonalAtSaddle()
const noexcept {
return immersionMode_ == detail::TopographicImmersionMode::Min4Max8; }
570 inline bool usesExteriorPadding()
const noexcept {
return convention_.
domainExtension == TopographicDomainExtension::ExteriorRing; }
577 inline int interpolationOffset()
const noexcept {
return usesExteriorPadding() ? TopographicInterpolationPadding : 0; }
585 inline int checkedInterpolatedExtent(
int extent)
const {
587 throw std::invalid_argument(
"TreeOfShapesProducer requires positive image dimensions.");
589 const std::int64_t
interpolated = TopographicInterpolationScale *
static_cast<std::int64_t
>(
extent) + (usesExteriorPadding() ? 1 : -1);
591 throw std::overflow_error(
"Tree-of-shapes interpolated dimension exceeds int range.");
602 inline int interpolatedNumRows(
int numRows)
const {
return checkedInterpolatedExtent(numRows); }
610 inline int interpolatedNumColumns(
int numColumns)
const {
return checkedInterpolatedExtent(numColumns); }
619 inline int checkedDomainSize(
int numRows,
int numColumns)
const {
621 throw std::overflow_error(
"Tree-of-shapes domain size exceeds int range.");
623 return numRows * numColumns;
632 inline int originalPointRow(
int row)
const noexcept {
return TopographicInterpolationScale * row + interpolationOffset(); }
640 inline int originalPointColumn(
int column)
const noexcept {
return TopographicInterpolationScale * column + interpolationOffset(); }
649 return static_cast<ToSGrayLevel>(TopographicInterpolationScale *
static_cast<int>(value));
662 throw std::invalid_argument(
"Tree-of-shapes infinity pixel must belong to the active topographic domain.");
674 inline void setDiagonal0Connection(AdjacencyUC&
adj,
int row,
int column)
const {
675 adj.setDiagonalConnection(row, column - 1, DiagonalConnection::Se);
676 adj.setDiagonalConnection(row + 1, column, DiagonalConnection::Nw);
678 adj.setDiagonalConnection(row - 1, column - 1, DiagonalConnection::Se);
679 adj.setDiagonalConnection(row, column, DiagonalConnection::Se | DiagonalConnection::Nw);
680 adj.setDiagonalConnection(row + 1, column + 1, DiagonalConnection::Nw);
682 adj.setDiagonalConnection(row - 1, column, DiagonalConnection::Se);
683 adj.setDiagonalConnection(row, column + 1, DiagonalConnection::Nw);
693 inline void setDiagonal1Connection(AdjacencyUC&
adj,
int row,
int column)
const {
694 adj.setDiagonalConnection(row, column - 1, DiagonalConnection::Ne);
695 adj.setDiagonalConnection(row - 1, column, DiagonalConnection::Sw);
697 adj.setDiagonalConnection(row - 1, column + 1, DiagonalConnection::Sw);
698 adj.setDiagonalConnection(row, column, DiagonalConnection::Sw | DiagonalConnection::Ne);
699 adj.setDiagonalConnection(row + 1, column - 1, DiagonalConnection::Ne);
701 adj.setDiagonalConnection(row + 1, column, DiagonalConnection::Ne);
702 adj.setDiagonalConnection(row, column + 1, DiagonalConnection::Sw);
705 std::tuple<std::vector<ToSGrayLevel>, std::vector<ToSGrayLevel>, AdjacencyUC>
720 throw std::logic_error(
"Tree-of-shapes padded immersion cannot be cropped.");
724 const int size = checkedDomainSize(rows, columns);
725 std::vector<ToSGrayLevel>
croppedMin(
static_cast<std::size_t
>(size));
726 std::vector<ToSGrayLevel>
croppedMax(
static_cast<std::size_t
>(size));
729 for (
int row = 0; row < rows; ++row) {
730 for (
int column = 0; column < columns; ++column) {
733 croppedMin[
static_cast<std::size_t
>(target)] =
paddedMin[
static_cast<std::size_t
>(source)];
734 croppedMax[
static_cast<std::size_t
>(target)] =
paddedMax[
static_cast<std::size_t
>(source)];
753 : convention_(std::
move(
convention)), immersionMode_(validateConvention(convention_)) {}
780 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-null image.");
782 if (!usesExteriorPadding()) {
784 paddedConvention.domainExtension = TopographicDomainExtension::ExteriorRing;
793 int numRows =
imgPtr->getNumRows();
794 int numColumns =
imgPtr->getNumColumns();
795 if (numRows <= 0 || numColumns <= 0) {
796 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-empty image.");
798 if (numRows == 1 && numColumns == 1) {
833 for (
PixelId pixel = 0; pixel < numColumns * numRows; ++pixel) {
837 if (row == 0 || row == numRows - 1 || column == 0 || column == numColumns - 1) {
851 for (
int value = 0; value < 256; ++value) {
857 throw std::runtime_error(
"Tree-of-shapes boundary histogram is inconsistent.");
870 const int*
adjRow =
nullptr;
876 if (column % 2 == 1 && row % 2 == 1)
883 if (column % 2 == 0 && row % 2 == 0) {
887 }
else if (column % 2 == 0 && row % 2 == 1) {
891 }
else if (column % 2 == 1 && row % 2 == 0) {
897 min = std::numeric_limits<int>::max();
898 max = std::numeric_limits<int>::min();
940 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-null image.");
942 if (!usesExteriorPadding()) {
943 TopographicConvention paddedConvention = convention_;
944 paddedConvention.
domainExtension = TopographicDomainExtension::ExteriorRing;
945 paddedConvention.infinityPixel = 0;
946 TreeOfShapesProducer paddedProducer(std::move(paddedConvention));
947 auto [paddedMin, paddedMax, paddedAdjacency] = paddedProducer.interpolateImage4c8c(imgPtr);
948 const int paddedRows = paddedProducer.interpolatedNumRows(imgPtr->getNumRows());
949 const int paddedColumns = paddedProducer.interpolatedNumColumns(imgPtr->getNumColumns());
950 return cropExteriorInterpolation(std::move(paddedMin), std::move(paddedMax), std::move(paddedAdjacency), paddedRows, paddedColumns,
true);
952 auto img = imgPtr->rawData();
953 int numRows = imgPtr->getNumRows();
954 int numColumns = imgPtr->getNumColumns();
955 if (numRows <= 0 || numColumns <= 0) {
956 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-empty image.");
959 int interpNumColumns = interpolatedNumColumns(numColumns);
960 int interpNumRows = interpolatedNumRows(numRows);
961 int size = checkedDomainSize(interpNumRows, interpNumColumns);
962 AdjacencyUC adj(interpNumRows, interpNumColumns,
true);
965 std::vector<ToSGrayLevel> interpolationMin(size);
966 std::vector<ToSGrayLevel> interpolationMax(size);
970 for (PixelId pixel = 0; pixel < numColumns * numRows; ++pixel) {
974 pT =
ImageUtils::to1D(originalPointRow(row), originalPointColumn(column), interpNumColumns);
977 interpolationMin[pT] = interpolationMax[pT] = img[pixel];
980 auto getValue = [&](
int row,
int column) ->
int {
981 int origRow = (row - 1) / 2;
982 int originalColumn = (column - 1) / 2;
987 for (
int row = 0; row < interpNumRows; row++) {
991 int v1 = getValue(row, column + 1);
995 column = interpNumColumns - 1;
996 v1 = getValue(row, column - 1);
1002 int v1 = getValue(row + 1, column + 1);
1006 column = interpNumColumns - 1;
1007 v1 = getValue(row + 1, column - 1);
1011 }
else if (row == interpNumRows - 1) {
1013 int v1 = getValue(row - 1, 1);
1017 column = interpNumColumns - 1;
1018 v1 = getValue(row - 1, column - 1);
1023 int v1 = getValue(row - 1, column + 1);
1024 int v2 = getValue(row + 1, column + 1);
1025 interpolationMin[
ImageUtils::to1D(row, 0, interpNumColumns)] = std::min(v1, v2);
1026 interpolationMax[
ImageUtils::to1D(row, 0, interpNumColumns)] = std::max(v1, v2);
1028 column = interpNumColumns - 1;
1029 v1 = getValue(row - 1, column - 1);
1030 v2 = getValue(row + 1, column - 1);
1031 interpolationMin[
ImageUtils::to1D(row, column, interpNumColumns)] = std::min(v1, v2);
1032 interpolationMax[
ImageUtils::to1D(row, column, interpNumColumns)] = std::max(v1, v2);
1037 for (
int column = 1; column < interpNumColumns - 1; column++) {
1039 if (column % 2 == 1) {
1041 int v1 = getValue(row + 1, column);
1045 row = interpNumRows - 1;
1046 v1 = getValue(row - 1, column);
1051 int v1 = getValue(row + 1, column - 1);
1052 int v2 = getValue(row + 1, column + 1);
1053 interpolationMin[
ImageUtils::to1D(row, column, interpNumColumns)] = std::min(v1, v2);
1054 interpolationMax[
ImageUtils::to1D(row, column, interpNumColumns)] = std::max(v1, v2);
1056 row = interpNumRows - 1;
1057 v1 = getValue(row - 1, column - 1);
1058 v2 = getValue(row - 1, column + 1);
1059 interpolationMin[
ImageUtils::to1D(row, column, interpNumColumns)] = std::min(v1, v2);
1060 interpolationMax[
ImageUtils::to1D(row, column, interpNumColumns)] = std::max(v1, v2);
1065 for (
int row = 1; row < interpNumRows - 1; row++) {
1066 for (
int column = 1; column < interpNumColumns - 1; column++) {
1067 if (row % 2 == 1 && column % 2 == 1)
1071 if (column % 2 == 0 && row % 2 == 1) {
1072 int v1 = getValue(row, column + 1);
1073 int v2 = getValue(row, column - 1);
1074 interpolationMin[pT] = std::min(v1, v2);
1075 interpolationMax[pT] = std::max(v1, v2);
1076 }
else if (column % 2 == 1 && row % 2 == 0) {
1077 int v1 = getValue(row + 1, column);
1078 int v2 = getValue(row - 1, column);
1079 interpolationMin[pT] = std::min(v1, v2);
1080 interpolationMax[pT] = std::max(v1, v2);
1085 for (
int row = 1; row < interpNumRows - 1; row++) {
1086 for (
int column = 1; column < interpNumColumns - 1; column++) {
1087 if (row % 2 == 1 && column % 2 == 1)
1090 if (row % 2 == 0 && column % 2 == 0) {
1093 int v0 = getValue(row - 1, column - 1);
1094 int v1 = getValue(row + 1, column - 1);
1095 int v2 = getValue(row - 1, column + 1);
1096 int v3 = getValue(row + 1, column + 1);
1098 const int min_v0v3 = std::min(v0, v3);
1099 const int max_v0v3 = std::max(v0, v3);
1100 const int min_v1v2 = std::min(v1, v2);
1101 const int max_v1v2 = std::max(v1, v2);
1102 const bool diagonal0IsHigh = min_v0v3 > max_v1v2;
1103 const bool diagonal1IsHigh = min_v1v2 > max_v0v3;
1105 if (diagonal0IsHigh || diagonal1IsHigh) {
1106 const bool chooseDiagonal0 = usesHighDiagonalAtSaddle() ? diagonal0IsHigh : !diagonal0IsHigh;
1107 if (chooseDiagonal0) {
1108 setDiagonal0Connection(adj, row, column);
1109 interpolationMin[pT] = min_v0v3;
1110 interpolationMax[pT] = max_v0v3;
1112 setDiagonal1Connection(adj, row, column);
1113 interpolationMin[pT] = min_v1v2;
1114 interpolationMax[pT] = max_v1v2;
1118 interpolationMin[pT] = std::min({v0, v1, v2, v3});
1119 interpolationMax[pT] = std::max({v0, v1, v2, v3});
1124 return std::make_tuple(std::move(interpolationMin), std::move(interpolationMax), std::move(adj));
1134 FloodResult floodImage(
const ImageUInt8Ptr& imgPtr)
const {
1136 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-null image.");
1139 int numRows = imgPtr->getNumRows();
1140 int numColumns = imgPtr->getNumColumns();
1142 int interpNumColumns = interpolatedNumColumns(numColumns);
1143 int interpNumRows = interpolatedNumRows(numRows);
1144 int size = checkedDomainSize(interpNumRows, interpNumColumns);
1145 const bool connectivityMap = usesConnectivityMap();
1146 auto [interpolationMin, interpolationMax, adj] = connectivityMap ? interpolateImage4c8c(imgPtr) : interpolateImage(imgPtr);
1148 std::vector<uint8_t> dejavu(size, 0);
1149 std::vector<PixelId> imgR(size);
1150 std::vector<ToSFloodDepth> imgU(size);
1151 std::vector<ToSGrayLevel> grayLevel(size);
1153 PriorityQueueToS queue(connectivityMap ? ToSUInt8Depth : ToSSelfDualDepth);
1154 const PixelId infinityPixel = validatedInfinityPixel(interpNumRows, interpNumColumns);
1155 int priorityQueueOld = interpolationMin[infinityPixel];
1156 queue.initial(infinityPixel, priorityQueueOld);
1157 dejavu[infinityPixel] =
true;
1160 ToSFloodDepth depth = 0;
1161 while (!queue.isEmpty()) {
1162 const PixelId pixel = queue.priorityPop();
1163 int priorityQueue = queue.getCurrentPriority();
1164 if (connectivityMap) {
1165 if (priorityQueue != priorityQueueOld)
1167 imgU[pixel] = depth;
1169 imgU[pixel] =
static_cast<ToSFloodDepth
>(priorityQueue);
1171 grayLevel[pixel] =
static_cast<ToSGrayLevel
>(priorityQueue);
1174 imgR[order++] = pixel;
1177 for (PixelId neighbor : adj.getNeighborIndices(pixel)) {
1178 if (!dejavu[neighbor]) {
1179 queue.priorityPush(neighbor, interpolationMin[neighbor], interpolationMax[neighbor]);
1180 dejavu[neighbor] =
true;
1183 priorityQueueOld = priorityQueue;
1185 if (order != size) {
1186 throw std::runtime_error(
"Tree-of-shapes propagation did not visit the complete interpolated domain.");
1188 return FloodResult{std::move(imgU), std::move(grayLevel), std::move(imgR), std::move(adj)};
1197 CanonicalFloodTree buildCanonicalFloodTree(
const ImageUInt8Ptr& imgPtr)
const {
1198 FloodResult flood = floodImage(imgPtr);
1199 const int numPixelsInterp =
static_cast<int>(flood.treeLevel.size());
1201 std::vector<PixelId> zPar(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1202 std::vector<PixelId> parentInterpolate(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1203 auto findRoot = [&](
PixelId pStar) {
1204 while (zPar[pStar] != pStar) {
1205 zPar[pStar] = zPar[zPar[pStar]];
1206 pStar = zPar[pStar];
1211 for (
int i = numPixelsInterp - 1; i >= 0; --i) {
1212 const PixelId pStar = flood.order[
static_cast<size_t>(i)];
1213 parentInterpolate[pStar] = pStar;
1214 zPar[pStar] = pStar;
1215 for (PixelId qStar : flood.adjacency.getNeighborIndices(pStar)) {
1216 if (zPar[qStar] != InvalidPixel) {
1217 const PixelId rStar = findRoot(qStar);
1218 if (pStar != rStar) {
1219 parentInterpolate[rStar] = pStar;
1220 zPar[rStar] = pStar;
1226 auto sameLevel = [&](
PixelId aStar,
PixelId bStar) {
return flood.treeLevel[aStar] == flood.treeLevel[bStar]; };
1227 for (PixelId pStar : flood.order) {
1228 const PixelId qStar = parentInterpolate[pStar];
1229 if (sameLevel(parentInterpolate[qStar], qStar)) {
1230 parentInterpolate[pStar] = parentInterpolate[qStar];
1234 return CanonicalFloodTree{std::move(flood.treeLevel), std::move(flood.grayLevel), std::move(flood.order), std::move(parentInterpolate)};
1244 std::tuple<std::vector<ToSFloodDepth>, std::vector<PixelId>, AdjacencyUC> sort(
const ImageUInt8Ptr& imgPtr)
const {
1245 FloodResult flood = floodImage(imgPtr);
1246 return std::make_tuple(std::move(flood.treeLevel), std::move(flood.order), std::move(flood.adjacency));
1257 inline bool isOriginal1D(PixelId pixel,
int interpNumColumns)
const {
1258 const int row = pixel / interpNumColumns;
1259 const int column = pixel - row * interpNumColumns;
1260 const int offset = interpolationOffset();
1261 return (row & 1) == offset && (column & 1) == offset;
1273 inline PixelId toOriginal1D(PixelId pStar,
int interNumColumns,
int numColumns)
const {
1274 int r = pStar / interNumColumns;
1275 int c = pStar - r * interNumColumns;
1276 const int offset = interpolationOffset();
1277 return ((r - offset) >> 1) * numColumns + ((c - offset) >> 1);
1294 [[nodiscard]] TreeOfShapesBuildResult buildTreeOfShapes(
const ImageUInt8Ptr& imgPtr)
const {
1296 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-null image.");
1298 const int numRows = imgPtr->getNumRows();
1299 const int numColumns = imgPtr->getNumColumns();
1300 const int numPixels = checkedDomainSize(numRows, numColumns);
1301 const int interpNumColumns = interpolatedNumColumns(numColumns);
1303 CanonicalFloodTree canonical = buildCanonicalFloodTree(imgPtr);
1304 const int numPixelsInterp =
static_cast<int>(canonical.parent.size());
1306 return canonical.treeLevel[
static_cast<size_t>(aStar)] == canonical.treeLevel[
static_cast<size_t>(bStar)];
1308 auto repOf = [&](
PixelId pStar) {
1309 const PixelId parentStar = canonical.parent[
static_cast<size_t>(pStar)];
1310 return (parentStar == pStar || sameLevel(parentStar, pStar)) ? parentStar : pStar;
1313 std::vector<PixelId> canonicalReps;
1314 canonicalReps.reserve(
static_cast<size_t>(numPixels));
1315 for (PixelId pStar : canonical.order) {
1316 const PixelId parentStar = canonical.parent[
static_cast<size_t>(pStar)];
1317 if (parentStar == pStar || !sameLevel(parentStar, pStar)) {
1318 canonicalReps.push_back(pStar);
1321 if (canonicalReps.empty()) {
1322 throw std::runtime_error(
"Tree-of-shapes construction produced no canonical interpolated node.");
1325 const PixelId rawRoot = canonicalReps.front();
1326 std::vector<PixelId> rawParent(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1327 std::vector<PixelId> firstRawChild(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1328 std::vector<PixelId> nextRawSibling(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1329 for (PixelId repStar : canonicalReps) {
1330 if (repStar == rawRoot) {
1331 rawParent[
static_cast<size_t>(repStar)] = repStar;
1334 const PixelId parentRep = repOf(canonical.parent[
static_cast<size_t>(repStar)]);
1335 if (parentRep < 0 || parentRep >= numPixelsInterp || rawParent[
static_cast<size_t>(parentRep)] == InvalidPixel) {
1336 throw std::runtime_error(
"Tree-of-shapes canonical node has no canonical parent.");
1338 rawParent[
static_cast<size_t>(repStar)] = parentRep;
1339 nextRawSibling[
static_cast<size_t>(repStar)] = firstRawChild[
static_cast<size_t>(parentRep)];
1340 firstRawChild[
static_cast<size_t>(parentRep)] = repStar;
1343 std::vector<int> directCount(
static_cast<size_t>(numPixelsInterp), 0);
1344 std::vector<PixelId> representativePixelByElement(
static_cast<size_t>(numPixels), InvalidPixel);
1345 for (PixelId pStar = 0; pStar < numPixelsInterp; ++pStar) {
1346 if (!isOriginal1D(pStar, interpNumColumns)) {
1349 const PixelId elementId = toOriginal1D(pStar, interpNumColumns, numColumns);
1350 if (elementId < 0 || elementId >= numPixels) {
1351 throw std::runtime_error(
"Tree-of-shapes projection produced an element outside the source domain.");
1353 const PixelId representativePixel = repOf(pStar);
1354 representativePixelByElement[
static_cast<size_t>(elementId)] = representativePixel;
1355 ++directCount[
static_cast<size_t>(representativePixel)];
1357 if (std::find(representativePixelByElement.begin(), representativePixelByElement.end(),
InvalidPixel) != representativePixelByElement.end()) {
1358 throw std::runtime_error(
"Tree-of-shapes projection did not assign every source-domain element.");
1364 std::vector<ToSFloodDepth>().swap(canonical.treeLevel);
1365 std::vector<PixelId>().swap(canonical.parent);
1366 std::vector<PixelId>().swap(canonical.order);
1370 std::vector<PixelId> projectedRep(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1371 for (
auto it = canonicalReps.rbegin(); it != canonicalReps.rend(); ++it) {
1373 int numProjectedChildren = 0;
1375 for (PixelId childStar = firstRawChild[
static_cast<size_t>(repStar)]; childStar !=
InvalidPixel;
1376 childStar = nextRawSibling[
static_cast<size_t>(childStar)]) {
1377 const PixelId childProjection = projectedRep[
static_cast<size_t>(childStar)];
1378 if (childProjection != InvalidPixel) {
1379 ++numProjectedChildren;
1380 onlyProjectedChild = childProjection;
1384 if (directCount[
static_cast<size_t>(repStar)] > 0 || numProjectedChildren >= 2) {
1385 projectedRep[
static_cast<size_t>(repStar)] = repStar;
1386 }
else if (numProjectedChildren == 1) {
1387 projectedRep[
static_cast<size_t>(repStar)] = onlyProjectedChild;
1391 const PixelId projectedRootRep = projectedRep[
static_cast<size_t>(rawRoot)];
1392 if (projectedRootRep == InvalidPixel || projectedRep[
static_cast<size_t>(projectedRootRep)] != projectedRootRep) {
1393 throw std::runtime_error(
"Tree-of-shapes projection produced an empty hierarchy.");
1396 std::vector<PixelId>().swap(firstRawChild);
1397 std::vector<PixelId>().swap(nextRawSibling);
1398 std::vector<int>().swap(directCount);
1400 std::vector<NodeId> nodeIdByRep(
static_cast<size_t>(numPixelsInterp), InvalidNode);
1402 for (PixelId repStar : canonicalReps) {
1403 if (projectedRep[
static_cast<size_t>(repStar)] == repStar) {
1404 nodeIdByRep[
static_cast<size_t>(repStar)] = numNodes++;
1408 TreeOfShapesBuildResult result;
1409 result.parent.assign(
static_cast<size_t>(numNodes),
InvalidNode);
1410 result.smallestNodeMap.assign(
static_cast<size_t>(numPixels),
InvalidNode);
1411 result.nodeAltitudes.assign(
static_cast<size_t>(numNodes), ToSGrayLevel{});
1412 result.root = nodeIdByRep[
static_cast<size_t>(projectedRootRep)];
1413 result.numRows = numRows;
1414 result.numColumns = numColumns;
1415 detail::TopologicalNativeHierarchyRecorder proofRecorder(
static_cast<std::size_t
>(numNodes),
static_cast<std::size_t
>(numPixels), result.root);
1417 for (PixelId repStar : canonicalReps) {
1418 const NodeId nodeId = nodeIdByRep[
static_cast<size_t>(repStar)];
1419 if (nodeId == InvalidNode) {
1423 if (repStar == projectedRootRep) {
1424 result.parent[
static_cast<size_t>(nodeId)] = nodeId;
1426 PixelId ancestorRep = rawParent[
static_cast<size_t>(repStar)];
1427 while (ancestorRep != InvalidPixel && nodeIdByRep[
static_cast<size_t>(ancestorRep)] ==
InvalidNode) {
1428 if (ancestorRep == rawParent[
static_cast<size_t>(ancestorRep)]) {
1432 ancestorRep = rawParent[
static_cast<size_t>(ancestorRep)];
1434 if (ancestorRep == InvalidPixel) {
1435 throw std::runtime_error(
"Tree-of-shapes retained node has no retained parent.");
1437 result.parent[
static_cast<size_t>(nodeId)] = nodeIdByRep[
static_cast<size_t>(ancestorRep)];
1439 proofRecorder.recordSupportedNode(nodeId, result.parent[
static_cast<std::size_t
>(nodeId)]);
1441 result.nodeAltitudes[
static_cast<size_t>(nodeId)] =
1445 for (PixelId elementId = 0; elementId < numPixels; ++elementId) {
1446 const PixelId representativePixel = representativePixelByElement[
static_cast<size_t>(elementId)];
1447 const NodeId smallestNodeId = nodeIdByRep[
static_cast<size_t>(representativePixel)];
1448 if (smallestNodeId == InvalidNode) {
1449 throw std::runtime_error(
"Tree-of-shapes proper part belongs to a contracted node.");
1451 result.smallestNodeMap[
static_cast<size_t>(elementId)] = smallestNodeId;
1452 proofRecorder.recordProperPart(elementId, smallestNodeId);
1454 for (NodeId nodeId = 0; nodeId < numNodes; ++nodeId) {
1455 const NodeId parentNodeId = result.parent[
static_cast<std::size_t
>(nodeId)];
1456 if (nodeId != result.root &&
1457 result.nodeAltitudes[
static_cast<std::size_t
>(nodeId)] == result.nodeAltitudes[
static_cast<std::size_t
>(parentNodeId)]) {
1458 throw std::runtime_error(
"Tree-of-shapes construction produced an equal-altitude parent-child edge.");
1461 result.topologyProof = std::move(proofRecorder).finish();
1477 validateConventionForImage(
imgPtr);
1478 return buildTreeOfShapes(
imgPtr);