Engauge Digitizer  2
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  GeometryWindow *NULL_GEOMETRY_WINDOW = 0;
93 
94  const DocumentModelPointMatch &modelPointMatch = cmdMediator->document().modelPointMatch();
95 
96  // Get point style for current graph, and then override with candidate color
97  const CurveStyles &curveStyles = cmdMediator->document().modelCurveStyles();
98  PointStyle pointStyle = curveStyles.pointStyle (activeCurve());
99  pointStyle.setPaletteColor (modelPointMatch.paletteColorCandidate());
100 
101  // Temporary point that user can see while DlgEditPoint is active
103  pointStyle,
104  posScreen,
105  NULL_GEOMETRY_WINDOW);
106 
107  context().mainWindow().scene().removeTemporaryPointIfExists(); // Only one temporary point at a time is allowed
108 
110  point);
111  m_posCandidatePoint = posScreen;
112 }
113 
114 QCursor DigitizeStatePointMatch::cursor(CmdMediator * /* cmdMediator */) const
115 {
116  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStatePointMatch::cursor";
117 
118  return QCursor (Qt::ArrowCursor);
119 }
120 
122 {
123  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::end";
124 
125  // Remove candidate point which may or may not exist at this point
127 
128  // Remove outline before leaving state
129  ENGAUGE_CHECK_PTR (m_outline);
130  context().mainWindow().scene().removeItem (m_outline);
131  m_outline = 0;
132 }
133 
134 QList<PointMatchPixel> DigitizeStatePointMatch::extractSamplePointPixels (const QImage &img,
135  const DocumentModelPointMatch &modelPointMatch,
136  const QPointF &posScreen) const
137 {
138  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::extractSamplePointPixels";
139 
140  // All points inside modelPointMatch.maxPointSize() are collected, whether or not they
141  // are on or off. Originally only the on points were collected, but obvious mismatches
142  // were happening (example, 3x3 point would appear to be found in several places inside 8x32 rectangle)
143  QList<PointMatchPixel> samplePointPixels;
144 
145  int radiusMax = modelPointMatch.maxPointSize() / 2;
146 
147  ColorFilter colorFilter;
148  for (int xOffset = -radiusMax; xOffset <= radiusMax; xOffset++) {
149  for (int yOffset = -radiusMax; yOffset <= radiusMax; yOffset++) {
150 
151  int x = posScreen.x() + xOffset;
152  int y = posScreen.y() + yOffset;
153  int radius = qSqrt (xOffset * xOffset + yOffset * yOffset);
154 
155  if (radius <= radiusMax) {
156 
157  bool pixelIsOn = colorFilter.pixelFilteredIsOn (img,
158  x,
159  y);
160 
161  PointMatchPixel point (xOffset,
162  yOffset,
163  pixelIsOn);
164 
165  samplePointPixels.push_back (point);
166  }
167  }
168  }
169 
170  return samplePointPixels;
171 }
172 
174  const QString &pointIdentifier)
175 {
176  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleContextMenuEventAxis "
177  << " point=" << pointIdentifier.toLatin1 ().data ();
178 }
179 
181  const QStringList &pointIdentifiers)
182 {
183  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch ::handleContextMenuEventGraph "
184  << "points=" << pointIdentifiers.join(",").toLatin1 ().data ();
185 }
186 
188 {
189  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleCurveChange";
190 }
191 
193  Qt::Key key,
194  bool /* atLeastOneSelectedItem */)
195 {
196  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleKeyPress"
197  << " key=" << QKeySequence (key).toString ().toLatin1 ().data ();
198 
199  // The selected key button has to be compatible with GraphicsView::keyPressEvent
200  if (key == Qt::Key_Right) {
201 
202  promoteCandidatePointToPermanentPoint (cmdMediator); // This removes the current temporary point
203 
204  popCandidatePoint(cmdMediator); // This creates a new temporary point
205 
206  }
207 }
208 
210  QPointF posScreen)
211 {
212 // LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStatePointMatch::handleMouseMove";
213 
214  const DocumentModelPointMatch &modelPointMatch = cmdMediator->document().modelPointMatch();
215 
216  m_outline->setRect (posScreen.x() - modelPointMatch.maxPointSize() / 2.0,
217  posScreen.y() - modelPointMatch.maxPointSize() / 2.0,
218  modelPointMatch.maxPointSize(),
219  modelPointMatch.maxPointSize());
220 
221  const QImage &img = context().mainWindow().imageFiltered();
222  int radiusLimit = cmdMediator->document().modelGeneral().cursorSize();
223  bool pixelShouldBeOn = pixelIsOnInImage (img,
224  posScreen.x(),
225  posScreen.y(),
226  radiusLimit);
227 
228  QColor penColorIs = m_outline->pen().color();
229  bool pixelIsOn = (penColorIs.red () != penColorIs.green()); // Considered on if not gray scale
230  if (pixelShouldBeOn != pixelIsOn) {
231  QColor penColorShouldBe (pixelShouldBeOn ? Qt::green : Qt::black);
232  m_outline->setPen (QPen (penColorShouldBe));
233  }
234 }
235 
237  QPointF /* posScreen */)
238 {
239  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleMousePress";
240 }
241 
243  QPointF posScreen)
244 {
245  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleMouseRelease";
246 
247  createPermanentPoint (cmdMediator,
248  posScreen);
249 
250  findPointsAndShowFirstCandidate (cmdMediator,
251  posScreen);
252 }
253 
254 void DigitizeStatePointMatch::findPointsAndShowFirstCandidate (CmdMediator *cmdMediator,
255  const QPointF &posScreen)
256 {
257  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::findPointsAndShowFirstCandidate";
258 
259  const DocumentModelPointMatch &modelPointMatch = cmdMediator->document().modelPointMatch();
260  const QImage &img = context().mainWindow().imageFiltered();
261 
262  QList<PointMatchPixel> samplePointPixels = extractSamplePointPixels (img,
263  modelPointMatch,
264  posScreen);
265 
266  QString curveName = activeCurve();
267  const Document &doc = cmdMediator->document();
268  const Curve *curve = doc.curveForCurveName (curveName);
269 
270  // The point match algorithm takes a few seconds, so set the cursor so user knows we are processing
271  QApplication::setOverrideCursor(Qt::WaitCursor);
272 
273  PointMatchAlgorithm pointMatchAlgorithm (context().isGnuplot());
274  m_candidatePoints = pointMatchAlgorithm.findPoints (samplePointPixels,
275  img,
276  modelPointMatch,
277  curve->points());
278 
279  QApplication::restoreOverrideCursor(); // Heavy duty processing has finished
280  context().mainWindow().showTemporaryMessage ("Right arrow adds next matched point");
281 
282  popCandidatePoint (cmdMediator);
283 }
284 
285 bool DigitizeStatePointMatch::pixelIsOnInImage (const QImage &img,
286  int x,
287  int y,
288  int radiusLimit) const
289 {
290  ColorFilter filter;
291 
292  // Examine all nearby pixels
293  bool pixelShouldBeOn = false;
294  for (int xOffset = -radiusLimit; xOffset <= radiusLimit; xOffset++) {
295  for (int yOffset = -radiusLimit; yOffset <= radiusLimit; yOffset++) {
296 
297  int radius = qSqrt (xOffset * xOffset + yOffset * yOffset);
298 
299  if (radius <= radiusLimit) {
300 
301  int xNearby = x + xOffset;
302  int yNearby = y + yOffset;
303 
304  if ((0 <= xNearby) &&
305  (0 <= yNearby) &&
306  (xNearby < img.width()) &&
307  (yNearby < img.height())) {
308 
309  if (filter.pixelFilteredIsOn (img,
310  xNearby,
311  yNearby)) {
312 
313  pixelShouldBeOn = true;
314  break;
315  }
316  }
317  }
318  }
319  }
320 
321  return pixelShouldBeOn;
322 }
323 
324 void DigitizeStatePointMatch::popCandidatePoint (CmdMediator *cmdMediator)
325 {
326  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStatePointMatch::popCandidatePoint";
327 
328  if (m_candidatePoints.count() > 0) {
329 
330  // Pop next point from list onto screen
331  QPoint posScreen = m_candidatePoints.first();
332  m_candidatePoints.pop_front ();
333 
334  createTemporaryPoint(cmdMediator,
335  posScreen);
336 
337  } else {
338 
339  // No more points. Inform user
340  QMessageBox::information (0,
341  QObject::tr ("Point Match"),
342  QObject::tr ("There are no more matching points"));
343 
344  }
345 }
346 
347 void DigitizeStatePointMatch::promoteCandidatePointToPermanentPoint(CmdMediator *cmdMediator)
348 {
349  createPermanentPoint (cmdMediator,
350  m_posCandidatePoint);
351 }
352 
354 {
355  return "DigitizeStatePointMatch";
356 }
357 
359 {
360  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::updateAfterPointAddition";
361 }
362 
364  const DocumentModelDigitizeCurve & /*modelDigitizeCurve */)
365 {
366  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::updateModelDigitizeCurve";
367 }
368 
370 {
371  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::updateModelSegments";
372 }
QImage imageFiltered() const
Background image that has been filtered for the current curve. This asserts if a curve-specific image...
virtual void handleContextMenuEventGraph(CmdMediator *cmdMediator, const QStringList &pointIdentifiers)
Handle a right click, on a graph point, that was intercepted earlier.
Model for DlgSettingsPointMatch and CmdSettingsPointMatch.
virtual void updateModelSegments(const DocumentModelSegments &modelSegments)
Update the segments given the new settings.
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...
bool pixelFilteredIsOn(const QImage &image, int x, int y) const
Return true if specified filtered pixel is on.
const Curve * curveForCurveName(const QString &curveName) const
See CurvesGraphs::curveForCurveNames, although this also works for AXIS_CURVE_NAME.
Definition: Document.cpp:305
void updateViewsOfSettings(const QString &activeCurve)
Update curve-specific view of settings. Private version gets active curve name from DigitizeStateCont...
DocumentModelGeneral modelGeneral() const
Get method for DocumentModelGeneral.
Definition: Document.cpp:693
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...
virtual void updateAfterPointAddition()
Update graphics attributes after possible new points. This is useful for highlight opacity...
Window that displays the geometry information, as a table, for the current curve. ...
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:20
DocumentModelPointMatch modelPointMatch() const
Get method for DocumentModelPointMatch.
Definition: Document.cpp:714
virtual QCursor cursor(CmdMediator *cmdMediator) const
Returns the state-specific cursor shape.
DigitizeStateContext & context()
Reference to the DigitizeStateContext that contains all the DigitizeStateAbstractBase subclasses...
const Points points() const
Return a shallow copy of the Points.
Definition: Curve.cpp:442
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.
Transformation transformation() const
Return read-only copy of transformation.
void showTemporaryMessage(const QString &temporaryMessage)
Show temporary message in status bar.
ColorPalette paletteColorCandidate() const
Get method for candidate color.
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...
GraphicsPoint * createPoint(const QString &identifier, const PointStyle &pointStyle, const QPointF &posScreen, GeometryWindow *geometryWindow)
Create one QGraphicsItem-based object that represents one Point. It is NOT added to m_graphicsLinesFo...
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.
virtual QString state() const
State name for debugging.
void setCursor(CmdMediator *cmdMediator)
Update the cursor according to the current state.
int cursorSize() const
Get method for effective cursor size.
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.
Command for adding one graph point.
virtual void handleContextMenuEventAxis(CmdMediator *cmdMediator, const QString &pointIdentifier)
Handle a right click, on an axis point, that was intercepted earlier.
virtual void handleCurveChange(CmdMediator *cmdMediator)
Handle the selection of a new curve. At a minimum, DigitizeStateSegment will generate a new set of Se...
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:41
Container for one set of digitized Points.
Definition: Curve.h:32
Graphics item for drawing a circular or polygonal Point.
Definition: GraphicsPoint.h:42
Algorithm returning a list of points that match the specified point.
void removeTemporaryPointIfExists()
Remove temporary point if it exists.
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
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.
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.
CurveStyles modelCurveStyles() const
Get method for CurveStyles.
Definition: Document.cpp:672
QString selectedGraphCurve() const
Curve name that is currently selected in m_cmbCurve.
QList< QPoint > findPoints(const QList< PointMatchPixel > &samplePointPixels, const QImage &imageProcessed, const DocumentModelPointMatch &modelPointMatch, const Points &pointsExisting)
Find points that match the specified sample point pixels. They are sorted by best-to-worst match...
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.
virtual QString activeCurve() const
Name of the active Curve. This can include AXIS_CURVE_NAME.
double maxPointSize() const
Get method for max point size.