Engauge Digitizer  2
 All Classes Files Functions Variables Enumerations Enumerator Friends Pages
ExportFileFunctions.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 "CallbackGatherXThetaValuesFunctions.h"
8 #include "CurveConnectAs.h"
9 #include "Document.h"
10 #include "EngaugeAssert.h"
11 #include "ExportFileFunctions.h"
12 #include "ExportLayoutFunctions.h"
13 #include "ExportOrdinalsSmooth.h"
14 #include "ExportXThetaValuesMergedFunctions.h"
15 #include "FormatCoordsUnits.h"
16 #include "Logger.h"
17 #include <QTextStream>
18 #include <QVector>
19 #include "Spline.h"
20 #include "SplinePair.h"
21 #include "Transformation.h"
22 #include <vector>
23 
24 using namespace std;
25 
27 {
28 }
29 
30 void ExportFileFunctions::exportAllPerLineXThetaValuesMerged (const DocumentModelExportFormat &modelExportOverride,
31  const Document &document,
32  const MainWindowModel &modelMainWindow,
33  const QStringList &curvesIncluded,
34  const ExportValuesXOrY &xThetaValues,
35  const QString &delimiter,
36  const Transformation &transformation,
37  QTextStream &str) const
38 {
39  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::exportAllPerLineXThetaValuesMerged";
40 
41  int curveCount = curvesIncluded.count();
42  int xThetaCount = xThetaValues.count();
43  QVector<QVector<QString*> > yRadiusValues (curveCount, QVector<QString*> (xThetaCount));
44  initializeYRadiusValues (curvesIncluded,
45  xThetaValues,
46  yRadiusValues);
47  loadYRadiusValues (modelExportOverride,
48  document,
49  modelMainWindow,
50  curvesIncluded,
51  transformation,
52  xThetaValues,
53  yRadiusValues);
54 
55  outputXThetaYRadiusValues (modelExportOverride,
56  document.modelCoords(),
57  modelMainWindow,
58  curvesIncluded,
59  xThetaValues,
60  transformation,
61  yRadiusValues,
62  delimiter,
63  str);
64  destroy2DArray (yRadiusValues);
65 }
66 
67 void ExportFileFunctions::exportOnePerLineXThetaValuesMerged (const DocumentModelExportFormat &modelExportOverride,
68  const Document &document,
69  const MainWindowModel &modelMainWindow,
70  const QStringList &curvesIncluded,
71  const ExportValuesXOrY &xThetaValues,
72  const QString &delimiter,
73  const Transformation &transformation,
74  QTextStream &str) const
75 {
76  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::exportOnePerLineXThetaValuesMerged";
77 
78  bool isFirst = true;
79 
80  QStringList::const_iterator itr;
81  for (itr = curvesIncluded.begin(); itr != curvesIncluded.end(); itr++) {
82 
83  insertLineSeparator (isFirst,
84  modelExportOverride.header(),
85  str);
86 
87  // This curve
88  const int CURVE_COUNT = 1;
89  QString curveIncluded = *itr;
90  QStringList curvesIncluded (curveIncluded);
91 
92  int xThetaCount = xThetaValues.count();
93  QVector<QVector<QString*> > yRadiusValues (CURVE_COUNT, QVector<QString*> (xThetaCount));
94  initializeYRadiusValues (curvesIncluded,
95  xThetaValues,
96  yRadiusValues);
97  loadYRadiusValues (modelExportOverride,
98  document,
99  modelMainWindow,
100  curvesIncluded,
101  transformation,
102  xThetaValues,
103  yRadiusValues);
104  outputXThetaYRadiusValues (modelExportOverride,
105  document.modelCoords(),
106  modelMainWindow,
107  curvesIncluded,
108  xThetaValues,
109  transformation,
110  yRadiusValues,
111  delimiter,
112  str);
113  destroy2DArray (yRadiusValues);
114  }
115 }
116 
118  const Document &document,
119  const MainWindowModel &modelMainWindow,
120  const Transformation &transformation,
121  QTextStream &str) const
122 {
123  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::exportToFile";
124 
125  // Identify curves to be included
126  QStringList curvesIncluded = curvesToInclude (modelExportOverride,
127  document,
128  document.curvesGraphsNames(),
129  CONNECT_AS_FUNCTION_SMOOTH,
130  CONNECT_AS_FUNCTION_STRAIGHT);
131 
132  // Delimiter
133  const QString delimiter = exportDelimiterToText (modelExportOverride.delimiter(),
134  modelExportOverride.header() == EXPORT_HEADER_GNUPLOT);
135 
136  // Get x/theta values to be used
137  CallbackGatherXThetaValuesFunctions ftor (modelExportOverride,
138  curvesIncluded,
139  transformation);
140  Functor2wRet<const QString &, const Point &, CallbackSearchReturn> ftorWithCallback = functor_ret (ftor,
142  document.iterateThroughCurvesPointsGraphs(ftorWithCallback);
143 
144  ExportXThetaValuesMergedFunctions exportXTheta (modelExportOverride,
145  ftor.xThetaValuesRaw(),
146  transformation);
147  ExportValuesXOrY xThetaValuesMerged = exportXTheta.xThetaValues ();
148 
149  // Skip if every curve was a relation
150  if (xThetaValuesMerged.count() > 0) {
151 
152  // Export in one of two layouts
153  if (modelExportOverride.layoutFunctions() == EXPORT_LAYOUT_ALL_PER_LINE) {
154  exportAllPerLineXThetaValuesMerged (modelExportOverride,
155  document,
156  modelMainWindow,
157  curvesIncluded,
158  xThetaValuesMerged,
159  delimiter,
160  transformation,
161  str);
162  } else {
163  exportOnePerLineXThetaValuesMerged (modelExportOverride,
164  document,
165  modelMainWindow,
166  curvesIncluded,
167  xThetaValuesMerged,
168  delimiter,
169  transformation,
170  str);
171  }
172  }
173 }
174 
175 void ExportFileFunctions::initializeYRadiusValues (const QStringList &curvesIncluded,
176  const ExportValuesXOrY &xThetaValuesMerged,
177  QVector<QVector<QString*> > &yRadiusValues) const
178 {
179  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::initializeYRadiusValues";
180 
181  // Initialize every entry with empty string
182  int curveCount = curvesIncluded.count();
183  int xThetaCount = xThetaValuesMerged.count();
184  for (int row = 0; row < xThetaCount; row++) {
185  for (int col = 0; col < curveCount; col++) {
186  yRadiusValues [col] [row] = new QString;
187  }
188  }
189 }
190 
191 double ExportFileFunctions::linearlyInterpolate (const Points &points,
192  double xThetaValue,
193  const Transformation &transformation) const
194 {
195  // LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::linearlyInterpolate";
196 
197  double yRadius = 0;
198  QPointF posGraphBefore; // Not set until ip=1
199  bool foundIt = false;
200  for (int ip = 0; ip < points.count(); ip++) {
201 
202  const Point &point = points.at (ip);
203  QPointF posGraph;
204  transformation.transformScreenToRawGraph (point.posScreen(),
205  posGraph);
206 
207  if (xThetaValue <= posGraph.x()) {
208 
209  foundIt = true;
210  if (ip == 0) {
211 
212  // Use first point
213  yRadius = posGraph.y();
214 
215  } else {
216 
217  // Between posGraphBefore and posGraph. Note that if posGraph.x()=posGraphBefore.x() then
218  // previous iteration of loop would have been used for interpolation, and then the loop was exited
219  double s = (xThetaValue - posGraphBefore.x()) / (posGraph.x() - posGraphBefore.x());
220  yRadius = (1.0 -s) * posGraphBefore.y() + s * posGraph.y();
221  }
222 
223  break;
224  }
225 
226  posGraphBefore = posGraph;
227  }
228 
229  if (!foundIt) {
230 
231  // Use last point
232  yRadius = posGraphBefore.y();
233 
234  }
235 
236  return yRadius;
237 }
238 
239 void ExportFileFunctions::loadYRadiusValues (const DocumentModelExportFormat &modelExportOverride,
240  const Document &document,
241  const MainWindowModel &modelMainWindow,
242  const QStringList &curvesIncluded,
243  const Transformation &transformation,
244  const ExportValuesXOrY &xThetaValues,
245  QVector<QVector<QString*> > &yRadiusValues) const
246 {
247  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::loadYRadiusValues";
248 
249  // Loop through curves
250  int curveCount = curvesIncluded.count();
251  for (int col = 0; col < curveCount; col++) {
252 
253  const QString curveName = curvesIncluded.at (col);
254 
255  const Curve *curve = document.curveForCurveName (curveName);
256  const Points points = curve->points ();
257 
258  if (modelExportOverride.pointsSelectionFunctions() == EXPORT_POINTS_SELECTION_FUNCTIONS_RAW) {
259 
260  // No interpolation. Raw points
261  loadYRadiusValuesForCurveRaw (document.modelCoords(),
262  modelMainWindow,
263  points,
264  xThetaValues,
265  transformation,
266  yRadiusValues [col]);
267  } else {
268 
269  // Interpolation
270  if (curve->curveStyle().lineStyle().curveConnectAs() == CONNECT_AS_FUNCTION_SMOOTH) {
271 
272  loadYRadiusValuesForCurveInterpolatedSmooth (document.modelCoords(),
273  modelMainWindow,
274  points,
275  xThetaValues,
276  transformation,
277  yRadiusValues [col]);
278 
279  } else {
280 
281  loadYRadiusValuesForCurveInterpolatedStraight (document.modelCoords(),
282  modelMainWindow,
283  points,
284  xThetaValues,
285  transformation,
286  yRadiusValues [col]);
287  }
288  }
289  }
290 }
291 
292 void ExportFileFunctions::loadYRadiusValuesForCurveInterpolatedSmooth (const DocumentModelCoords &modelCoords,
293  const MainWindowModel &modelMainWindow,
294  const Points &points,
295  const ExportValuesXOrY &xThetaValues,
296  const Transformation &transformation,
297  QVector<QString*> &yRadiusValues) const
298 {
299  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::loadYRadiusValuesForCurveInterpolatedSmooth";
300 
301  // Convert screen coordinates to graph coordinates, in vectors suitable for spline fitting
302  vector<double> t;
303  vector<SplinePair> xy;
304  ExportOrdinalsSmooth ordinalsSmooth;
305 
306  ordinalsSmooth.loadSplinePairsWithTransformation (points,
307  transformation,
308  t,
309  xy);
310 
311  // Formatting
312  FormatCoordsUnits format;
313  QString dummyXThetaOut;
314 
315  if (points.count() == 0) {
316 
317  // Since there are no values, leave the field empty
318  for (int row = 0; row < xThetaValues.count(); row++) {
319  *(yRadiusValues [row]) = "";
320  }
321 
322  } else if (points.count() == 1 ||
323  points.count() == 2) {
324 
325  // Apply the single value everywhere (N=1) or do linear interpolation (N=2)
326  for (int row = 0; row < xThetaValues.count(); row++) {
327 
328  double xTheta = xThetaValues.at (row);
329  double yRadius;
330  if (points.count() == 1) {
331  yRadius = xy.at (0).y ();
332  } else {
333  double x0 = xy.at (0).x ();
334  double x1 = xy.at (1).x ();
335  double y0 = xy.at (0).y ();
336  double y1 = xy.at (1).y ();
337  if (x0 == x1) {
338  // Cannot do linear interpolation using two points at the same x value
339  yRadius = xy.at (0).y ();
340  } else {
341  double s = (xTheta - x0) / (x1 - x0);
342  yRadius = (1.0 - s) * y0 + s * y1;
343  }
344  }
345  format.unformattedToFormatted (xTheta,
346  yRadius,
347  modelCoords,
348  modelMainWindow,
349  dummyXThetaOut,
350  *(yRadiusValues [row]),
351  transformation);
352  }
353 
354  } else {
355 
356  // Iteration accuracy versus number of iterations 8->256, 10->1024, 12->4096. Single pixel accuracy out of
357  // typical image size of 1024x1024 means around 10 iterations gives decent accuracy for numbers much bigger
358  // than 1. A value of 12 gave some differences in the least significant figures of numbers like 10^-3 in
359  // the regression tests. Toggling between 30 and 32 made no difference in the regression tests.
360  const int MAX_ITERATIONS = 32;
361 
362  // Spline class requires at least one point
363  if (xy.size() > 0) {
364 
365  // Fit a spline
366  Spline spline (t,
367  xy);
368 
369  // Get value at desired points
370  for (int row = 0; row < xThetaValues.count(); row++) {
371 
372  double xTheta = xThetaValues.at (row);
373  SplinePair splinePairFound = spline.findSplinePairForFunctionX (xTheta,
374  MAX_ITERATIONS);
375  double yRadius = splinePairFound.y ();
376 
377  // Save y/radius value for this row into yRadiusValues, after appropriate formatting
378  QString dummyXThetaOut;
379  format.unformattedToFormatted (xTheta,
380  yRadius,
381  modelCoords,
382  modelMainWindow,
383  dummyXThetaOut,
384  *(yRadiusValues [row]),
385  transformation);
386  }
387  }
388  }
389 }
390 
391 void ExportFileFunctions::loadYRadiusValuesForCurveInterpolatedStraight (const DocumentModelCoords &modelCoords,
392  const MainWindowModel &modelMainWindow,
393  const Points &points,
394  const ExportValuesXOrY &xThetaValues,
395  const Transformation &transformation,
396  QVector<QString*> &yRadiusValues) const
397 {
398  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::loadYRadiusValuesForCurveInterpolatedStraight";
399 
400  FormatCoordsUnits format;
401 
402  // Get value at desired points
403  for (int row = 0; row < xThetaValues.count(); row++) {
404 
405  double xThetaValue = xThetaValues.at (row);
406 
407  double yRadius = linearlyInterpolate (points,
408  xThetaValue,
409  transformation);
410 
411  // Save y/radius value for this row into yRadiusValues, after appropriate formatting
412  QString dummyXThetaOut;
413  format.unformattedToFormatted (xThetaValue,
414  yRadius,
415  modelCoords,
416  modelMainWindow,
417  dummyXThetaOut,
418  *(yRadiusValues [row]),
419  transformation);
420  }
421 }
422 
423 void ExportFileFunctions::loadYRadiusValuesForCurveRaw (const DocumentModelCoords &modelCoords,
424  const MainWindowModel &modelMainWindow,
425  const Points &points,
426  const ExportValuesXOrY &xThetaValues,
427  const Transformation &transformation,
428  QVector<QString*> &yRadiusValues) const
429 {
430  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::loadYRadiusValuesForCurveRaw";
431 
432  FormatCoordsUnits format;
433 
434  // Since the curve points may be a subset of xThetaValues (in which case the non-applicable xThetaValues will have
435  // blanks for the yRadiusValues), we iterate over the smaller set
436  for (int pt = 0; pt < points.count(); pt++) {
437 
438  const Point &point = points.at (pt);
439 
440  QPointF posGraph;
441  transformation.transformScreenToRawGraph (point.posScreen(),
442  posGraph);
443 
444  // Find the closest point in xThetaValues. This is probably an N-squared algorithm, which is less than optimial,
445  // but the delay should be insignificant with normal-sized export files
446  double closestSeparation = 0.0;
447  int rowClosest = 0;
448  for (int row = 0; row < xThetaValues.count(); row++) {
449 
450  double xThetaValue = xThetaValues.at (row);
451 
452  double separation = qAbs (posGraph.x() - xThetaValue);
453 
454  if ((row == 0) ||
455  (separation < closestSeparation)) {
456 
457  closestSeparation = separation;
458  rowClosest = row;
459 
460  }
461  }
462 
463  // Save y/radius value for this row into yRadiusValues, after appropriate formatting
464  QString dummyXThetaOut;
465  format.unformattedToFormatted (posGraph.x(),
466  posGraph.y(),
467  modelCoords,
468  modelMainWindow,
469  dummyXThetaOut,
470  *(yRadiusValues [rowClosest]),
471  transformation);
472  }
473 }
474 
475 void ExportFileFunctions::outputXThetaYRadiusValues (const DocumentModelExportFormat &modelExportOverride,
476  const DocumentModelCoords &modelCoords,
477  const MainWindowModel &modelMainWindow,
478  const QStringList &curvesIncluded,
479  const ExportValuesXOrY &xThetaValuesMerged,
480  const Transformation &transformation,
481  QVector<QVector<QString*> > &yRadiusValues,
482  const QString &delimiter,
483  QTextStream &str) const
484 {
485  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::outputXThetaYRadiusValues";
486 
487  // Header
488  if (modelExportOverride.header() != EXPORT_HEADER_NONE) {
489  if (modelExportOverride.header() == EXPORT_HEADER_GNUPLOT) {
490  str << curveSeparator (str.string());
491  str << gnuplotComment();
492  }
493  str << modelExportOverride.xLabel();
494  QStringList::const_iterator itrHeader;
495  for (itrHeader = curvesIncluded.begin(); itrHeader != curvesIncluded.end(); itrHeader++) {
496  QString curveName = *itrHeader;
497  str << delimiter << curveName;
498  }
499  str << "\n";
500  }
501 
502  FormatCoordsUnits format;
503  const double DUMMY_Y_RADIUS = 1.0;
504 
505  for (int row = 0; row < xThetaValuesMerged.count(); row++) {
506 
507  if (rowHasAtLeastOneYRadiusEntry (yRadiusValues,
508  row)) {
509 
510  double xTheta = xThetaValuesMerged.at (row);
511 
512  // Output x/theta value for this row
513  QString xThetaString, yRadiusString;
514  format.unformattedToFormatted (xTheta,
515  DUMMY_Y_RADIUS,
516  modelCoords,
517  modelMainWindow,
518  xThetaString,
519  yRadiusString,
520  transformation);
521  str << xThetaString;
522 
523  for (int col = 0; col < yRadiusValues.count(); col++) {
524 
525  str << delimiter << *(yRadiusValues [col] [row]);
526  }
527 
528  str << "\n";
529  }
530  }
531 }
532 
533 bool ExportFileFunctions::rowHasAtLeastOneYRadiusEntry (const QVector<QVector<QString*> > &yRadiusValues,
534  int row) const
535 {
536  bool hasEntry = false;
537 
538  for (int col = 0; col < yRadiusValues.count(); col++) {
539 
540  QString entry = *(yRadiusValues [col] [row]);
541  if (!entry.isEmpty()) {
542 
543  hasEntry = true;
544  break;
545 
546  }
547  }
548 
549  return hasEntry;
550 }
void transformScreenToRawGraph(const QPointF &coordScreen, QPointF &coordGraph) const
Transform from cartesian pixel screen coordinates to cartesian/polar graph coordinates.
ExportPointsSelectionFunctions pointsSelectionFunctions() const
Get method for point selection for functions.
Creates the set of merged x/theta values for exporting functions, using interpolation.
ExportLayoutFunctions layoutFunctions() const
Get method for functions layout.
Cubic interpolation given independent and dependent value vectors.
Definition: Spline.h:21
const Points points() const
Return a shallow copy of the Points.
Definition: Curve.cpp:393
Model for DlgSettingsExportFormat and CmdSettingsExportFormat.
ExportValuesXOrY xThetaValues() const
Resulting x/theta values for all included functions.
LineStyle lineStyle() const
Get method for LineStyle.
Definition: CurveStyle.cpp:26
ExportFileFunctions()
Single constructor.
DocumentModelCoords modelCoords() const
Get method for DocumentModelCoords.
Definition: Document.cpp:646
double y() const
Get method for y.
Definition: SplinePair.cpp:71
Class that represents one digitized point. The screen-to-graph coordinate transformation is always ex...
Definition: Point.h:23
QPointF posScreen() const
Accessor for screen position.
Definition: Point.cpp:392
CallbackSearchReturn callback(const QString &curveName, const Point &point)
Callback method.
ExportHeader header() const
Get method for header.
void unformattedToFormatted(double xThetaUnformatted, double yRadiusUnformatted, const DocumentModelCoords &modelCoords, const MainWindowModel &mainWindowModel, QString &xThetaFormatted, QString &yRadiusFormatted, const Transformation &transformation) const
Convert unformatted numeric value to formatted string. Transformation is used to determine best resol...
Affine transformation between screen and graph coordinates, based on digitized axis points...
QString xLabel() const
Get method for x label.
void loadSplinePairsWithTransformation(const Points &points, const Transformation &transformation, std::vector< double > &t, std::vector< SplinePair > &xy) const
Load t (=ordinal) and xy (=screen position) spline pairs, converting screen coordinates to graph coor...
Model for DlgSettingsMainWindow.
Utility class to interpolate points spaced evenly along a piecewise defined curve with fitted spline...
ExportDelimiter delimiter() const
Get method for delimiter.
Model for DlgSettingsCoords and CmdSettingsCoords.
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
QStringList curvesGraphsNames() const
See CurvesGraphs::curvesGraphsNames.
Definition: Document.cpp:317
Highest-level wrapper around other Formats classes.
const Curve * curveForCurveName(const QString &curveName) const
See CurvesGraphs::curveForCurveNames, although this also works for AXIS_CURVE_NAME.
Definition: Document.cpp:303
CurveStyle curveStyle() const
Return the curve style.
Definition: Curve.cpp:139
void iterateThroughCurvesPointsGraphs(const Functor2wRet< const QString &, const Point &, CallbackSearchReturn > &ftorWithCallback)
See Curve::iterateThroughCurvePoints, for all the graphs curves.
Definition: Document.cpp:420
CurveConnectAs curveConnectAs() const
Get method for connect type.
Definition: LineStyle.cpp:63
Callback for collecting X/Theta independent variables, for functions, in preparation for exporting...
Single X/Y pair for cubic spline interpolation initialization and calculations.
Definition: SplinePair.h:11
void exportToFile(const DocumentModelExportFormat &modelExportOverride, const Document &document, const MainWindowModel &modelMainWindow, const Transformation &transformation, QTextStream &str) const
Export Document points according to the settings.