File indexing completed on 2024-05-12 03:47:30

0001 /*
0002     File                 : Segments.cpp
0003     Project              : LabPlot
0004     Description          : Contains methods to trace curve of image/plot
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2015 Ankit Wagadre <wagadre.ankit@gmail.com>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "Segments.h"
0011 #include "backend/datapicker/ImageEditor.h"
0012 #include "backend/datapicker/Segment.h"
0013 
0014 #include <QGraphicsItem>
0015 #include <QGraphicsScene>
0016 #include <QImage>
0017 
0018 /**
0019  * \class Segments
0020  * \brief container to open image/plot.
0021  *
0022  * this class is a container for all of the segment. a segment is a set
0023  * of linked lines that run along a curve in the original image. the
0024  * main complication is that curves in the original image cross each other
0025  * and other things like grid lines. we rely on the user to link
0026  * multiple segments together to get points along the entire curve length
0027  * * \ingroup datapicker
0028  */
0029 
0030 Segments::Segments(DatapickerImage* image)
0031     : m_image(image) {
0032 }
0033 
0034 /*!
0035     segments are built when the original image is loaded. they start out hidden
0036      and remain so until showSegments is called
0037 */
0038 void Segments::makeSegments(QImage& imageProcessed) {
0039     //  QElapsedTimer timer;
0040     //  timer.start();
0041     clearSegments();
0042 
0043     const int width = imageProcessed.width();
0044     const int height = imageProcessed.height();
0045 
0046     // for each new column of pixels, loop through the runs. a run is defined as
0047     // one or more colored pixels that are all touching, with one uncolored pixel or the
0048     // image boundary at each end of the set. for each set in the current column, count
0049     // the number of runs it touches in the adjacent (left and right) columns. here is
0050     // the pseudocode:
0051     //   if ((L > 1) || (R > 1))
0052     //     "this run is at a branch point so ignore the set"
0053     //   else
0054     //     if (L == 0)
0055     //       "this run is the start of a new segment"
0056     //     else
0057     //       "this run is appended to the segment on the left
0058 
0059     bool* lastBool = new bool[height];
0060     bool* currBool = new bool[height];
0061     bool* nextBool = new bool[height];
0062     auto** lastSegment = new Segment*[(size_t)height];
0063     auto** currSegment = new Segment*[(size_t)height];
0064     loadSegment(lastSegment, height);
0065 
0066     // initialize one column of boolean flags using the pixels of the specified column
0067     for (int y = 0; y < height; ++y) {
0068         lastBool[y] = false;
0069         currBool[y] = ImageEditor::processedPixelIsOn(imageProcessed, 0, y);
0070         nextBool[y] = ImageEditor::processedPixelIsOn(imageProcessed, 1, y);
0071     }
0072 
0073     for (int x = 0; x < width; ++x) {
0074         matchRunsToSegments(x, height, lastBool, lastSegment, currBool, currSegment, nextBool);
0075 
0076         // get ready for next column
0077         for (int y = 0; y < height; ++y) {
0078             lastBool[y] = currBool[y];
0079             currBool[y] = nextBool[y];
0080         }
0081 
0082         if (x + 1 < width) {
0083             for (int y = 0; y < height; ++y)
0084                 nextBool[y] = ImageEditor::processedPixelIsOn(imageProcessed, x + 1, y);
0085         }
0086         scrollSegment(lastSegment, currSegment, height);
0087     }
0088 
0089     delete[] lastBool;
0090     delete[] currBool;
0091     delete[] nextBool;
0092     delete[] lastSegment;
0093     delete[] currSegment;
0094     //  qDebug() << "Made segments in " << timer.elapsed() << "ms";
0095 }
0096 
0097 /*!
0098     scroll the segment pointers of the right column into the left column
0099 */
0100 void Segments::scrollSegment(Segment** left, Segment** right, int height) {
0101     for (int y = 0; y < height; ++y)
0102         left[y] = right[y];
0103 }
0104 
0105 /*!
0106     identify the runs in a column, and connect them to segments
0107 */
0108 void Segments::matchRunsToSegments(int x, int height, bool* lastBool, Segment** lastSegment, const bool* currBool, Segment** currSegment, bool* nextBool) {
0109     loadSegment(currSegment, height);
0110 
0111     int yStart = 0;
0112     bool inRun = false;
0113     for (int y = 0; y < height; ++y) {
0114         if (!inRun && currBool[y]) {
0115             inRun = true;
0116             yStart = y;
0117         }
0118 
0119         if ((y + 1 >= height) || !currBool[y + 1]) {
0120             if (inRun)
0121                 finishRun(lastBool, nextBool, lastSegment, currSegment, x, yStart, y, height);
0122 
0123             inRun = false;
0124         }
0125     }
0126 
0127     removeUnneededLines(lastSegment, currSegment, height);
0128 }
0129 
0130 /*!
0131     remove unneeded lines belonging to segments that just finished in the previous column.
0132 */
0133 void Segments::removeUnneededLines(Segment** lastSegment, Segment** currSegment, int height) {
0134     Segment* segLast = nullptr;
0135     for (int yLast = 0; yLast < height; ++yLast) {
0136         if (lastSegment[yLast] && (lastSegment[yLast] != segLast)) {
0137             segLast = lastSegment[yLast];
0138 
0139             bool found = false;
0140             for (int yCur = 0; yCur < height; ++yCur)
0141                 if (segLast == currSegment[yCur]) {
0142                     found = true;
0143                     break;
0144                 }
0145 
0146             if (!found) {
0147                 if (segLast->length < m_image->minSegmentLength()) {
0148                     // remove whole segment since it is too short
0149                     m_image->scene()->removeItem(segLast->graphicsItem());
0150                     segments.removeOne(segLast);
0151                 }
0152             }
0153         }
0154     }
0155 }
0156 
0157 /*!
0158     initialize one column of segment pointers
0159 */
0160 void Segments::loadSegment(Segment** columnSegment, int height) {
0161     for (int y = 0; y < height; ++y)
0162         columnSegment[y] = nullptr;
0163 }
0164 
0165 void Segments::clearSegments() {
0166     for (auto* seg : segments)
0167         m_image->scene()->removeItem(seg->graphicsItem());
0168 
0169     segments.clear();
0170 }
0171 
0172 /*!
0173     set segments visible
0174 */
0175 void Segments::setSegmentsVisible(bool on) {
0176     for (auto* seg : segments)
0177         seg->setVisible(on);
0178 }
0179 
0180 void Segments::setAcceptHoverEvents(bool on) {
0181     for (auto* seg : segments) {
0182         QGraphicsItem* item = seg->graphicsItem();
0183         item->setAcceptHoverEvents(on);
0184         item->setFlag(QGraphicsItem::ItemIsSelectable, on);
0185     }
0186 }
0187 
0188 /*!
0189     process a run of pixels. if there are fewer than two adjacent pixel runs on
0190     either side, this run will be added to an existing segment, or the start of
0191     a new segment
0192 */
0193 void Segments::finishRun(bool* lastBool, bool* nextBool, Segment** lastSegment, Segment** currSegment, int x, int yStart, int yStop, int height) {
0194     // count runs that touch on the left
0195     if (adjacentRuns(lastBool, yStart, yStop, height) > 1)
0196         return;
0197 
0198     // count runs that touch on the right
0199     if (adjacentRuns(nextBool, yStart, yStop, height) > 1)
0200         return;
0201 
0202     Segment* seg;
0203     int y = (int)((yStart + yStop) / 2);
0204     if (adjacentSegments(lastSegment, yStart, yStop, height) == 0) {
0205         seg = new Segment(m_image);
0206         QLine* line = new QLine(QPoint(x, y), QPoint(x, y));
0207         seg->path.append(line);
0208         seg->yLast = y;
0209         segments.append(seg);
0210     } else {
0211         // this is the continuation of an existing segment
0212         seg = adjacentSegment(lastSegment, yStart, yStop, height);
0213         QLine* line = new QLine(QPoint(x - 1, seg->yLast), QPoint(x, y));
0214         seg->length += abs(1 + (seg->yLast - y) * (seg->yLast - y));
0215         seg->path.append(line);
0216         seg->yLast = y;
0217     }
0218 
0219     seg->retransform();
0220 
0221     for (int y = yStart; y <= yStop; ++y)
0222         currSegment[y] = seg;
0223 }
0224 
0225 /*!
0226     return the number of runs adjacent to the pixels from yStart to yStop (inclusive)
0227 */
0228 int Segments::adjacentRuns(const bool* columnBool, int yStart, int yStop, int height) {
0229     int runs = 0;
0230     bool inRun = false;
0231     for (int y = yStart - 1; y <= yStop + 1; ++y) {
0232         if ((0 <= y) && (y < height)) {
0233             if (!inRun && columnBool[y]) {
0234                 inRun = true;
0235                 ++runs;
0236             } else if (inRun && !columnBool[y])
0237                 inRun = false;
0238         }
0239     }
0240 
0241     return runs;
0242 }
0243 
0244 /*!
0245     find the single segment pointer among the adjacent pixels from yStart-1 to yStop+1
0246 */
0247 Segment* Segments::adjacentSegment(Segment** lastSegment, int yStart, int yStop, int height) {
0248     for (int y = yStart - 1; y <= yStop + 1; ++y) {
0249         if ((0 <= y) && (y < height))
0250             if (lastSegment[y])
0251                 return lastSegment[y];
0252     }
0253 
0254     return nullptr;
0255 }
0256 
0257 /*!
0258     return the number of segments adjacent to the pixels from yStart to yStop (inclusive)
0259 */
0260 int Segments::adjacentSegments(Segment** lastSegment, int yStart, int yStop, int height) {
0261     int count = 0;
0262     bool inSegment = false;
0263     for (int y = yStart - 1; y <= yStop + 1; ++y) {
0264         if ((0 <= y) && (y < height)) {
0265             if (!inSegment && lastSegment[y]) {
0266                 inSegment = true;
0267                 ++count;
0268             } else if (inSegment && !lastSegment[y])
0269                 inSegment = false;
0270         }
0271     }
0272 
0273     return count;
0274 }