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