Skip to content
Snippets Groups Projects
Select Git revision
  • 580ff8c2ba9737bddafe0df1113f98e53dae61d3
  • master default protected
  • develop
  • 1.0
4 results

main_program.c

  • UsgsAstroFrameSensorModel.cpp 50.04 KiB
    #include "UsgsAstroFrameSensorModel.h"
    
    #include <iomanip>
    #include <iostream>
    #include <sstream>
    
    #include <json/json.hpp>
    
    #include <Error.h>
    #include <Version.h>
    
    #define MESSAGE_LOG(logger, ...) if (logger) { logger->info(__VA_ARGS__); }
    
    using json = nlohmann::json;
    using namespace std;
    
    // Declaration of static variables
    const std::string UsgsAstroFrameSensorModel::_SENSOR_MODEL_NAME
                                          = "USGS_ASTRO_FRAME_SENSOR_MODEL";
    const int UsgsAstroFrameSensorModel::NUM_PARAMETERS = 7;
    const std::string UsgsAstroFrameSensorModel::m_parameterName[] = {
      "X Sensor Position (m)",  // 0
      "Y Sensor Position (m)",  // 1
      "Z Sensor Position (m)",  // 2
      "w",                      // 3
      "v1",                     // 4
      "v2",                     // 5
      "v3"                      // 6
    };
    
    UsgsAstroFrameSensorModel::UsgsAstroFrameSensorModel() {
        reset();
    }
    
    void UsgsAstroFrameSensorModel::reset() {
        m_modelName = _SENSOR_MODEL_NAME;
        m_platformName = "";
        m_sensorName = "";
        m_imageIdentifier = "";
        m_collectionIdentifier = "";
        m_majorAxis = 0.0;
        m_minorAxis = 0.0;
        m_focalLength = 0.0;
        m_startingDetectorSample = 0.0;
        m_startingDetectorLine = 0.0;
        m_detectorSampleSumming = 1.0;
        m_detectorLineSumming = 1.0;
        m_targetName = "";
        m_ifov = 0;
        m_instrumentID = "";
        m_focalLengthEpsilon = 0.0;
        m_originalHalfLines = 0.0;
        m_spacecraftName = "";
        m_pixelPitch = 0.0;
        m_ephemerisTime = 0.0;
        m_originalHalfSamples = 0.0;
        m_nLines = 0;
        m_nSamples = 0;
    
        m_currentParameterValue = std::vector<double>(NUM_PARAMETERS, 0.0);
        m_currentParameterCovariance = std::vector<double>(NUM_PARAMETERS*NUM_PARAMETERS,0.0);
        for (int i = 0; i < NUM_PARAMETERS*NUM_PARAMETERS; i += NUM_PARAMETERS+1) {
          m_currentParameterCovariance[i] = 1;
        }
        m_noAdjustments = std::vector<double>(NUM_PARAMETERS,0.0);
        m_ccdCenter = std::vector<double>(2, 0.0);
        m_spacecraftVelocity = std::vector<double>(3, 0.0);
        m_sunPosition = std::vector<double>(3, 0.0);
        m_distortionType = DistortionType::TRANSVERSE;
        m_opticalDistCoeffs = std::vector<double>(20, 0.0);
        m_transX = std::vector<double>(3, 0.0);
        m_transY = std::vector<double>(3, 0.0);
        m_iTransS = std::vector<double>(3, 0.0);
        m_iTransL = std::vector<double>(3, 0.0);
        m_boresight = std::vector<double>(3, 0.0);
        m_parameterType = std::vector<csm::param::Type>(NUM_PARAMETERS, csm::param::REAL);
        m_referencePointXyz.x = 0;
        m_referencePointXyz.y = 0;
        m_referencePointXyz.z = 0;
        m_logFile = "";
        m_logger.reset();
    }
    
    
    UsgsAstroFrameSensorModel::~UsgsAstroFrameSensorModel() {}
    
    /**
     * @brief UsgsAstroFrameSensorModel::groundToImage
     * @param groundPt
     * @param desiredPrecision
     * @param achievedPrecision
     * @param warnings
     * @return Returns <line, sample> coordinate in the image corresponding to the ground point
     * without bundle adjustment correction.
     */
    csm::ImageCoord UsgsAstroFrameSensorModel::groundToImage(const csm::EcefCoord &groundPt,
                                  double desiredPrecision,
                                  double *achievedPrecision,
                                  csm::WarningList *warnings) const {
      MESSAGE_LOG(this->m_logger, "Computing groundToImage(No adjustments) for {}, {}, {}, with desired precision {}",
                  groundPt.x, groundPt.y, groundPt.z, desiredPrecision);
    
      return groundToImage(groundPt, m_noAdjustments, desiredPrecision, achievedPrecision, warnings);
    }
    
    
    /**
     * @brief UsgsAstroFrameSensorModel::groundToImage
     * @param groundPt
     * @param adjustments
     * @param desired_precision
     * @param achieved_precision
     * @param warnings
     * @return Returns <line,sample> coordinate in the image corresponding to the ground point.
     * This function applies bundle adjustments to the final value.
     */
    csm::ImageCoord UsgsAstroFrameSensorModel::groundToImage(
        const csm::EcefCoord&      groundPt,
        const std::vector<double>& adjustments,
        double                     desired_precision,
        double*                    achieved_precision,
        csm::WarningList*          warnings ) const {
    
      MESSAGE_LOG(this->m_logger, "Computing groundToImage for {}, {}, {}, with desired precision {}",
                  groundPt.x, groundPt.y, groundPt.z, desired_precision);
    
      double x = groundPt.x;
      double y = groundPt.y;
      double z = groundPt.z;
    
      double xo = x - getValue(0,adjustments);
      double yo = y - getValue(1,adjustments);
      double zo = z - getValue(2,adjustments);
    
      double f;
      f = m_focalLength;
    
      // Camera rotation matrix
      double m[3][3];
      calcRotationMatrix(m,adjustments);
    
      // Sensor position
      double undistortedx, undistortedy, denom;
      denom = m[0][2] * xo + m[1][2] * yo + m[2][2] * zo;
      undistortedx = (f * (m[0][0] * xo + m[1][0] * yo + m[2][0] * zo)/denom);
      undistortedy = (f * (m[0][1] * xo + m[1][1] * yo + m[2][1] * zo)/denom);
    
      // Apply the distortion to the line/sample location and then convert back to line/sample
      double distortedX, distortedY;
      applyDistortion(undistortedx, undistortedy, distortedX, distortedY,
                      m_opticalDistCoeffs, m_distortionType);
    
    
      // Convert distorted mm into line/sample
      double sample, line;
      computePixel(
        distortedX, distortedY,
        m_ccdCenter[1], m_ccdCenter[0],
        m_detectorSampleSumming, m_detectorLineSumming,
        m_startingDetectorSample, m_startingDetectorLine,
        &m_iTransS[0], &m_iTransL[0],
        line, sample);
    
      return csm::ImageCoord(line, sample);
    }
    
    
    csm::ImageCoordCovar UsgsAstroFrameSensorModel::groundToImage(const csm::EcefCoordCovar &groundPt,
                                       double desiredPrecision,
                                       double *achievedPrecision,
                                       csm::WarningList *warnings) const {
        MESSAGE_LOG(this->m_logger, "Computeing groundToImage(Covar) for {}, {}, {}, with desired precision {}",
                    groundPt.x, groundPt.y, groundPt.z, desiredPrecision);
    
        csm::EcefCoord gp;
        gp.x = groundPt.x;
        gp.y = groundPt.y;
        gp.z = groundPt.z;
    
        csm::ImageCoord ip = groundToImage(
          gp, desiredPrecision, achievedPrecision, warnings);
       csm::ImageCoordCovar result(ip.line, ip.samp);
       // This is a partial, incorrect implementation to test if SocetGXP needs
       // this method implemented in order to load the sensor.
       return result;
    }
    
    
    csm::EcefCoord UsgsAstroFrameSensorModel::imageToGround(const csm::ImageCoord &imagePt,
                                                     double height,
                                                     double desiredPrecision,
                                                     double *achievedPrecision,
                                                     csm::WarningList *warnings) const {
    
      MESSAGE_LOG(this->m_logger, "Computing imageToGround for {}, {}, {}, with desired precision {}",
                    imagePt.line, imagePt.samp, height, desiredPrecision);
    
      double sample = imagePt.samp;
      double line = imagePt.line;
    
      // Here is where we should be able to apply an adjustment to opk
      double m[3][3];
      calcRotationMatrix(m);
      MESSAGE_LOG(this->m_logger, "Calculated rotation matrix [{}, {}, {}], [{}, {}, {}], [{}, {}, {}]",
                    m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2]);
    
      // Apply the principal point offset, assuming the pp is given in pixels
      double xl, yl, zl;
    
      // Convert from the pixel space into the metric space
      double x_camera, y_camera;
      computeDistortedFocalPlaneCoordinates(
            line, sample,
            m_ccdCenter[1], m_ccdCenter[0],
            m_detectorSampleSumming, m_detectorLineSumming,
            m_startingDetectorSample, m_startingDetectorLine,
            &m_iTransS[0], &m_iTransL[0],
            x_camera, y_camera);
    
      // Apply the distortion model (remove distortion)
      double undistortedX, undistortedY;
      removeDistortion(x_camera, y_camera, undistortedX, undistortedY,
                       m_opticalDistCoeffs,
                       m_distortionType);
      MESSAGE_LOG(this->m_logger, "Found undistortedX: {}, and undistortedY: {}",
                                   undistortedX, undistortedY);
    
      // Now back from distorted mm to pixels
      xl = m[0][0] * undistortedX + m[0][1] * undistortedY - m[0][2] * - m_focalLength;
      yl = m[1][0] * undistortedX + m[1][1] * undistortedY - m[1][2] * - m_focalLength;
      zl = m[2][0] * undistortedX + m[2][1] * undistortedY - m[2][2] * - m_focalLength;
      MESSAGE_LOG(this->m_logger, "Compute xl, yl, zl as {}, {}, {}", xl, yl, zl);
    
      double xc, yc, zc;
      xc = m_currentParameterValue[0];
      yc = m_currentParameterValue[1];
      zc = m_currentParameterValue[2];
      MESSAGE_LOG(this->m_logger, "Set xc, yc, zc to {}, {}, {}",
                                  m_currentParameterValue[0], m_currentParameterValue[1], m_currentParameterValue[2]);
    
      // Intersect with some height about the ellipsoid.
      double x, y, z;
      losEllipsoidIntersect(height, xc, yc, zc, xl, yl, zl, x, y, z);
    
      return csm::EcefCoord(x, y, z);
    }
    
    
    csm::EcefCoordCovar UsgsAstroFrameSensorModel::imageToGround(const csm::ImageCoordCovar &imagePt, double height,
                                      double heightVariance, double desiredPrecision,
                                      double *achievedPrecision,
                                      csm::WarningList *warnings) const {
    
        MESSAGE_LOG(this->m_logger, "Computeing imageToGround(Covar) for {}, {}, {}, with desired precision {}",
                    imagePt.line, imagePt.samp, height, desiredPrecision);
        // This is an incomplete implementation to see if SocetGXP needs this method implemented.
        csm::EcefCoordCovar result;
        return result;
    }
    
    
    csm::EcefLocus UsgsAstroFrameSensorModel::imageToProximateImagingLocus(const csm::ImageCoord &imagePt,
                                                                    const csm::EcefCoord &groundPt,
                                                                    double desiredPrecision,
                                                                    double *achievedPrecision,
                                                                    csm::WarningList *warnings) const {
      // Ignore the ground point?
      MESSAGE_LOG(this->m_logger, "Computeing imageToProximateImagingLocus(No ground) for point {}, {}, {}, with desired precision {}",
                                   imagePt.line, imagePt.samp, desiredPrecision);
      return imageToRemoteImagingLocus(imagePt);
    }
    
    
    csm::EcefLocus UsgsAstroFrameSensorModel::imageToRemoteImagingLocus(const csm::ImageCoord &imagePt,
                                                                 double desiredPrecision,
                                                                 double *achievedPrecision,
                                                                 csm::WarningList *warnings) const {
      MESSAGE_LOG(this->m_logger, "Computeing imageToProximateImagingLocus for {}, {}, {}, with desired precision {}",
                                   imagePt.line, imagePt.samp, desiredPrecision);
      // Find the line,sample on the focal plane (mm)
      double focalPlaneX, focalPlaneY;
      computeDistortedFocalPlaneCoordinates(
            imagePt.line, imagePt.samp,
            m_ccdCenter[1], m_ccdCenter[0],
            m_detectorSampleSumming, m_detectorLineSumming,
            m_startingDetectorSample, m_startingDetectorLine,
            &m_iTransS[0], &m_iTransL[0],
            focalPlaneY, focalPlaneY);
    
      // Distort
      double undistortedFocalPlaneX, undistortedFocalPlaneY;
      removeDistortion(focalPlaneX, focalPlaneY,
                       undistortedFocalPlaneX, undistortedFocalPlaneY,
                       m_opticalDistCoeffs,
                       m_distortionType);
    
      // Get rotation matrix and transform to a body-fixed frame
      double m[3][3];
      calcRotationMatrix(m);
      std::vector<double> lookC { undistortedFocalPlaneX, undistortedFocalPlaneY, m_focalLength };
      std::vector<double> lookB {
        m[0][0] * lookC[0] + m[0][1] * lookC[1] + m[0][2] * lookC[2],
        m[1][0] * lookC[0] + m[1][1] * lookC[1] + m[1][2] * lookC[2],
        m[2][0] * lookC[0] + m[2][1] * lookC[1] + m[2][2] * lookC[2]
      };
    
      // Get unit vector
      double mag = sqrt(lookB[0] * lookB[0] + lookB[1] * lookB[1] + lookB[2] * lookB[2]);
      std::vector<double> lookBUnit {
        lookB[0] / mag,
        lookB[1] / mag,
        lookB[2] / mag
      };
    
      return csm::EcefLocus(m_currentParameterValue[0], m_currentParameterValue[1], m_currentParameterValue[2],
          lookBUnit[0], lookBUnit[1], lookBUnit[2]);
    }
    
    
    csm::ImageCoord UsgsAstroFrameSensorModel::getImageStart() const {
    
      MESSAGE_LOG(this->m_logger, "Accessing Image Start line: {}, sample: {}",
                                  m_startingDetectorLine, m_startingDetectorSample);
      csm::ImageCoord start;
      start.samp = m_startingDetectorSample;
      start.line = m_startingDetectorLine;
      return start;
    }
    
    
    csm::ImageVector UsgsAstroFrameSensorModel::getImageSize() const {
    
      MESSAGE_LOG(this->m_logger, "Accessing Image Size line: {}, sample: {}",
                                  m_nLines, m_nSamples);
      csm::ImageVector size;
      size.line = m_nLines;
      size.samp = m_nSamples;
      return size;
    }
    
    
    std::pair<csm::ImageCoord, csm::ImageCoord> UsgsAstroFrameSensorModel::getValidImageRange() const {
        MESSAGE_LOG(this->m_logger, "Accessing Image Range");
        csm::ImageCoord min_pt(m_startingDetectorLine, m_startingDetectorSample);
        csm::ImageCoord max_pt(m_nLines, m_nSamples);
        return std::pair<csm::ImageCoord, csm::ImageCoord>(min_pt, max_pt);
    }
    
    
    std::pair<double, double> UsgsAstroFrameSensorModel::getValidHeightRange() const {
        MESSAGE_LOG(this->m_logger, "Accessing Image Height min: {}, max: {}",
                                    m_minElevation, m_maxElevation);
        return std::pair<double, double>(m_minElevation, m_maxElevation);
    }
    
    
    csm::EcefVector UsgsAstroFrameSensorModel::getIlluminationDirection(const csm::EcefCoord &groundPt) const {
      // ground (body-fixed) - sun (body-fixed) gives us the illumination direction.
      MESSAGE_LOG(this->m_logger, "Accessing illumination direction for ground point {}, {}, {}",
                  groundPt.x, groundPt.y, groundPt.z);
      return csm::EcefVector {
        groundPt.x - m_sunPosition[0],
        groundPt.y - m_sunPosition[1],
        groundPt.z - m_sunPosition[2]
      };
    }
    
    
    double UsgsAstroFrameSensorModel::getImageTime(const csm::ImageCoord &imagePt) const {
      MESSAGE_LOG(this->m_logger, "Accessing image time for image point {}, {}",
                  imagePt.line, imagePt.samp);
      // check if the image point is in range
      if (imagePt.samp >= m_startingDetectorSample &&
          imagePt.samp <= (m_startingDetectorSample + m_nSamples) &&
          imagePt.line >= m_startingDetectorSample &&
          imagePt.line <= (m_startingDetectorLine + m_nLines)) {
        return m_ephemerisTime;
      }
      else {
        throw csm::Error(csm::Error::BOUNDS,
                         "Image Coordinate out of Bounds",
                         "UsgsAstroFrameSensorModel::getImageTime");
      }
    }
    
    
    csm::EcefCoord UsgsAstroFrameSensorModel::getSensorPosition(const csm::ImageCoord &imagePt) const {
      MESSAGE_LOG(this->m_logger, "Accessing sensor position for image point {}, {}",
                  imagePt.line, imagePt.samp);
      // check if the image point is in range
      if (imagePt.samp >= m_startingDetectorSample &&
          imagePt.samp <= (m_startingDetectorSample + m_nSamples) &&
          imagePt.line >= m_startingDetectorSample &&
          imagePt.line <= (m_startingDetectorLine + m_nLines)) {
        csm::EcefCoord sensorPosition;
        sensorPosition.x = m_currentParameterValue[0];
        sensorPosition.y = m_currentParameterValue[1];
        sensorPosition.z = m_currentParameterValue[2];
    
        return sensorPosition;
      }
      else {
        throw csm::Error(csm::Error::BOUNDS,
                         "Image Coordinate out of Bounds",
                         "UsgsAstroFrameSensorModel::getSensorPosition");
      }
    }
    
    
    csm::EcefCoord UsgsAstroFrameSensorModel::getSensorPosition(double time) const {
        MESSAGE_LOG(this->m_logger, "Accessing sensor position for time {}", time);
        if (time == m_ephemerisTime){
            csm::EcefCoord sensorPosition;
            sensorPosition.x = m_currentParameterValue[0];
            sensorPosition.y = m_currentParameterValue[1];
            sensorPosition.z = m_currentParameterValue[2];
    
            return sensorPosition;
        } else {
            std::string aMessage = "Valid image time is %d", m_ephemerisTime;
            throw csm::Error(csm::Error::BOUNDS,
                             aMessage,
                             "UsgsAstroFrameSensorModel::getSensorPosition");
        }
    }
    
    
    csm::EcefVector UsgsAstroFrameSensorModel::getSensorVelocity(const csm::ImageCoord &imagePt) const {
      MESSAGE_LOG(this->m_logger, "Accessing sensor velocity for image point {}, {}",
                  imagePt.line, imagePt.samp);
      // Make sure the passed coordinate is with the image dimensions.
      if (imagePt.samp < 0.0 || imagePt.samp > m_nSamples ||
          imagePt.line < 0.0 || imagePt.line > m_nLines) {
        throw csm::Error(csm::Error::BOUNDS, "Image coordinate out of bounds.",
                         "UsgsAstroFrameSensorModel::getSensorVelocity");
      }
    
      // Since this is a frame, just return the sensor velocity the ISD gave us.
      return csm::EcefVector {
        m_spacecraftVelocity[0],
        m_spacecraftVelocity[1],
        m_spacecraftVelocity[2]
      };
    }
    
    
    csm::EcefVector UsgsAstroFrameSensorModel::getSensorVelocity(double time) const {
        MESSAGE_LOG(this->m_logger, "Accessing sensor position for time {}", time);
        if (time == m_ephemerisTime){
            return csm::EcefVector {
              m_spacecraftVelocity[0],
              m_spacecraftVelocity[1],
              m_spacecraftVelocity[2]
            };
        } else {
            std::string aMessage = "Valid image time is %d", m_ephemerisTime;
            throw csm::Error(csm::Error::BOUNDS,
                             aMessage,
                             "UsgsAstroFrameSensorModel::getSensorVelocity");
        }
    }
    
    
    csm::RasterGM::SensorPartials UsgsAstroFrameSensorModel::computeSensorPartials(int index,
                                               const csm::EcefCoord &groundPt,
                                               double desiredPrecision,
                                               double *achievedPrecision,
                                               csm::WarningList *warnings) const {
        MESSAGE_LOG(this->m_logger, "Computing sensor partials image point from ground point {}, {}, {} \
                                     and desiredPrecision: {}", groundPt.x, groundPt.y, groundPt.z, desiredPrecision);
    
        csm::ImageCoord img_pt = groundToImage(groundPt, desiredPrecision, achievedPrecision);
    
        return computeSensorPartials(index, img_pt, groundPt, desiredPrecision, achievedPrecision);
    }
    
    
    /**
     * @brief UsgsAstroFrameSensorModel::computeSensorPartials
     * @param index
     * @param imagePt
     * @param groundPt
     * @param desiredPrecision
     * @param achievedPrecision
     * @param warnings
     * @return The partial derivatives in the line,sample directions.
     *
     * Research:  We should investigate using a central difference scheme to approximate
     * the partials.  It is more accurate, but it might be costlier calculation-wise.
     *
     */
    csm::RasterGM::SensorPartials UsgsAstroFrameSensorModel::computeSensorPartials(int index,
                                              const csm::ImageCoord &imagePt,
                                              const csm::EcefCoord &groundPt,
                                              double desiredPrecision,
                                              double *achievedPrecision,
                                              csm::WarningList *warnings) const {
    
      MESSAGE_LOG(this->m_logger, "Computing sensor partials for ground point {}, {}, {}\
                                   with point: {}, {}, index: {}, and desiredPrecision: {}",
                                   groundPt.x, groundPt.y, groundPt.z, imagePt.line, imagePt.samp,
                                   index, desiredPrecision);
      double delta = 1.0;
      // The rotation parameters will usually be small (<1),
      // so a delta of 1.0 is too small.
      // Rotation parameters are indices 3-6
      if (index > 2 && index < 7) {
        delta = 0.01;
      }
      // Update the parameter
      std::vector<double>adj(NUM_PARAMETERS, 0.0);
      adj[index] = delta;
    
      csm::ImageCoord imagePt1 = groundToImage(groundPt,adj,desiredPrecision,achievedPrecision);
    
      csm::RasterGM::SensorPartials partials;
    
      partials.first = (imagePt1.line - imagePt.line)/delta;
      partials.second = (imagePt1.samp - imagePt.samp)/delta;
    
      return partials;
    
    }
    
    std::vector<csm::RasterGM::SensorPartials> UsgsAstroFrameSensorModel::computeAllSensorPartials(
        const csm::ImageCoord& imagePt,
        const csm::EcefCoord& groundPt,
        csm::param::Set pset,
        double desiredPrecision,
        double *achievedPrecision,
        csm::WarningList *warnings) const {
      MESSAGE_LOG(this->m_logger, "Computing all sensor partials for ground point {}, {}, {}\
                                   with point: {}, {}, pset: {}, and desiredPrecision: {}",
                                   groundPt.x, groundPt.y, groundPt.z, imagePt.line, imagePt.samp,
                                   pset, desiredPrecision);
      std::vector<int> indices = getParameterSetIndices(pset);
      size_t num = indices.size();
      std::vector<csm::RasterGM::SensorPartials> partials;
      for (int index = 0;index < num;index++){
        partials.push_back(computeSensorPartials(
            indices[index],
            imagePt, groundPt,
            desiredPrecision, achievedPrecision, warnings));
      }
      return partials;
    }
    
    std::vector<csm::RasterGM::SensorPartials> UsgsAstroFrameSensorModel::computeAllSensorPartials(
        const csm::EcefCoord& groundPt,
        csm::param::Set pset,
        double desiredPrecision,
        double *achievedPrecision,
        csm::WarningList *warnings) const {
      MESSAGE_LOG(this->m_logger, "Computing all sensor partials image point from ground point {}, {}, {} \
                                   and desiredPrecision: {}", groundPt.x, groundPt.y, groundPt.z, desiredPrecision);
      csm::ImageCoord imagePt = groundToImage(groundPt,
                                              desiredPrecision, achievedPrecision, warnings);
      return computeAllSensorPartials(imagePt, groundPt,
                                      pset, desiredPrecision, achievedPrecision, warnings);
        }
    
    std::vector<double> UsgsAstroFrameSensorModel::computeGroundPartials(const csm::EcefCoord
                                                                         &groundPt) const {
      MESSAGE_LOG(this->m_logger, "Computing ground partials for ground point {}, {}, {}",
                                   groundPt.x, groundPt.y, groundPt.z);
      // Partial of line, sample wrt X, Y, Z
      double x = groundPt.x;
      double y = groundPt.y;
      double z = groundPt.z;
    
      csm::ImageCoord ipB = groundToImage(groundPt);
      csm::EcefCoord nextPoint = imageToGround(csm::ImageCoord(ipB.line + 1, ipB.samp + 1));
      double dx = nextPoint.x - x;
      double dy = nextPoint.y - y;
      double dz = nextPoint.z - z;
      double pixelGroundSize = sqrt((dx*dx + dy*dy + dz*dz) / 2.0);
    
      // If the ground size is too small, try the opposite direction
      if (pixelGroundSize < 1e-10) {
        nextPoint = imageToGround(csm::ImageCoord(ipB.line - 1, ipB.samp - 1));
        dx = nextPoint.x - x;
        dy = nextPoint.y - y;
        dz = nextPoint.z - z;
        pixelGroundSize = sqrt((dx*dx + dy*dy + dz*dz) / 2.0);
      }
    
      csm::ImageCoord ipX = groundToImage(csm::EcefCoord(x + pixelGroundSize, y, z));
      csm::ImageCoord ipY = groundToImage(csm::EcefCoord(x, y + pixelGroundSize, z));
      csm::ImageCoord ipZ = groundToImage(csm::EcefCoord(x, y, z + pixelGroundSize));
    
      std::vector<double> partials(6, 0.0);
      partials[0] = (ipX.line - ipB.line) / pixelGroundSize;
      partials[3] = (ipX.samp - ipB.samp) / pixelGroundSize;
      partials[1] = (ipY.line - ipB.line) / pixelGroundSize;
      partials[4] = (ipY.samp - ipB.samp) / pixelGroundSize;
      partials[2] = (ipZ.line - ipB.line) / pixelGroundSize;
      partials[5] = (ipZ.samp - ipB.samp) / pixelGroundSize;
    
      MESSAGE_LOG(this->m_logger, "Computing ground partials results:\nLine: {}, {}, {}\nSample: {}, {}, {}",
                                   partials[0], partials[1], partials[2], partials[3], partials[4], partials[5]);
    
      return partials;
    }
    
    
    const csm::CorrelationModel& UsgsAstroFrameSensorModel::getCorrelationModel() const {
      MESSAGE_LOG(this->m_logger, "Accessing correlation model");
      return _no_corr_model;
    }
    
    
    std::vector<double> UsgsAstroFrameSensorModel::getUnmodeledCrossCovariance(const csm::ImageCoord &pt1,
                                                    const csm::ImageCoord &pt2) const {
    
       // No unmodeled error
       MESSAGE_LOG(this->m_logger, "Accessing unmodeled cross covar with \
                                    point1: {}, {} and point2: {}, {}",
                                    pt1.line, pt1.samp, pt2.line, pt2.samp);
       return std::vector<double>(4, 0.0);
    }
    
    
    csm::Version UsgsAstroFrameSensorModel::getVersion() const {
      MESSAGE_LOG(this->m_logger, "Accessing CSM version");
      return csm::Version(0,1,0);
    }
    
    
    std::string UsgsAstroFrameSensorModel::getModelName() const {
      MESSAGE_LOG(this->m_logger, "Accessing CSM name {}", _SENSOR_MODEL_NAME);
      return _SENSOR_MODEL_NAME;
    }
    
    
    std::string UsgsAstroFrameSensorModel::getPedigree() const {
      MESSAGE_LOG(this->m_logger, "Accessing CSM pedigree");
      return "USGS_FRAMER";
    }
    
    
    std::string UsgsAstroFrameSensorModel::getImageIdentifier() const {
      MESSAGE_LOG(this->m_logger, "Accessing image ID {}", m_imageIdentifier);
      return m_imageIdentifier;
    }
    
    
    void UsgsAstroFrameSensorModel::setImageIdentifier(const std::string& imageId,
                                                csm::WarningList* warnings) {
      MESSAGE_LOG(this->m_logger, "Setting image ID to {}", imageId);
      m_imageIdentifier = imageId;
    }
    
    
    std::string UsgsAstroFrameSensorModel::getSensorIdentifier() const {
      MESSAGE_LOG(this->m_logger, "Accessing sensor ID");
      return m_sensorName;
    }
    
    
    std::string UsgsAstroFrameSensorModel::getPlatformIdentifier() const {
      MESSAGE_LOG(this->m_logger, "Accessing platform ID");
      return m_platformName;
    }
    
    
    std::string UsgsAstroFrameSensorModel::getCollectionIdentifier() const {
      MESSAGE_LOG(this->m_logger, "Accessing collection ID");
      return m_collectionIdentifier;
    }
    
    
    std::string UsgsAstroFrameSensorModel::getTrajectoryIdentifier() const {
      MESSAGE_LOG(this->m_logger, "Accessing trajectory ID");
      return "";
    }
    
    
    std::string UsgsAstroFrameSensorModel::getSensorType() const {
        MESSAGE_LOG(this->m_logger, "Accessing sensor type");
        return CSM_SENSOR_TYPE_EO;
    }
    
    
    std::string UsgsAstroFrameSensorModel::getSensorMode() const {
        MESSAGE_LOG(this->m_logger, "Accessing sensor mode");
        return CSM_SENSOR_MODE_FRAME;
    }
    
    
    std::string UsgsAstroFrameSensorModel::getReferenceDateAndTime() const {
      MESSAGE_LOG(this->m_logger, "Accessing reference data and time");
      return "";
    }
    
    
    std::string UsgsAstroFrameSensorModel::getModelState() const {
        MESSAGE_LOG(this->m_logger, "Dumping model state");
        json state = {
          {"m_modelName", _SENSOR_MODEL_NAME},
          {"m_sensorName", m_sensorName},
          {"m_platformName", m_platformName},
          {"m_focalLength" , m_focalLength},
          {"m_iTransS", {m_iTransS[0], m_iTransS[1], m_iTransS[2]}},
          {"m_iTransL", {m_iTransL[0], m_iTransL[1], m_iTransL[2]}},
          {"m_boresight", {m_boresight[0], m_boresight[1], m_boresight[2]}},
          {"m_transX", {m_transX[0], m_transX[1], m_transX[2]}},
          {"m_transY", {m_transY[0], m_transY[1], m_transY[2]}},
          {"m_iTransS", {m_iTransS[0], m_iTransS[1], m_iTransS[2]}},
          {"m_iTransL", {m_iTransL[0], m_iTransL[1], m_iTransL[2]}},
          {"m_majorAxis", m_majorAxis},
          {"m_minorAxis", m_minorAxis},
          {"m_spacecraftVelocity", {m_spacecraftVelocity[0], m_spacecraftVelocity[1], m_spacecraftVelocity[2]}},
          {"m_sunPosition", {m_sunPosition[0], m_sunPosition[1], m_sunPosition[2]}},
          {"m_startingDetectorSample", m_startingDetectorSample},
          {"m_startingDetectorLine", m_startingDetectorLine},
          {"m_detectorSampleSumming", m_detectorSampleSumming},
          {"m_detectorLineSumming", m_detectorLineSumming},
          {"m_targetName", m_targetName},
          {"m_ifov", m_ifov},
          {"m_instrumentID", m_instrumentID},
          {"m_focalLengthEpsilon", m_focalLengthEpsilon},
          {"m_ccdCenter", {m_ccdCenter[0], m_ccdCenter[1]}},
          {"m_minElevation", m_minElevation},
          {"m_maxElevation", m_maxElevation},
          {"m_distortionType", m_distortionType},
          {"m_opticalDistCoeffs", m_opticalDistCoeffs},
          {"m_originalHalfLines", m_originalHalfLines},
          {"m_originalHalfSamples", m_originalHalfSamples},
          {"m_spacecraftName", m_spacecraftName},
          {"m_pixelPitch", m_pixelPitch},
          {"m_ephemerisTime", m_ephemerisTime},
          {"m_nLines", m_nLines},
          {"m_nSamples", m_nSamples},
          {"m_currentParameterValue", m_currentParameterValue},
          {"m_imageIdentifier", m_imageIdentifier},
          {"m_collectionIdentifier", m_collectionIdentifier},
          {"m_referencePointXyz", {m_referencePointXyz.x,
                                   m_referencePointXyz.y,
                                   m_referencePointXyz.z}},
          {"m_currentParameterCovariance", m_currentParameterCovariance},
          {"m_logFile", m_logFile}
        };
    
        return state.dump();
    }
    
    bool UsgsAstroFrameSensorModel::isValidModelState(const std::string& stringState, csm::WarningList *warnings) {
      MESSAGE_LOG(this->m_logger, "Checking if model has valid state");
      std::vector<std::string> requiredKeywords = {
        "m_modelName",
        "m_majorAxis",
        "m_minorAxis",
        "m_focalLength",
        "m_startingDetectorSample",
        "m_startingDetectorLine",
        "m_detectorSampleSumming",
        "m_detectorLineSumming",
        "m_focalLengthEpsilon",
        "m_nLines",
        "m_nSamples",
        "m_currentParameterValue",
        "m_ccdCenter",
        "m_spacecraftVelocity",
        "m_sunPosition",
        "m_distortionType",
        "m_opticalDistCoeffs",
        "m_transX",
        "m_transY",
        "m_iTransS",
        "m_iTransL"
      };
    
      json jsonState = json::parse(stringState);
      std::vector<std::string> missingKeywords;
    
      for (auto &key : requiredKeywords) {
        if (jsonState.find(key) == jsonState.end()) {
          missingKeywords.push_back(key);
        }
      }
    
      if (!missingKeywords.empty() && warnings) {
        std::ostringstream oss;
        std::copy(missingKeywords.begin(), missingKeywords.end(), std::ostream_iterator<std::string>(oss, " "));
        warnings->push_back(csm::Warning(
          csm::Warning::DATA_NOT_AVAILABLE,
          "State has missing keywrods: " + oss.str(),
          "UsgsAstroFrameSensorModel::isValidModelState"
        ));
      }
    
      std::string modelName = jsonState.value<std::string>("m_modelName", "");
    
      if (modelName != _SENSOR_MODEL_NAME && warnings) {
        warnings->push_back(csm::Warning(
          csm::Warning::DATA_NOT_AVAILABLE,
          "Incorrect model name in state, expected " + _SENSOR_MODEL_NAME + " but got " + modelName,
          "UsgsAstroFrameSensorModel::isValidModelState"
        ));
      }
    
      return modelName == _SENSOR_MODEL_NAME && missingKeywords.empty();
    }
    
    
    bool UsgsAstroFrameSensorModel::isValidIsd(const std::string& Isd, csm::WarningList *warnings) {
      // no obvious clean way to truely validate ISD with it's nested structure,
      // or rather, it would be a pain to maintain, so just check if
      // we can get a valid state from ISD. Once ISD schema is 100% clear
      // we can change this.
      MESSAGE_LOG(this->m_logger, "Building isd to check model state");
       try {
         std::string state = constructStateFromIsd(Isd, warnings);
         return isValidModelState(state, warnings);
       }
       catch(...) {
         return false;
       }
    }
    
    
    void UsgsAstroFrameSensorModel::replaceModelState(const std::string& stringState) {
    
        json state = json::parse(stringState);
        MESSAGE_LOG(this->m_logger, "Replaceing model state");
        // The json library's .at() will except if key is missing
        try {
            m_modelName = state.at("m_modelName").get<std::string>();
            m_majorAxis = state.at("m_majorAxis").get<double>();
            m_minorAxis = state.at("m_minorAxis").get<double>();
            m_focalLength = state.at("m_focalLength").get<double>();
            m_startingDetectorSample = state.at("m_startingDetectorSample").get<double>();
            m_startingDetectorLine = state.at("m_startingDetectorLine").get<double>();
            m_detectorSampleSumming = state.at("m_detectorSampleSumming").get<double>();
            m_detectorLineSumming = state.at("m_detectorLineSumming").get<double>();
            m_focalLengthEpsilon = state.at("m_focalLengthEpsilon").get<double>();
            m_nLines = state.at("m_nLines").get<int>();
            m_nSamples = state.at("m_nSamples").get<int>();
            m_currentParameterValue = state.at("m_currentParameterValue").get<std::vector<double>>();
            m_ccdCenter = state.at("m_ccdCenter").get<std::vector<double>>();
            m_spacecraftVelocity = state.at("m_spacecraftVelocity").get<std::vector<double>>();
            m_sunPosition = state.at("m_sunPosition").get<std::vector<double>>();
            m_distortionType = (DistortionType)state.at("m_distortionType").get<int>();
            m_opticalDistCoeffs = state.at("m_opticalDistCoeffs").get<std::vector<double>>();
            m_transX = state.at("m_transX").get<std::vector<double>>();
            m_transY = state.at("m_transY").get<std::vector<double>>();
            m_iTransS = state.at("m_iTransS").get<std::vector<double>>();
            m_iTransL = state.at("m_iTransL").get<std::vector<double>>();
            m_imageIdentifier = state.at("m_imageIdentifier").get<std::string>();
            m_platformName = state.at("m_platformName").get<std::string>();
            m_sensorName = state.at("m_sensorName").get<std::string>();
            m_collectionIdentifier = state.at("m_collectionIdentifier").get<std::string>();
            // Set reference point to the center of the image
            m_referencePointXyz = imageToGround(csm::ImageCoord(m_nLines/2.0, m_nSamples/2.0));
            m_currentParameterCovariance = state.at("m_currentParameterCovariance").get<std::vector<double>>();
            m_logFile = state.at("m_logFile").get<std::string>();
            if (m_logFile.empty()) {
              m_logger.reset();
            }
            else {
              m_logger = spdlog::get(m_logFile);
              if (!m_logger) {
                m_logger = spdlog::basic_logger_mt(m_logFile, m_logFile);
              }
            }
        }
        catch(std::out_of_range& e) {
          throw csm::Error(csm::Error::SENSOR_MODEL_NOT_CONSTRUCTIBLE,
                           "State keywords required to generate sensor model missing: " + std::string(e.what()) + "\nUsing model string: " + stringState,
                           "UsgsAstroFrameSensorModel::replaceModelState");
        }
    }
    
    
    std::string UsgsAstroFrameSensorModel::constructStateFromIsd(const std::string& jsonIsd, csm::WarningList* warnings) {
        MESSAGE_LOG(this->m_logger, "Constructing state from isd");
        json isd = json::parse(jsonIsd);
        json state = {};
    
        csm::WarningList* parsingWarnings = new csm::WarningList;
    
        state["m_modelName"] = getSensorModelName(isd, parsingWarnings);
        state["m_imageIdentifier"] = getImageId(isd, parsingWarnings);
        state["m_sensorName"] = getSensorName(isd, parsingWarnings);
        state["m_platformName"] = getPlatformName(isd, parsingWarnings);
    
        state["m_startingDetectorSample"] = getDetectorStartingSample(isd, parsingWarnings);
        state["m_startingDetectorLine"] = getDetectorStartingLine(isd, parsingWarnings);
        state["m_detectorSampleSumming"] = getSampleSumming(isd, parsingWarnings);
        state["m_detectorLineSumming"] = getLineSumming(isd, parsingWarnings);
    
        // get focal length
        state["m_focalLength"] = getFocalLength(isd, parsingWarnings);
        state["m_focalLengthEpsilon"] = getFocalLengthEpsilon(isd);
    
    
        state["m_currentParameterValue"] = json();
    
        // get sensor_position
        std::vector<double> position = getSensorPositions(isd, parsingWarnings);
        if (!position.empty() && position.size() != 3) {
          parsingWarnings->push_back(
            csm::Warning(
              csm::Warning::DATA_NOT_AVAILABLE,
              "Sensor position does not have 3 values.",
              "UsgsAstroFrameSensorModel::constructStateFromIsd()"));
          state["m_currentParameterValue"][0] = 0;
          state["m_currentParameterValue"][1] = 0;
          state["m_currentParameterValue"][2] = 0;
        }
        else {
          state["m_currentParameterValue"] = position;
        }
    
        // get sensor_velocity
        std::vector<double> velocity = getSensorVelocities(isd, parsingWarnings);
        if (!velocity.empty() && velocity.size() != 3) {
          parsingWarnings->push_back(
            csm::Warning(
              csm::Warning::DATA_NOT_AVAILABLE,
              "Sensor velocity does not have 3 values.",
              "UsgsAstroFrameSensorModel::constructStateFromIsd()"));
        }
        else {
          state["m_spacecraftVelocity"] = velocity;
        }
    
    
        // get sun_position
        // sun position is not strictly necessary, but is required for getIlluminationDirection.
        state["m_sunPosition"] = getSunPositions(isd);
    
        // get sensor_orientation quaternion
        std::vector<double> quaternion = getSensorOrientations(isd, parsingWarnings);
        if (quaternion.size() != 4) {
          parsingWarnings->push_back(
            csm::Warning(
              csm::Warning::DATA_NOT_AVAILABLE,
              "Sensor orientation quaternion does not have 4 values.",
              "UsgsAstroFrameSensorModel::constructStateFromIsd()"));
        }
        else {
          state["m_currentParameterValue"][3] = quaternion[0];
          state["m_currentParameterValue"][4] = quaternion[1];
          state["m_currentParameterValue"][5] = quaternion[2];
          state["m_currentParameterValue"][6] = quaternion[3];
        }
    
        // get optical_distortion
        state["m_distortionType"] = getDistortionModel(isd, warnings);
        state["m_opticalDistCoeffs"] = getDistortionCoeffs(isd, warnings);
    
        // get detector_center
        state["m_ccdCenter"][0] = getDetectorCenterLine(isd, parsingWarnings);
        state["m_ccdCenter"][1] = getDetectorCenterSample(isd, parsingWarnings);
    
    
        // get radii
        state["m_minorAxis"] = getSemiMinorRadius(isd, parsingWarnings);
        state["m_majorAxis"] = getSemiMajorRadius(isd, parsingWarnings);
    
    
        // get reference_height
        state["m_minElevation"] = getMinHeight(isd, parsingWarnings);
        state["m_maxElevation"] = getMaxHeight(isd, parsingWarnings);
    
    
        state["m_ephemerisTime"] = getCenterTime(isd, parsingWarnings);
        state["m_nLines"] = getTotalLines(isd, parsingWarnings);
        state["m_nSamples"] = getTotalSamples(isd, parsingWarnings);
    
        state["m_iTransL"] = getFocal2PixelLines(isd, parsingWarnings);
        state["m_iTransS"] = getFocal2PixelSamples(isd, parsingWarnings);
    
        // We don't pass the pixel to focal plane transformation so invert the
        // focal plane to pixel transformation
        try {
          double determinant = state["m_iTransL"][1].get<double>() * state["m_iTransS"][2].get<double>() -
                               state["m_iTransL"][2].get<double>() * state["m_iTransS"][1].get<double>();
    
          state["m_transX"][1] =  state["m_iTransL"][1].get<double>() / determinant;
          state["m_transX"][2] = -state["m_iTransS"][1].get<double>() / determinant;
          state["m_transX"][0] = -(state["m_transX"][1].get<double>() * state["m_iTransL"][0].get<double>() +
                                  state["m_transX"][2].get<double>() * state["m_iTransS"][0].get<double>());
    
          state["m_transY"][1] = -state["m_iTransL"][2].get<double>() / determinant;
          state["m_transY"][2] =  state["m_iTransS"][2].get<double>() / determinant;
          state["m_transY"][0] = -(state["m_transY"][1].get<double>() * state["m_iTransL"][0].get<double>() +
                                   state["m_transY"][2].get<double>() * state["m_iTransS"][0].get<double>());
        }
        catch (...) {
          parsingWarnings->push_back(
            csm::Warning(
              csm::Warning::DATA_NOT_AVAILABLE,
              "Could not compute detector pixel to focal plane coordinate transformation.",
              "UsgsAstroFrameSensorModel::constructStateFromIsd()"));
        }
    
    
        state["m_referencePointXyz"] = std::vector<double>(3, 0.0);
        state["m_currentParameterCovariance"] = std::vector<double>(NUM_PARAMETERS*NUM_PARAMETERS,0.0);
        for (int i = 0; i < NUM_PARAMETERS*NUM_PARAMETERS; i += NUM_PARAMETERS+1) {
          state["m_currentParameterCovariance"][i] = 1;
        }
        state["m_collectionIdentifier"] = "";
    
        // Get the optional logging file
        state["m_logFile"] = getLogFile(isd);
    
    
        if (!parsingWarnings->empty()) {
          if (warnings) {
            warnings->insert(warnings->end(), parsingWarnings->begin(), parsingWarnings->end());
          }
          delete parsingWarnings;
          parsingWarnings = nullptr;
          throw csm::Error(csm::Error::SENSOR_MODEL_NOT_CONSTRUCTIBLE,
                           "ISD is invalid for creating the sensor model.",
                           "UsgsAstroFrameSensorModel::constructStateFromIsd");
        }
    
        delete parsingWarnings;
        parsingWarnings = nullptr;
    
        return state.dump();
    }
    
    
    
    csm::EcefCoord UsgsAstroFrameSensorModel::getReferencePoint() const {
      MESSAGE_LOG(this->m_logger, "Accessing reference point x: {}, y: {}, z: {}",
                                  m_referencePointXyz.x, m_referencePointXyz.y, m_referencePointXyz.z);
      return m_referencePointXyz;
    }
    
    
    void UsgsAstroFrameSensorModel::setReferencePoint(const csm::EcefCoord &groundPt) {
      MESSAGE_LOG(this->m_logger, "Setting reference point to {}, {}, {}",
                                   groundPt.x, groundPt.y, groundPt.z);
      m_referencePointXyz = groundPt;
    }
    
    
    int UsgsAstroFrameSensorModel::getNumParameters() const {
      MESSAGE_LOG(this->m_logger, "Accessing num parameters: {}", NUM_PARAMETERS);
      return NUM_PARAMETERS;
    }
    
    
    std::string UsgsAstroFrameSensorModel::getParameterName(int index) const {
      MESSAGE_LOG(this->m_logger, "Setting parameter name to {}", index);
      return m_parameterName[index];
    }
    
    
    std::string UsgsAstroFrameSensorModel::getParameterUnits(int index) const {
      MESSAGE_LOG(this->m_logger, "Accessing parameter units for {}", index);
      if (index < 3) {
        return "m";
      }
      else {
        return "radians";
      }
    }
    
    
    bool UsgsAstroFrameSensorModel::hasShareableParameters() const {
      MESSAGE_LOG(this->m_logger, "Checking for shareable parameters");
      return false;
    }
    
    
    bool UsgsAstroFrameSensorModel::isParameterShareable(int index) const {
      MESSAGE_LOG(this->m_logger, "Checking is parameter: {} is shareable", index);
      return false;
    }
    
    
    csm::SharingCriteria UsgsAstroFrameSensorModel::getParameterSharingCriteria(int index) const {
       MESSAGE_LOG(this->m_logger, "Checking sharing criteria for parameter {}. "
                   "Sharing is not supported, throwing exception", index);
       // Parameter sharing is not supported for this sensor,
       // all indices are out of range
       throw csm::Error(
          csm::Error::INDEX_OUT_OF_RANGE,
          "Index out of range.",
          "UsgsAstroLsSensorModel::getParameterSharingCriteria");
    }
    
    
    double UsgsAstroFrameSensorModel::getParameterValue(int index) const {
      MESSAGE_LOG(this->m_logger, "Accessing parameter value {} at index: {}", m_currentParameterValue[index], index);
      return m_currentParameterValue[index];
    
    }
    
    
    void UsgsAstroFrameSensorModel::setParameterValue(int index, double value) {
      MESSAGE_LOG(this->m_logger, "Setting parameter value: {} at index: {}", value, index);
      m_currentParameterValue[index] = value;
    }
    
    
    csm::param::Type UsgsAstroFrameSensorModel::getParameterType(int index) const {
      MESSAGE_LOG(this->m_logger, "Accessing parameter type: {} at index: {}", m_parameterType[index], index);
      return m_parameterType[index];
    }
    
    
    void UsgsAstroFrameSensorModel::setParameterType(int index, csm::param::Type pType) {
        MESSAGE_LOG(this->m_logger, "Setting parameter type: {} at index: {}", pType, index);
        m_parameterType[index] = pType;
    }
    
    
    double UsgsAstroFrameSensorModel::getParameterCovariance(int index1, int index2) const {
       int index = UsgsAstroFrameSensorModel::NUM_PARAMETERS * index1 + index2;
       MESSAGE_LOG(this->m_logger, "Accessing parameter covar: {} between index1: {} and index2: {}", m_currentParameterCovariance[index], index1, index2);
       return m_currentParameterCovariance[index];
    }
    
    
    void UsgsAstroFrameSensorModel::setParameterCovariance(int index1, int index2, double covariance) {
       MESSAGE_LOG(this->m_logger, "Setting parameter covar: {} between index1: {} and index2: {}",
                                    covariance, index1, index2);
       int index = UsgsAstroFrameSensorModel::NUM_PARAMETERS * index1 + index2;
       m_currentParameterCovariance[index] = covariance;
    }
    
    
    int UsgsAstroFrameSensorModel::getNumGeometricCorrectionSwitches() const {
      MESSAGE_LOG(this->m_logger, "Accessing num geom correction switches");
      return 0;
    }
    
    
    std::string UsgsAstroFrameSensorModel::getGeometricCorrectionName(int index) const {
       MESSAGE_LOG(this->m_logger, "Accessing name of geometric correction switch {}. "
                   "Geometric correction switches are not supported, throwing exception",
                   index);
       // Since there are no geometric corrections, all indices are out of range
       throw csm::Error(
          csm::Error::INDEX_OUT_OF_RANGE,
          "Index is out of range.",
          "UsgsAstroLsSensorModel::getGeometricCorrectionName");
    }
    
    
    void UsgsAstroFrameSensorModel::setGeometricCorrectionSwitch(int index,
                                                          bool value,
                                                          csm::param::Type pType) {
       MESSAGE_LOG(this->m_logger, "Setting geometric correction switch {} to {} "
                   "with parameter type {}. "
                   "Geometric correction switches are not supported, throwing exception",
                   index, value, pType);
       // Since there are no geometric corrections, all indices are out of range
       throw csm::Error(
          csm::Error::INDEX_OUT_OF_RANGE,
          "Index is out of range.",
          "UsgsAstroLsSensorModel::setGeometricCorrectionSwitch");
    }
    
    
    bool UsgsAstroFrameSensorModel::getGeometricCorrectionSwitch(int index) const {
       MESSAGE_LOG(this->m_logger, "Accessing value of geometric correction switch {}. "
                   "Geometric correction switches are not supported, throwing exception",
                   index);
       // Since there are no geometric corrections, all indices are out of range
       throw csm::Error(
          csm::Error::INDEX_OUT_OF_RANGE,
          "Index is out of range.",
          "UsgsAstroLsSensorModel::getGeometricCorrectionSwitch");
    }
    
    
    std::vector<double> UsgsAstroFrameSensorModel::getCrossCovarianceMatrix(
        const GeometricModel &comparisonModel,
        csm::param::Set pSet,
        const GeometricModelList &otherModels) const {
       MESSAGE_LOG(this->m_logger, "Accessing cross covariance matrix");
    
       // No correlation between models.
       const std::vector<int>& indices = getParameterSetIndices(pSet);
       size_t num_rows = indices.size();
       const std::vector<int>& indices2 = comparisonModel.getParameterSetIndices(pSet);
       size_t num_cols = indices.size();
    
       return std::vector<double>(num_rows * num_cols, 0.0);
    }
    
    
    csm::Ellipsoid UsgsAstroFrameSensorModel::getEllipsoid() const {
       MESSAGE_LOG(this->m_logger, "Accessing ellipsoid radii {} {}",
                   m_majorAxis, m_minorAxis);
       return csm::Ellipsoid(m_majorAxis, m_minorAxis);
    }
    
    
    void UsgsAstroFrameSensorModel::setEllipsoid(const csm::Ellipsoid &ellipsoid) {
       MESSAGE_LOG(this->m_logger, "Setting ellipsoid radii {} {}",
                   ellipsoid.getSemiMajorRadius(), ellipsoid.getSemiMinorRadius());
       m_majorAxis = ellipsoid.getSemiMajorRadius();
       m_minorAxis = ellipsoid.getSemiMinorRadius();
    }
    
    
    void UsgsAstroFrameSensorModel::calcRotationMatrix(
        double m[3][3]) const {
      MESSAGE_LOG(this->m_logger, "Calculating rotation matrix");
      // Trigonometric functions for rotation matrix
      double x = m_currentParameterValue[3];
      double y = m_currentParameterValue[4];
      double z = m_currentParameterValue[5];
      double w = m_currentParameterValue[6];
    
      double norm = sqrt(x * x + y * y + z * z + w * w);
      x /= norm;
      y /= norm;
      w /= norm;
      z /= norm;
    
      m[0][0] = w*w + x*x - y*y - z*z;
      m[0][1] = 2 * (x*y - w*z);
      m[0][2] = 2 * (w*y + x*z);
      m[1][0] = 2 * (x*y + w*z);
      m[1][1] = w*w - x*x + y*y - z*z;
      m[1][2] = 2 * (y*z - w*x);
      m[2][0] = 2 * (x*z - w*y);
      m[2][1] = 2 * (w*x + y*z);
      m[2][2] = w*w - x*x - y*y + z*z;
    }
    
    
    void UsgsAstroFrameSensorModel::calcRotationMatrix(
      double m[3][3], const std::vector<double> &adjustments) const {
      MESSAGE_LOG(this->m_logger, "Calculating rotation matrix with adjustments");
      // Trigonometric functions for rotation matrix
      double x = getValue(3, adjustments);
      double y = getValue(4, adjustments);
      double z = getValue(5, adjustments);
      double w = getValue(6, adjustments);
    
      m[0][0] = w*w + x*x - y*y - z*z;
      m[0][1] = 2 * (x*y - w*z);
      m[0][2] = 2 * (w*y + x*z);
      m[1][0] = 2 * (x*y + w*z);
      m[1][1] = w*w - x*x + y*y - z*z;
      m[1][2] = 2 * (y*z - w*x);
      m[2][0] = 2 * (x*z - w*y);
      m[2][1] = 2 * (w*x + y*z);
      m[2][2] = w*w - x*x - y*y + z*z;
    }
    
    
    void UsgsAstroFrameSensorModel::losEllipsoidIntersect(
          const double height,
          const double xc,
          const double yc,
          const double zc,
          const double xl,
          const double yl,
          const double zl,
          double&       x,
          double&       y,
          double&       z ) const
    {
       MESSAGE_LOG(this->m_logger, "Calculating losEllipsoidIntersect with height: {},\n\
                                    xc: {}, yc: {}, zc: {}\n\
                                    xl: {}, yl: {}, zl: {}", height,
                                    xc, yc, zc,
                                    xl, yl, zl);
       // Helper function which computes the intersection of the image ray
       // with an expanded ellipsoid.  All vectors are in earth-centered-fixed
       // coordinate system with origin at the center of the earth.
    
       double ap, bp, k;
       ap = m_majorAxis + height;
       bp = m_minorAxis + height;
       k = ap * ap / (bp * bp);
    
       // Solve quadratic equation for scale factor
       // applied to image ray to compute ground point
    
       double at, bt, ct, quadTerm;
       at = xl * xl + yl * yl + k * zl * zl;
       bt = 2.0 * (xl * xc + yl * yc + k * zl * zc);
       ct = xc * xc + yc * yc + k * zc * zc - ap * ap;
       quadTerm = bt * bt - 4.0 * at * ct;
    
       // If quadTerm is negative, the image ray does not
       // intersect the ellipsoid. Setting the quadTerm to
       // zero means solving for a point on the ray nearest
       // the surface of the ellisoid.
    
       if ( 0.0 > quadTerm )
       {
          quadTerm = 0.0;
       }
       double scale;
       scale = (-bt - sqrt (quadTerm)) / (2.0 * at);
       // Compute ground point vector
    
       x = xc + scale * xl;
       y = yc + scale * yl;
       z = zc + scale * zl;
    
       MESSAGE_LOG(this->m_logger, "Calculated losEllipsoidIntersect at: {}, {}, {}",
                                    x, y, z);
    }
    
    
    /***** Helper Functions *****/
    
    double UsgsAstroFrameSensorModel::getValue(
       int index,
       const std::vector<double> &adjustments) const
    {
       MESSAGE_LOG(this->m_logger, "Accessing value: {} at index: {}, with adjustments", m_currentParameterValue[index] + adjustments[index], index);
       return m_currentParameterValue[index] + adjustments[index];
    }
    
    std::shared_ptr<spdlog::logger> UsgsAstroFrameSensorModel::getLogger() {
      return m_logger;
    }
    
    void UsgsAstroFrameSensorModel::setLogger(std::shared_ptr<spdlog::logger> logger) {
      m_logger = logger;
    }