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