525 using AdjacencyUC = detail::AdjacencyUC;
527 using DiagonalConnection = detail::DiagonalConnection;
529 using PriorityQueueToS = detail::PriorityQueueToS;
534 std::vector<ToSFloodDepth> treeLevel;
536 std::vector<ToSGrayLevel> grayLevel;
538 std::vector<PixelId> order;
540 AdjacencyUC adjacency;
544 struct CanonicalFloodTree {
546 std::vector<ToSFloodDepth> treeLevel;
548 std::vector<ToSGrayLevel> grayLevel;
550 std::vector<PixelId> order;
552 std::vector<PixelId> parent;
558 detail::TopographicImmersionMode immersionMode_ = detail::TopographicImmersionMode::SelfDualSpan;
568 throw std::invalid_argument(
"Tree-of-shapes infinity pixel must be non-negative.");
570 if (std::holds_alternative<SelfDualSpanImmersion>(
convention.immersion)) {
576 if (
convention.altitudeEncoding == TopographicAltitudeEncoding::UInt8 &&
577 convention.domainExtension == TopographicDomainExtension::ExteriorRing) {
578 throw std::invalid_argument(
"Self-dual span immersion over an exterior ring cannot publish 8-bit altitudes because its boundary reference "
579 "level may fall on a half level; declare TopographicAltitudeEncoding::ExactDoubled or "
580 "TopographicDomainExtension::None.");
582 return detail::TopographicImmersionMode::SelfDualSpan;
584 if (
const auto*
canonical = std::get_if<CanonicalComplementaryGridImmersion>(&
convention.immersion)) {
585 return canonical->pairing == ComplementaryPairing::Min4Max8 ? detail::TopographicImmersionMode::Min4Max8
586 : detail::TopographicImmersionMode::Min8Max4;
588 const auto&
adjacencies = std::get<ComplementaryGridImmersion>(
convention.immersion).complementaryAdjacencies;
590 return detail::TopographicImmersionMode::Min4Max8;
593 return detail::TopographicImmersionMode::Min8Max4;
595 throw std::invalid_argument(
"Complementary-grid immersion requires minimum/maximum adjacencies in the canonical 4/8 or 8/4 pairing.");
601 throw std::invalid_argument(
"TreeOfShapesBuilder requires a non-null image.");
603 if (
const auto* immersion = std::get_if<ComplementaryGridImmersion>(&convention_.
immersion)) {
604 const auto&
adjacencies = immersion->complementaryAdjacencies;
605 const int rows =
image->getNumRows();
606 const int columns =
image->getNumColumns();
607 if (
adjacencies.minAdjacency.getNumRows() != rows ||
adjacencies.minAdjacency.getNumColumns() != columns ||
609 throw std::invalid_argument(
"Tree-of-shapes complementary adjacencies must match the source image domain.");
612 static_cast<void>(validatedInfinityPixel(interpolatedNumRows(
image->getNumRows()), interpolatedNumColumns(
image->getNumColumns())));
622 const auto*
canonical = std::get_if<CanonicalComplementaryGridImmersion>(&convention_.
immersion);
639 inline bool usesConnectivityMap()
const noexcept {
640 return immersionMode_ != detail::TopographicImmersionMode::SelfDualSpan;
648 inline bool usesHighDiagonalAtSaddle()
const noexcept {
return immersionMode_ == detail::TopographicImmersionMode::Min4Max8; }
655 inline bool usesExteriorPadding()
const noexcept {
return convention_.
domainExtension == TopographicDomainExtension::ExteriorRing; }
662 inline int interpolationOffset()
const noexcept {
return usesExteriorPadding() ? TopographicInterpolationPadding : 0; }
670 inline int checkedInterpolatedExtent(
int extent)
const {
672 throw std::invalid_argument(
"TreeOfShapesBuilder requires positive image dimensions.");
674 const std::int64_t
interpolated = TopographicInterpolationScale *
static_cast<std::int64_t
>(
extent) + (usesExteriorPadding() ? 1 : -1);
676 throw std::overflow_error(
"Tree-of-shapes interpolated dimension exceeds int range.");
687 inline int interpolatedNumRows(
int numRows)
const {
return checkedInterpolatedExtent(numRows); }
695 inline int interpolatedNumColumns(
int numColumns)
const {
return checkedInterpolatedExtent(numColumns); }
704 inline int checkedDomainSize(
int numRows,
int numColumns)
const {
706 throw std::overflow_error(
"Tree-of-shapes domain size exceeds int range.");
708 return numRows * numColumns;
717 inline int originalPointRow(
int row)
const noexcept {
return TopographicInterpolationScale * row + interpolationOffset(); }
725 inline int originalPointColumn(
int column)
const noexcept {
return TopographicInterpolationScale * column + interpolationOffset(); }
734 return static_cast<ToSGrayLevel>(TopographicInterpolationScale *
static_cast<int>(value));
747 throw std::invalid_argument(
"Tree-of-shapes infinity pixel must belong to the active topographic domain.");
759 inline void setDiagonal0Connection(AdjacencyUC&
adj,
int row,
int column)
const {
760 adj.setDiagonalConnection(row, column - 1, DiagonalConnection::Se);
761 adj.setDiagonalConnection(row + 1, column, DiagonalConnection::Nw);
763 adj.setDiagonalConnection(row - 1, column - 1, DiagonalConnection::Se);
764 adj.setDiagonalConnection(row, column, DiagonalConnection::Se | DiagonalConnection::Nw);
765 adj.setDiagonalConnection(row + 1, column + 1, DiagonalConnection::Nw);
767 adj.setDiagonalConnection(row - 1, column, DiagonalConnection::Se);
768 adj.setDiagonalConnection(row, column + 1, DiagonalConnection::Nw);
778 inline void setDiagonal1Connection(AdjacencyUC&
adj,
int row,
int column)
const {
779 adj.setDiagonalConnection(row, column - 1, DiagonalConnection::Ne);
780 adj.setDiagonalConnection(row - 1, column, DiagonalConnection::Sw);
782 adj.setDiagonalConnection(row - 1, column + 1, DiagonalConnection::Sw);
783 adj.setDiagonalConnection(row, column, DiagonalConnection::Sw | DiagonalConnection::Ne);
784 adj.setDiagonalConnection(row + 1, column - 1, DiagonalConnection::Ne);
786 adj.setDiagonalConnection(row + 1, column, DiagonalConnection::Ne);
787 adj.setDiagonalConnection(row, column + 1, DiagonalConnection::Sw);
790 std::tuple<std::vector<ToSGrayLevel>, std::vector<ToSGrayLevel>, AdjacencyUC>
805 throw std::logic_error(
"Tree-of-shapes padded immersion cannot be cropped.");
809 const int size = checkedDomainSize(rows, columns);
810 std::vector<ToSGrayLevel>
croppedMin(
static_cast<std::size_t
>(size));
811 std::vector<ToSGrayLevel>
croppedMax(
static_cast<std::size_t
>(size));
814 for (
int row = 0; row < rows; ++row) {
815 for (
int column = 0; column < columns; ++column) {
818 croppedMin[
static_cast<std::size_t
>(target)] =
paddedMin[
static_cast<std::size_t
>(source)];
819 croppedMax[
static_cast<std::size_t
>(target)] =
paddedMax[
static_cast<std::size_t
>(source)];
838 : convention_(std::
move(
convention)), immersionMode_(validateConvention(convention_)) {}
869 throw std::invalid_argument(
"TreeOfShapesBuilder requires a non-null image.");
871 if (!usesExteriorPadding()) {
873 paddedConvention.domainExtension = TopographicDomainExtension::ExteriorRing;
878 paddedConvention.altitudeEncoding = TopographicAltitudeEncoding::ExactDoubled;
886 int numRows =
imgPtr->getNumRows();
887 int numColumns =
imgPtr->getNumColumns();
888 if (numRows <= 0 || numColumns <= 0) {
889 throw std::invalid_argument(
"TreeOfShapesBuilder requires a non-empty image.");
891 if (numRows == 1 && numColumns == 1) {
926 for (
PixelId pixel = 0; pixel < numColumns * numRows; ++pixel) {
930 if (row == 0 || row == numRows - 1 || column == 0 || column == numColumns - 1) {
944 for (
int value = 0; value < 256; ++value) {
950 throw std::runtime_error(
"Tree-of-shapes boundary histogram is inconsistent.");
963 const int*
adjRow =
nullptr;
969 if (column % 2 == 1 && row % 2 == 1)
976 if (column % 2 == 0 && row % 2 == 0) {
980 }
else if (column % 2 == 0 && row % 2 == 1) {
984 }
else if (column % 2 == 1 && row % 2 == 0) {
990 min = std::numeric_limits<int>::max();
991 max = std::numeric_limits<int>::min();
1036 throw std::invalid_argument(
"TreeOfShapesBuilder requires a non-null image.");
1038 if (!usesExteriorPadding()) {
1039 TopographicConvention paddedConvention = convention_;
1040 paddedConvention.
domainExtension = TopographicDomainExtension::ExteriorRing;
1041 paddedConvention.infinityPixel = 0;
1045 paddedConvention.altitudeEncoding = TopographicAltitudeEncoding::ExactDoubled;
1046 TreeOfShapesBuilder paddedProducer(std::move(paddedConvention));
1047 auto [paddedMin, paddedMax, paddedAdjacency] = paddedProducer.interpolateImage4c8c(imgPtr);
1048 const int paddedRows = paddedProducer.interpolatedNumRows(imgPtr->getNumRows());
1049 const int paddedColumns = paddedProducer.interpolatedNumColumns(imgPtr->getNumColumns());
1050 return cropExteriorInterpolation(std::move(paddedMin), std::move(paddedMax), std::move(paddedAdjacency), paddedRows, paddedColumns,
true);
1052 auto img = imgPtr->rawData();
1053 int numRows = imgPtr->getNumRows();
1054 int numColumns = imgPtr->getNumColumns();
1055 if (numRows <= 0 || numColumns <= 0) {
1056 throw std::invalid_argument(
"TreeOfShapesBuilder requires a non-empty image.");
1059 int interpNumColumns = interpolatedNumColumns(numColumns);
1060 int interpNumRows = interpolatedNumRows(numRows);
1061 int size = checkedDomainSize(interpNumRows, interpNumColumns);
1062 AdjacencyUC adj(interpNumRows, interpNumColumns,
true);
1065 std::vector<ToSGrayLevel> interpolationMin(size);
1066 std::vector<ToSGrayLevel> interpolationMax(size);
1070 for (PixelId pixel = 0; pixel < numColumns * numRows; ++pixel) {
1074 pT =
ImageUtils::to1D(originalPointRow(row), originalPointColumn(column), interpNumColumns);
1077 interpolationMin[pT] = interpolationMax[pT] = img[pixel];
1080 auto getValue = [&](
int row,
int column) ->
int {
1081 int origRow = (row - 1) / 2;
1082 int originalColumn = (column - 1) / 2;
1087 for (
int row = 0; row < interpNumRows; row++) {
1091 int v1 = getValue(row, column + 1);
1095 column = interpNumColumns - 1;
1096 v1 = getValue(row, column - 1);
1102 int v1 = getValue(row + 1, column + 1);
1106 column = interpNumColumns - 1;
1107 v1 = getValue(row + 1, column - 1);
1111 }
else if (row == interpNumRows - 1) {
1113 int v1 = getValue(row - 1, 1);
1117 column = interpNumColumns - 1;
1118 v1 = getValue(row - 1, column - 1);
1123 int v1 = getValue(row - 1, column + 1);
1124 int v2 = getValue(row + 1, column + 1);
1125 interpolationMin[
ImageUtils::to1D(row, 0, interpNumColumns)] = std::min(v1, v2);
1126 interpolationMax[
ImageUtils::to1D(row, 0, interpNumColumns)] = std::max(v1, v2);
1128 column = interpNumColumns - 1;
1129 v1 = getValue(row - 1, column - 1);
1130 v2 = getValue(row + 1, column - 1);
1131 interpolationMin[
ImageUtils::to1D(row, column, interpNumColumns)] = std::min(v1, v2);
1132 interpolationMax[
ImageUtils::to1D(row, column, interpNumColumns)] = std::max(v1, v2);
1137 for (
int column = 1; column < interpNumColumns - 1; column++) {
1139 if (column % 2 == 1) {
1141 int v1 = getValue(row + 1, column);
1145 row = interpNumRows - 1;
1146 v1 = getValue(row - 1, column);
1151 int v1 = getValue(row + 1, column - 1);
1152 int v2 = getValue(row + 1, column + 1);
1153 interpolationMin[
ImageUtils::to1D(row, column, interpNumColumns)] = std::min(v1, v2);
1154 interpolationMax[
ImageUtils::to1D(row, column, interpNumColumns)] = std::max(v1, v2);
1156 row = interpNumRows - 1;
1157 v1 = getValue(row - 1, column - 1);
1158 v2 = getValue(row - 1, column + 1);
1159 interpolationMin[
ImageUtils::to1D(row, column, interpNumColumns)] = std::min(v1, v2);
1160 interpolationMax[
ImageUtils::to1D(row, column, interpNumColumns)] = std::max(v1, v2);
1165 for (
int row = 1; row < interpNumRows - 1; row++) {
1166 for (
int column = 1; column < interpNumColumns - 1; column++) {
1167 if (row % 2 == 1 && column % 2 == 1)
1171 if (column % 2 == 0 && row % 2 == 1) {
1172 int v1 = getValue(row, column + 1);
1173 int v2 = getValue(row, column - 1);
1174 interpolationMin[pT] = std::min(v1, v2);
1175 interpolationMax[pT] = std::max(v1, v2);
1176 }
else if (column % 2 == 1 && row % 2 == 0) {
1177 int v1 = getValue(row + 1, column);
1178 int v2 = getValue(row - 1, column);
1179 interpolationMin[pT] = std::min(v1, v2);
1180 interpolationMax[pT] = std::max(v1, v2);
1185 for (
int row = 1; row < interpNumRows - 1; row++) {
1186 for (
int column = 1; column < interpNumColumns - 1; column++) {
1187 if (row % 2 == 1 && column % 2 == 1)
1190 if (row % 2 == 0 && column % 2 == 0) {
1193 int v0 = getValue(row - 1, column - 1);
1194 int v1 = getValue(row + 1, column - 1);
1195 int v2 = getValue(row - 1, column + 1);
1196 int v3 = getValue(row + 1, column + 1);
1198 const int min_v0v3 = std::min(v0, v3);
1199 const int max_v0v3 = std::max(v0, v3);
1200 const int min_v1v2 = std::min(v1, v2);
1201 const int max_v1v2 = std::max(v1, v2);
1202 const bool diagonal0IsHigh = min_v0v3 > max_v1v2;
1203 const bool diagonal1IsHigh = min_v1v2 > max_v0v3;
1205 if (diagonal0IsHigh || diagonal1IsHigh) {
1206 const bool chooseDiagonal0 = usesHighDiagonalAtSaddle() ? diagonal0IsHigh : !diagonal0IsHigh;
1207 if (chooseDiagonal0) {
1208 setDiagonal0Connection(adj, row, column);
1209 interpolationMin[pT] = min_v0v3;
1210 interpolationMax[pT] = max_v0v3;
1212 setDiagonal1Connection(adj, row, column);
1213 interpolationMin[pT] = min_v1v2;
1214 interpolationMax[pT] = max_v1v2;
1218 interpolationMin[pT] = std::min({v0, v1, v2, v3});
1219 interpolationMax[pT] = std::max({v0, v1, v2, v3});
1224 return std::make_tuple(std::move(interpolationMin), std::move(interpolationMax), std::move(adj));
1241 FloodResult floodImage(
const ImageUInt8Ptr& imgPtr)
const {
1243 throw std::invalid_argument(
"TreeOfShapesBuilder requires a non-null image.");
1246 int numRows = imgPtr->getNumRows();
1247 int numColumns = imgPtr->getNumColumns();
1249 int interpNumColumns = interpolatedNumColumns(numColumns);
1250 int interpNumRows = interpolatedNumRows(numRows);
1251 int size = checkedDomainSize(interpNumRows, interpNumColumns);
1252 const bool connectivityMap = usesConnectivityMap();
1253 auto [interpolationMin, interpolationMax, adj] = connectivityMap ? interpolateImage4c8c(imgPtr) : interpolateImage(imgPtr);
1255 std::vector<uint8_t> dejavu(size, 0);
1256 std::vector<PixelId> imgR(size);
1257 std::vector<ToSFloodDepth> imgU(size);
1258 std::vector<ToSGrayLevel> grayLevel(size);
1260 PriorityQueueToS queue(connectivityMap ? ToSUInt8Depth : ToSSelfDualDepth);
1261 const PixelId infinityPixel = validatedInfinityPixel(interpNumRows, interpNumColumns);
1262 int priorityQueueOld = interpolationMin[infinityPixel];
1263 queue.initial(infinityPixel, priorityQueueOld);
1264 dejavu[infinityPixel] =
true;
1267 ToSFloodDepth depth = 0;
1268 while (!queue.isEmpty()) {
1269 const PixelId pixel = queue.priorityPop();
1270 int priorityQueue = queue.getCurrentPriority();
1271 if (connectivityMap) {
1272 if (priorityQueue != priorityQueueOld)
1274 imgU[pixel] = depth;
1276 imgU[pixel] =
static_cast<ToSFloodDepth
>(priorityQueue);
1278 grayLevel[pixel] =
static_cast<ToSGrayLevel
>(priorityQueue);
1281 imgR[order++] = pixel;
1284 for (PixelId neighbor : adj.getNeighborIndices(pixel)) {
1285 if (!dejavu[neighbor]) {
1286 queue.priorityPush(neighbor, interpolationMin[neighbor], interpolationMax[neighbor]);
1287 dejavu[neighbor] =
true;
1290 priorityQueueOld = priorityQueue;
1292 if (order != size) {
1293 throw std::runtime_error(
"Tree-of-shapes propagation did not visit the complete interpolated domain.");
1295 return FloodResult{std::move(imgU), std::move(grayLevel), std::move(imgR), std::move(adj)};
1309 CanonicalFloodTree buildCanonicalFloodTree(
const ImageUInt8Ptr& imgPtr)
const {
1310 FloodResult flood = floodImage(imgPtr);
1311 const int numPixelsInterp =
static_cast<int>(flood.treeLevel.size());
1313 std::vector<PixelId> zPar(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1314 std::vector<PixelId> parentInterpolate(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1315 auto findRoot = [&](
PixelId pStar) {
1316 while (zPar[pStar] != pStar) {
1317 zPar[pStar] = zPar[zPar[pStar]];
1318 pStar = zPar[pStar];
1323 for (
int i = numPixelsInterp - 1; i >= 0; --i) {
1324 const PixelId pStar = flood.order[
static_cast<size_t>(i)];
1325 parentInterpolate[pStar] = pStar;
1326 zPar[pStar] = pStar;
1327 for (PixelId qStar : flood.adjacency.getNeighborIndices(pStar)) {
1328 if (zPar[qStar] != InvalidPixel) {
1329 const PixelId rStar = findRoot(qStar);
1330 if (pStar != rStar) {
1331 parentInterpolate[rStar] = pStar;
1332 zPar[rStar] = pStar;
1338 auto sameLevel = [&](
PixelId aStar,
PixelId bStar) {
return flood.treeLevel[aStar] == flood.treeLevel[bStar]; };
1339 for (PixelId pStar : flood.order) {
1340 const PixelId qStar = parentInterpolate[pStar];
1341 if (sameLevel(parentInterpolate[qStar], qStar)) {
1342 parentInterpolate[pStar] = parentInterpolate[qStar];
1346 return CanonicalFloodTree{std::move(flood.treeLevel), std::move(flood.grayLevel), std::move(flood.order), std::move(parentInterpolate)};
1356 std::tuple<std::vector<ToSFloodDepth>, std::vector<PixelId>, AdjacencyUC> sort(
const ImageUInt8Ptr& imgPtr)
const {
1357 FloodResult flood = floodImage(imgPtr);
1358 return std::make_tuple(std::move(flood.treeLevel), std::move(flood.order), std::move(flood.adjacency));
1369 inline bool isOriginal1D(PixelId pixel,
int interpNumColumns)
const {
1370 const int row = pixel / interpNumColumns;
1371 const int column = pixel - row * interpNumColumns;
1372 const int offset = interpolationOffset();
1373 return (row & 1) == offset && (column & 1) == offset;
1385 inline PixelId toOriginal1D(PixelId pStar,
int interNumColumns,
int numColumns)
const {
1386 int r = pStar / interNumColumns;
1387 int c = pStar - r * interNumColumns;
1388 const int offset = interpolationOffset();
1389 return ((r - offset) >> 1) * numColumns + ((c - offset) >> 1);
1414 template <
class Altitude>
1415 [[nodiscard]] NativeHierarchyOwned<Altitude> computeIntrinsicAttributes(CanonicalFloodTree canonical, GridDomain2D gridDomain) {
1416 const int numRows = gridDomain.rows;
1417 const int numColumns = gridDomain.columns;
1418 const int numPixels = checkedDomainSize(numRows, numColumns);
1419 const int interpNumColumns = interpolatedNumColumns(numColumns);
1421 const int numPixelsInterp =
static_cast<int>(canonical.parent.size());
1423 return canonical.treeLevel[
static_cast<size_t>(aStar)] == canonical.treeLevel[
static_cast<size_t>(bStar)];
1425 auto repOf = [&](
PixelId pStar) {
1426 const PixelId parentStar = canonical.parent[
static_cast<size_t>(pStar)];
1427 return (parentStar == pStar || sameLevel(parentStar, pStar)) ? parentStar : pStar;
1430 std::vector<PixelId> canonicalReps;
1431 canonicalReps.reserve(
static_cast<size_t>(numPixels));
1432 for (PixelId pStar : canonical.order) {
1433 const PixelId parentStar = canonical.parent[
static_cast<size_t>(pStar)];
1434 if (parentStar == pStar || !sameLevel(parentStar, pStar)) {
1435 canonicalReps.push_back(pStar);
1438 if (canonicalReps.empty()) {
1439 throw std::runtime_error(
"Tree-of-shapes construction produced no canonical interpolated node.");
1442 const PixelId rawRoot = canonicalReps.front();
1443 std::vector<PixelId> rawParent(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1444 std::vector<PixelId> firstRawChild(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1445 std::vector<PixelId> nextRawSibling(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1446 for (PixelId repStar : canonicalReps) {
1447 if (repStar == rawRoot) {
1448 rawParent[
static_cast<size_t>(repStar)] = repStar;
1451 const PixelId parentRep = repOf(canonical.parent[
static_cast<size_t>(repStar)]);
1452 if (parentRep < 0 || parentRep >= numPixelsInterp || rawParent[
static_cast<size_t>(parentRep)] == InvalidPixel) {
1453 throw std::runtime_error(
"Tree-of-shapes canonical node has no canonical parent.");
1455 rawParent[
static_cast<size_t>(repStar)] = parentRep;
1456 nextRawSibling[
static_cast<size_t>(repStar)] = firstRawChild[
static_cast<size_t>(parentRep)];
1457 firstRawChild[
static_cast<size_t>(parentRep)] = repStar;
1460 std::vector<int> directCount(
static_cast<size_t>(numPixelsInterp), 0);
1461 std::vector<PixelId> representativePixelByElement(
static_cast<size_t>(numPixels), InvalidPixel);
1462 for (PixelId pStar = 0; pStar < numPixelsInterp; ++pStar) {
1463 if (!isOriginal1D(pStar, interpNumColumns)) {
1466 const PixelId elementId = toOriginal1D(pStar, interpNumColumns, numColumns);
1467 if (elementId < 0 || elementId >= numPixels) {
1468 throw std::runtime_error(
"Tree-of-shapes projection produced an element outside the source domain.");
1470 const PixelId representativePixel = repOf(pStar);
1471 representativePixelByElement[
static_cast<size_t>(elementId)] = representativePixel;
1472 ++directCount[
static_cast<size_t>(representativePixel)];
1474 if (std::find(representativePixelByElement.begin(), representativePixelByElement.end(),
InvalidPixel) != representativePixelByElement.end()) {
1475 throw std::runtime_error(
"Tree-of-shapes projection did not assign every source-domain element.");
1481 std::vector<ToSFloodDepth>().swap(canonical.treeLevel);
1482 std::vector<PixelId>().swap(canonical.parent);
1483 std::vector<PixelId>().swap(canonical.order);
1487 std::vector<PixelId> projectedRep(
static_cast<size_t>(numPixelsInterp), InvalidPixel);
1488 for (
auto it = canonicalReps.rbegin(); it != canonicalReps.rend(); ++it) {
1490 int numProjectedChildren = 0;
1492 for (PixelId childStar = firstRawChild[
static_cast<size_t>(repStar)]; childStar !=
InvalidPixel;
1493 childStar = nextRawSibling[
static_cast<size_t>(childStar)]) {
1494 const PixelId childProjection = projectedRep[
static_cast<size_t>(childStar)];
1495 if (childProjection != InvalidPixel) {
1496 ++numProjectedChildren;
1497 onlyProjectedChild = childProjection;
1501 if (directCount[
static_cast<size_t>(repStar)] > 0 || numProjectedChildren >= 2) {
1502 projectedRep[
static_cast<size_t>(repStar)] = repStar;
1503 }
else if (numProjectedChildren == 1) {
1504 projectedRep[
static_cast<size_t>(repStar)] = onlyProjectedChild;
1508 const PixelId projectedRootRep = projectedRep[
static_cast<size_t>(rawRoot)];
1509 if (projectedRootRep == InvalidPixel || projectedRep[
static_cast<size_t>(projectedRootRep)] != projectedRootRep) {
1510 throw std::runtime_error(
"Tree-of-shapes projection produced an empty hierarchy.");
1513 std::vector<PixelId>().swap(firstRawChild);
1514 std::vector<PixelId>().swap(nextRawSibling);
1515 std::vector<int>().swap(directCount);
1517 std::vector<NodeId> nodeIdByRep(
static_cast<size_t>(numPixelsInterp), InvalidNode);
1519 for (PixelId repStar : canonicalReps) {
1520 if (projectedRep[
static_cast<size_t>(repStar)] == repStar) {
1521 nodeIdByRep[
static_cast<size_t>(repStar)] = numNodes++;
1525 std::vector<NodeId> nodeParent(
static_cast<size_t>(numNodes), InvalidNode);
1526 std::vector<NodeId> smallestNode(
static_cast<size_t>(numPixels), InvalidNode);
1527 std::vector<Altitude> nodeValuation(
static_cast<size_t>(numNodes));
1528 const NodeId root = nodeIdByRep[
static_cast<size_t>(projectedRootRep)];
1530 for (PixelId repStar : canonicalReps) {
1531 const NodeId nodeId = nodeIdByRep[
static_cast<size_t>(repStar)];
1532 if (nodeId == InvalidNode) {
1536 if (repStar == projectedRootRep) {
1537 nodeParent[
static_cast<size_t>(nodeId)] = nodeId;
1539 PixelId ancestorRep = rawParent[
static_cast<size_t>(repStar)];
1540 while (ancestorRep != InvalidPixel && nodeIdByRep[
static_cast<size_t>(ancestorRep)] ==
InvalidNode) {
1541 if (ancestorRep == rawParent[
static_cast<size_t>(ancestorRep)]) {
1545 ancestorRep = rawParent[
static_cast<size_t>(ancestorRep)];
1547 if (ancestorRep == InvalidPixel) {
1548 throw std::runtime_error(
"Tree-of-shapes retained node has no retained parent.");
1550 nodeParent[
static_cast<size_t>(nodeId)] = nodeIdByRep[
static_cast<size_t>(ancestorRep)];
1553 nodeValuation[
static_cast<size_t>(nodeId)] =
1554 ToSAltitudeEncodingFor<Altitude>::type::encode(canonical.grayLevel[
static_cast<size_t>(repStar)], immersionMode_);
1557 for (PixelId elementId = 0; elementId < numPixels; ++elementId) {
1558 const PixelId representativePixel = representativePixelByElement[
static_cast<size_t>(elementId)];
1559 const NodeId smallestNodeId = nodeIdByRep[
static_cast<size_t>(representativePixel)];
1560 if (smallestNodeId == InvalidNode) {
1561 throw std::runtime_error(
"Tree-of-shapes proper part belongs to a contracted node.");
1563 smallestNode[
static_cast<size_t>(elementId)] = smallestNodeId;
1565 for (NodeId nodeId = 0; nodeId < numNodes; ++nodeId) {
1566 const NodeId parentNodeId = nodeParent[
static_cast<std::size_t
>(nodeId)];
1567 if (nodeId != root && nodeValuation[
static_cast<std::size_t
>(nodeId)] == nodeValuation[
static_cast<std::size_t
>(parentNodeId)]) {
1568 throw std::runtime_error(
"Tree-of-shapes construction produced an equal-altitude parent-child edge.");
1571 return makeNativeHierarchyOwned<Altitude>(
1572 std::move(nodeParent), std::move(smallestNode), std::move(nodeValuation), root, gridDomain,
1598 throw std::invalid_argument(
"Tree-of-shapes altitude type does not match the altitude encoding declared by the topographic convention.");
1600 validateConventionForImage(
imgPtr);