532 using AdjacencyUC = detail::AdjacencyUC;
534 using DiagonalConnection = detail::DiagonalConnection;
536 using PriorityQueueToS = detail::PriorityQueueToS;
541 std::vector<ToSFloodDepth> treeLevel;
543 std::vector<ToSGrayLevel> grayLevel;
545 std::vector<PixelId> order;
547 AdjacencyUC adjacency;
551 struct CanonicalFloodTree {
553 std::vector<ToSFloodDepth> treeLevel;
555 std::vector<ToSGrayLevel> grayLevel;
557 std::vector<PixelId> order;
559 std::vector<PixelId> parent;
565 detail::TopographicImmersionMode immersionMode_ = detail::TopographicImmersionMode::SelfDualSpan;
575 throw std::invalid_argument(
"Tree-of-shapes infinity pixel must be non-negative.");
577 if (std::holds_alternative<SelfDualSpanImmersion>(
convention.immersion)) {
583 if (
convention.altitudeEncoding == TopographicAltitudeEncoding::UInt8 &&
584 convention.domainExtension == TopographicDomainExtension::ExteriorRing) {
585 throw std::invalid_argument(
"Self-dual span immersion over an exterior ring cannot publish 8-bit altitudes because its boundary reference "
586 "level may fall on a half level; declare TopographicAltitudeEncoding::ExactDoubled or "
587 "TopographicDomainExtension::None.");
589 return detail::TopographicImmersionMode::SelfDualSpan;
591 if (
const auto*
canonical = std::get_if<CanonicalComplementaryGridImmersion>(&
convention.immersion)) {
592 return canonical->pairing == ComplementaryPairing::Min4Max8 ? detail::TopographicImmersionMode::Min4Max8
593 : detail::TopographicImmersionMode::Min8Max4;
595 const auto&
adjacencies = std::get<ComplementaryGridImmersion>(
convention.immersion).complementaryAdjacencies;
597 return detail::TopographicImmersionMode::Min4Max8;
600 return detail::TopographicImmersionMode::Min8Max4;
602 throw std::invalid_argument(
"Complementary-grid immersion requires minimum/maximum adjacencies in the canonical 4/8 or 8/4 pairing.");
608 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-null image.");
610 if (
const auto* immersion = std::get_if<ComplementaryGridImmersion>(&convention_.
immersion)) {
611 const auto&
adjacencies = immersion->complementaryAdjacencies;
612 const int rows =
image->getNumRows();
613 const int columns =
image->getNumColumns();
614 if (
adjacencies.minAdjacency.getNumRows() != rows ||
adjacencies.minAdjacency.getNumColumns() != columns ||
616 throw std::invalid_argument(
"Tree-of-shapes complementary adjacencies must match the source image domain.");
619 static_cast<void>(validatedInfinityPixel(interpolatedNumRows(
image->getNumRows()), interpolatedNumColumns(
image->getNumColumns())));
627 inline bool usesConnectivityMap()
const noexcept {
628 return immersionMode_ != detail::TopographicImmersionMode::SelfDualSpan;
636 inline bool usesHighDiagonalAtSaddle()
const noexcept {
return immersionMode_ == detail::TopographicImmersionMode::Min4Max8; }
643 inline bool usesExteriorPadding()
const noexcept {
return convention_.
domainExtension == TopographicDomainExtension::ExteriorRing; }
650 inline int interpolationOffset()
const noexcept {
return usesExteriorPadding() ? TopographicInterpolationPadding : 0; }
658 inline int checkedInterpolatedExtent(
int extent)
const {
660 throw std::invalid_argument(
"TreeOfShapesProducer requires positive image dimensions.");
662 const std::int64_t
interpolated = TopographicInterpolationScale *
static_cast<std::int64_t
>(
extent) + (usesExteriorPadding() ? 1 : -1);
664 throw std::overflow_error(
"Tree-of-shapes interpolated dimension exceeds int range.");
675 inline int interpolatedNumRows(
int numRows)
const {
return checkedInterpolatedExtent(numRows); }
683 inline int interpolatedNumColumns(
int numColumns)
const {
return checkedInterpolatedExtent(numColumns); }
692 inline int checkedDomainSize(
int numRows,
int numColumns)
const {
694 throw std::overflow_error(
"Tree-of-shapes domain size exceeds int range.");
696 return numRows * numColumns;
705 inline int originalPointRow(
int row)
const noexcept {
return TopographicInterpolationScale * row + interpolationOffset(); }
713 inline int originalPointColumn(
int column)
const noexcept {
return TopographicInterpolationScale * column + interpolationOffset(); }
722 return static_cast<ToSGrayLevel>(TopographicInterpolationScale *
static_cast<int>(value));
735 throw std::invalid_argument(
"Tree-of-shapes infinity pixel must belong to the active topographic domain.");
747 inline void setDiagonal0Connection(AdjacencyUC&
adj,
int row,
int column)
const {
748 adj.setDiagonalConnection(row, column - 1, DiagonalConnection::Se);
749 adj.setDiagonalConnection(row + 1, column, DiagonalConnection::Nw);
751 adj.setDiagonalConnection(row - 1, column - 1, DiagonalConnection::Se);
752 adj.setDiagonalConnection(row, column, DiagonalConnection::Se | DiagonalConnection::Nw);
753 adj.setDiagonalConnection(row + 1, column + 1, DiagonalConnection::Nw);
755 adj.setDiagonalConnection(row - 1, column, DiagonalConnection::Se);
756 adj.setDiagonalConnection(row, column + 1, DiagonalConnection::Nw);
766 inline void setDiagonal1Connection(AdjacencyUC&
adj,
int row,
int column)
const {
767 adj.setDiagonalConnection(row, column - 1, DiagonalConnection::Ne);
768 adj.setDiagonalConnection(row - 1, column, DiagonalConnection::Sw);
770 adj.setDiagonalConnection(row - 1, column + 1, DiagonalConnection::Sw);
771 adj.setDiagonalConnection(row, column, DiagonalConnection::Sw | DiagonalConnection::Ne);
772 adj.setDiagonalConnection(row + 1, column - 1, DiagonalConnection::Ne);
774 adj.setDiagonalConnection(row + 1, column, DiagonalConnection::Ne);
775 adj.setDiagonalConnection(row, column + 1, DiagonalConnection::Sw);
778 std::tuple<std::vector<ToSGrayLevel>, std::vector<ToSGrayLevel>, AdjacencyUC>
793 throw std::logic_error(
"Tree-of-shapes padded immersion cannot be cropped.");
797 const int size = checkedDomainSize(rows, columns);
798 std::vector<ToSGrayLevel>
croppedMin(
static_cast<std::size_t
>(size));
799 std::vector<ToSGrayLevel>
croppedMax(
static_cast<std::size_t
>(size));
802 for (
int row = 0; row < rows; ++row) {
803 for (
int column = 0; column < columns; ++column) {
806 croppedMin[
static_cast<std::size_t
>(target)] =
paddedMin[
static_cast<std::size_t
>(source)];
807 croppedMax[
static_cast<std::size_t
>(target)] =
paddedMax[
static_cast<std::size_t
>(source)];
826 : convention_(std::
move(
convention)), immersionMode_(validateConvention(convention_)) {}
853 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-null image.");
855 if (!usesExteriorPadding()) {
857 paddedConvention.domainExtension = TopographicDomainExtension::ExteriorRing;
862 paddedConvention.altitudeEncoding = TopographicAltitudeEncoding::ExactDoubled;
870 int numRows =
imgPtr->getNumRows();
871 int numColumns =
imgPtr->getNumColumns();
872 if (numRows <= 0 || numColumns <= 0) {
873 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-empty image.");
875 if (numRows == 1 && numColumns == 1) {
910 for (
PixelId pixel = 0; pixel < numColumns * numRows; ++pixel) {
914 if (row == 0 || row == numRows - 1 || column == 0 || column == numColumns - 1) {
928 for (
int value = 0; value < 256; ++value) {
934 throw std::runtime_error(
"Tree-of-shapes boundary histogram is inconsistent.");
947 const int*
adjRow =
nullptr;
953 if (column % 2 == 1 && row % 2 == 1)
960 if (column % 2 == 0 && row % 2 == 0) {
964 }
else if (column % 2 == 0 && row % 2 == 1) {
968 }
else if (column % 2 == 1 && row % 2 == 0) {
974 min = std::numeric_limits<int>::max();
975 max = std::numeric_limits<int>::min();
1017 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-null image.");
1019 if (!usesExteriorPadding()) {
1020 TopographicConvention paddedConvention = convention_;
1021 paddedConvention.
domainExtension = TopographicDomainExtension::ExteriorRing;
1022 paddedConvention.infinityPixel = 0;
1026 paddedConvention.altitudeEncoding = TopographicAltitudeEncoding::ExactDoubled;
1027 TreeOfShapesProducer paddedProducer(std::move(paddedConvention));
1028 auto [paddedMin, paddedMax, paddedAdjacency] = paddedProducer.interpolateImage4c8c(imgPtr);
1029 const int paddedRows = paddedProducer.interpolatedNumRows(imgPtr->getNumRows());
1030 const int paddedColumns = paddedProducer.interpolatedNumColumns(imgPtr->getNumColumns());
1031 return cropExteriorInterpolation(std::move(paddedMin), std::move(paddedMax), std::move(paddedAdjacency), paddedRows, paddedColumns,
true);
1033 auto img = imgPtr->rawData();
1034 int numRows = imgPtr->getNumRows();
1035 int numColumns = imgPtr->getNumColumns();
1036 if (numRows <= 0 || numColumns <= 0) {
1037 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-empty image.");
1040 int interpNumColumns = interpolatedNumColumns(numColumns);
1041 int interpNumRows = interpolatedNumRows(numRows);
1042 int size = checkedDomainSize(interpNumRows, interpNumColumns);
1043 AdjacencyUC adj(interpNumRows, interpNumColumns,
true);
1046 std::vector<ToSGrayLevel> interpolationMin(size);
1047 std::vector<ToSGrayLevel> interpolationMax(size);
1051 for (PixelId pixel = 0; pixel < numColumns * numRows; ++pixel) {
1055 pT =
ImageUtils::to1D(originalPointRow(row), originalPointColumn(column), interpNumColumns);
1058 interpolationMin[pT] = interpolationMax[pT] = img[pixel];
1061 auto getValue = [&](
int row,
int column) ->
int {
1062 int origRow = (row - 1) / 2;
1063 int originalColumn = (column - 1) / 2;
1068 for (
int row = 0; row < interpNumRows; row++) {
1072 int v1 = getValue(row, column + 1);
1076 column = interpNumColumns - 1;
1077 v1 = getValue(row, column - 1);
1083 int v1 = getValue(row + 1, column + 1);
1087 column = interpNumColumns - 1;
1088 v1 = getValue(row + 1, column - 1);
1092 }
else if (row == interpNumRows - 1) {
1094 int v1 = getValue(row - 1, 1);
1098 column = interpNumColumns - 1;
1099 v1 = getValue(row - 1, column - 1);
1104 int v1 = getValue(row - 1, column + 1);
1105 int v2 = getValue(row + 1, column + 1);
1106 interpolationMin[
ImageUtils::to1D(row, 0, interpNumColumns)] = std::min(v1, v2);
1107 interpolationMax[
ImageUtils::to1D(row, 0, interpNumColumns)] = std::max(v1, v2);
1109 column = interpNumColumns - 1;
1110 v1 = getValue(row - 1, column - 1);
1111 v2 = getValue(row + 1, column - 1);
1112 interpolationMin[
ImageUtils::to1D(row, column, interpNumColumns)] = std::min(v1, v2);
1113 interpolationMax[
ImageUtils::to1D(row, column, interpNumColumns)] = std::max(v1, v2);
1118 for (
int column = 1; column < interpNumColumns - 1; column++) {
1120 if (column % 2 == 1) {
1122 int v1 = getValue(row + 1, column);
1126 row = interpNumRows - 1;
1127 v1 = getValue(row - 1, column);
1132 int v1 = getValue(row + 1, column - 1);
1133 int v2 = getValue(row + 1, column + 1);
1134 interpolationMin[
ImageUtils::to1D(row, column, interpNumColumns)] = std::min(v1, v2);
1135 interpolationMax[
ImageUtils::to1D(row, column, interpNumColumns)] = std::max(v1, v2);
1137 row = interpNumRows - 1;
1138 v1 = getValue(row - 1, column - 1);
1139 v2 = getValue(row - 1, column + 1);
1140 interpolationMin[
ImageUtils::to1D(row, column, interpNumColumns)] = std::min(v1, v2);
1141 interpolationMax[
ImageUtils::to1D(row, column, interpNumColumns)] = std::max(v1, v2);
1146 for (
int row = 1; row < interpNumRows - 1; row++) {
1147 for (
int column = 1; column < interpNumColumns - 1; column++) {
1148 if (row % 2 == 1 && column % 2 == 1)
1152 if (column % 2 == 0 && row % 2 == 1) {
1153 int v1 = getValue(row, column + 1);
1154 int v2 = getValue(row, column - 1);
1155 interpolationMin[pT] = std::min(v1, v2);
1156 interpolationMax[pT] = std::max(v1, v2);
1157 }
else if (column % 2 == 1 && row % 2 == 0) {
1158 int v1 = getValue(row + 1, column);
1159 int v2 = getValue(row - 1, column);
1160 interpolationMin[pT] = std::min(v1, v2);
1161 interpolationMax[pT] = std::max(v1, v2);
1166 for (
int row = 1; row < interpNumRows - 1; row++) {
1167 for (
int column = 1; column < interpNumColumns - 1; column++) {
1168 if (row % 2 == 1 && column % 2 == 1)
1171 if (row % 2 == 0 && column % 2 == 0) {
1174 int v0 = getValue(row - 1, column - 1);
1175 int v1 = getValue(row + 1, column - 1);
1176 int v2 = getValue(row - 1, column + 1);
1177 int v3 = getValue(row + 1, column + 1);
1179 const int min_v0v3 = std::min(v0, v3);
1180 const int max_v0v3 = std::max(v0, v3);
1181 const int min_v1v2 = std::min(v1, v2);
1182 const int max_v1v2 = std::max(v1, v2);
1183 const bool diagonal0IsHigh = min_v0v3 > max_v1v2;
1184 const bool diagonal1IsHigh = min_v1v2 > max_v0v3;
1186 if (diagonal0IsHigh || diagonal1IsHigh) {
1187 const bool chooseDiagonal0 = usesHighDiagonalAtSaddle() ? diagonal0IsHigh : !diagonal0IsHigh;
1188 if (chooseDiagonal0) {
1189 setDiagonal0Connection(adj, row, column);
1190 interpolationMin[pT] = min_v0v3;
1191 interpolationMax[pT] = max_v0v3;
1193 setDiagonal1Connection(adj, row, column);
1194 interpolationMin[pT] = min_v1v2;
1195 interpolationMax[pT] = max_v1v2;
1199 interpolationMin[pT] = std::min({v0, v1, v2, v3});
1200 interpolationMax[pT] = std::max({v0, v1, v2, v3});
1205 return std::make_tuple(std::move(interpolationMin), std::move(interpolationMax), std::move(adj));
1215 FloodResult floodImage(
const ImageUInt8Ptr& imgPtr)
const {
1217 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-null image.");
1220 int numRows = imgPtr->getNumRows();
1221 int numColumns = imgPtr->getNumColumns();
1223 int interpNumColumns = interpolatedNumColumns(numColumns);
1224 int interpNumRows = interpolatedNumRows(numRows);
1225 int size = checkedDomainSize(interpNumRows, interpNumColumns);
1226 const bool connectivityMap = usesConnectivityMap();
1227 auto [interpolationMin, interpolationMax, adj] = connectivityMap ? interpolateImage4c8c(imgPtr) : interpolateImage(imgPtr);
1229 std::vector<uint8_t> dejavu(size, 0);
1230 std::vector<PixelId> imgR(size);
1231 std::vector<ToSFloodDepth> imgU(size);
1232 std::vector<ToSGrayLevel> grayLevel(size);
1234 PriorityQueueToS queue(connectivityMap ? ToSUInt8Depth : ToSSelfDualDepth);
1235 const PixelId infinityPixel = validatedInfinityPixel(interpNumRows, interpNumColumns);
1236 int priorityQueueOld = interpolationMin[infinityPixel];
1237 queue.initial(infinityPixel, priorityQueueOld);
1238 dejavu[infinityPixel] =
true;
1241 ToSFloodDepth depth = 0;
1242 while (!queue.isEmpty()) {
1243 const PixelId pixel = queue.priorityPop();
1244 int priorityQueue = queue.getCurrentPriority();
1245 if (connectivityMap) {
1246 if (priorityQueue != priorityQueueOld)
1248 imgU[pixel] = depth;
1250 imgU[pixel] =
static_cast<ToSFloodDepth
>(priorityQueue);
1252 grayLevel[pixel] =
static_cast<ToSGrayLevel
>(priorityQueue);
1255 imgR[order++] = pixel;
1258 for (PixelId neighbor : adj.getNeighborIndices(pixel)) {
1259 if (!dejavu[neighbor]) {
1260 queue.priorityPush(neighbor, interpolationMin[neighbor], interpolationMax[neighbor]);
1261 dejavu[neighbor] =
true;
1264 priorityQueueOld = priorityQueue;
1266 if (order != size) {
1267 throw std::runtime_error(
"Tree-of-shapes propagation did not visit the complete interpolated domain.");
1269 return FloodResult{std::move(imgU), std::move(grayLevel), std::move(imgR), std::move(adj)};
1278 CanonicalFloodTree buildCanonicalFloodTree(
const ImageUInt8Ptr& imgPtr)
const {
1279 FloodResult flood = floodImage(imgPtr);
1280 const int numPixelsInterp =
static_cast<int>(flood.treeLevel.size());
1282 std::vector<PixelId> zPar(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1283 std::vector<PixelId> parentInterpolate(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1284 auto findRoot = [&](
PixelId pStar) {
1285 while (zPar[pStar] != pStar) {
1286 zPar[pStar] = zPar[zPar[pStar]];
1287 pStar = zPar[pStar];
1292 for (
int i = numPixelsInterp - 1; i >= 0; --i) {
1293 const PixelId pStar = flood.order[
static_cast<size_t>(i)];
1294 parentInterpolate[pStar] = pStar;
1295 zPar[pStar] = pStar;
1296 for (PixelId qStar : flood.adjacency.getNeighborIndices(pStar)) {
1297 if (zPar[qStar] != InvalidPixel) {
1298 const PixelId rStar = findRoot(qStar);
1299 if (pStar != rStar) {
1300 parentInterpolate[rStar] = pStar;
1301 zPar[rStar] = pStar;
1307 auto sameLevel = [&](
PixelId aStar,
PixelId bStar) {
return flood.treeLevel[aStar] == flood.treeLevel[bStar]; };
1308 for (PixelId pStar : flood.order) {
1309 const PixelId qStar = parentInterpolate[pStar];
1310 if (sameLevel(parentInterpolate[qStar], qStar)) {
1311 parentInterpolate[pStar] = parentInterpolate[qStar];
1315 return CanonicalFloodTree{std::move(flood.treeLevel), std::move(flood.grayLevel), std::move(flood.order), std::move(parentInterpolate)};
1325 std::tuple<std::vector<ToSFloodDepth>, std::vector<PixelId>, AdjacencyUC> sort(
const ImageUInt8Ptr& imgPtr)
const {
1326 FloodResult flood = floodImage(imgPtr);
1327 return std::make_tuple(std::move(flood.treeLevel), std::move(flood.order), std::move(flood.adjacency));
1338 inline bool isOriginal1D(PixelId pixel,
int interpNumColumns)
const {
1339 const int row = pixel / interpNumColumns;
1340 const int column = pixel - row * interpNumColumns;
1341 const int offset = interpolationOffset();
1342 return (row & 1) == offset && (column & 1) == offset;
1354 inline PixelId toOriginal1D(PixelId pStar,
int interNumColumns,
int numColumns)
const {
1355 int r = pStar / interNumColumns;
1356 int c = pStar - r * interNumColumns;
1357 const int offset = interpolationOffset();
1358 return ((r - offset) >> 1) * numColumns + ((c - offset) >> 1);
1376 template <
class Altitude> [[nodiscard]] TreeOfShapesBuildResult<Altitude> buildTreeOfShapes(
const ImageUInt8Ptr& imgPtr)
const {
1378 throw std::invalid_argument(
"TreeOfShapesProducer requires a non-null image.");
1380 const int numRows = imgPtr->getNumRows();
1381 const int numColumns = imgPtr->getNumColumns();
1382 const int numPixels = checkedDomainSize(numRows, numColumns);
1383 const int interpNumColumns = interpolatedNumColumns(numColumns);
1385 CanonicalFloodTree canonical = buildCanonicalFloodTree(imgPtr);
1386 const int numPixelsInterp =
static_cast<int>(canonical.parent.size());
1388 return canonical.treeLevel[
static_cast<size_t>(aStar)] == canonical.treeLevel[
static_cast<size_t>(bStar)];
1390 auto repOf = [&](
PixelId pStar) {
1391 const PixelId parentStar = canonical.parent[
static_cast<size_t>(pStar)];
1392 return (parentStar == pStar || sameLevel(parentStar, pStar)) ? parentStar : pStar;
1395 std::vector<PixelId> canonicalReps;
1396 canonicalReps.reserve(
static_cast<size_t>(numPixels));
1397 for (PixelId pStar : canonical.order) {
1398 const PixelId parentStar = canonical.parent[
static_cast<size_t>(pStar)];
1399 if (parentStar == pStar || !sameLevel(parentStar, pStar)) {
1400 canonicalReps.push_back(pStar);
1403 if (canonicalReps.empty()) {
1404 throw std::runtime_error(
"Tree-of-shapes construction produced no canonical interpolated node.");
1407 const PixelId rawRoot = canonicalReps.front();
1408 std::vector<PixelId> rawParent(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1409 std::vector<PixelId> firstRawChild(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1410 std::vector<PixelId> nextRawSibling(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1411 for (PixelId repStar : canonicalReps) {
1412 if (repStar == rawRoot) {
1413 rawParent[
static_cast<size_t>(repStar)] = repStar;
1416 const PixelId parentRep = repOf(canonical.parent[
static_cast<size_t>(repStar)]);
1417 if (parentRep < 0 || parentRep >= numPixelsInterp || rawParent[
static_cast<size_t>(parentRep)] == InvalidPixel) {
1418 throw std::runtime_error(
"Tree-of-shapes canonical node has no canonical parent.");
1420 rawParent[
static_cast<size_t>(repStar)] = parentRep;
1421 nextRawSibling[
static_cast<size_t>(repStar)] = firstRawChild[
static_cast<size_t>(parentRep)];
1422 firstRawChild[
static_cast<size_t>(parentRep)] = repStar;
1425 std::vector<int> directCount(
static_cast<size_t>(numPixelsInterp), 0);
1426 std::vector<PixelId> representativePixelByElement(
static_cast<size_t>(numPixels), InvalidPixel);
1427 for (PixelId pStar = 0; pStar < numPixelsInterp; ++pStar) {
1428 if (!isOriginal1D(pStar, interpNumColumns)) {
1431 const PixelId elementId = toOriginal1D(pStar, interpNumColumns, numColumns);
1432 if (elementId < 0 || elementId >= numPixels) {
1433 throw std::runtime_error(
"Tree-of-shapes projection produced an element outside the source domain.");
1435 const PixelId representativePixel = repOf(pStar);
1436 representativePixelByElement[
static_cast<size_t>(elementId)] = representativePixel;
1437 ++directCount[
static_cast<size_t>(representativePixel)];
1439 if (std::find(representativePixelByElement.begin(), representativePixelByElement.end(),
InvalidPixel) != representativePixelByElement.end()) {
1440 throw std::runtime_error(
"Tree-of-shapes projection did not assign every source-domain element.");
1446 std::vector<ToSFloodDepth>().swap(canonical.treeLevel);
1447 std::vector<PixelId>().swap(canonical.parent);
1448 std::vector<PixelId>().swap(canonical.order);
1452 std::vector<PixelId> projectedRep(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1453 for (
auto it = canonicalReps.rbegin(); it != canonicalReps.rend(); ++it) {
1455 int numProjectedChildren = 0;
1457 for (PixelId childStar = firstRawChild[
static_cast<size_t>(repStar)]; childStar !=
InvalidPixel;
1458 childStar = nextRawSibling[
static_cast<size_t>(childStar)]) {
1459 const PixelId childProjection = projectedRep[
static_cast<size_t>(childStar)];
1460 if (childProjection != InvalidPixel) {
1461 ++numProjectedChildren;
1462 onlyProjectedChild = childProjection;
1466 if (directCount[
static_cast<size_t>(repStar)] > 0 || numProjectedChildren >= 2) {
1467 projectedRep[
static_cast<size_t>(repStar)] = repStar;
1468 }
else if (numProjectedChildren == 1) {
1469 projectedRep[
static_cast<size_t>(repStar)] = onlyProjectedChild;
1473 const PixelId projectedRootRep = projectedRep[
static_cast<size_t>(rawRoot)];
1474 if (projectedRootRep == InvalidPixel || projectedRep[
static_cast<size_t>(projectedRootRep)] != projectedRootRep) {
1475 throw std::runtime_error(
"Tree-of-shapes projection produced an empty hierarchy.");
1478 std::vector<PixelId>().swap(firstRawChild);
1479 std::vector<PixelId>().swap(nextRawSibling);
1480 std::vector<int>().swap(directCount);
1482 std::vector<NodeId> nodeIdByRep(
static_cast<size_t>(numPixelsInterp), InvalidNode);
1484 for (PixelId repStar : canonicalReps) {
1485 if (projectedRep[
static_cast<size_t>(repStar)] == repStar) {
1486 nodeIdByRep[
static_cast<size_t>(repStar)] = numNodes++;
1490 TreeOfShapesBuildResult<Altitude> result;
1491 result.parent.assign(
static_cast<size_t>(numNodes),
InvalidNode);
1492 result.smallestNodeMap.assign(
static_cast<size_t>(numPixels),
InvalidNode);
1493 result.nodeAltitudes.assign(
static_cast<size_t>(numNodes), Altitude{});
1494 result.root = nodeIdByRep[
static_cast<size_t>(projectedRootRep)];
1495 result.numRows = numRows;
1496 result.numColumns = numColumns;
1497 detail::TopologicalNativeHierarchyRecorder proofRecorder(
static_cast<std::size_t
>(numNodes),
static_cast<std::size_t
>(numPixels), result.root);
1499 for (PixelId repStar : canonicalReps) {
1500 const NodeId nodeId = nodeIdByRep[
static_cast<size_t>(repStar)];
1501 if (nodeId == InvalidNode) {
1505 if (repStar == projectedRootRep) {
1506 result.parent[
static_cast<size_t>(nodeId)] = nodeId;
1508 PixelId ancestorRep = rawParent[
static_cast<size_t>(repStar)];
1509 while (ancestorRep != InvalidPixel && nodeIdByRep[
static_cast<size_t>(ancestorRep)] ==
InvalidNode) {
1510 if (ancestorRep == rawParent[
static_cast<size_t>(ancestorRep)]) {
1514 ancestorRep = rawParent[
static_cast<size_t>(ancestorRep)];
1516 if (ancestorRep == InvalidPixel) {
1517 throw std::runtime_error(
"Tree-of-shapes retained node has no retained parent.");
1519 result.parent[
static_cast<size_t>(nodeId)] = nodeIdByRep[
static_cast<size_t>(ancestorRep)];
1521 proofRecorder.recordSupportedNode(nodeId, result.parent[
static_cast<std::size_t
>(nodeId)]);
1523 result.nodeAltitudes[
static_cast<size_t>(nodeId)] =
1524 ToSAltitudeEncodingFor<Altitude>::type::encode(canonical.grayLevel[
static_cast<size_t>(repStar)], immersionMode_);
1527 for (PixelId elementId = 0; elementId < numPixels; ++elementId) {
1528 const PixelId representativePixel = representativePixelByElement[
static_cast<size_t>(elementId)];
1529 const NodeId smallestNodeId = nodeIdByRep[
static_cast<size_t>(representativePixel)];
1530 if (smallestNodeId == InvalidNode) {
1531 throw std::runtime_error(
"Tree-of-shapes proper part belongs to a contracted node.");
1533 result.smallestNodeMap[
static_cast<size_t>(elementId)] = smallestNodeId;
1534 proofRecorder.recordProperPart(elementId, smallestNodeId);
1536 for (NodeId nodeId = 0; nodeId < numNodes; ++nodeId) {
1537 const NodeId parentNodeId = result.parent[
static_cast<std::size_t
>(nodeId)];
1538 if (nodeId != result.root &&
1539 result.nodeAltitudes[
static_cast<std::size_t
>(nodeId)] == result.nodeAltitudes[
static_cast<std::size_t
>(parentNodeId)]) {
1540 throw std::runtime_error(
"Tree-of-shapes construction produced an equal-altitude parent-child edge.");
1543 result.topologyProof = std::move(proofRecorder).finish();
1566 throw std::invalid_argument(
"Tree-of-shapes altitude type does not match the altitude encoding declared by the topographic convention.");
1568 validateConventionForImage(
imgPtr);