diff --git a/include/usgscsm/UsgsAstroLsSensorModel.h b/include/usgscsm/UsgsAstroLsSensorModel.h index 78bb3633fe9ac28c8e037fc9a3dba63486b05341..f935cd2014612265ecb8c2b1aff858bac3303d8c 100644 --- a/include/usgscsm/UsgsAstroLsSensorModel.h +++ b/include/usgscsm/UsgsAstroLsSensorModel.h @@ -108,6 +108,11 @@ public: std::vector<double> m_positions; std::vector<double> m_velocities; std::vector<double> m_quaternions; + std::vector<int> m_detectorNodes; + std::vector<double> m_detectorXCoords; + std::vector<double> m_detectorYCoords; + std::vector<double> m_detectorLineCoeffs; + double m_averageDetectorSize; std::vector<double> m_currentParameterValue; std::vector<csm::param::Type> m_parameterType; csm::EcefCoord m_referencePointXyz; diff --git a/include/usgscsm/Utilities.h b/include/usgscsm/Utilities.h index 44375d9b2f75994348c4541f9045b6d6a14fa927..94459d9d4ed1bda9d0f1b2fadfd4a18b79c2f603 100644 --- a/include/usgscsm/Utilities.h +++ b/include/usgscsm/Utilities.h @@ -12,6 +12,24 @@ #include <Warning.h> +double distanceToLine(double x, double y, + double a, double b, double c); + +double distanceToPlane(double x, double y, double z, + double a, double b, double c, double d); + +void line(double x1, double y1, double x2, double y2, + double &a, double &b, double &c); + +void plane(double x0, double y0, double z0, + double v1x, double v1y, double v1z, + double v2x, double v2y, double v2z, + double &a, double &b, double &c, double &d); + +std::vector<int> fitLinearApproximation(const std::vector<double> &x, + const std::vector<double> &y, + double tolerance); + // methods pulled out of los2ecf and computeViewingPixel // for now, put everything in here. diff --git a/src/UsgsAstroLsSensorModel.cpp b/src/UsgsAstroLsSensorModel.cpp index c1da7469836d19e315a7a68b7efcd3cdb20d7652..ec9350b2c294db8434ceaae506d61ef217682f34 100644 --- a/src/UsgsAstroLsSensorModel.cpp +++ b/src/UsgsAstroLsSensorModel.cpp @@ -506,6 +506,11 @@ void UsgsAstroLsSensorModel::reset() m_positions.clear(); // 42 m_velocities.clear(); // 43 m_quaternions.clear(); // 44 + m_detectorNodes.clear(); + m_detectorXCoords.clear(); + m_detectorYCoords.clear(); + m_detectorLineCoeffs.clear(); + m_averageDetectorSize = 0.0; m_currentParameterValue.assign(NUM_PARAMETERS,0.0); m_parameterType.assign(NUM_PARAMETERS,csm::param::REAL); @@ -595,6 +600,37 @@ void UsgsAstroLsSensorModel::updateState() MESSAGE_LOG(m_logger, "updateState: half time duration set to {}", m_halfTime) + // compute linearized detector coordinates + m_detectorXCoords.resize(m_nSamples); + m_detectorYCoords.resize(m_nSamples); + + for (int i = 0; i < m_nSamples; i++) { + computeDistortedFocalPlaneCoordinates( + 0.5, i, + m_detectorSampleOrigin, m_detectorLineOrigin, + m_detectorSampleSumming, 1.0, + m_startingSample, 0.0, + m_iTransS, m_iTransL, + m_detectorXCoords[i], m_detectorYCoords[i] + ); + } + m_averageDetectorSize = 0; + for (int i = 1; i < m_nSamples; i++) { + double xDist = m_detectorXCoords[i] - m_detectorXCoords[i-1]; + double yDist = m_detectorYCoords[i] - m_detectorYCoords[i-1]; + m_averageDetectorSize += sqrt(xDist*xDist + yDist*yDist); + } + m_averageDetectorSize /= (m_nSamples-1); + m_detectorNodes = fitLinearApproximation(m_detectorXCoords, m_detectorYCoords, + m_averageDetectorSize); + + m_detectorLineCoeffs.resize(3 * (m_detectorNodes.size() - 1)); + for (size_t i = 0; i + 1 < m_detectorNodes.size(); i++) { + line(m_detectorXCoords[m_detectorNodes[i]], m_detectorYCoords[m_detectorNodes[i]], + m_detectorXCoords[m_detectorNodes[i+1]], m_detectorYCoords[m_detectorNodes[i+1]], + m_detectorLineCoeffs[3*i], m_detectorLineCoeffs[3*i+1], m_detectorLineCoeffs[3*i+2]); + } + // Parameter covariance, hardcoded accuracy values int num_params = NUM_PARAMETERS; int num_paramsSquare = num_params * num_params; diff --git a/src/Utilities.cpp b/src/Utilities.cpp index dbcc87d0e6c73d66ca74bab02658a4f67287ff01..5c17d131b6b27bc124fcbc1fdddb968b701b786f 100644 --- a/src/Utilities.cpp +++ b/src/Utilities.cpp @@ -1,8 +1,108 @@ #include "Utilities.h" + +#include <cmath> #include <Error.h> +#include <stack> +#include <utility> using json = nlohmann::json; +// Calculate the signed distance from a 2D point to a line given in standard form +// +// --NOTE-- This function assumes that the coefficients of the line have been reduced +// so that sqrt(a^2 + b^2) = 1 +double distanceToLine(double x, double y, + double a, double b, double c) { + return a*x + b*y + c; +} + +// Calculate the distance from a 3D point to a plane given in standard form +// +// --NOTE-- This function assumes that the coefficients of the plane have been reduced +// so that sqrt(a^2 + b^2 + c^2) = 1 +double distanceToPlane(double x, double y, double z, + double a, double b, double c, double d) { + return std::abs(a*x + b*y + c*z + d); +} + +// Calculate the standard form coefficients of a line that passes through two points +// +// --NOTE-- The coefficients have been reduced such that sqrt(a^2 + b^2) = 1 +void line(double x1, double y1, double x2, double y2, + double &a, double &b, double &c) { + a = y1 - y2; + b = x2 - x1; + c = x1*y2 - x2*y1; + double scale = sqrt(a*a + b*b); + a /= scale; + b /= scale; + c /= scale; +} + +// Calculate the standard form coefficients of a plane that passes through a point +// and contains two vectors. +// +// --NOTE-- The coefficients have been reduced such that sqrt(a^2 + b^2 + c^2) = 1 +void plane(double x0, double y0, double z0, + double v1x, double v1y, double v1z, + double v2x, double v2y, double v2z, + double &a, double &b, double &c, double &d) { + // Compute normal vector and scale + a = v1y*v2z - v1z*v2y; + b = v1z*v2x - v1x*v2z; + c = v1x*v2y - v1y*v2x; + double scale = sqrt(a*a + b*b + c*c); + a /= scale; + b /= scale; + c /= scale; + // Shift to point + d = - (a*x0 + b*y0 + c*z0); +} + + + +// Fit a piecewise-linear approximations to 2D data within a tolerance +// +// Returns a vector of node indices +std::vector<int> fitLinearApproximation(const std::vector<double> &x, + const std::vector<double> &y, + double tolerance) { + std::vector<int> nodes; + nodes.push_back(0); + + std::stack<std::pair<int, int>> workStack; + workStack.push(std::make_pair(0, x.size() - 1)); + while (!workStack.empty()) { + std::pair<int, int> range = workStack.top(); + workStack.pop(); + double a, b, c; + line(x[range.first], y[range.first], x[range.second], y[range.second], a, b, c); + double maxError = 0; + int maxIndex = (range.second + range.first) / 2; + for (int i = range.first + 1; i < range.second; i++) { + double error = std::abs(distanceToLine(x[i], y[i], a, b, c)); + if (error > maxError) { + maxError = error; + maxIndex = i; + } + } + // If the max error is greater than the tolerance, split at the largest error + // Do not split if the range only contains two nodes + if (maxError > tolerance && range.second - range.first > 1) { + // Append the second range and then the first range + // so that nodes are added in the same order they came in + workStack.push(std::make_pair(maxIndex, range.second)); + workStack.push(std::make_pair(range.first, maxIndex)); + } + else { + // segment is good so append last point to nodes + nodes.push_back(range.second); + } + } + + return nodes; +} + // Calculates a rotation matrix from Euler angles // in - euler[3] // out - rotationMatrix[9] diff --git a/tests/UtilitiesTests.cpp b/tests/UtilitiesTests.cpp index 19ca11b5db7e7ff3819fd0637e43f491073c7255..d6fdca569ba8617f5b011db8ff6c0c5f2a28d455 100644 --- a/tests/UtilitiesTests.cpp +++ b/tests/UtilitiesTests.cpp @@ -11,6 +11,81 @@ using json = nlohmann::json; +TEST(UtilitiesTests, distanceToLine) { + // Line passing through (0, 1) and (2,2) + double a = -1.0 / sqrt(5); + double b = 2.0 / sqrt(5); + double c = -2.0 / sqrt(5); + // Point on line + EXPECT_NEAR(distanceToLine(4.0, 3.0, a, b, c), 0, 1e-12); + // Point above line + EXPECT_DOUBLE_EQ(distanceToLine(1.0, 4.0, a, b, c), sqrt(5)); + // Point below line + EXPECT_DOUBLE_EQ(distanceToLine(4.5, 2.0, a, b, c), -sqrt(5) / 2.0); +} + +TEST(UtilitiesTests, line) { + double x1 = 0.0; + double y1 = 1.0; + double x2 = 2.0; + double y2 = 2.0; + double a, b, c; + line(x1, y1, x2, y2, a, b, c); + EXPECT_DOUBLE_EQ(a, -1.0 / sqrt(5)); + EXPECT_DOUBLE_EQ(b, 2.0 / sqrt(5)); + EXPECT_DOUBLE_EQ(c, -2.0 / sqrt(5)); +} + +TEST(UtilitiesTests, distanceToPlane) { + // Plane passing through (0, 0, 1), (0, 2, 0) and (3, 0, 0) + double a = 2.0 / 7.0; + double b = 3.0 / 7.0; + double c = 6.0 / 7.0; + double d = -12.0 / 7.0; + // Points on plane + EXPECT_NEAR(distanceToPlane(0.0, 0.0, 2.0, a, b, c, d), 0.0, 1e-12); + EXPECT_NEAR(distanceToPlane(0.0, 4.0, 0.0, a, b, c, d), 0.0, 1e-12); + EXPECT_NEAR(distanceToPlane(6.0, 0.0, 0.0, a, b, c, d), 0.0, 1e-12); + // Points off plane + EXPECT_DOUBLE_EQ(distanceToPlane(0.0, 0.0, 0.0, a, b, c, d), 12.0 / 7.0); + EXPECT_DOUBLE_EQ(distanceToPlane(0.0, 0.0, 1.0, a, b, c, d), 6.0 / 7.0); + EXPECT_DOUBLE_EQ(distanceToPlane(0.0, 2.0, 0.0, a, b, c, d), 6.0 / 7.0); + EXPECT_DOUBLE_EQ(distanceToPlane(3.0, 0.0, 0.0, a, b, c, d), 6.0 / 7.0); +} + +TEST(UtilitiesTests, plane) { + double x0 = 0.0; + double y0 = 0.0; + double z0 = 2.0; + double v1x = 0.0; + double v1y = 4.0; + double v1z = -2.0; + double v2x = 6.0; + double v2y = 0.0; + double v2z = -2.0; + double a, b, c, d; + plane(x0, y0, z0, v1x, v1y, v1z, v2x, v2y, v2z, a, b, c, d); + EXPECT_DOUBLE_EQ(a, -2.0 / 7.0); + EXPECT_DOUBLE_EQ(b, -3.0 / 7.0); + EXPECT_DOUBLE_EQ(c, -6.0 / 7.0); + EXPECT_DOUBLE_EQ(d, 12.0 / 7.0); +} + +TEST(UtilitiesTests, fitLinearApproximation) { + std::vector<double> x = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + std::vector<double> y = {1, 0, 5, 7, -2, 1, 2, 2, 1, 0, 1}; + + std::vector<int> nodes = fitLinearApproximation(x, y, 1.0); + ASSERT_EQ(nodes.size(), 7); + EXPECT_EQ(nodes[0], 0); + EXPECT_EQ(nodes[1], 1); + EXPECT_EQ(nodes[2], 3); + EXPECT_EQ(nodes[3], 4); + EXPECT_EQ(nodes[4], 6); + EXPECT_EQ(nodes[5], 9); + EXPECT_EQ(nodes[6], 10); +} + TEST(UtilitiesTests, calculateRotationMatrixFromEuler) { double euler[3], rotationMatrix[9];