File indexing completed on 2025-07-06 03:31:53
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 }