From c7c54e88396d5cd04a6bae7f3bddaf8daf96f805 Mon Sep 17 00:00:00 2001
From: Tyler Wilson <tjwilson@usgs.gov>
Date: Mon, 27 Aug 2018 14:21:56 -0700
Subject: [PATCH] A few more focal plane tests (#101)

* Added framing test for setFocalPlane.  Also enabled the testing of protected member functions, and modification of the ISD within the test fixture.

* Added jupyter_notebooks folder to CSM-CameraModel.

* Refactored setFocalPlane test so that the ISD changes are made local to the test (and not global).

* Refactored tests for setFocalPlane, distortionJacobian to modify Isd parameters and use them in tests.

* Added two new failing tests and a printIsd() helper function in the FrameIsdTest fixture to help with debugging.

* Fixed the failing tests.  They were due to the Jacobian being unexpectedly singular.

* Added a createModel function to the FrameIsdTest fixture.  Also added 2 more tests.

* Made requested changes to clean up tests, and added a parameterized testing fixture.

* Removed a debugging statement in the JacobianTest.
---
 include/usgscsm/UsgsAstroFrameSensorModel.h   |   4 +
 .../UsgsAstroFrameSensorModelTesting.ipynb    | 115 +++--
 src/UsgsAstroFrameSensorModel.cpp             |  16 +-
 tests/FrameCameraTests.cpp                    | 423 ++++++++++++------
 4 files changed, 362 insertions(+), 196 deletions(-)

diff --git a/include/usgscsm/UsgsAstroFrameSensorModel.h b/include/usgscsm/UsgsAstroFrameSensorModel.h
index fb9052a..c82d1d8 100644
--- a/include/usgscsm/UsgsAstroFrameSensorModel.h
+++ b/include/usgscsm/UsgsAstroFrameSensorModel.h
@@ -299,10 +299,14 @@ class UsgsAstroFrameSensorModel : public csm::RasterGM {
     static const std::string _SENSOR_MODEL_NAME;
 
 protected:
+
+    FRIEND_TEST(FramerParameterizedTest,JacobianTest);
     FRIEND_TEST(FrameIsdTest, setFocalPlane1);
     FRIEND_TEST(FrameIsdTest, Jacobian1);
     FRIEND_TEST(FrameIsdTest, setFocalPlane_AllCoefficientsOne);
     FRIEND_TEST(FrameIsdTest, distortMe_AllCoefficientsOne);
+    FRIEND_TEST(FrameIsdTest, setFocalPlane_AlternatingOnes);
+    FRIEND_TEST(FrameIsdTest, distortMe_AlternatingOnes);
 
     virtual bool setFocalPlane(double dx,double dy,double &undistortedX,double &undistortedY) const;
     virtual void distortionFunction(double ux, double uy, double &dx, double &dy) const;
diff --git a/jupyter_notebooks/UsgsAstroFrameSensorModelTesting.ipynb b/jupyter_notebooks/UsgsAstroFrameSensorModelTesting.ipynb
index 1a87cb4..98eb4b7 100644
--- a/jupyter_notebooks/UsgsAstroFrameSensorModelTesting.ipynb
+++ b/jupyter_notebooks/UsgsAstroFrameSensorModelTesting.ipynb
@@ -14,7 +14,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 284,
+   "execution_count": 94,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -24,7 +24,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 285,
+   "execution_count": 95,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -79,7 +79,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 286,
+   "execution_count": 96,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -88,7 +88,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 287,
+   "execution_count": 97,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -97,7 +97,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 288,
+   "execution_count": 98,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -108,7 +108,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 289,
+   "execution_count": 99,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -117,7 +117,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 290,
+   "execution_count": 100,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -134,7 +134,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 291,
+   "execution_count": 101,
    "metadata": {},
    "outputs": [
     {
@@ -153,7 +153,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 292,
+   "execution_count": 102,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -162,7 +162,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 293,
+   "execution_count": 103,
    "metadata": {},
    "outputs": [
     {
@@ -179,7 +179,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 294,
+   "execution_count": 104,
    "metadata": {},
    "outputs": [
     {
@@ -201,7 +201,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 295,
+   "execution_count": 105,
    "metadata": {},
    "outputs": [
     {
@@ -210,7 +210,7 @@
        "400.0"
       ]
      },
-     "execution_count": 295,
+     "execution_count": 105,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -223,7 +223,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 296,
+   "execution_count": 106,
    "metadata": {},
    "outputs": [
     {
@@ -232,7 +232,7 @@
        "990.0"
       ]
      },
-     "execution_count": 296,
+     "execution_count": 106,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -244,7 +244,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 297,
+   "execution_count": 107,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -253,7 +253,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 298,
+   "execution_count": 108,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -262,7 +262,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 299,
+   "execution_count": 109,
    "metadata": {},
    "outputs": [
     {
@@ -289,7 +289,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 300,
+   "execution_count": 110,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -316,7 +316,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 301,
+   "execution_count": 111,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -325,7 +325,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 302,
+   "execution_count": 112,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -334,7 +334,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 303,
+   "execution_count": 113,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -343,7 +343,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 304,
+   "execution_count": 114,
    "metadata": {},
    "outputs": [
     {
@@ -372,7 +372,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 305,
+   "execution_count": 115,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -394,7 +394,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 306,
+   "execution_count": 116,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -418,7 +418,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 307,
+   "execution_count": 117,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -427,7 +427,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 308,
+   "execution_count": 118,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -436,7 +436,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 309,
+   "execution_count": 119,
    "metadata": {},
    "outputs": [
     {
@@ -466,7 +466,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 310,
+   "execution_count": 120,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -485,7 +485,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 311,
+   "execution_count": 121,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -562,7 +562,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 312,
+   "execution_count": 122,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -588,33 +588,32 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 313,
+   "execution_count": 123,
    "metadata": {},
    "outputs": [],
    "source": [
     "def undistortMe(dx,dy,odtx,odty):\n",
-    "    epsilon =1.4e-5\n",
+    "    epsilon =1.4e-7\n",
     "    maxIts = 60\n",
     "    #initial guess\n",
     "    x = dx\n",
     "    y = dy\n",
     "    [fx,fy]= distortMe(x,y,odtx,odty)\n",
-    "    for i in range(maxIts):\n",
-    "        print(i)\n",
+    "    for i in range(maxIts):        \n",
     "        [fx,fy]=distortMe(x,y,odtx,odty)\n",
     "        fx=dx-fx\n",
     "        fy = dy-fy\n",
-    "        J = Jacobian(x,y,odtx,odty)\n",
-    "        determinant = J[0]*J[3]-J[1]*J[2]\n",
+    "        [Jxx,Jxy,Jyx,Jyy] = Jacobian(x,y,odtx,odty)\n",
+    "        determinant = Jxx*Jyy-Jxy*Jyx\n",
     "       \n",
     "        if (abs(determinant) < 1e-7):\n",
     "            print(\"Jacobian is singular.\")\n",
     "            print(determinant)\n",
     "            break;\n",
     "        else:\n",
-    "            x = x+(J[3]*fx-J[1]*fy)/determinant\n",
-    "            y = y+(J[0]*fy-J[2]*fx)/determinant\n",
-    "        if (abs(fx)+abs(fy) <= epsilon):\n",
+    "            x = x+(Jyy*fx-Jxy*fy)/determinant\n",
+    "            y = y+(Jxx*fy-Jyx*fx)/determinant\n",
+    "        if ( (abs(fx)+abs(fy)) <= epsilon):\n",
     "            return [x,y]\n",
     "    return [dx,dy]    \n",
     "    \n",
@@ -624,37 +623,14 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 314,
+   "execution_count": 124,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "0\n",
-      "1\n",
-      "2\n",
-      "3\n",
-      "4\n",
-      "5\n",
-      "6\n",
-      "7\n",
-      "8\n",
-      "9\n",
-      "10\n",
-      "11\n",
-      "12\n",
-      "13\n",
-      "14\n",
-      "15\n",
-      "16\n",
+      "[232.5, 121.0, 128.5, 240.0]\n",
       "[908.5, 963.75]\n",
       "[7.499999999999999, 7.5]\n"
      ]
@@ -663,10 +639,19 @@
    "source": [
     "distortion = distortMe(7.5,7.5,odtx,odty)\n",
     "undistorted = undistortMe(distortion[0],distortion[1],odtx,odty)\n",
+    "J=Jacobian(2.5,2.5,odtx,odty)\n",
     "\n",
+    "print([Jxx,Jxy,Jyx,Jyy])\n",
     "print(distortion)\n",
     "print (undistorted)"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
diff --git a/src/UsgsAstroFrameSensorModel.cpp b/src/UsgsAstroFrameSensorModel.cpp
index 8ff4026..f3050b4 100644
--- a/src/UsgsAstroFrameSensorModel.cpp
+++ b/src/UsgsAstroFrameSensorModel.cpp
@@ -1043,10 +1043,9 @@ bool UsgsAstroFrameSensorModel::setFocalPlane(double dx,double dy,
                                        double &undistortedX,
                                        double &undistortedY ) const {
 
-
   // Solve the distortion equation using the Newton-Raphson method.
   // Set the error tolerance to about one millionth of a NAC pixel.
-  const double tol = 1.4E-5;
+  const double tol = 1.4E-7;
 
   // The maximum number of iterations of the Newton-Raphson method.
   const int maxTries = 60;
@@ -1066,7 +1065,8 @@ bool UsgsAstroFrameSensorModel::setFocalPlane(double dx,double dy,
 
   distortionFunction(x, y, fx, fy);
 
-  for (int count = 1; ((fabs(fx) + fabs(fy)) > tol) && (count < maxTries); count++) {
+
+  for (int count = 1; ((fabs(fx) +fabs(fy)) > tol) && (count < maxTries); count++) {
 
     this->distortionFunction(x, y, fx, fy);
 
@@ -1076,12 +1076,16 @@ bool UsgsAstroFrameSensorModel::setFocalPlane(double dx,double dy,
     distortionJacobian(x, y, Jxx, Jxy, Jyx, Jyy);
 
     double determinant = Jxx * Jyy - Jxy * Jyx;
-    if (fabs(determinant) < 1E-6) {
+    if (fabs(determinant) < 1E-7) {
+
+      cout << "Singular determinant." << endl;
+      undistortedX = x;
+      undistortedY = y;
       //
       // Near-zero determinant. Add error handling here.
       //
       //-- Just break out and return with no convergence
-      break;
+      return false;
     }
 
     x = x + (Jyy * fx - Jxy * fy) / determinant;
@@ -1092,6 +1096,7 @@ bool UsgsAstroFrameSensorModel::setFocalPlane(double dx,double dy,
     // The method converged to a root.
     undistortedX = x;
     undistortedY = y;
+    return true;
 
   }
   else {
@@ -1099,6 +1104,7 @@ bool UsgsAstroFrameSensorModel::setFocalPlane(double dx,double dy,
     // number of iterations. Return with no distortion.
     undistortedX = dx;
     undistortedY = dy;
+    return false;
   }
 
   return true;
diff --git a/tests/FrameCameraTests.cpp b/tests/FrameCameraTests.cpp
index cdf235d..454d663 100644
--- a/tests/FrameCameraTests.cpp
+++ b/tests/FrameCameraTests.cpp
@@ -13,6 +13,61 @@
 using namespace std;
 using json = nlohmann::json;
 
+
+class FramerParameterizedTest : public ::testing::TestWithParam<csm::ImageCoord> {
+
+protected:
+  csm::Isd isd;
+
+
+      void printIsd(csm::Isd &localIsd) {
+           multimap<string,string> isdmap= localIsd.parameters();
+           for (auto it = isdmap.begin(); it != isdmap.end();++it){
+
+                      cout << it->first << " : " << it->second << endl;
+           }
+       }
+      UsgsAstroFrameSensorModel* createModel(csm::Isd &modifiedIsd) {
+
+        UsgsAstroFramePlugin frameCameraPlugin;
+        csm::Model *model = frameCameraPlugin.constructModelFromISD(
+              modifiedIsd,"USGS_ASTRO_FRAME_SENSOR_MODEL");
+
+        UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
+
+        if (sensorModel)
+          return sensorModel;
+        else
+          return nullptr;
+
+
+      }
+
+
+
+   virtual void SetUp() {
+
+
+      std::ifstream isdFile("data/simpleFramerISD.json");
+      json jsonIsd = json::parse(isdFile);
+      for (json::iterator it = jsonIsd.begin(); it != jsonIsd.end(); ++it) {
+         json jsonValue = it.value();
+         if (jsonValue.size() > 1) {
+            for (int i = 0; i < jsonValue.size(); i++) {
+               isd.addParam(it.key(), jsonValue[i].dump());
+            }
+         }
+         else {
+            isd.addParam(it.key(), jsonValue.dump());
+         }
+      }
+      isdFile.close();
+   }
+};
+
+
+
+
 class FrameIsdTest : public ::testing::Test {
    protected:
 
@@ -24,9 +79,23 @@ class FrameIsdTest : public ::testing::Test {
 
                       cout << it->first << " : " << it->second << endl;
            }
+       }
+      UsgsAstroFrameSensorModel* createModel(csm::Isd &modifiedIsd) {
+
+        UsgsAstroFramePlugin frameCameraPlugin;
+        csm::Model *model = frameCameraPlugin.constructModelFromISD(
+              modifiedIsd,"USGS_ASTRO_FRAME_SENSOR_MODEL");
+
+        UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
+
+        if (sensorModel)
+          return sensorModel;
+        else
+          return nullptr;
+
+      }
 
 
-    }
 
    virtual void SetUp() {
       std::ifstream isdFile("data/simpleFramerISD.json");
@@ -94,6 +163,57 @@ class FrameSensorModel : public ::testing::Test {
       }
 };
 
+
+
+INSTANTIATE_TEST_CASE_P(JacobianTest,FramerParameterizedTest,
+                        ::testing::Values(csm::ImageCoord(2.5,2.5),csm::ImageCoord(7.5,7.5)));
+
+TEST_P(FramerParameterizedTest,JacobianTest) {
+
+  int precision  = 20;
+  std:string odtx_key= "odt_x";
+  std::string odty_key="odt_y";
+
+  isd.clearParams(odty_key);
+  isd.clearParams(odtx_key);
+
+
+  vector<double> odtx{1.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0};
+  vector<double> odty{0.0,1.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0,1.0};
+
+
+   for (auto & val: odtx){
+      ostringstream strval;
+      strval << setprecision(precision) << val;
+      isd.addParam("odt_x", strval.str());
+   }
+   for (auto & val: odty){
+      ostringstream strval;
+      strval << setprecision(precision) << val;
+      isd.addParam("odt_y", strval.str());
+   }
+
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
+
+   double Jxx,Jxy,Jyx,Jyy;
+
+   ASSERT_NE(sensorModel, nullptr);
+
+   csm::ImageCoord imagePt1 = GetParam();
+   cout << "[" << imagePt1.samp << "," << imagePt1.line << "]"<< endl;
+   sensorModel->distortionJacobian(imagePt1.samp, imagePt1.line, Jxx, Jxy,Jyx,Jyy);
+
+
+   double determinant = fabs(Jxx*Jyy - Jxy*Jyx);
+
+   EXPECT_GT(determinant,1e-3);
+
+   delete sensorModel;
+   sensorModel=NULL;
+
+
+}
+
 //NOTE: The imagePt format is (Lines,Samples)
 
 //centered and slightly off-center:
@@ -146,7 +266,6 @@ TEST_F(FrameSensorModel, OffBody4) {
 
 TEST_F(FrameIsdTest, setFocalPlane1) {
   int precision  = 20;
-  UsgsAstroFramePlugin frameCameraPlugin;
   std:string odtx_key= "odt_x";
   std::string odty_key="odt_y";
 
@@ -171,17 +290,14 @@ TEST_F(FrameIsdTest, setFocalPlane1) {
       isd.addParam("odt_y", strval.str());
    }
 
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
-
-
-
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
-
+   UsgsAstroFrameSensorModel* sensorModel =createModel(isd);
+   ASSERT_NE(sensorModel, nullptr);
    sensorModel->setFocalPlane(imagePt.samp, imagePt.line, ux, uy);
    EXPECT_NEAR(imagePt.samp,7.5,1e-8 );
-   EXPECT_NEAR(imagePt.line,7.5,1e-8);
+   EXPECT_NEAR(imagePt.line,7.5,1e-8);   
+   delete sensorModel;
+   sensorModel = NULL;
+
 
 }
 
@@ -190,7 +306,6 @@ TEST_F(FrameIsdTest, setFocalPlane1) {
 TEST_F(FrameIsdTest, Jacobian1) {
 
   int precision  = 20;
-  UsgsAstroFramePlugin frameCameraPlugin;
   std:string odtx_key= "odt_x";
   std::string odty_key="odt_y";
 
@@ -213,30 +328,29 @@ TEST_F(FrameIsdTest, Jacobian1) {
       isd.addParam("odt_y", strval.str());
    }
 
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
-
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
 
 
    double Jxx,Jxy,Jyx,Jyy;
-   sensorModel->distortionJacobian(imagePt.samp, imagePt.line, Jxx, Jxy,Jyx,Jyy);
-
 
+   ASSERT_NE(sensorModel, nullptr);
+   sensorModel->distortionJacobian(imagePt.samp, imagePt.line, Jxx, Jxy,Jyx,Jyy);
 
    EXPECT_NEAR(Jxx,56.25,1e-8 );
    EXPECT_NEAR(Jxy,112.5,1e-8);
    EXPECT_NEAR(Jyx,56.25,1e-8);
    EXPECT_NEAR(Jyy,281.25,1e-8);
 
+   delete sensorModel;
+   sensorModel = NULL;
+
 }
 
 
 TEST_F(FrameIsdTest, distortMe_AllCoefficientsOne) {
 
   int precision  = 20;
-  UsgsAstroFramePlugin frameCameraPlugin;
+
   std:string odtx_key= "odt_x";
   std::string odty_key="odt_y";
 
@@ -259,29 +373,24 @@ TEST_F(FrameIsdTest, distortMe_AllCoefficientsOne) {
       isd.addParam("odt_y", strval.str());
    }
 
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
 
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
 
-
-   printIsd(isd);
    double dx,dy;
+   ASSERT_NE(sensorModel, nullptr);
    sensorModel->distortionFunction(imagePt.samp, imagePt.line,dx,dy );
 
-   cout << "dx:  " << dx << endl;
-   cout << "dy:  " << dy << endl;
-
    EXPECT_NEAR(dx,1872.25,1e-8 );
    EXPECT_NEAR(dy,1872.25,1e-8);
 
+   delete sensorModel;
+   sensorModel = NULL;
+
 }
 
 TEST_F(FrameIsdTest, setFocalPlane_AllCoefficientsOne) {
 
   int precision  = 20;
-  UsgsAstroFramePlugin frameCameraPlugin;
   std:string odtx_key= "odt_x";
   std::string odty_key="odt_y";
 
@@ -304,14 +413,13 @@ TEST_F(FrameIsdTest, setFocalPlane_AllCoefficientsOne) {
       isd.addParam("odt_y", strval.str());
    }
 
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
 
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
+
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
 
 
    double ux,uy;
+   ASSERT_NE(sensorModel, nullptr);
    sensorModel->setFocalPlane(imagePt.samp, imagePt.line,ux,uy );
 
 
@@ -320,6 +428,92 @@ TEST_F(FrameIsdTest, setFocalPlane_AllCoefficientsOne) {
    EXPECT_NEAR(ux,imagePt.samp,1e-8 );
    EXPECT_NEAR(uy,imagePt.line,1e-8);
 
+   delete sensorModel;
+   sensorModel = NULL;
+
+}
+
+
+TEST_F(FrameIsdTest, distortMe_AlternatingOnes) {
+
+  int precision  = 20;
+  std:string odtx_key= "odt_x";
+  std::string odty_key="odt_y";
+
+  isd.clearParams(odty_key);
+  isd.clearParams(odtx_key);
+
+   csm::ImageCoord imagePt(7.5, 7.5);
+
+   vector<double> odtx{1.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0};
+   vector<double> odty{0.0,1.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0,1.0};
+
+   for (auto & val: odtx){
+      ostringstream strval;
+      strval << setprecision(precision) << val;
+      isd.addParam("odt_x", strval.str());
+   }
+   for (auto & val: odty){
+      ostringstream strval;
+      strval << setprecision(precision) << val;
+      isd.addParam("odt_y", strval.str());
+   }
+
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
+
+   double dx,dy;
+   ASSERT_NE(sensorModel, nullptr);
+   sensorModel->distortionFunction(imagePt.samp, imagePt.line,dx,dy );
+
+   EXPECT_NEAR(dx,908.5,1e-8 );
+   EXPECT_NEAR(dy,963.75,1e-8);
+
+   delete sensorModel;
+   sensorModel = NULL;
+
+}
+
+
+
+
+TEST_F(FrameIsdTest, setFocalPlane_AlternatingOnes) {
+
+  int precision  = 20;
+  std:string odtx_key= "odt_x";
+  std::string odty_key="odt_y";
+
+  isd.clearParams(odty_key);
+  isd.clearParams(odtx_key);
+
+   csm::ImageCoord imagePt(963.75, 908.5);
+
+   vector<double> odtx{1.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0};
+   vector<double> odty{0.0,1.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0,1.0};
+
+   for (auto & val: odtx){
+      ostringstream strval;
+      strval << setprecision(precision) << val;
+      isd.addParam("odt_x", strval.str());
+   }
+   for (auto & val: odty){
+      ostringstream strval;
+      strval << setprecision(precision) << val;
+      isd.addParam("odt_y", strval.str());
+   }
+
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
+
+   double ux,uy;
+   ASSERT_NE(sensorModel, nullptr);
+
+   sensorModel->setFocalPlane(imagePt.samp, imagePt.line,ux,uy );
+
+   EXPECT_NEAR(ux,7.5,1e-8 );
+   EXPECT_NEAR(uy,7.5,1e-8);
+
+   delete sensorModel;
+   sensorModel = NULL;
+
 }
 
 
@@ -330,13 +524,9 @@ TEST_F(FrameIsdTest, FL500_OffBody4) {
    std::string newValue = "500.0";
    isd.clearParams(key);
    isd.addParam(key,newValue);
-   UsgsAstroFramePlugin frameCameraPlugin;
 
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
 
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
 
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(15.0, 15.0);
@@ -344,19 +534,17 @@ TEST_F(FrameIsdTest, FL500_OffBody4) {
    EXPECT_NEAR(groundPt.x, 9.77688917, 1e-8);
    EXPECT_NEAR(groundPt.y, -1.48533467, 1e-8);
    EXPECT_NEAR(groundPt.z, -1.48533467, 1e-8);
+
+   delete sensorModel;
+   sensorModel = NULL;
 }
 TEST_F(FrameIsdTest, FL500_OffBody3) {
    std::string key = "focal_length";
    std::string newValue = "500.0";
    isd.clearParams(key);
    isd.addParam(key,newValue);
-   UsgsAstroFramePlugin frameCameraPlugin;
-
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
 
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
 
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(0.0, 0.0);
@@ -364,19 +552,17 @@ TEST_F(FrameIsdTest, FL500_OffBody3) {
    EXPECT_NEAR(groundPt.x, 9.77688917, 1e-8);
    EXPECT_NEAR(groundPt.y, 1.48533467, 1e-8);
    EXPECT_NEAR(groundPt.z, 1.48533467, 1e-8);
+
+   delete sensorModel;
+   sensorModel = NULL;
 }
 TEST_F(FrameIsdTest, FL500_Center) {
    std::string key = "focal_length";
    std::string newValue = "500.0";
    isd.clearParams(key);
    isd.addParam(key,newValue);
-   UsgsAstroFramePlugin frameCameraPlugin;
-
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
 
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
 
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(7.5, 7.5);
@@ -384,19 +570,17 @@ TEST_F(FrameIsdTest, FL500_Center) {
    EXPECT_NEAR(groundPt.x, 10.0, 1e-8);
    EXPECT_NEAR(groundPt.y, 0.0, 1e-8);
    EXPECT_NEAR(groundPt.z, 0.0, 1e-8);
+
+   delete sensorModel;
+   sensorModel = NULL;
 }
 TEST_F(FrameIsdTest, FL500_SlightlyOffCenter) {
    std::string key = "focal_length";
    std::string newValue = "500.0";
    isd.clearParams(key);
    isd.addParam(key,newValue);
-   UsgsAstroFramePlugin frameCameraPlugin;
 
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
-
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
 
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(7.5, 6.5);
@@ -405,6 +589,9 @@ TEST_F(FrameIsdTest, FL500_SlightlyOffCenter) {
    EXPECT_NEAR(groundPt.y, 0.0, 1e-8);
    EXPECT_NEAR(groundPt.z, 1.98000392e-01, 1e-8);
 
+   delete sensorModel;
+   sensorModel = NULL;
+
 }
 
 //Observer x position:
@@ -413,13 +600,8 @@ TEST_F(FrameIsdTest, X10_SlightlyOffCenter) {
    std::string newValue = "10.0";
    isd.clearParams(key);
    isd.addParam(key,newValue);
-   UsgsAstroFramePlugin frameCameraPlugin;
-
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
 
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
 
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(7.5, 6.5);
@@ -428,19 +610,17 @@ TEST_F(FrameIsdTest, X10_SlightlyOffCenter) {
    EXPECT_NEAR(groundPt.y, 0.0, 1e-8);
    EXPECT_NEAR(groundPt.z, 0.0, 1e-8);
 
+   delete sensorModel;
+   sensorModel = NULL;
+
 }
 TEST_F(FrameIsdTest, X1e9_SlightlyOffCenter) {
    std::string key = "x_sensor_origin";
    std::string newValue = "1000000000.0";
    isd.clearParams(key);
    isd.addParam(key,newValue);
-   UsgsAstroFramePlugin frameCameraPlugin;
-
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
 
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
 
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(7.5, 6.5);
@@ -449,7 +629,10 @@ TEST_F(FrameIsdTest, X1e9_SlightlyOffCenter) {
    EXPECT_NEAR(groundPt.x, 3.99998400e+03, 1e-4);
    EXPECT_NEAR(groundPt.y, 0.0, 1e-4);
    EXPECT_NEAR(groundPt.z, 1.99999200e+06, 1e-4);
-   
+
+   delete sensorModel;
+   sensorModel = NULL;
+
 }
 
 //Angle rotations:
@@ -459,21 +642,19 @@ TEST_F(FrameIsdTest, Rotation_omegaPi_Center) {
    strval << std::setprecision(20) << M_PI;
    isd.clearParams(key);
    isd.addParam(key,strval.str());
-   UsgsAstroFramePlugin frameCameraPlugin;
-         
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
-   
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
-   
+
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
+
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(7.5, 7.5);
    csm::EcefCoord groundPt = sensorModel->imageToGround(imagePt, 0.0);
    EXPECT_NEAR(groundPt.x, -10.0, 1e-8);
    EXPECT_NEAR(groundPt.y, 0.0, 1e-8);
    EXPECT_NEAR(groundPt.z, 0.0, 1e-8);
-   
+
+   delete sensorModel;
+   sensorModel = NULL;
+
 }
 TEST_F(FrameIsdTest, Rotation_NPole_Center) {
    std::string key = "phi";
@@ -493,21 +674,19 @@ TEST_F(FrameIsdTest, Rotation_NPole_Center) {
    newValue = "1000.0";
    isd.clearParams(key);
    isd.addParam(key,newValue);
-   UsgsAstroFramePlugin frameCameraPlugin;
-         
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
-   
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
-   
+
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
+
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(7.5, 7.5);
    csm::EcefCoord groundPt = sensorModel->imageToGround(imagePt, 0.0);
    EXPECT_NEAR(groundPt.x, 0.0, 1e-8);
    EXPECT_NEAR(groundPt.y, 0.0, 1e-8);
    EXPECT_NEAR(groundPt.z, 10.0, 1e-8);
-   
+
+   delete sensorModel;
+   sensorModel = NULL;
+
 }
 TEST_F(FrameIsdTest, Rotation_SPole_Center) {
    std::string key = "phi";
@@ -526,14 +705,9 @@ TEST_F(FrameIsdTest, Rotation_SPole_Center) {
    newValue = "-1000.0";
    isd.clearParams(key);
    isd.addParam(key,newValue);
-   UsgsAstroFramePlugin frameCameraPlugin;
-         
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
-   
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
-   
+
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
+
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(7.5, 7.5);
    csm::EcefCoord groundPt = sensorModel->imageToGround(imagePt, 0.0);
@@ -541,6 +715,9 @@ TEST_F(FrameIsdTest, Rotation_SPole_Center) {
    EXPECT_NEAR(groundPt.y, 0.0, 1e-8);
    EXPECT_NEAR(groundPt.z, -10.0, 1e-8);
 
+   delete sensorModel;
+   sensorModel = NULL;
+
 }
 
 
@@ -550,65 +727,59 @@ TEST_F(FrameIsdTest, SemiMajorAxis100x_Center) {
    std::string newValue = "1.0";
    isd.clearParams(key);
    isd.addParam(key,newValue);
-   UsgsAstroFramePlugin frameCameraPlugin;
-         
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
-   
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
-   
+
+
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
+
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(7.5, 7.5);
    csm::EcefCoord groundPt = sensorModel->imageToGround(imagePt, 0.0);
    EXPECT_NEAR(groundPt.x, 1000.0, 1e-8);
    EXPECT_NEAR(groundPt.y, 0.0, 1e-8);
    EXPECT_NEAR(groundPt.z, 0.0, 1e-8);
-   
+
+   delete sensorModel;
+   sensorModel = NULL;
+
 }
 TEST_F(FrameIsdTest, SemiMajorAxis10x_SlightlyOffCenter) {
    std::string key = "semi_major_axis";
    std::string newValue = "0.10";
    isd.clearParams(key);
    isd.addParam(key,newValue);
-   UsgsAstroFramePlugin frameCameraPlugin;
-         
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
-   
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
-   
+
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(7.5, 6.5);
    csm::EcefCoord groundPt = sensorModel->imageToGround(imagePt, 0.0);
-   //Note: In the following, the tolerance was increased due to the combination of an offset image point and 
+   //Note: In the following, the tolerance was increased due to the combination of an offset image point and
    //      a very large deviation from sphericity.
    EXPECT_NEAR(groundPt.x, 9.83606557e+01, 1e-7);
    EXPECT_NEAR(groundPt.y, 0.0, 1e-7);
    EXPECT_NEAR(groundPt.z, 1.80327869, 1e-7);
-   
+
+   delete sensorModel;
+   sensorModel = NULL;
+
 }
-// The following test is for the scenario where the semi_minor_axis is actually larger 
+// The following test is for the scenario where the semi_minor_axis is actually larger
 // than the semi_major_axis:
 TEST_F(FrameIsdTest, SemiMinorAxis10x_SlightlyOffCenter) {
    std::string key = "semi_minor_axis";
    std::string newValue = "0.10";
    isd.clearParams(key);
    isd.addParam(key,newValue);
-   UsgsAstroFramePlugin frameCameraPlugin;
-         
-   csm::Model *model = frameCameraPlugin.constructModelFromISD(
-         isd,
-         "USGS_ASTRO_FRAME_SENSOR_MODEL");
-   
-   UsgsAstroFrameSensorModel* sensorModel = dynamic_cast<UsgsAstroFrameSensorModel *>(model);
-   
+
+   UsgsAstroFrameSensorModel* sensorModel = createModel(isd);
+
    ASSERT_NE(sensorModel, nullptr);
    csm::ImageCoord imagePt(7.5, 6.5);
    csm::EcefCoord groundPt = sensorModel->imageToGround(imagePt, 0.0);
    EXPECT_NEAR(groundPt.x, 9.99803960, 1e-8);
    EXPECT_NEAR(groundPt.y, 0.0, 1e-8);
    EXPECT_NEAR(groundPt.z, 1.98000392, 1e-8);
-   
-}
\ No newline at end of file
+
+   delete sensorModel;
+   sensorModel = NULL;
+
+}
-- 
GitLab