From a055c42bf4807158f3751ff03ae2d9a0545b1803 Mon Sep 17 00:00:00 2001 From: Oleg Alexandrov <oleg.alexandrov@gmail.com> Date: Mon, 6 Nov 2023 19:08:33 -0800 Subject: [PATCH] Modularize the distortion operation Modularize the undistortion operation --- include/usgscsm/Distortion.h | 8 +- include/usgscsm/Utilities.h | 9 ++ src/Distortion.cpp | 188 +++-------------------------------- src/Utilities.cpp | 51 ++++++++++ 4 files changed, 79 insertions(+), 177 deletions(-) diff --git a/include/usgscsm/Distortion.h b/include/usgscsm/Distortion.h index 7561816..81867ac 100644 --- a/include/usgscsm/Distortion.h +++ b/include/usgscsm/Distortion.h @@ -10,18 +10,18 @@ enum DistortionType { RADIAL, TRANSVERSE, KAGUYALISM, DAWNFC, LROLROCNAC, CAHVOR // Transverse distortion Jacobian void transverseDistortionJacobian(double x, double y, double *jacobian, - const std::vector<double> opticalDistCoeffs); + std::vector<double> const& opticalDistCoeffs); void computeTransverseDistortion(double ux, double uy, double &dx, double &dy, - const std::vector<double> opticalDistCoeffs); + std::vector<double> const& opticalDistCoeffs); void removeDistortion(double dx, double dy, double &ux, double &uy, - const std::vector<double> opticalDistCoeffs, + std::vector<double> const& opticalDistCoeffs, DistortionType distortionType, const double tolerance = 1.0E-6); void applyDistortion(double ux, double uy, double &dx, double &dy, - const std::vector<double> opticalDistCoeffs, + std::vector<double> const& opticalDistCoeffs, DistortionType distortionType, const double desiredPrecision = 1.0E-6, const double tolerance = 1.0E-6); diff --git a/include/usgscsm/Utilities.h b/include/usgscsm/Utilities.h index f110629..7247560 100644 --- a/include/usgscsm/Utilities.h +++ b/include/usgscsm/Utilities.h @@ -65,6 +65,15 @@ void lagrangeInterp(const int &numTime, const double *valueArray, double brentRoot(double lowerBound, double upperBound, std::function<double(double)> func, double epsilon = 1e-10); +// Use the Newton-Raphson method undistort a pixel (dx, dy), producing (ux, uy). +void newtonRaphson(double dx, double dy, double &ux, double &uy, + std::vector<double> const& opticalDistCoeffs, + DistortionType distortionType, const double tolerance, + std::function<void(double, double, double &, double &, + std::vector<double> const&)> distortionFunction, + std::function<void(double, double, double *, + std::vector<double> const&)> distortionJacobian); + // Evaluate a polynomial function. // Coefficients should be ordered least order to greatest I.E. {1, 2, 3} is 1 + // 2x + 3x^2 diff --git a/src/Distortion.cpp b/src/Distortion.cpp index 5971501..25c75fe 100644 --- a/src/Distortion.cpp +++ b/src/Distortion.cpp @@ -1,11 +1,12 @@ #include "Distortion.h" +#include "Utilities.h" #include <Error.h> #include <string> // Jacobian for Transverse distortion void transverseDistortionJacobian(double x, double y, double *jacobian, - const std::vector<double> opticalDistCoeffs) { + std::vector<double> const& opticalDistCoeffs) { double d_dx[10]; d_dx[0] = 0; d_dx[1] = 1; @@ -58,7 +59,7 @@ void transverseDistortionJacobian(double x, double y, double *jacobian, * tuple */ void computeTransverseDistortion(double ux, double uy, double &dx, double &dy, - const std::vector<double> opticalDistCoeffs) { + std::vector<double> const& opticalDistCoeffs) { double f[10]; f[0] = 1; f[1] = ux; @@ -96,8 +97,8 @@ void computeRadTanDistortion(double ux, double uy, double &dx, double &dy, if (opticalDistCoeffs.size() != 5) { csm::Error::ErrorType errorType = csm::Error::INDEX_OUT_OF_RANGE; std::string message = - "Distortion coefficients for the radtan distortion model must be of size 5. " - "Got: " + std::to_string(opticalDistCoeffs.size()); + "Distortion coefficients for the radtan distortion model must be of size 5, " + "in the order k1, k2, p1, p2, k3. Got: " + std::to_string(opticalDistCoeffs.size()); std::string function = "computeRadTanDistortion"; throw csm::Error(errorType, message, function); } @@ -150,73 +151,10 @@ void radTanDistortionJacobian(double x, double y, double *jacobian, jacobian[3] = (1.0 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) + y * (k1 * dr2dy + k2 * dr2dy * 2.0 * r2 + k3 * dr2dy * 3.0 * r2 * r2) + p1 * (dr2dy + 4.0 * y) + 2.0 * p2 * x; - -#if 0 -// Check the partial derivatives with numerical differentiation - { - double eps = 1e-4; - double y0 = y; - y = y0 + eps; - r2 = (x * x) + (y * y); - - double dx1 = x * (1.0 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - + (2.0 * p1 * x * y + p2 * (r2 + 2.0 * x * x)); - - double dy1 = y * (1.0 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - + (p1 * (r2 + 2.0 * y * y) + 2.0 * p2 * x * y); - - y = y0 - eps; - r2 = (x * x) + (y * y); - - double dx2 = x * (1.0 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - + (2.0 * p1 * x * y + p2 * (r2 + 2.0 * x * x)); - - double dy2 = y * (1.0 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - + (p1 * (r2 + 2.0 * y * y) + 2.0 * p2 * x * y); - - std::cout << "--numerical1 is " << (dx1-dx2)/(2*eps) << " " << jacobian[1] << std::endl; - std::cout << "diff1 is " << (dx1-dx2)/(2*eps) - jacobian[1] << std::endl; - - std::cout << "--numerical3 is " << (dy1-dy2)/(2*eps) << " " << jacobian[3] << std::endl; - std::cout << "diff3 is " << (dy1-dy2)/(2*eps) - jacobian[3] << std::endl; - - y = y0; - } - - { - double eps = 1e-4; - double x0 = x; - x = x0 + eps; - r2 = (x * x) + (y * y); - - double dx1 = x * (1.0 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - + (2.0 * p1 * x * y + p2 * (r2 + 2.0 * x * x)); - - double dy1 = y * (1.0 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - + (p1 * (r2 + 2.0 * y * y) + 2.0 * p2 * x * y); - - x = x0 - eps; - r2 = (x * x) + (y * y); - - double dx2 = x * (1.0 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - + (2.0 * p1 * x * y + p2 * (r2 + 2.0 * x * x)); - - double dy2 = y * (1.0 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - + (p1 * (r2 + 2.0 * y * y) + 2.0 * p2 * x * y); - - std::cout << "--numerical0 is " << (dx1-dx2)/(2*eps) << " " << jacobian[0] << std::endl; - std::cout << "diff0 is " << (dx1-dx2)/(2*eps) - jacobian[0] << std::endl; - - std::cout << "--numerical2 is " << (dy1-dy2)/(2*eps) << " " << jacobian[2] << std::endl; - std::cout << "diff2 is " << (dy1-dy2)/(2*eps) - jacobian[2] << std::endl; - - x = x0; - } -#endif } void removeDistortion(double dx, double dy, double &ux, double &uy, - const std::vector<double> opticalDistCoeffs, + std::vector<double> const& opticalDistCoeffs, DistortionType distortionType, const double tolerance) { ux = dx; uy = dy; @@ -244,55 +182,8 @@ void removeDistortion(double dx, double dy, double &ux, double &uy, // Solve the distortion equation using the Newton-Raphson method. // Set the error tolerance to about one millionth of a NAC pixel. // The maximum number of iterations of the Newton-Raphson method. - const int maxTries = 20; - - double x; - double y; - double fx; - double fy; - double jacobian[4]; - - // Initial guess at the root - x = dx; - y = dy; - - computeTransverseDistortion(x, y, fx, fy, opticalDistCoeffs); - - for (int count = 1; - ((fabs(fx) + fabs(fy)) > tolerance) && (count < maxTries); count++) { - computeTransverseDistortion(x, y, fx, fy, opticalDistCoeffs); - - fx = dx - fx; - fy = dy - fy; - - transverseDistortionJacobian(x, y, jacobian, opticalDistCoeffs); - - // Jxx * Jyy - Jxy * Jyx - double determinant = - jacobian[0] * jacobian[3] - jacobian[1] * jacobian[2]; - if (fabs(determinant) < 1E-6) { - ux = x; - uy = y; - // - // Near-zero determinant. Add error handling here. - // - //-- Just break out and return with no convergence - return; - } - - x = x + (jacobian[3] * fx - jacobian[1] * fy) / determinant; - y = y + (jacobian[0] * fy - jacobian[2] * fx) / determinant; - } - - if ((fabs(fx) + fabs(fy)) <= tolerance) { - // The method converged to a root. - ux = x; - uy = y; - - return; - } - // Otherwise method did not converge to a root within the maximum - // number of iterations + newtonRaphson(dx, dy, ux, uy, opticalDistCoeffs, distortionType, tolerance, + computeTransverseDistortion, transverseDistortionJacobian); } break; case KAGUYALISM: { @@ -354,7 +245,7 @@ void removeDistortion(double dx, double dy, double &ux, double &uy, bool done; /**************************************************************************** - * Pre-loop intializations + * Pre-loop initializations ****************************************************************************/ r2 = dy * dy + dx * dx; @@ -388,7 +279,7 @@ void removeDistortion(double dx, double dy, double &ux, double &uy, } while (!done); /**************************************************************************** - * Sucess ... + * Success ... ****************************************************************************/ ux = guess_ux; @@ -451,61 +342,12 @@ void removeDistortion(double dx, double dy, double &ux, double &uy, break; // Compute undistorted focal plane coordinate given distorted coordinates - // with the RADTAN model. See computeRadTanDistortion() for more details. + // with the radtan model. See computeRadTanDistortion() for more details. case RADTAN: { - // Solve the distortion equation using the Newton-Raphson method. - // Set the error tolerance to about one millionth of a NAC pixel. - // The maximum number of iterations of the Newton-Raphson method. - const int maxTries = 20; - - double x; - double y; - double fx; - double fy; - double jacobian[4]; - - // Initial guess at the root - x = dx; - y = dy; - - computeRadTanDistortion(x, y, fx, fy, opticalDistCoeffs); - - for (int count = 1; - ((fabs(fx) + fabs(fy)) > tolerance) && (count < maxTries); count++) { - computeRadTanDistortion(x, y, fx, fy, opticalDistCoeffs); - - fx = dx - fx; - fy = dy - fy; - - radTanDistortionJacobian(x, y, jacobian, opticalDistCoeffs); - - // Jxx * Jyy - Jxy * Jyx - double determinant = - jacobian[0] * jacobian[3] - jacobian[1] * jacobian[2]; - if (fabs(determinant) < 1e-6) { - ux = x; - uy = y; - // - // Near-zero determinant. Add error handling here. - // - //-- Just break out and return with no convergence - return; - } - - x = x + (jacobian[3] * fx - jacobian[1] * fy) / determinant; - y = y + (jacobian[0] * fy - jacobian[2] * fx) / determinant; - } - - if ((fabs(fx) + fabs(fy)) <= tolerance) { - // The method converged to a root. - ux = x; - uy = y; - - return; - } - // Otherwise method did not converge to a root within the maximum - // number of iterations + newtonRaphson(dx, dy, ux, uy, opticalDistCoeffs, distortionType, tolerance, + computeRadTanDistortion, radTanDistortionJacobian); + } break; @@ -513,7 +355,7 @@ void removeDistortion(double dx, double dy, double &ux, double &uy, } void applyDistortion(double ux, double uy, double &dx, double &dy, - const std::vector<double> opticalDistCoeffs, + std::vector<double> const& opticalDistCoeffs, DistortionType distortionType, const double desiredPrecision, const double tolerance) { dx = ux; diff --git a/src/Utilities.cpp b/src/Utilities.cpp index 46be263..f3463ff 100644 --- a/src/Utilities.cpp +++ b/src/Utilities.cpp @@ -401,6 +401,57 @@ double brentRoot(double lowerBound, double upperBound, return nextPoint; } +// Use the Newton-Raphson method undistort a pixel (dx, dy), producing (ux, uy). +void newtonRaphson(double dx, double dy, double &ux, double &uy, + std::vector<double> const& opticalDistCoeffs, + DistortionType distortionType, const double tolerance, + std::function<void(double, double, double &, double &, + std::vector<double> const&)> distortionFunction, + std::function<void(double, double, double *, + std::vector<double> const&)> distortionJacobian) { + + const int maxTries = 20; + + double x, y, fx, fy, jacobian[4]; + + // Initial guess for the root + x = dx; + y = dy; + + distortionFunction(x, y, fx, fy, opticalDistCoeffs); + + for (int count = 1; + ((fabs(fx) + fabs(fy)) > tolerance) && (count < maxTries); count++) { + distortionFunction(x, y, fx, fy, opticalDistCoeffs); + + fx = dx - fx; + fy = dy - fy; + + distortionJacobian(x, y, jacobian, opticalDistCoeffs); + + // Jxx * Jyy - Jxy * Jyx + double determinant = + jacobian[0] * jacobian[3] - jacobian[1] * jacobian[2]; + if (fabs(determinant) < 1e-6) { + ux = x; + uy = y; + // Near-zero determinant. Cannot continue. Return most recent result. + return; + } + + x = x + (jacobian[3] * fx - jacobian[1] * fy) / determinant; + y = y + (jacobian[0] * fy - jacobian[2] * fx) / determinant; + } + + if ((fabs(fx) + fabs(fy)) <= tolerance) { + // The method converged to a root. + ux = x; + uy = y; + + return; + } +} + double evaluatePolynomial(const std::vector<double> &coeffs, double x) { if (coeffs.empty()) { throw std::invalid_argument("Polynomial coeffs must be non-empty."); -- GitLab