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