Engauge Digitizer  2
 All Classes Files Functions Variables Enumerations Enumerator Friends Pages
SegmentFactory.cpp
1 #include "ColorFilter.h"
2 #include "DocumentModelSegments.h"
3 #include "EngaugeAssert.h"
4 #include "Logger.h"
5 #include <QApplication>
6 #include <QGraphicsScene>
7 #include <QProgressDialog>
8 #include "Segment.h"
9 #include "SegmentFactory.h"
10 
11 using namespace std;
12 
13 SegmentFactory::SegmentFactory(QGraphicsScene &scene,
14  bool isGnuplot) :
15  m_scene (scene),
16  m_isGnuplot (isGnuplot)
17 {
18  LOG4CPP_INFO_S ((*mainCat)) << "SegmentFactory::SegmentFactory";
19 }
20 
21 int SegmentFactory::adjacentRuns(bool *columnBool,
22  int yStart,
23  int yStop,
24  int height)
25 {
26  int runs = 0;
27  bool inRun = false;
28  for (int y = yStart - 1; y <= yStop + 1; y++) {
29  if ((0 <= y) && (y < height)) {
30  if (!inRun && columnBool [y]) {
31  inRun = true;
32  ++runs;
33  } else if (inRun && !columnBool [y]) {
34  inRun = false;
35  }
36  }
37  }
38 
39  return runs;
40 }
41 
42 Segment *SegmentFactory::adjacentSegment(SegmentVector &lastSegment,
43  int yStart,
44  int yStop,
45  int height)
46 {
47  for (int y = yStart - 1; y <= yStop + 1; y++) {
48  if ((0 <= y) && (y < height)) {
49 
50  ENGAUGE_ASSERT (y < height);
51  if (lastSegment [y]) {
52  return lastSegment [y];
53  }
54  }
55  }
56 
57  return 0;
58 }
59 
60 int SegmentFactory::adjacentSegments(SegmentVector &lastSegment,
61  int yStart,
62  int yStop,
63  int height)
64 {
65  int adjacentSegments = 0;
66 
67  bool inSegment = false;
68  for (int y = yStart - 1; y <= yStop + 1; y++) {
69  if ((0 <= y) && (y < height)) {
70 
71  ENGAUGE_ASSERT (y < height);
72  if (!inSegment && lastSegment [y]) {
73 
74  inSegment = true;
75  ++adjacentSegments;
76  } else if (inSegment && !lastSegment [y]) {
77  inSegment = false;
78  }
79  }
80  }
81 
82  return adjacentSegments;
83 }
84 
85 QList<QPoint> SegmentFactory::fillPoints(const DocumentModelSegments &modelSegments,
86  QList<Segment*> segments)
87 {
88  LOG4CPP_INFO_S ((*mainCat)) << "SegmentFactory::fillPoints";
89 
90  QList<QPoint> list;
91  QList<Segment*>::iterator itr;
92  for (itr = segments.begin (); itr != segments.end(); itr++) {
93 
94  Segment *segment = *itr;
95  ENGAUGE_CHECK_PTR(segment);
96  list += segment->fillPoints(modelSegments);
97  }
98 
99  return list;
100 }
101 
102 void SegmentFactory::finishRun(bool *lastBool,
103  bool *nextBool,
104  SegmentVector &lastSegment,
105  SegmentVector &currSegment,
106  int x,
107  int yStart,
108  int yStop,
109  int height,
110  const DocumentModelSegments &modelSegments,
111  int* madeLines)
112 {
113  LOG4CPP_DEBUG_S ((*mainCat)) << "SegmentFactory::finishRun"
114  << " column=" << x
115  << " rows=" << yStart << "-" << yStop
116  << " runsOnLeft=" << adjacentRuns (nextBool, yStart, yStop, height)
117  << " runsOnRight=" << adjacentSegments (lastSegment, yStart, yStop, height);
118 
119  // When looking at adjacent columns, include pixels that touch diagonally since
120  // those may also diagonally touch nearby runs in the same column (which would indicate
121  // a branch)
122 
123  // Count runs that touch on the left
124  if (adjacentRuns(lastBool, yStart, yStop, height) > 1) {
125  return;
126  }
127 
128  // Count runs that touch on the right
129  if (adjacentRuns(nextBool, yStart, yStop, height) > 1) {
130  return;
131  }
132 
133  Segment *seg;
134  if (adjacentSegments(lastSegment, yStart, yStop, height) == 0) {
135 
136  // This is the start of a new segment
137  seg = new Segment(m_scene,
138  (int) (0.5 + (yStart + yStop) / 2.0),
139  m_isGnuplot);
140  ENGAUGE_CHECK_PTR (seg);
141 
142  } else {
143 
144  // This is the continuation of an existing segment
145  seg = adjacentSegment(lastSegment, yStart, yStop, height);
146 
147  ++(*madeLines);
148  ENGAUGE_CHECK_PTR(seg);
149  seg->appendColumn(x, (int) (0.5 + (yStart + yStop) / 2.0), modelSegments);
150  }
151 
152  for (int y = yStart; y <= yStop; y++) {
153 
154  ENGAUGE_ASSERT (y < height);
155  currSegment [y] = seg;
156  }
157 }
158 
159 void SegmentFactory::loadBool (const ColorFilter &filter,
160  bool *columnBool,
161  const QImage &image,
162  int x)
163 {
164  for (int y = 0; y < image.height(); y++) {
165  if (x < 0) {
166  columnBool [y] = false;
167  } else {
168  columnBool [y] = filter.pixelFilteredIsOn (image, x, y);
169  }
170  }
171 }
172 
173 void SegmentFactory::loadSegment (SegmentVector &columnSegment,
174  int height)
175 {
176  for (int y = 0; y < height; y++) {
177  columnSegment [y] = 0;
178  }
179 }
180 
181 void SegmentFactory::makeSegments (const QImage &imageFiltered,
182  const DocumentModelSegments &modelSegments,
183  QList<Segment*> &segments)
184 {
185  LOG4CPP_INFO_S ((*mainCat)) << "SegmentFactory::makeSegments";
186 
187  // Statistics that show up in debug spew
188  int madeLines = 0;
189  int shortLines = 0; // Lines rejected since their segments are too short
190  int foldedLines = 0; // Lines rejected since they could be into other lines
191 
192  // debugging with modal progress dialog box is problematic so make switchable
193  const bool useDlg = true;
194 
195  // For each new column of pixels, loop through the runs. a run is defined as
196  // one or more colored pixels that are all touching, with one uncolored pixel or the
197  // image boundary at each end of the set. for each set in the current column, count
198  // the number of runs it touches in the adjacent (left and right) columns. here is
199  // the pseudocode:
200  // if ((L > 1) || (R > 1))
201  // "this run is at a branch point so ignore the set"
202  // else
203  // if (L == 0)
204  // "this run is the start of a new segment"
205  // else
206  // "this run is appended to the segment on the left
207  int width = imageFiltered.width();
208  int height = imageFiltered.height();
209 
210  QProgressDialog* dlg;
211  if (useDlg)
212  {
213 
214  dlg = new QProgressDialog("Scanning segments in image", "Cancel", 0, width);
215  ENGAUGE_CHECK_PTR (dlg);
216  dlg->show();
217  }
218 
219  bool* lastBool = new bool [height];
220  ENGAUGE_CHECK_PTR(lastBool);
221  bool* currBool = new bool [height];
222  ENGAUGE_CHECK_PTR(currBool);
223  bool* nextBool = new bool [height];
224  ENGAUGE_CHECK_PTR(nextBool);
225  SegmentVector lastSegment (height);
226  SegmentVector currSegment (height);
227 
228  ColorFilter filter;
229  loadBool(filter, lastBool, imageFiltered, -1);
230  loadBool(filter, currBool, imageFiltered, 0);
231  loadBool(filter, nextBool, imageFiltered, 1);
232  loadSegment(lastSegment, height);
233 
234  for (int x = 0; x < width; x++) {
235 
236  if (useDlg) {
237 
238  // Update progress bar
239  dlg->setValue(x);
240  qApp->processEvents();
241 
242  if (dlg->wasCanceled()) {
243 
244  // Quit scanning. only existing segments will be available
245  break;
246  }
247  }
248 
249  matchRunsToSegments(x,
250  height,
251  lastBool,
252  lastSegment,
253  currBool,
254  currSegment,
255  nextBool,
256  modelSegments,
257  &madeLines,
258  &foldedLines,
259  &shortLines,
260  segments);
261 
262  // Get ready for next column
263  scrollBool(lastBool, currBool, height);
264  scrollBool(currBool, nextBool, height);
265  if (x + 1 < width) {
266  loadBool(filter, nextBool, imageFiltered, x + 1);
267  }
268  scrollSegment(lastSegment, currSegment, height);
269  }
270 
271  if (useDlg) {
272 
273  dlg->setValue(width);
274  delete dlg;
275  }
276 
277  removeEmptySegments (segments);
278 
279  LOG4CPP_INFO_S ((*mainCat)) << "SegmentFactory::makeSegments"
280  << " linesCreated=" << madeLines
281  << " linesTooShortSoRemoved=" << shortLines
282  << " linesFoldedTogether=" << foldedLines;
283 
284  delete[] lastBool;
285  delete[] currBool;
286  delete[] nextBool;
287 }
288 
289 void SegmentFactory::matchRunsToSegments(int x,
290  int height,
291  bool *lastBool,
292  SegmentVector &lastSegment,
293  bool* currBool,
294  SegmentVector &currSegment,
295  bool *nextBool,
296  const DocumentModelSegments &modelSegments,
297  int *madeLines,
298  int *foldedLines,
299  int *shortLines,
300  QList<Segment*> &segments)
301 {
302  loadSegment(currSegment,
303  height);
304 
305  int yStart = 0;
306  bool inRun = false;
307  for (int y = 0; y < height; y++) {
308 
309  ENGAUGE_ASSERT (y < height);
310  if (!inRun && currBool [y]) {
311  inRun = true;
312  yStart = y;
313  }
314 
315  if ((y + 1 >= height) || !currBool [y + 1]) {
316  if (inRun) {
317  finishRun(lastBool,
318  nextBool,
319  lastSegment,
320  currSegment,
321  x, yStart,
322  y,
323  height,
324  modelSegments,
325  madeLines);
326  }
327 
328  inRun = false;
329  }
330  }
331 
332  removeUnneededLines(lastSegment,
333  currSegment,
334  height,
335  foldedLines,
336  shortLines,
337  modelSegments,
338  segments);
339 }
340 
341 void SegmentFactory::removeEmptySegments (QList<Segment*> &segments) const
342 {
343  LOG4CPP_DEBUG_S ((*mainCat)) << "SegmentFactory::removeUnneededLines";
344 
345  for (int i = segments.count(); i > 0;) {
346 
347  --i;
348  Segment *segment = segments.at (i);
349 
350  if (segment->lineCount () == 0) {
351 
352  // Remove this Segment
353  delete segment;
354 
355  segments.removeAt (i);
356  }
357  }
358 }
359 
360 void SegmentFactory::removeUnneededLines(SegmentVector &lastSegment,
361  SegmentVector &currSegment,
362  int height,
363  int *foldedLines,
364  int *shortLines,
365  const DocumentModelSegments &modelSegments,
366  QList<Segment*> &segments)
367 {
368  LOG4CPP_DEBUG_S ((*mainCat)) << "SegmentFactory::removeUnneededLines";
369 
370  Segment *segLast = 0;
371  for (int yLast = 0; yLast < height; yLast++) {
372 
373  ENGAUGE_ASSERT (yLast < height);
374  if (lastSegment [yLast] && (lastSegment [yLast] != segLast)) {
375 
376  segLast = lastSegment [yLast];
377 
378  // If the segment is found in the current column then it is still in work so postpone processing
379  bool found = false;
380  for (int yCur = 0; yCur < height; yCur++) {
381 
382  ENGAUGE_ASSERT (yCur < height);
383  if (segLast == currSegment [yCur]) {
384  found = true;
385  break;
386  }
387  }
388 
389  if (!found) {
390 
391  ENGAUGE_CHECK_PTR(segLast);
392  if (segLast->length() < (modelSegments.minLength() - 1) * modelSegments.pointSeparation()) {
393 
394  // Remove whole segment since it is too short. Do NOT set segLast to zero since that
395  // would cause this same segment to be deleted again in the next pixel if the segment
396  // covers more than one pixel
397  *shortLines += segLast->lineCount();
398  delete segLast;
399  lastSegment [yLast] = 0;
400 
401  } else {
402 
403  // Keep segment, but try to fold lines
404  segLast->removeUnneededLines(foldedLines);
405 
406  // Add to the output array since it is done and sufficiently long
407  segments.push_back (segLast);
408 
409  }
410  }
411  }
412  }
413 }
414 
415 void SegmentFactory::scrollBool(bool *left,
416  bool *right,
417  int height)
418 {
419  for (int y = 0; y < height; y++) {
420  left [y] = right [y];
421  }
422 }
423 
424 void SegmentFactory::scrollSegment(SegmentVector &left,
425  SegmentVector &right,
426  int height)
427 {
428  for (int y = 0; y < height; y++) {
429  left [y] = right [y];
430  }
431 }
432 
433 void SegmentFactory::clearSegments (QList<Segment*> &segments)
434 {
435  LOG4CPP_DEBUG_S ((*mainCat)) << "SegmentFactory::clearSegments";
436 
437  QList<Segment*>::iterator itr;
438  for (itr = segments.begin(); itr != segments.end(); itr++) {
439 
440  Segment *segment = *itr;
441 
442  delete segment;
443  }
444 
445  segments.clear ();
446 }
void makeSegments(const QImage &imageFiltered, const DocumentModelSegments &modelSegments, QList< Segment * > &segments)
Main entry point for creating all Segments for the filtered image.
int lineCount() const
Get method for number of lines.
Definition: Segment.cpp:377
QList< QPoint > fillPoints(const DocumentModelSegments &modelSegments, QList< Segment * > segments)
Return segment fill points for all segments, for previewing.
void removeUnneededLines(int *foldedLines)
Try to compress a segment that was just completed, by folding together line from point i to point i+1...
Definition: Segment.cpp:413
SegmentFactory(QGraphicsScene &scene, bool isGnuplot)
Single constructor.
Class for filtering image to remove unimportant information.
Definition: ColorFilter.h:12
void clearSegments(QList< Segment * > &segments)
Remove the segments created by makeSegments.
double pointSeparation() const
Get method for point separation.
void appendColumn(int x, int y, const DocumentModelSegments &modelSegments)
Add some more pixels in a new column to an active segment.
Definition: Segment.cpp:39
double length() const
Get method for length in pixels.
Definition: Segment.cpp:372
Selectable piecewise-defined line that follows a filtered line in the image.
Definition: Segment.h:15
QList< QPoint > fillPoints(const DocumentModelSegments &modelSegments)
Create evenly spaced points along the segment.
Definition: Segment.cpp:202
bool pixelFilteredIsOn(const QImage &image, int x, int y) const
Return true if specified filtered pixel is on.
double minLength() const
Get method for min length.
Model for DlgSettingsSegments and CmdSettingsSegments.