v0.2.54..v0.2.55 changeset NodeDensityTileBoundsCalculator.cpp
Garret Voltz edited this page Aug 14, 2020
·
1 revision
diff --git a/hoot-core/src/main/cpp/hoot/core/conflate/tile/NodeDensityTileBoundsCalculator.cpp b/hoot-core/src/main/cpp/hoot/core/conflate/tile/NodeDensityTileBoundsCalculator.cpp
new file mode 100644
index 0000000..dc3b195
--- /dev/null
+++ b/hoot-core/src/main/cpp/hoot/core/conflate/tile/NodeDensityTileBoundsCalculator.cpp
@@ -0,0 +1,721 @@
+/*
+ * This file is part of Hootenanny.
+ *
+ * Hootenanny is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --------------------------------------------------------------------
+ *
+ * The following copyright notices are generated automatically. If you
+ * have a new notice to add, please use the format:
+ * " * @copyright Copyright ..."
+ * This will properly maintain the copyright information. DigitalGlobe
+ * copyrights will be updated automatically.
+ *
+ * @copyright Copyright (C) 2015, 2016, 2017, 2018, 2019, 2020 DigitalGlobe (http://www.digitalglobe.com/)
+ */
+
+#include "NodeDensityTileBoundsCalculator.h"
+
+// hoot
+#include <hoot/core/visitors/CalculateMapBoundsVisitor.h>
+#include <hoot/core/elements/Node.h>
+#include <hoot/core/util/ConfigOptions.h>
+#include <hoot/core/util/Log.h>
+#include <hoot/core/util/GeometryUtils.h>
+#include <hoot/core/util/StringUtils.h>
+
+// Qt
+#include <QImage>
+#include <QPainter>
+
+using namespace geos::geom;
+using namespace std;
+
+namespace hoot
+{
+
+int NodeDensityTileBoundsCalculator::logWarnCount = 0;
+
+NodeDensityTileBoundsCalculator::NodeDensityTileBoundsCalculator() :
+_pixelSize(0.001),
+_maxNodesPerTile(1000),
+_slop(0.1),
+_maxNodeCountInOneTile(0),
+_minNodeCountInOneTile(LONG_MAX),
+_pixelSizeRetryReductionFactor(10),
+_maxNumTries(3),
+_maxTimePerAttempt(-1)
+{
+}
+
+void NodeDensityTileBoundsCalculator::calculateTiles(OsmMapPtr map)
+{
+ if (_maxTimePerAttempt > 0)
+ {
+ _timer.restart();
+ }
+
+ LOG_VARD(map->getNodeCount());
+ if (map->getNodeCount() <= _maxNodesPerTile)
+ {
+ LOG_STATUS(
+ "Node count " << StringUtils::formatLargeNumber(map->getNodeCount()) << " is less than "
+ "specified maximum node count per tile. Returning a single tile covering all of the input "
+ "data...");
+
+ const geos::geom::Envelope bounds = CalculateMapBoundsVisitor::getGeosBounds(map);
+ std::vector<geos::geom::Envelope> subTiles;
+ subTiles.push_back(bounds);
+ _tiles.push_back(subTiles);
+ std::vector<long> subCounts;
+ subCounts.push_back(map->getNodeCount());
+ _nodeCounts.push_back(subCounts);
+ _minNodeCountInOneTile = map->getNodeCount();
+ _maxNodeCountInOneTile = map->getNodeCount();
+ }
+ else
+ {
+ int tryCtr = 0;
+ while (_tiles.empty() && tryCtr < _maxNumTries)
+ {
+ tryCtr++;
+ LOG_STATUS(
+ "Running node density tiles calculation attempt " << tryCtr << " / " <<
+ _maxNumTries << " with pixel size: " << _pixelSize << ", max allowed nodes: " <<
+ StringUtils::formatLargeNumber(_maxNodesPerTile) << ", and total input node size: " <<
+ StringUtils::formatLargeNumber(map->getNodeCount()) << "...");
+
+ cv::Mat r1, r2;
+ _renderImage(map, r1, r2);
+ // We're calculating for unknown1 only, so fill the second matrix with all zeroes.
+ cv::Mat zeros = cv::Mat::zeros(r1.size(), r1.type());
+ _setImages(r1, zeros);
+
+ try
+ {
+ _calculateTiles();
+ }
+ catch (const TileCalcException& e)
+ {
+ QString msg =
+ "Tile calculation attempt " + QString::number(tryCtr) + " / " +
+ QString::number(_maxNumTries) + " failed.";
+ if (tryCtr == _maxNumTries)
+ {
+ msg += " Aborting calculation.";
+ LOG_ERROR(msg);
+ throw e;
+ }
+ else
+ {
+ msg += " Retrying calculation...";
+ LOG_STATUS(msg);
+ if (_pixelSizeRetryReductionFactor != -1)
+ {
+ _pixelSize -= _pixelSize * (_pixelSizeRetryReductionFactor / 100.0);
+ }
+ }
+ }
+ }
+ }
+}
+
+void NodeDensityTileBoundsCalculator::_calculateMin()
+{
+ int w = ceil((_envelope.MaxX - _envelope.MinX) / _pixelSize) + 1;
+ LOG_VART(w);
+ int h = ceil((_envelope.MaxY - _envelope.MinY) / _pixelSize) + 1;
+ LOG_VART(h);
+
+ assert(h == _r1.rows && w == _r1.cols);
+ assert(_r1.rows == _r2.rows && _r1.cols == _r2.cols);
+
+ _min = cv::Mat(cvSize(w, h), CV_32SC1);
+ LOG_VART(_min);
+
+ _maxValue = 1.0;
+ for (int py = 0; py < h; py++)
+ {
+ int32_t* row1 = _r1.ptr<int32_t>(py);
+ int32_t* row2 = _r2.ptr<int32_t>(py);
+ int32_t* rowM = _min.ptr<int32_t>(py);
+
+ for (int px = 0; px < w; px++)
+ {
+ rowM[px] = std::min(row1[px], row2[px]);
+ _maxValue = std::max(_maxValue, std::max(row1[px], row2[px]));
+ }
+ }
+}
+
+QString NodeDensityTileBoundsCalculator::tilesToString(const vector<vector<Envelope>>& tiles)
+{
+ QString str;
+ for (size_t tx = 0; tx < tiles.size(); tx++)
+ {
+ for (size_t ty = 0; ty < tiles[tx].size(); ty++)
+ {
+ str += GeometryUtils::toConfigString(tiles[tx][ty]) + "\n";
+ }
+ }
+ str.chop(1);
+ return str;
+}
+
+void NodeDensityTileBoundsCalculator::_checkForTimeout()
+{
+ if (_maxTimePerAttempt > 0 && (_timer.elapsed() / 1000) > _maxTimePerAttempt)
+ {
+ throw TileCalcException(
+ "Calculation timed out at " + QString::number(_timer.elapsed() / 1000) +
+ " seconds out of maximum allowed " + QString::number(_maxTimePerAttempt) + " seconds.");
+ }
+}
+
+void NodeDensityTileBoundsCalculator::_calculateTiles()
+{
+ LOG_INFO("Calculating tiles...");
+
+ size_t width = 1;
+ vector<PixelBox> boxes;
+ boxes.resize(1);
+ boxes[0] = PixelBox(0, _r1.cols - 1, 0, _r1.rows - 1);
+
+ double nodeCount = _sumPixels(boxes[0]);
+ LOG_DEBUG("w: " << _r1.cols << " h: " << _r1.rows);
+ LOG_DEBUG("Total node count: " << nodeCount);
+ LOG_VARD(_maxNodesPerTile);
+
+ while (!_isDone(boxes))
+ {
+ width *= 2;
+
+ vector<PixelBox> nextLayer;
+ nextLayer.resize(width * width);
+
+ LOG_TRACE("width: " << width);
+ for (size_t i = 0; i < boxes.size(); i++)
+ {
+ PixelBox& b = boxes[i];
+ double splitX = _calculateSplitX(b);
+ int tx = i % (width / 2);
+ int ty = i / (width / 2);
+ LOG_TRACE(" i: " << i << " tx: " << tx << " ty: " << ty);
+
+ double splitYLeft = _calculateSplitY(PixelBox(b.minX, splitX, b.minY, b.maxY));
+ nextLayer[(tx * 2 + 0) + (ty * 2 + 0) * width] =
+ PixelBox(b.minX, splitX, b.minY, splitYLeft);
+ nextLayer[(tx * 2 + 0) + (ty * 2 + 1) * width] =
+ PixelBox(b.minX, splitX, splitYLeft + 1, b.maxY);
+
+ double splitYRight = _calculateSplitY(PixelBox(splitX + 1, b.maxX, b.minY, b.maxY));
+ nextLayer[(tx * 2 + 1) + (ty * 2 + 0) * width] =
+ PixelBox(splitX + 1, b.maxX, b.minY, splitYRight);
+ nextLayer[(tx * 2 + 1) + (ty * 2 + 1) * width] =
+ PixelBox(splitX + 1, b.maxX, splitYRight + 1, b.maxY);
+ }
+
+ boxes = nextLayer;
+
+ _checkForTimeout();
+ }
+
+ _tiles.clear();
+ _maxNodeCountInOneTile = 0;
+ _minNodeCountInOneTile = LONG_MAX;
+ LOG_VARD(width);
+ _tiles.resize(width);
+ _nodeCounts.clear();
+ _nodeCounts.resize(width);
+
+ for (size_t tx = 0; tx < width; tx++)
+ {
+ _tiles[tx].resize(width);
+ _nodeCounts[tx].resize(width);
+ for (size_t ty = 0; ty < width; ty++)
+ {
+ PixelBox& pb = boxes[tx + ty * width];
+ LOG_VART(pb.getWidth());
+ LOG_VART(pb.getHeight());
+
+ if (pb.getWidth() < 3 || pb.getHeight() < 3)
+ {
+ throw TileCalcException(
+ "Node density tiles pixel box must be at least three pixels wide and tall. Try "
+ "reducing the pixel size or increasing the maximum allowed nodes per tile. Current "
+ "pixel box width: " + QString::number(pb.getWidth()) + "; height: " +
+ QString::number(pb.getHeight()));
+ }
+
+ const long nodeCount = _sumPixels(pb);
+ _nodeCounts[tx][ty] = nodeCount;
+ _maxNodeCountInOneTile = std::max(_maxNodeCountInOneTile, nodeCount);
+ _minNodeCountInOneTile = std::min(_minNodeCountInOneTile, nodeCount);
+
+ _tiles[tx][ty] = _toEnvelope(pb);
+ }
+
+ _checkForTimeout();
+ }
+
+ if (_maxNodeCountInOneTile == 0)
+ {
+ throw TileCalcException(
+ "The maximum node density tiles node count in one tile is zero. Try reducing the pixel "
+ "size or increasing the maximum allowed nodes per tile.");
+ }
+ LOG_TRACE("Tiles: " + tilesToString(_tiles));
+
+ _exportResult(boxes, "tmp/result.png");
+}
+
+int NodeDensityTileBoundsCalculator::_calculateSplitX(PixelBox& b)
+{
+ double total = _sumPixels(b);
+ LOG_VART(total);
+
+ double left = _sumPixels(b.getColumnBox(b.minX));
+ LOG_VART(left);
+
+ int best = (b.maxX + b.minX) / 2;
+ double bestSum = numeric_limits<double>::max();
+
+ double thisSlop = _slop + 1.0 / (double)(b.maxX - b.minX);
+ LOG_VART(thisSlop);
+
+ LOG_VART(b.getWidth());
+ if (b.getWidth() < 6)
+ {
+ throw TileCalcException(
+ "Node density tiles pixel box must be at least six pixels wide. Try reducing the input "
+ "pixel size or increasing the maximum nodes allowed per tile. Current pixel box width: " +
+ QString::number(b.getWidth()));
+ }
+
+ for (int c = b.minX + 2; c < b.maxX - 2; c++)
+ {
+ double colSum = _sumPixels(b.getColumnBox(c));
+ LOG_VART(colSum);
+ double colSumMin =
+ _sumPixels(b.getColumnBox(c), _min) + _sumPixels(b.getColumnBox(c + 1), _min);
+ LOG_VART(colSumMin);
+ left += colSum;
+
+ double slop = abs(0.5 - left / total);
+ if ((slop < thisSlop) && colSumMin < bestSum)
+ {
+ best = c;
+ bestSum = colSumMin;
+ }
+ }
+ LOG_VART(left);
+ LOG_VART(best);
+ LOG_VART(bestSum);
+
+ if (bestSum == numeric_limits<double>::max())
+ {
+ if (logWarnCount < Log::getWarnMessageLimit())
+ {
+ LOG_WARN("Node density tiles bestSum isn't valid. " << b.toString());
+ }
+ else if (logWarnCount == Log::getWarnMessageLimit())
+ {
+ LOG_WARN(className() << ": " << Log::LOG_WARN_LIMIT_REACHED_MESSAGE);
+ }
+ logWarnCount++;
+ }
+
+ LOG_VART(best);
+ return best;
+}
+
+int NodeDensityTileBoundsCalculator::_calculateSplitY(const PixelBox& b)
+{
+ double total = _sumPixels(b);
+ LOG_VART(total);
+
+ double bottom = _sumPixels(b.getRowBox(b.minY));
+ LOG_VART(bottom);
+
+ int best = (b.maxY + b.minY) / 2;
+ double bestSum = numeric_limits<double>::max();
+
+ double thisSlop = _slop + 1.0 / (double)(b.maxY - b.minY);
+ LOG_VART(thisSlop);
+
+ if (b.getHeight() < 6)
+ {
+ throw TileCalcException(
+ "Node density tiles pixel box must be at least six pixels high. Try reducing the input "
+ "pixel size or increasing the maximum nodes allowed per tile. Current pixel box height: " +
+ QString::number(b.getHeight()));
+ }
+
+ for (int r = b.minY + 2; r < b.maxY - 2; r++)
+ {
+ double rowSum = _sumPixels(b.getRowBox(r));
+ LOG_VART(rowSum);
+ double rowSumMin = _sumPixels(b.getRowBox(r), _min) + _sumPixels(b.getRowBox(r + 1), _min);
+ LOG_VART(rowSumMin);
+ bottom += rowSum;
+
+ double slop = abs(0.5 - bottom / total);
+ if ((slop < thisSlop) && rowSumMin < bestSum)
+ {
+ best = r;
+ bestSum = rowSumMin;
+ }
+ }
+ LOG_VART(bottom);
+ LOG_VART(best);
+ LOG_VART(bestSum);
+
+ if (bestSum == numeric_limits<double>::max())
+ {
+ if (logWarnCount < Log::getWarnMessageLimit())
+ {
+ LOG_WARN(
+ "Node density tiles bestSum isn't valid. " << b.toString() << " total: " << total <<
+ " size: " << b.maxY - b.minY);
+ }
+ else if (logWarnCount == Log::getWarnMessageLimit())
+ {
+ LOG_WARN(className() << ": " << Log::LOG_WARN_LIMIT_REACHED_MESSAGE);
+ }
+ logWarnCount++;
+ }
+
+ LOG_VART(best);
+ return best;
+}
+
+void NodeDensityTileBoundsCalculator::_countNode(const std::shared_ptr<Node> &n)
+{
+ double x = n->getX();
+ double y = n->getY();
+
+ int px = (x - _envelope.MinX) / _pixelSize;
+ LOG_VART(px);
+ int py = (y - _envelope.MinY) / _pixelSize;
+ LOG_VART(py);
+
+ if (px < 0 || px >= _r1.cols || py < 0 || py >= _r1.rows)
+ {
+ throw HootException("Node density tiles pixel out of bounds.");
+ }
+ else
+ {
+ int32_t* row;
+ if (n->getStatus() == Status::Unknown1)
+ {
+ row = _r1.ptr<int32_t>(py);
+ row[px] += 1.0;
+ }
+ else if (n->getStatus() == Status::Unknown2)
+ {
+ row = _r2.ptr<int32_t>(py);
+ row[px] += 1.0;
+ }
+ }
+}
+
+double NodeDensityTileBoundsCalculator::_evaluateSplitPoint(const PixelBox& pb, const Pixel& p)
+{
+ // This function has two goals:
+ // * minimize the number of nodes intersected by a split
+ // * minimize the difference between quadrant counts, or ignore the quadrant counts if all
+ // the quadrants are below _maxNodesPerTile
+ //
+ // Smaller scores are better.
+
+ double llSum = _sumPixels(PixelBox(pb.minX, p.x, pb.minY, p.y));
+ double ulSum = _sumPixels(PixelBox(pb.minX, p.x, p.y + 1, pb.maxY));
+ double lrSum = _sumPixels(PixelBox(p.x + 1, pb.maxX, pb.minY, p.y));
+ double urSum = _sumPixels(PixelBox(p.x + 1, pb.maxX, p.y + 1, pb.maxY));
+
+ double total = llSum + ulSum + lrSum + urSum;
+# ifdef DEBUG
+ assert(fabs(total - _sumPixels(pb)) < 0.1);
+# endif
+
+ double avg = total / 4.0;
+
+ double slop = fabs(llSum - avg) / avg;
+ slop += fabs(ulSum - avg) / avg;
+ slop += fabs(lrSum - avg) / avg;
+ slop += fabs(urSum - avg) / avg;
+ slop /= 4.0;
+
+ double slopMultiplier;
+ if (slop > _slop)
+ {
+ slopMultiplier = 2.0 + slop;
+ }
+ else
+ {
+ slopMultiplier = 1.0 + slop;
+ }
+
+ double intersects = 0.0;
+ intersects += _sumPixels(PixelBox(p.x, p.x, pb.minY, pb.maxY));
+ intersects += _sumPixels(PixelBox(pb.minX, pb.maxX, p.y, p.y));
+
+ return intersects * slopMultiplier;
+}
+
+void NodeDensityTileBoundsCalculator::_exportImage(cv::Mat &r, QString output)
+{
+ QImage qImage(r.cols, r.rows, QImage::Format_RGB16);
+ if (qImage.isNull())
+ {
+ throw HootException(
+ QString("Node density tiles: Unable to allocate image of size %1x%2")
+ .arg(r.cols)
+ .arg(r.rows));
+ }
+ QPainter pt(&qImage);
+ pt.setRenderHint(QPainter::Antialiasing, false);
+ pt.fillRect(pt.viewport(), Qt::black);
+ QPen pen;
+ pen.setWidth(0);
+ pen.setColor(qRgb(1, 0, 0));
+ pt.setPen(pen);
+
+ LOG_VART(_maxValue);
+
+ LOG_VART(r.cols); //18k
+ for (int y = 0; y < r.rows; y++)
+ {
+ int32_t* row = r.ptr<int32_t>(y);
+ for (int x = 0; x < r.cols; x++)
+ {
+ double l;
+ if (row[x] == 0)
+ {
+ l = 0.0;
+ }
+ else
+ {
+ l = log(row[x]) / log(_maxValue);
+ }
+ int v = l * 255;
+ qImage.setPixel(x, r.rows - y - 1, qRgb(v, v, 50));
+ }
+
+ _checkForTimeout();
+ }
+
+ qImage.save(output);
+}
+
+void NodeDensityTileBoundsCalculator::_exportResult(const vector<PixelBox>& boxes, QString output)
+{
+ QImage qImage(_r1.cols, _r1.rows, QImage::Format_RGB16);
+ if (qImage.isNull())
+ {
+ throw HootException(
+ QString("Node density tiles: Unable to allocate image of size %1x%2")
+ .arg(_r1.cols)
+ .arg(_r1.rows));
+ }
+ QPainter pt(&qImage);
+ pt.setRenderHint(QPainter::Antialiasing, false);
+ pt.fillRect(pt.viewport(), Qt::black);
+ QPen pen;
+ pen.setWidth(0);
+ pen.setColor(qRgb(1, 0, 0));
+ pt.setPen(pen);
+
+ LOG_TRACE("max value: " << _maxValue);
+
+ LOG_VART(_r1.rows); //14k
+ LOG_VART(_r1.cols); //18k
+ for (int y = 0; y < _r1.rows; y++)
+ {
+ int32_t* row1 = _r1.ptr<int32_t>(y);
+ int32_t* row2 = _r2.ptr<int32_t>(y);
+ for (int x = 0; x < _r1.cols; x++)
+ {
+ double l1 = row1[x] <= 0 ? 0.0 : log(row1[x]) / log(_maxValue);
+ double l2 = row2[x] <= 0 ? 0.0 : log(row2[x]) / log(_maxValue);
+
+ qImage.setPixel(x, _r1.rows - y - 1, qRgb(l1 * 255, l2 * 255, 0));
+ }
+
+ _checkForTimeout();
+ }
+
+ pt.setPen(QPen(QColor(0, 0, 255, 100)));
+ for (size_t i = 0; i < boxes.size(); i++)
+ {
+ const PixelBox& b = boxes[i];
+ pt.drawRect(b.minX, _r1.rows - b.maxY - 1, b.maxX - b.minX, b.maxY - b.minY);
+
+ _checkForTimeout();
+ }
+
+ qImage.save(output);
+}
+
+bool NodeDensityTileBoundsCalculator::_isDone(vector<PixelBox>& boxes)
+{
+ LOG_VART(boxes.size());
+
+ bool smallEnough = true;
+ bool minSize = false;
+
+ for (size_t i = 0; i < boxes.size(); i++)
+ {
+ PixelBox& b = boxes[i];
+ if (b.getWidth() == 1 || b.getHeight() == 1)
+ {
+ minSize = true;
+ }
+
+ if (_sumPixels(b) > _maxNodesPerTile)
+ {
+ smallEnough = false;
+ }
+ }
+
+ if (minSize == true && smallEnough == false)
+ {
+ throw TileCalcException(
+ "Could not find a node density tiles solution. Try reducing the pixel size or "
+ "increasing the maximum nodes allowed per tile.");
+ }
+ else
+ {
+ return smallEnough;
+ }
+}
+
+void NodeDensityTileBoundsCalculator::_renderImage(const std::shared_ptr<OsmMap>& map)
+{
+ _envelope = CalculateMapBoundsVisitor::getBounds(map);
+
+ _renderImage(map, _r1, _r2);
+
+ _calculateMin();
+
+ _exportImage(_r1, "tmp/r1.png");
+ _exportImage(_r2, "tmp/r2.png");
+ _exportImage(_min, "tmp/min.png");
+}
+
+void NodeDensityTileBoundsCalculator::_renderImage(const std::shared_ptr<OsmMap>& map, cv::Mat& r1,
+ cv::Mat& r2)
+{
+ LOG_INFO("Rendering images...");
+
+ _envelope = CalculateMapBoundsVisitor::getBounds(map);
+ if (Log::getInstance().getLevel() <= Log::Debug)
+ {
+ std::shared_ptr<geos::geom::Envelope> tempEnv(GeometryUtils::toEnvelope(_envelope));
+ LOG_VART(tempEnv->toString());
+ }
+ LOG_VART(_pixelSize);
+ LOG_VART(StringUtils::formatLargeNumber(map->getNodeCount()));
+
+ int w = ceil((_envelope.MaxX - _envelope.MinX) / _pixelSize) + 1;
+ LOG_VART(w);
+ int h = ceil((_envelope.MaxY - _envelope.MinY) / _pixelSize) + 1;
+ LOG_VART(h)
+
+ _r1 = cv::Mat(cvSize(w, h), CV_32SC1);
+ _r2 = cv::Mat(cvSize(w, h), CV_32SC1);
+
+ for (int py = 0; py < h; py++)
+ {
+ int32_t* row1 = _r1.ptr<int32_t>(py);
+ int32_t* row2 = _r2.ptr<int32_t>(py);
+
+ for (int px = 0; px < w; px++)
+ {
+ row1[px] = 0.0;
+ row2[px] = 0.0;
+ }
+ }
+
+ const NodeMap& nm = map->getNodes();
+ LOG_VART(nm.size());
+ long nodeCtr = 0;
+ const int statusUpdateInterval = ConfigOptions().getTaskStatusUpdateInterval();
+ for (NodeMap::const_iterator it = nm.begin(); it != nm.end(); ++it)
+ {
+ const std::shared_ptr<Node>& n = it->second;
+ _countNode(n);
+
+ nodeCtr++;
+ if (nodeCtr % statusUpdateInterval == 0)
+ {
+ PROGRESS_INFO(
+ "Counted " << StringUtils::formatLargeNumber(nodeCtr) << " / " <<
+ StringUtils::formatLargeNumber(nm.size()) << " nodes.");
+ }
+ }
+
+ r1 = _r1;
+ r2 = _r2;
+}
+
+void NodeDensityTileBoundsCalculator::_setImages(const cv::Mat& r1, const cv::Mat& r2)
+{
+ LOG_INFO("Exporting images...");
+
+ _r1 = r1;
+ _r2 = r2;
+
+ _calculateMin();
+
+ _exportImage(_r1, "tmp/r1.png");
+ _exportImage(_r2, "tmp/r2.png");
+ _exportImage(_min, "tmp/min.png");
+}
+
+long NodeDensityTileBoundsCalculator::_sumPixels(const PixelBox& pb)
+{
+ return _sumPixels(pb, _r1) + _sumPixels(pb, _r2);
+}
+
+long NodeDensityTileBoundsCalculator::_sumPixels(const PixelBox& pb, cv::Mat& r)
+{
+ LOG_VART(pb.minY);
+ LOG_VART(pb.maxY);
+ LOG_VART(pb.minX);
+ LOG_VART(pb.maxX);
+
+ long sum = 0.0;
+ for (int py = pb.minY; py <= pb.maxY; py++)
+ {
+ int32_t* row = r.ptr<int32_t>(py);
+
+ for (int px = pb.minX; px <= pb.maxX; px++)
+ {
+ sum += row[px];
+ }
+ }
+ LOG_VART(sum);
+ return sum;
+}
+
+Envelope NodeDensityTileBoundsCalculator::_toEnvelope(const PixelBox& pb)
+{
+ return Envelope(_envelope.MinX + pb.minX * _pixelSize,
+ _envelope.MinX + (pb.maxX + 1) * _pixelSize,
+ _envelope.MinY + pb.minY * _pixelSize,
+ _envelope.MinY + (pb.maxY + 1) * _pixelSize);
+}
+
+}