Engauge Digitizer 2
Loading...
Searching...
No Matches
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
24using namespace std;
25
29
30void 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
67void 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
174void 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
190double 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
238void 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
291void 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
386void 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
418void 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
470void 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
528bool 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}
Callback for collecting X/Theta independent variables, for functions, in preparation for exporting.
CallbackSearchReturn callback(const QString &curveName, const Point &point)
Callback method.
ValuesVectorXOrY xThetaValuesRaw() const
Resulting x/theta values for all included functions.
LineStyle lineStyle() const
Get method for LineStyle.
Container for one set of digitized Points.
Definition Curve.h:33
CurveStyle curveStyle() const
Return the curve style.
Definition Curve.cpp:138
const Points points() const
Return a shallow copy of the Points.
Definition Curve.cpp:392
Model for DlgSettingsCoords and CmdSettingsCoords.
Model for DlgSettingsExportFormat and CmdSettingsExportFormat.
ExportHeader header() const
Get method for header.
QString xLabel() const
Get method for x label.
ExportPointsSelectionFunctions pointsSelectionFunctions() const
Get method for point selection for functions.
ExportDelimiter delimiter() const
Get method for delimiter.
ExportLayoutFunctions layoutFunctions() const
Get method for functions layout.
Storage of one imported image and the data attached to that image.
Definition Document.h:41
void iterateThroughCurvesPointsGraphs(const Functor2wRet< const QString &, const Point &, CallbackSearchReturn > &ftorWithCallback)
See Curve::iterateThroughCurvePoints, for all the graphs curves.
Definition Document.cpp:385
QStringList curvesGraphsNames() const
See CurvesGraphs::curvesGraphsNames.
Definition Document.cpp:312
DocumentModelCoords modelCoords() const
Get method for DocumentModelCoords.
Definition Document.cpp:611
const Curve * curveForCurveName(const QString &curveName) const
See CurvesGraphs::curveForCurveNames, although this also works for AXIS_CURVE_NAME.
Definition Document.cpp:298
void destroy2DArray(QVector< QVector< QString * > > &array) const
Deallocate memory for array.
QString curveSeparator(const QString *string) const
Gnuplot requires, and other graphing tools probably prefer, blank lines between successive curves.
QString gnuplotComment() const
Gnuplot comment delimiter.
QStringList curvesToInclude(const DocumentModelExportFormat &modelExportOverride, const Document &document, const QStringList &curvesGraphsNames, CurveConnectAs curveConnectAs1, CurveConnectAs curveConnectAs2) const
Identify curves to include in export. The specified DocumentModelExportFormat overrides same data in ...
void insertLineSeparator(bool &isFirst, ExportHeader exportHeader, QTextStream &str) const
Insert line(s) between successive sets of curves.
ExportFileFunctions()
Single constructor.
void exportToFile(const DocumentModelExportFormat &modelExportOverride, const Document &document, const MainWindowModel &modelMainWindow, const Transformation &transformation, QTextStream &str) const
Export Document points according to the settings.
Utility class to interpolate points spaced evenly along a piecewise defined curve with fitted spline.
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...
Creates the set of merged x/theta values for exporting functions, using interpolation.
ExportValuesXOrY xThetaValues() const
Resulting x/theta values for all included functions.
Highest-level wrapper around other Formats classes.
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...
CurveConnectAs curveConnectAs() const
Get method for connect type.
Definition LineStyle.cpp:63
Model for DlgSettingsMainWindow.
Class that represents one digitized point. The screen-to-graph coordinate transformation is always ex...
Definition Point.h:24
QPointF posScreen() const
Accessor for screen position.
Definition Point.cpp:392
Single X/Y pair for cubic spline interpolation initialization and calculations.
Definition SplinePair.h:12
double y() const
Get method for y.
Cubic interpolation given independent and dependent value vectors.
Definition Spline.h:22
Affine transformation between screen and graph coordinates, based on digitized axis points.
void transformScreenToRawGraph(const QPointF &coordScreen, QPointF &coordGraph) const
Transform from cartesian pixel screen coordinates to cartesian/polar graph coordinates.