SplinePair SplinePair::operator/(const SplinePair &other) const { SplinePair result (m_x / other.x(), m_y / other.y()); return result; }
void ExportFileRelations::loadXThetaYRadiusValuesForCurveInterpolatedSmooth (const DocumentModelCoords &modelCoords, const DocumentModelGeneral &modelGeneral, const MainWindowModel &modelMainWindow, const Points &points, const ExportValuesOrdinal &ordinals, QVector<QString*> &xThetaValues, QVector<QString*> &yRadiusValues, const Transformation &transformation) const { LOG4CPP_INFO_S ((*mainCat)) << "ExportFileRelations::loadXThetaYRadiusValuesForCurveInterpolatedSmooth"; vector<double> t; vector<SplinePair> xy; ExportOrdinalsSmooth ordinalsSmooth; ordinalsSmooth.loadSplinePairsWithTransformation (points, transformation, t, xy); // Spline class requires at least one point if (xy.size() > 0) { // Fit a spline Spline spline (t, xy); FormatCoordsUnits format; // Extract the points for (int row = 0; row < ordinals.count(); row++) { double ordinal = ordinals.at (row); SplinePair splinePairFound = spline.interpolateCoeff(ordinal); double xTheta = splinePairFound.x (); double yRadius = splinePairFound.y (); // Save values for this row into xThetaValues and yRadiusValues, after appropriate formatting format.unformattedToFormatted (xTheta, yRadius, modelCoords, modelGeneral, modelMainWindow, *(xThetaValues [row]), *(yRadiusValues [row]), transformation); } } }
SplinePair Spline::findSplinePairForFunctionX (double x, int numIterations) const { SplinePair spCurrent; double tLow = m_t[0]; double tHigh = m_t[m_xy.size() - 1]; double tCurrent = (tHigh + tLow) / 2.0; double tDelta = (tHigh - tLow) / 4.0; for (int iteration = 0; iteration < numIterations; iteration++) { spCurrent = interpolateCoeff (tCurrent); if (spCurrent.x() > x) { tCurrent -= tDelta; } else { tCurrent += tDelta; } tDelta /= 2.0; } return spCurrent; }
void TestSpline::testSplinesAsControlPoints () { const int T_START = 1, T_STOP = 7; const double SPLINE_EPSILON = 0.01; const int NUM_T = 60; bool success = true; vector<double> t; vector<SplinePair> xy; // Independent variable must be evenly spaced t.push_back (T_START); t.push_back (2); t.push_back (3); t.push_back (4); t.push_back (5); t.push_back (6); t.push_back (T_STOP); // Simple curve, with x values tweaked slightly (from even spacing) to make the test data more stressing xy.push_back (SplinePair (1, 0.22)); xy.push_back (SplinePair (1.8, 0.04)); xy.push_back (SplinePair (3.2, -0.13)); xy.push_back (SplinePair (4.3, -0.17)); xy.push_back (SplinePair (5, -0.04)); xy.push_back (SplinePair (5.8, 0.09)); xy.push_back (SplinePair (7, 0.11)); Spline s (t, xy); for (int i = 0; i <= NUM_T; i++) { double t = T_START + (double) i * (T_STOP - T_START) / (double) NUM_T; SplinePair spCoeff = s.interpolateCoeff (t); SplinePair spBezier = s.interpolateControlPoints (t); double xCoeff = spCoeff.x(); double yCoeff = spCoeff.y(); double xControl = spBezier.x(); double yControl = spBezier.y(); if (qAbs (xCoeff - xControl) > SPLINE_EPSILON) { success = false; } if (qAbs (yCoeff - yControl) > SPLINE_EPSILON) { success = false; } } QVERIFY (success); }
ExportValuesOrdinal ExportOrdinalsSmooth::ordinalsAtIntervalsGraph (const vector<double> &t, const vector<SplinePair> &xy, double pointsInterval) const { LOG4CPP_INFO_S ((*mainCat)) << "ExportOrdinalsSmooth::ordinalsAtIntervalsGraph"; const double NUM_SMALLER_INTERVALS = 1000; // Results. Initially empty, but at the end it will have tMin, ..., tMax ExportValuesOrdinal ordinals; // Fit a spline Spline spline (t, xy); // Integrate the distances for the subintervals double integratedSeparation = 0; QPointF posLast (xy [0].x(), xy [0].y()); // Simplest method to find the intervals is to break up the curve into many smaller intervals, and then aggregate them // into intervals that, as much as possible, have the desired length. Simplicity wins out over accuracy in this // approach - accuracy is sacrificed to achieve simplicity double tMin = t.front(); double tMax = t.back(); double tLast = 0.0; int iTLastInterval = 0; for (int iT = 0; iT < NUM_SMALLER_INTERVALS; iT++) { double t = tMin + ((tMax - tMin) * iT) / (NUM_SMALLER_INTERVALS - 1.0); SplinePair pairNew = spline.interpolateCoeff(t); QPointF posNew = QPointF (pairNew.x(), pairNew.y()); QPointF posDelta = posNew - posLast; double integratedSeparationDelta = qSqrt (posDelta.x() * posDelta.x() + posDelta.y() * posDelta.y()); integratedSeparation += integratedSeparationDelta; while (integratedSeparation >= pointsInterval) { // End of current interval, and start of next interval. For better accuracy without having to crank up // the number of points by orders of magnitude, we use linear interpolation double sInterp; if (iT == 0) { sInterp = 0.0; } else { sInterp = (double) pointsInterval / (double) integratedSeparation; } double tInterp = (1.0 - sInterp) * tLast + sInterp * t; integratedSeparation -= pointsInterval; // Part of delta that was not used gets applied to next interval tLast = tInterp; ordinals.push_back (tInterp); iTLastInterval = iT; } tLast = t; posLast = posNew; } if (iTLastInterval < NUM_SMALLER_INTERVALS - 1) { // Add last point so we end up at tMax ordinals.push_back (tMax); } return ordinals; }
void ExportFileFunctions::loadYRadiusValuesForCurveInterpolatedSmooth (const DocumentModelCoords &modelCoords, const MainWindowModel &modelMainWindow, const Points &points, const ExportValuesXOrY &xThetaValues, const Transformation &transformation, QVector<QString*> &yRadiusValues) const { LOG4CPP_INFO_S ((*mainCat)) << "ExportFileFunctions::loadYRadiusValuesForCurveInterpolatedSmooth"; // Convert screen coordinates to graph coordinates, in vectors suitable for spline fitting vector<double> t; vector<SplinePair> xy; ExportOrdinalsSmooth ordinalsSmooth; ordinalsSmooth.loadSplinePairsWithTransformation (points, transformation, t, xy); // Formatting FormatCoordsUnits format; QString dummyXThetaOut; if (points.count() == 0) { // Since there are no values, leave the field empty for (int row = 0; row < xThetaValues.count(); row++) { *(yRadiusValues [row]) = ""; } } else if (points.count() == 1 || points.count() == 2) { // Apply the single value everywhere (N=1) or do linear interpolation (N=2) for (int row = 0; row < xThetaValues.count(); row++) { double xTheta = xThetaValues.at (row); double yRadius; if (points.count() == 1) { yRadius = xy.at (0).y (); } else { double x0 = xy.at (0).x (); double x1 = xy.at (1).x (); double y0 = xy.at (0).y (); double y1 = xy.at (1).y (); if (x0 == x1) { // Cannot do linear interpolation using two points at the same x value yRadius = xy.at (0).y (); } else { double s = (xTheta - x0) / (x1 - x0); yRadius = (1.0 - s) * y0 + s * y1; } } format.unformattedToFormatted (xTheta, yRadius, modelCoords, modelMainWindow, dummyXThetaOut, *(yRadiusValues [row]), transformation); } } else { // Iteration accuracy versus number of iterations 8->256, 10->1024, 12->4096. Single pixel accuracy out of // typical image size of 1024x1024 means around 10 iterations gives decent accuracy for numbers much bigger // than 1. A value of 12 gave some differences in the least significant figures of numbers like 10^-3 in // the regression tests. Toggling between 30 and 32 made no difference in the regression tests. const int MAX_ITERATIONS = 32; // Spline class requires at least one point if (xy.size() > 0) { // Fit a spline Spline spline (t, xy); // Get value at desired points for (int row = 0; row < xThetaValues.count(); row++) { double xTheta = xThetaValues.at (row); SplinePair splinePairFound = spline.findSplinePairForFunctionX (xTheta, MAX_ITERATIONS); double yRadius = splinePairFound.y (); // Save y/radius value for this row into yRadiusValues, after appropriate formatting QString dummyXThetaOut; format.unformattedToFormatted (xTheta, yRadius, modelCoords, modelMainWindow, dummyXThetaOut, *(yRadiusValues [row]), transformation); } } } }
SplinePair::SplinePair(const SplinePair &other) : m_x (other.x()), m_y (other.y()) { }