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 
135  // Get x/theta values to be used
136  CallbackGatherXThetaValuesFunctions ftor (modelExportOverride,
137  curvesIncluded,
138  transformation);
139  Functor2wRet<const QString &, const Point &, CallbackSearchReturn> ftorWithCallback = functor_ret (ftor,
141  document.iterateThroughCurvesPointsGraphs(ftorWithCallback);
142 
143  ExportXThetaValuesMergedFunctions exportXTheta (modelExportOverride,
144  ftor.xThetaValuesRaw(),
145  transformation);
146  ExportValuesXOrY xThetaValuesMerged = exportXTheta.xThetaValues ();
147 
148  // Skip if every curve was a relation
149  if (xThetaValuesMerged.count() > 0) {
150 
151  // Export in one of two layouts
152  if (modelExportOverride.layoutFunctions() == EXPORT_LAYOUT_ALL_PER_LINE) {
153  exportAllPerLineXThetaValuesMerged (modelExportOverride,
154  document,
155  modelMainWindow,
156  curvesIncluded,
157  xThetaValuesMerged,
158  delimiter,
159  transformation,
160  str);
161  } else {
162  exportOnePerLineXThetaValuesMerged (modelExportOverride,
163  document,
164  modelMainWindow,
165  curvesIncluded,
166  xThetaValuesMerged,
167  delimiter,
168  transformation,
169  str);
170  }
171  }
172 }
173 
174 void ExportFileFunctions::initializeYRadiusValues (const QStringList &curvesIncluded,
175  const ExportValuesXOrY &xThetaValuesMerged,
176  QVector<QVector<QString*> > &yRadiusValues) const
177 {
178  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::initializeYRadiusValues";
179 
180  // Initialize every entry with empty string
181  int curveCount = curvesIncluded.count();
182  int xThetaCount = xThetaValuesMerged.count();
183  for (int row = 0; row < xThetaCount; row++) {
184  for (int col = 0; col < curveCount; col++) {
185  yRadiusValues [col] [row] = new QString;
186  }
187  }
188 }
189 
190 double ExportFileFunctions::linearlyInterpolate (const Points &points,
191  double xThetaValue,
192  const Transformation &transformation) const
193 {
194  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::linearlyInterpolate";
195 
196  double yRadius = 0;
197  QPointF posGraphBefore; // Not set until ip=1
198  bool foundIt = false;
199  for (int ip = 0; ip < points.count(); ip++) {
200 
201  const Point &point = points.at (ip);
202  QPointF posGraph;
203  transformation.transformScreenToRawGraph (point.posScreen(),
204  posGraph);
205 
206  if (xThetaValue <= posGraph.x()) {
207 
208  foundIt = true;
209  if (ip == 0) {
210 
211  // Use first point
212  yRadius = posGraph.y();
213 
214  } else {
215 
216  // Between posGraphBefore and posGraph. Note that if posGraph.x()=posGraphBefore.x() then
217  // previous iteration of loop would have been used for interpolation, and then the loop was exited
218  double s = (xThetaValue - posGraphBefore.x()) / (posGraph.x() - posGraphBefore.x());
219  yRadius = (1.0 -s) * posGraphBefore.y() + s * posGraph.y();
220  }
221 
222  break;
223  }
224 
225  posGraphBefore = posGraph;
226  }
227 
228  if (!foundIt) {
229 
230  // Use last point
231  yRadius = posGraphBefore.y();
232 
233  }
234 
235  return yRadius;
236 }
237 
238 void ExportFileFunctions::loadYRadiusValues (const DocumentModelExportFormat &modelExportOverride,
239  const Document &document,
240  const MainWindowModel &modelMainWindow,
241  const QStringList &curvesIncluded,
242  const Transformation &transformation,
243  const ExportValuesXOrY &xThetaValues,
244  QVector<QVector<QString*> > &yRadiusValues) const
245 {
246  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::loadYRadiusValues";
247 
248  // Loop through curves
249  int curveCount = curvesIncluded.count();
250  for (int col = 0; col < curveCount; col++) {
251 
252  const QString curveName = curvesIncluded.at (col);
253 
254  const Curve *curve = document.curveForCurveName (curveName);
255  const Points points = curve->points ();
256 
257  if (modelExportOverride.pointsSelectionFunctions() == EXPORT_POINTS_SELECTION_FUNCTIONS_RAW) {
258 
259  // No interpolation. Raw points
260  loadYRadiusValuesForCurveRaw (document.modelCoords(),
261  modelMainWindow,
262  points,
263  xThetaValues,
264  transformation,
265  yRadiusValues [col]);
266  } else {
267 
268  // Interpolation
269  if (curve->curveStyle().lineStyle().curveConnectAs() == CONNECT_AS_FUNCTION_SMOOTH) {
270 
271  loadYRadiusValuesForCurveInterpolatedSmooth (document.modelCoords(),
272  modelMainWindow,
273  points,
274  xThetaValues,
275  transformation,
276  yRadiusValues [col]);
277 
278  } else {
279 
280  loadYRadiusValuesForCurveInterpolatedStraight (document.modelCoords(),
281  modelMainWindow,
282  points,
283  xThetaValues,
284  transformation,
285  yRadiusValues [col]);
286  }
287  }
288  }
289 }
290 
291 void ExportFileFunctions::loadYRadiusValuesForCurveInterpolatedSmooth (const DocumentModelCoords &modelCoords,
292  const MainWindowModel &modelMainWindow,
293  const Points &points,
294  const ExportValuesXOrY &xThetaValues,
295  const Transformation &transformation,
296  QVector<QString*> &yRadiusValues) const
297 {
298  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::loadYRadiusValuesForCurveInterpolatedSmooth";
299 
300  // Convert screen coordinates to graph coordinates, in vectors suitable for spline fitting
301  vector<double> t;
302  vector<SplinePair> xy;
303  ExportOrdinalsSmooth ordinalsSmooth;
304 
305  ordinalsSmooth.loadSplinePairsWithTransformation (points,
306  transformation,
307  t,
308  xy);
309 
310  // Formatting
311  FormatCoordsUnits format;
312  QString dummyXThetaOut;
313 
314  if (points.count() == 0) {
315 
316  // Since there are no values, leave the field empty
317  for (int row = 0; row < xThetaValues.count(); row++) {
318  *(yRadiusValues [row]) = "";
319  }
320 
321  } else if (points.count() == 1 ||
322  points.count() == 2) {
323 
324  // Apply the single value everywhere (N=1) or do linear interpolation (N=2)
325  for (int row = 0; row < xThetaValues.count(); row++) {
326 
327  double xTheta = xThetaValues.at (row);
328  double yRadius;
329  if (points.count() == 1) {
330  yRadius = xy.at (0).y ();
331  } else {
332  double x0 = xy.at (0).x ();
333  double x1 = xy.at (1).x ();
334  double y0 = xy.at (0).y ();
335  double y1 = xy.at (1).y ();
336  if (x0 == x1) {
337  // Cannot do linear interpolation using two points at the same x value
338  yRadius = xy.at (0).y ();
339  } else {
340  double s = (xTheta - x0) / (x1 - x0);
341  yRadius = (1.0 - s) * y0 + s * y1;
342  }
343  }
344  format.unformattedToFormatted (xTheta,
345  yRadius,
346  modelCoords,
347  modelMainWindow,
348  dummyXThetaOut,
349  *(yRadiusValues [row]),
350  transformation);
351  }
352 
353  } else {
354 
355  // Iteration accuracy versus number of iterations 8->256, 10->1024, 12->4096. Single pixel accuracy out of
356  // typical image size of 1024x1024 means around 10 iterations gives decent accuracy for numbers much bigger
357  // than 1. A value of 12 gave some differences in the least significant figures of numbers like 10^-3 in
358  // the regression tests. Toggling between 30 and 32 made no difference in the regression tests.
359  const int MAX_ITERATIONS = 32;
360 
361  // Fit a spline
362  Spline spline (t,
363  xy);
364 
365  // Get value at desired points
366  for (int row = 0; row < xThetaValues.count(); row++) {
367 
368  double xTheta = xThetaValues.at (row);
369  SplinePair splinePairFound = spline.findSplinePairForFunctionX (xTheta,
370  MAX_ITERATIONS);
371  double yRadius = splinePairFound.y ();
372 
373  // Save y/radius value for this row into yRadiusValues, after appropriate formatting
374  QString dummyXThetaOut;
375  format.unformattedToFormatted (xTheta,
376  yRadius,
377  modelCoords,
378  modelMainWindow,
379  dummyXThetaOut,
380  *(yRadiusValues [row]),
381  transformation);
382  }
383  }
384 }
385 
386 void ExportFileFunctions::loadYRadiusValuesForCurveInterpolatedStraight (const DocumentModelCoords &modelCoords,
387  const MainWindowModel &modelMainWindow,
388  const Points &points,
389  const ExportValuesXOrY &xThetaValues,
390  const Transformation &transformation,
391  QVector<QString*> &yRadiusValues) const
392 {
393  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::loadYRadiusValuesForCurveInterpolatedStraight";
394 
395  FormatCoordsUnits format;
396 
397  // Get value at desired points
398  for (int row = 0; row < xThetaValues.count(); row++) {
399 
400  double xThetaValue = xThetaValues.at (row);
401 
402  double yRadius = linearlyInterpolate (points,
403  xThetaValue,
404  transformation);
405 
406  // Save y/radius value for this row into yRadiusValues, after appropriate formatting
407  QString dummyXThetaOut;
408  format.unformattedToFormatted (xThetaValue,
409  yRadius,
410  modelCoords,
411  modelMainWindow,
412  dummyXThetaOut,
413  *(yRadiusValues [row]),
414  transformation);
415  }
416 }
417 
418 void ExportFileFunctions::loadYRadiusValuesForCurveRaw (const DocumentModelCoords &modelCoords,
419  const MainWindowModel &modelMainWindow,
420  const Points &points,
421  const ExportValuesXOrY &xThetaValues,
422  const Transformation &transformation,
423  QVector<QString*> &yRadiusValues) const
424 {
425  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::loadYRadiusValuesForCurveRaw";
426 
427  FormatCoordsUnits format;
428 
429  // Since the curve points may be a subset of xThetaValues (in which case the non-applicable xThetaValues will have
430  // blanks for the yRadiusValues), we iterate over the smaller set
431  for (int pt = 0; pt < points.count(); pt++) {
432 
433  const Point &point = points.at (pt);
434 
435  QPointF posGraph;
436  transformation.transformScreenToRawGraph (point.posScreen(),
437  posGraph);
438 
439  // Find the closest point in xThetaValues. This is probably an N-squared algorithm, which is less than optimial,
440  // but the delay should be insignificant with normal-sized export files
441  double closestSeparation = 0.0;
442  int rowClosest = 0;
443  for (int row = 0; row < xThetaValues.count(); row++) {
444 
445  double xThetaValue = xThetaValues.at (row);
446 
447  double separation = qAbs (posGraph.x() - xThetaValue);
448 
449  if ((row == 0) ||
450  (separation < closestSeparation)) {
451 
452  closestSeparation = separation;
453  rowClosest = row;
454 
455  }
456  }
457 
458  // Save y/radius value for this row into yRadiusValues, after appropriate formatting
459  QString dummyXThetaOut;
460  format.unformattedToFormatted (posGraph.x(),
461  posGraph.y(),
462  modelCoords,
463  modelMainWindow,
464  dummyXThetaOut,
465  *(yRadiusValues [rowClosest]),
466  transformation);
467  }
468 }
469 
470 void ExportFileFunctions::outputXThetaYRadiusValues (const DocumentModelExportFormat &modelExportOverride,
471  const DocumentModelCoords &modelCoords,
472  const MainWindowModel &modelMainWindow,
473  const QStringList &curvesIncluded,
474  const ExportValuesXOrY &xThetaValuesMerged,
475  const Transformation &transformation,
476  QVector<QVector<QString*> > &yRadiusValues,
477  const QString &delimiter,
478  QTextStream &str) const
479 {
480  LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::outputXThetaYRadiusValues";
481 
482  // Header
483  if (modelExportOverride.header() != EXPORT_HEADER_NONE) {
484  if (modelExportOverride.header() == EXPORT_HEADER_GNUPLOT) {
485  str << curveSeparator (str.string());
486  str << gnuplotComment();
487  }
488  str << modelExportOverride.xLabel();
489  QStringList::const_iterator itrHeader;
490  for (itrHeader = curvesIncluded.begin(); itrHeader != curvesIncluded.end(); itrHeader++) {
491  QString curveName = *itrHeader;
492  str << delimiter << curveName;
493  }
494  str << "\n";
495  }
496 
497  FormatCoordsUnits format;
498  const double DUMMY_Y_RADIUS = 1.0;
499 
500  for (int row = 0; row < xThetaValuesMerged.count(); row++) {
501 
502  if (rowHasAtLeastOneYRadiusEntry (yRadiusValues,
503  row)) {
504 
505  double xTheta = xThetaValuesMerged.at (row);
506 
507  // Output x/theta value for this row
508  QString xThetaString, yRadiusString;
509  format.unformattedToFormatted (xTheta,
510  DUMMY_Y_RADIUS,
511  modelCoords,
512  modelMainWindow,
513  xThetaString,
514  yRadiusString,
515  transformation);
516  str << xThetaString;
517 
518  for (int col = 0; col < yRadiusValues.count(); col++) {
519 
520  str << delimiter << *(yRadiusValues [col] [row]);
521  }
522 
523  str << "\n";
524  }
525  }
526 }
527 
528 bool ExportFileFunctions::rowHasAtLeastOneYRadiusEntry (const QVector<QVector<QString*> > &yRadiusValues,
529  int row) const
530 {
531  bool hasEntry = false;
532 
533  for (int col = 0; col < yRadiusValues.count(); col++) {
534 
535  QString entry = *(yRadiusValues [col] [row]);
536  if (!entry.isEmpty()) {
537 
538  hasEntry = true;
539  break;
540 
541  }
542  }
543 
544  return hasEntry;
545 }
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:611
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:40
Container for one set of digitized Points.
Definition: Curve.h:32
QStringList curvesGraphsNames() const
See CurvesGraphs::curvesGraphsNames.
Definition: Document.cpp:312
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:298
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:385
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.