Engauge Digitizer  2
 All Classes Files Functions Variables Enumerations Enumerator Friends Pages
DigitizeStatePointMatch.cpp
1 /******************************************************************************************************
2  * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3  * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4  * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5  ******************************************************************************************************/
6 
7 #include "CmdAddPointGraph.h"
8 #include "CmdMediator.h"
9 #include "ColorFilter.h"
10 #include "CurveStyles.h"
11 #include "DigitizeStateContext.h"
12 #include "DigitizeStatePointMatch.h"
13 #include "EngaugeAssert.h"
14 #include "EnumsToQt.h"
15 #include "GraphicsPoint.h"
16 #include "GraphicsScene.h"
17 #include "GraphicsView.h"
18 #include "Logger.h"
19 #include "MainWindow.h"
20 #include "OrdinalGenerator.h"
21 #include "PointMatchAlgorithm.h"
22 #include "PointStyle.h"
23 #include <QApplication>
24 #include <QCursor>
25 #include <QGraphicsEllipseItem>
26 #include <QGraphicsScene>
27 #include <QImage>
28 #include <qmath.h>
29 #include <QMessageBox>
30 #include <QPen>
31 
32 const double Z_VALUE = 200.0;
33 
35  DigitizeStateAbstractBase (context),
36  m_outline (0),
37  m_candidatePoint (0)
38 {
39 }
40 
41 DigitizeStatePointMatch::~DigitizeStatePointMatch ()
42 {
43 }
44 
46 {
48 }
49 
51  DigitizeState /* previousState */)
52 {
53  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::begin";
54 
55  setCursor(cmdMediator);
56  context().setDragMode(QGraphicsView::NoDrag);
58 
59  // Add outline that will move with the cursor
60  m_outline = new QGraphicsEllipseItem ();
61  context().mainWindow().scene().addItem (m_outline);
62  m_outline->setPen (QPen (Qt::black));
63  m_outline->setVisible (true);
64  m_outline->setZValue (Z_VALUE);
65 }
66 
67 void DigitizeStatePointMatch::createPermanentPoint (CmdMediator *cmdMediator,
68  const QPointF &posScreen)
69 {
70  // Create command to add point
71  OrdinalGenerator ordinalGenerator;
72  Document &document = cmdMediator->document ();
73  const Transformation &transformation = context ().mainWindow ().transformation();
74  QUndoCommand *cmd = new CmdAddPointGraph (context ().mainWindow(),
75  document,
76  context ().mainWindow().selectedGraphCurve(),
77  posScreen,
78  ordinalGenerator.generateCurvePointOrdinal(document,
79  transformation,
80  posScreen,
81  activeCurve ()));
82  context().appendNewCmd(cmdMediator,
83  cmd);
84 
85 }
86 
87 void DigitizeStatePointMatch::createTemporaryPoint (CmdMediator *cmdMediator,
88  const QPoint &posScreen)
89 {
90  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStatePointMatch::createTemporaryPoint";
91 
92  const DocumentModelPointMatch &modelPointMatch = cmdMediator->document().modelPointMatch();
93 
94  // Get point style for current graph, and then override with candidate color
95  const CurveStyles &curveStyles = cmdMediator->document().modelCurveStyles();
96  PointStyle pointStyle = curveStyles.pointStyle (activeCurve());
97  pointStyle.setPaletteColor (modelPointMatch.paletteColorCandidate());
98 
99  // Temporary point that user can see while DlgEditPoint is active
101  pointStyle,
102  posScreen);
103 
104  context().mainWindow().scene().removeTemporaryPointIfExists(); // Only one temporary point at a time is allowed
105 
107  point);
108  m_posCandidatePoint = posScreen;
109 }
110 
111 QCursor DigitizeStatePointMatch::cursor(CmdMediator * /* cmdMediator */) const
112 {
113  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStatePointMatch::cursor";
114 
115  return QCursor (Qt::ArrowCursor);
116 }
117 
119 {
120  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::end";
121 
122  // Remove candidate point which may or may not exist at this point
124 
125  // Remove outline before leaving state
126  ENGAUGE_CHECK_PTR (m_outline);
127  context().mainWindow().scene().removeItem (m_outline);
128  m_outline = 0;
129 }
130 
131 QList<PointMatchPixel> DigitizeStatePointMatch::extractSamplePointPixels (const QImage &img,
132  const DocumentModelPointMatch &modelPointMatch,
133  const QPointF &posScreen) const
134 {
135  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::extractSamplePointPixels";
136 
137  // All points inside modelPointMatch.maxPointSize() are collected, whether or not they
138  // are on or off. Originally only the on points were collected, but obvious mismatches
139  // were happening (example, 3x3 point would appear to be found in several places inside 8x32 rectangle)
140  QList<PointMatchPixel> samplePointPixels;
141 
142  int radiusMax = modelPointMatch.maxPointSize() / 2;
143 
144  ColorFilter colorFilter;
145  for (int xOffset = -radiusMax; xOffset <= radiusMax; xOffset++) {
146  for (int yOffset = -radiusMax; yOffset <= radiusMax; yOffset++) {
147 
148  int x = posScreen.x() + xOffset;
149  int y = posScreen.y() + yOffset;
150  int radius = qSqrt (xOffset * xOffset + yOffset * yOffset);
151 
152  if (radius <= radiusMax) {
153 
154  bool pixelIsOn = colorFilter.pixelFilteredIsOn (img,
155  x,
156  y);
157 
158  PointMatchPixel point (xOffset,
159  yOffset,
160  pixelIsOn);
161 
162  samplePointPixels.push_back (point);
163  }
164  }
165  }
166 
167  return samplePointPixels;
168 }
169 
171 {
172  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleCurveChange";
173 }
174 
176  Qt::Key key,
177  bool /* atLeastOneSelectedItem */)
178 {
179  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleKeyPress"
180  << " key=" << QKeySequence (key).toString ().toLatin1 ().data ();
181 
182  // The selected key button has to be compatible with GraphicsView::keyPressEvent
183  if (key == Qt::Key_Right) {
184 
185  promoteCandidatePointToPermanentPoint (cmdMediator); // This removes the current temporary point
186 
187  popCandidatePoint(cmdMediator); // This creates a new temporary point
188 
189  }
190 }
191 
193  QPointF posScreen)
194 {
195 // LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStatePointMatch::handleMouseMove";
196 
197  const DocumentModelPointMatch &modelPointMatch = cmdMediator->document().modelPointMatch();
198 
199  m_outline->setRect (posScreen.x() - modelPointMatch.maxPointSize() / 2.0,
200  posScreen.y() - modelPointMatch.maxPointSize() / 2.0,
201  modelPointMatch.maxPointSize(),
202  modelPointMatch.maxPointSize());
203 
204  const QImage &img = context().mainWindow().imageFiltered();
205  int radiusLimit = cmdMediator->document().modelGeneral().cursorSize();
206  bool pixelShouldBeOn = pixelIsOnInImage (img,
207  posScreen.x(),
208  posScreen.y(),
209  radiusLimit);
210 
211  QColor penColorIs = m_outline->pen().color();
212  bool pixelIsOn = (penColorIs.red () != penColorIs.green()); // Considered on if not gray scale
213  if (pixelShouldBeOn != pixelIsOn) {
214  QColor penColorShouldBe (pixelShouldBeOn ? Qt::green : Qt::black);
215  m_outline->setPen (QPen (penColorShouldBe));
216  }
217 }
218 
220  QPointF /* posScreen */)
221 {
222  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleMousePress";
223 }
224 
226  QPointF posScreen)
227 {
228  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleMouseRelease";
229 
230  createPermanentPoint (cmdMediator,
231  posScreen);
232 
233  findPointsAndShowFirstCandidate (cmdMediator,
234  posScreen);
235 }
236 
237 void DigitizeStatePointMatch::findPointsAndShowFirstCandidate (CmdMediator *cmdMediator,
238  const QPointF &posScreen)
239 {
240  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::findPointsAndShowFirstCandidate";
241 
242  const DocumentModelPointMatch &modelPointMatch = cmdMediator->document().modelPointMatch();
243  const QImage &img = context().mainWindow().imageFiltered();
244 
245  QList<PointMatchPixel> samplePointPixels = extractSamplePointPixels (img,
246  modelPointMatch,
247  posScreen);
248 
249  QString curveName = activeCurve();
250  const Document &doc = cmdMediator->document();
251  const Curve *curve = doc.curveForCurveName (curveName);
252 
253  // The point match algorithm takes a few seconds, so set the cursor so user knows we are processing
254  QApplication::setOverrideCursor(Qt::WaitCursor);
255 
256  PointMatchAlgorithm pointMatchAlgorithm (context().isGnuplot());
257  m_candidatePoints = pointMatchAlgorithm.findPoints (samplePointPixels,
258  img,
259  modelPointMatch,
260  curve->points());
261 
262  QApplication::restoreOverrideCursor(); // Heavy duty processing has finished
263  context().mainWindow().showTemporaryMessage ("Right arrow adds next matched point");
264 
265  popCandidatePoint (cmdMediator);
266 }
267 
268 bool DigitizeStatePointMatch::pixelIsOnInImage (const QImage &img,
269  int x,
270  int y,
271  int radiusLimit) const
272 {
273  ColorFilter filter;
274 
275  // Examine all nearby pixels
276  bool pixelShouldBeOn = false;
277  for (int xOffset = -radiusLimit; xOffset <= radiusLimit; xOffset++) {
278  for (int yOffset = -radiusLimit; yOffset <= radiusLimit; yOffset++) {
279 
280  int radius = qSqrt (xOffset * xOffset + yOffset * yOffset);
281 
282  if (radius <= radiusLimit) {
283 
284  int xNearby = x + xOffset;
285  int yNearby = y + yOffset;
286 
287  if ((0 <= xNearby) &&
288  (0 <= yNearby) &&
289  (xNearby < img.width()) &&
290  (yNearby < img.height())) {
291 
292  if (filter.pixelFilteredIsOn (img,
293  xNearby,
294  yNearby)) {
295 
296  pixelShouldBeOn = true;
297  break;
298  }
299  }
300  }
301  }
302  }
303 
304  return pixelShouldBeOn;
305 }
306 
307 void DigitizeStatePointMatch::popCandidatePoint (CmdMediator *cmdMediator)
308 {
309  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStatePointMatch::popCandidatePoint";
310 
311  if (m_candidatePoints.count() > 0) {
312 
313  // Pop next point from list onto screen
314  QPoint posScreen = m_candidatePoints.first();
315  m_candidatePoints.pop_front ();
316 
317  createTemporaryPoint(cmdMediator,
318  posScreen);
319 
320  } else {
321 
322  // No more points. Inform user
323  QMessageBox::information (0,
324  QObject::tr ("Point Match"),
325  QObject::tr ("There are no more matching points"));
326 
327  }
328 }
329 
330 void DigitizeStatePointMatch::promoteCandidatePointToPermanentPoint(CmdMediator *cmdMediator)
331 {
332  createPermanentPoint (cmdMediator,
333  m_posCandidatePoint);
334 }
335 
337 {
338  return "DigitizeStatePointMatch";
339 }
340 
342  const DocumentModelDigitizeCurve & /*modelDigitizeCurve */)
343 {
344  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::updateModelDigitizeCurve";
345 }
346 
348 {
349  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::updateModelSegments";
350 }
double maxPointSize() const
Get method for max point size.
const PointStyle pointStyle(const QString &curveName) const
Get method for copying one point style. Cannot return just a reference or else there is a warning abo...
Model for DlgSettingsPointMatch and CmdSettingsPointMatch.
virtual void updateModelSegments(const DocumentModelSegments &modelSegments)
Update the segments given the new settings.
DocumentModelPointMatch modelPointMatch() const
Get method for DocumentModelPointMatch.
Definition: Document.cpp:653
const Points points() const
Return a shallow copy of the Points.
Definition: Curve.cpp:392
Transformation transformation() const
Return read-only copy of transformation.
void setDragMode(QGraphicsView::DragMode dragMode)
Set QGraphicsView drag mode (in m_view). Called from DigitizeStateAbstractBase subclasses.
Model for DlgSettingsCurveProperties and CmdSettingsCurveProperties.
Definition: CurveStyles.h:22
Single on or off pixel out of the pixels that define the point match mode&#39;s candidate point...
void updateViewsOfSettings(const QString &activeCurve)
Update curve-specific view of settings. Private version gets active curve name from DigitizeStateCont...
QString selectedGraphCurve() const
Curve name that is currently selected in m_cmbCurve.
int cursorSize() const
Get method for effective cursor size.
Document & document()
Provide the Document to commands, primarily for undo/redo processing.
Definition: CmdMediator.cpp:72
Class for filtering image to remove unimportant information.
Definition: ColorFilter.h:18
DigitizeStateContext & context()
Reference to the DigitizeStateContext that contains all the DigitizeStateAbstractBase subclasses...
MainWindow & mainWindow()
Reference to the MainWindow, without const.
static QString temporaryPointIdentifier()
Point identifier for temporary point that is used by DigitzeStateAxis.
Definition: Point.cpp:501
virtual void handleMousePress(CmdMediator *cmdMediator, QPointF posScreen)
Handle a mouse press that was intercepted earlier.
void showTemporaryMessage(const QString &temporaryMessage)
Show temporary message in status bar.
virtual void handleMouseMove(CmdMediator *cmdMediator, QPointF posScreen)
Handle a mouse move. This is part of an experiment to see if augmenting the cursor in Point Match mod...
Model for DlgSettingsDigitizeCurve and CmdSettingsDigitizeCurve.
virtual void handleKeyPress(CmdMediator *cmdMediator, Qt::Key key, bool atLeastOneSelectedItem)
Handle a key press that was intercepted earlier.
Affine transformation between screen and graph coordinates, based on digitized axis points...
Details for a specific Point.
Definition: PointStyle.h:20
GraphicsScene & scene()
Scene container for the QImage and QGraphicsItems.
void setCursor(CmdMediator *cmdMediator)
Update the cursor according to the current state.
CurveStyles modelCurveStyles() const
Get method for CurveStyles.
Definition: Document.cpp:618
Container for all DigitizeStateAbstractBase subclasses. This functions as the context class in a stan...
void appendNewCmd(CmdMediator *cmdMediator, QUndoCommand *cmd)
Append just-created QUndoCommand to command stack. This is called from DigitizeStateAbstractBase subc...
virtual void handleMouseRelease(CmdMediator *cmdMediator, QPointF posScreen)
Handle a mouse release that was intercepted earlier.
DigitizeStatePointMatch(DigitizeStateContext &context)
Single constructor.
virtual QCursor cursor(CmdMediator *cmdMediator) const
Returns the state-specific cursor shape.
Command for adding one graph point.
virtual void handleCurveChange(CmdMediator *cmdMediator)
Handle the selection of a new curve. At a minimum, DigitizeStateSegment will generate a new set of Se...
virtual QString state() const
State name for debugging.
void setPaletteColor(ColorPalette paletteColor)
Set method for point color.
Definition: PointStyle.cpp:277
Storage of one imported image and the data attached to that image.
Definition: Document.h:40
Container for one set of digitized Points.
Definition: Curve.h:32
QImage imageFiltered() const
Background image that has been filtered for the current curve. This asserts if a curve-specific image...
Graphics item for drawing a circular or polygonal Point.
Definition: GraphicsPoint.h:39
Algorithm returning a list of points that match the specified point.
ColorPalette paletteColorCandidate() const
Get method for candidate color.
void removeTemporaryPointIfExists()
Remove temporary point if it exists.
bool pixelFilteredIsOn(const QImage &image, int x, int y) const
Return true if specified filtered pixel is on.
virtual void begin(CmdMediator *cmdMediator, DigitizeState previousState)
Method that is called at the exact moment a state is entered.
virtual void end()
Method that is called at the exact moment a state is exited. Typically called just before begin for t...
Utility class for generating ordinal numbers.
Command queue stack.
Definition: CmdMediator.h:23
const Curve * curveForCurveName(const QString &curveName) const
See CurvesGraphs::curveForCurveNames, although this also works for AXIS_CURVE_NAME.
Definition: Document.cpp:298
void addTemporaryPoint(const QString &identifier, GraphicsPoint *point)
Add one temporary point to m_graphicsLinesForCurves. Non-temporary points are handled by the updateLi...
Model for DlgSettingsSegments and CmdSettingsSegments.
GraphicsPoint * createPoint(const QString &identifier, const PointStyle &pointStyle, const QPointF &posScreen)
Create one QGraphicsItem-based object that represents one Point. It is NOT added to m_graphicsLinesFo...
Base class for all digitizing states. This serves as an interface to DigitizeStateContext.
virtual void updateModelDigitizeCurve(CmdMediator *cmdMediator, const DocumentModelDigitizeCurve &modelDigitizeCurve)
Update the digitize curve settings.
double generateCurvePointOrdinal(const Document &document, const Transformation &transformation, const QPointF &posScreen, const QString &curveName)
Select ordinal so new point curve passes smoothly through existing points.
DocumentModelGeneral modelGeneral() const
Get method for DocumentModelGeneral.
Definition: Document.cpp:639
virtual QString activeCurve() const
Name of the active Curve. This can include AXIS_CURVE_NAME.