Skip to content
Snippets Groups Projects
Select Git revision
  • 880ca93bb2985ee10adc29bca2b09b9c01065058
  • master default protected
  • rocky_8
  • rocky_9
  • pasture
  • testing
  • query
  • v0.2.9
  • v0.2.8
  • v0.2.7
  • v0.2.6
  • v0.2.5
  • v0.2.4
  • v0.2.3
  • v0.2.2
  • v0.2.1
  • v0.2.0
  • v0.1.2
  • v0.1.1
  • v0.1
20 results

tape_client.py

Blame
  • Utilities.cpp 50.75 KiB
    #include "Utilities.h"
    
    
    #include <Error.h>
    #include <cmath>
    #include <stack>
    #include <stdexcept>
    #include <utility>
    #include <ctime>
    #include <iostream>
    #include <fstream>
    
    #include "ale/Distortion.h"
    
    using json = nlohmann::json;
    
    // Calculates a rotation matrix from Euler angles
    // in - euler[3]
    // out - rotationMatrix[9]
    void calculateRotationMatrixFromEuler(double euler[], double rotationMatrix[]) {
      double cos_a = cos(euler[0]);
      double sin_a = sin(euler[0]);
      double cos_b = cos(euler[1]);
      double sin_b = sin(euler[1]);
      double cos_c = cos(euler[2]);
      double sin_c = sin(euler[2]);
    
      rotationMatrix[0] = cos_b * cos_c;
      rotationMatrix[1] = -cos_a * sin_c + sin_a * sin_b * cos_c;
      rotationMatrix[2] = sin_a * sin_c + cos_a * sin_b * cos_c;
      rotationMatrix[3] = cos_b * sin_c;
      rotationMatrix[4] = cos_a * cos_c + sin_a * sin_b * sin_c;
      rotationMatrix[5] = -sin_a * cos_c + cos_a * sin_b * sin_c;
      rotationMatrix[6] = -sin_b;
      rotationMatrix[7] = sin_a * cos_b;
      rotationMatrix[8] = cos_a * cos_b;
    }
    
    // uses a quaternion to calculate a rotation matrix.
    // in - q[4]
    // out - rotationMatrix[9]
    void calculateRotationMatrixFromQuaternions(double q[4],
                                                double rotationMatrix[9]) {
      double norm = sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
      q[0] /= norm;
      q[1] /= norm;
      q[2] /= norm;
      q[3] /= norm;
    
      rotationMatrix[0] = q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3];
      rotationMatrix[1] = 2 * (q[0] * q[1] - q[2] * q[3]);
      rotationMatrix[2] = 2 * (q[0] * q[2] + q[1] * q[3]);
      rotationMatrix[3] = 2 * (q[0] * q[1] + q[2] * q[3]);
      rotationMatrix[4] = -q[0] * q[0] + q[1] * q[1] - q[2] * q[2] + q[3] * q[3];
      rotationMatrix[5] = 2 * (q[1] * q[2] - q[0] * q[3]);
      rotationMatrix[6] = 2 * (q[0] * q[2] - q[1] * q[3]);
      rotationMatrix[7] = 2 * (q[1] * q[2] + q[0] * q[3]);
      rotationMatrix[8] = -q[0] * q[0] - q[1] * q[1] + q[2] * q[2] + q[3] * q[3];
    }
    
    // Compute the distorted focal plane coordinate for a given image pixel
    // in - line
    // in - sample
    // in - sampleOrigin - the origin of the ccd coordinate system relative to the
    // top left of the ccd in - lineOrigin - the origin of the ccd coordinate system
    // relative to the top left of the ccd in - sampleSumming in - startingSample -
    // first ccd sample for the image in - iTransS[3] - the transformation from
    // focal plane to ccd samples in - iTransL[3] - the transformation from focal
    // plane to ccd lines out - distortedX out - distortedY
    void computeDistortedFocalPlaneCoordinates(
        const double &line, const double &sample, const double &sampleOrigin,
        const double &lineOrigin, const double &sampleSumming,
        const double &lineSumming, const double &startingSample,
        const double &startingLine, const double iTransS[], const double iTransL[],
        double &distortedX, double &distortedY) {
      double detSample = sample * sampleSumming + startingSample;
      double detLine = line * lineSumming + startingLine;
      double m11 = iTransL[1];
      double m12 = iTransL[2];
      double m21 = iTransS[1];
      double m22 = iTransS[2];
      double t1 = detLine - lineOrigin - iTransL[0];
      double t2 = detSample - sampleOrigin - iTransS[0];
      double determinant = m11 * m22 - m12 * m21;
      double p11 = m22 / determinant;
      double p12 = -m12 / determinant;
      double p21 = -m21 / determinant;
      double p22 = m11 / determinant;
    
      distortedX = p11 * t1 + p12 * t2;
      distortedY = p21 * t1 + p22 * t2;
    }
    
    
    // Compute the de-jittered pixel coordinate given a jittered image coordinate
    // a set of jitter coefficients and line exposure times for rolling shutter.
    // Jitter coefficients are in largest power first order. There is no constant
    // coefficient. For example {1, 2, 3} would correspond to 1*t^3 + 2*t&2 + 3*t.
    void removeJitter(
        const double &line, const double &sample,
        const std::vector<double> lineJitterCoeffs, const std::vector<double> sampleJitterCoeffs,
        const std::vector<double> lineTimes,
        double &dejitteredLine, double &dejitteredSample) {
      // Check input
      if (lineJitterCoeffs.size() != sampleJitterCoeffs.size() ||
          lineTimes.size() == 0) {
        throw csm::Error(
            csm::Error::INDEX_OUT_OF_RANGE,
            "Jitter coefficient vectors must be the same size.",
            "removeJitter");
      }
      if (lineTimes.size() == 0) {
        throw csm::Error(
            csm::Error::INDEX_OUT_OF_RANGE,
            "Line exposure times must be non-empty.",
            "removeJitter");
      }
      double lineJitter = 0;
      double sampleJitter = 0;
      // Bound line index to the vector of line exposure times;
      double time = lineTimes[std::max(std::min((int)std::round(line), (int)lineTimes.size()), 1) - 1];
      for (unsigned int n = 0; n < lineJitterCoeffs.size(); n++) {
        double timeTerm = pow(time, lineJitterCoeffs.size() - n);
        lineJitter += lineJitterCoeffs[n] * timeTerm;
        sampleJitter += sampleJitterCoeffs[n] * timeTerm;
      }
      dejitteredLine = line - lineJitter;
      dejitteredSample = sample - sampleJitter;
    
      return;
    }
    
    
    // Compute the jittered pixel coordinate given a de-jittered image coordinate
    // a set of jitter coefficients and line exposure times for rolling shutter.
    // Jitter coefficients are in largest power first order. There is no constant
    // coefficient. For example {1, 2, 3} would correspond to 1*t^3 + 2*t&2 + 3*t.
    // This uses an iterative method so a tolerance and maximum number of iteration
    // are required to determine when to stop iterating.
    void addJitter(
        const double &line, const double &sample,
        const double &tolerance, const int &maxIts,
        const std::vector<double> lineJitterCoeffs, const std::vector<double> sampleJitterCoeffs,
        const std::vector<double> lineTimes,
        double &jitteredLine, double &jitteredSample) {
      int iteration = 0;
      double dejitteredLine = line - 1;
      double dejitteredSample = sample - 1;
      double currentLine = line;
      double currentSample =  sample;
    
      while (iteration < maxIts) {
        removeJitter(
            currentLine, currentSample,
            lineJitterCoeffs, sampleJitterCoeffs, lineTimes,
            dejitteredLine, dejitteredSample);
    
        if (fabs(dejitteredLine - line) < tolerance &&
            fabs(dejitteredSample - sample) < tolerance) {
          break;
        }
    
        currentLine = line + currentLine - dejitteredLine;
        currentSample = sample + currentSample - dejitteredSample;
        iteration++;
      }
    
      jitteredLine = currentLine;
      jitteredSample = currentSample;
    
      return;
    }
    
    
    // Compute the image pixel for a distorted focal plane coordinate
    // in - line
    // in - sample
    // in - sampleOrigin - the origin of the ccd coordinate system relative to the
    // top left of the ccd in - lineOrigin - the origin of the ccd coordinate system
    // relative to the top left of the ccd in - sampleSumming in - startingSample -
    // first ccd sample for the image in - iTransS[3] - the transformation from
    // focal plane to ccd samples in - iTransL[3] - the transformation from focal
    // plane to ccd lines out - natFocalPlane
    void computePixel(const double &distortedX, const double &distortedY,
                      const double &sampleOrigin, const double &lineOrigin,
                      const double &sampleSumming, const double &lineSumming,
                      const double &startingSample, const double &startingLine,
                      const double iTransS[], const double iTransL[], double &line,
                      double &sample) {
      double centeredSample =
          iTransS[0] + iTransS[1] * distortedX + iTransS[2] * distortedY;
      double centeredLine =
          iTransL[0] + iTransL[1] * distortedX + iTransL[2] * distortedY;
      double detSample = centeredSample + sampleOrigin;
      double detLine = centeredLine + lineOrigin;
      sample = (detSample - startingSample) / sampleSumming;
      line = (detLine - startingLine) / lineSumming;
    }
    
    // Define imaging ray in image space (In other words, create a look vector in
    // camera space) in - undistortedFocalPlaneX in - undistortedFocalPlaneY in -
    // zDirection - Either 1 or -1. The direction of the boresight in - focalLength
    // out - cameraLook[3]
    void createCameraLookVector(const double &undistortedFocalPlaneX,
                                const double &undistortedFocalPlaneY,
                                const double &zDirection, const double &focalLength,
                                double cameraLook[]) {
      cameraLook[0] = -undistortedFocalPlaneX * zDirection;
      cameraLook[1] = -undistortedFocalPlaneY * zDirection;
      cameraLook[2] = -focalLength;
      double magnitude =
          sqrt(cameraLook[0] * cameraLook[0] + cameraLook[1] * cameraLook[1] +
               cameraLook[2] * cameraLook[2]);
      cameraLook[0] /= magnitude;
      cameraLook[1] /= magnitude;
      cameraLook[2] /= magnitude;
    }
    
    // Lagrange Interpolation for equally spaced data
    void lagrangeInterp(const int &numTime, const double *valueArray,
                        const double &startTime, const double &delTime,
                        const double &time, const int &vectorLength,
                        const int &i_order, double *valueVector) {
      // Lagrange interpolation for uniform post interval.
      // Largest order possible is 8th. Points far away from
      // data center are handled gracefully to avoid failure.
    
      if (numTime < 2) {
        throw csm::Error(
            csm::Error::INDEX_OUT_OF_RANGE,
            "At least 2 points are required to perform Lagrange interpolation.",
            "lagrangeInterp");
      }
    
      // Compute index
    
      double fndex = (time - startTime) / delTime;
      int index = static_cast<int>(fndex);
    
      if (index < 0) {
        index = 0;
      }
      if (index > numTime - 2) {
        index = numTime - 2;
      }
    
      // Define order, max is 8
    
      int order;
      if (index >= 3 && index < numTime - 4) {
        order = 8;
      } else if (index >= 2 && index < numTime - 3) {
        order = 6;
      } else if (index >= 1 && index < numTime - 2) {
        order = 4;
      } else {
        order = 2;
      }
      if (order > i_order) {
        order = i_order;
      }
    
      // Compute interpolation coefficients
      double tp3, tp2, tp1, tm1, tm2, tm3, tm4, d[8];
      double t0 = fndex - index;
      if (order == 2) {
        tm1 = t0 - 1;
        d[0] = -tm1;
        d[1] = t0;
      } else if (order == 4) {
        tp1 = t0 + 1;
        tm1 = t0 - 1;
        tm2 = t0 - 2;
        d[0] = -t0 * tm1 * tm2 / 6.0;
        d[1] = tp1 * tm1 * tm2 / 2.0;
        d[2] = -tp1 * t0 * tm2 / 2.0;
        d[3] = tp1 * t0 * tm1 / 6.0;
      } else if (order == 6) {
        tp2 = t0 + 2;
        tp1 = t0 + 1;
        tm1 = t0 - 1;
        tm2 = t0 - 2;
        tm3 = t0 - 3;
        d[0] = -tp1 * t0 * tm1 * tm2 * tm3 / 120.0;
        d[1] = tp2 * t0 * tm1 * tm2 * tm3 / 24.0;
        d[2] = -tp2 * tp1 * tm1 * tm2 * tm3 / 12.0;
        d[3] = tp2 * tp1 * t0 * tm2 * tm3 / 12.0;
        d[4] = -tp2 * tp1 * t0 * tm1 * tm3 / 24.0;
        d[5] = tp2 * tp1 * t0 * tm1 * tm2 / 120.0;
      } else if (order == 8) {
        tp3 = t0 + 3;
        tp2 = t0 + 2;
        tp1 = t0 + 1;
        tm1 = t0 - 1;
        tm2 = t0 - 2;
        tm3 = t0 - 3;
        tm4 = t0 - 4;
        // The denominators are hard-coded because the sampling is uniform
        // hence they can be computed explicitly.
        // Note: Trying pre-compute the many repeated multiplications
        // below does not speed things up.
        d[0] = -tp2 * tp1 * t0 * tm1 * tm2 * tm3 * tm4 / 5040.0;
        d[1] = tp3 * tp1 * t0 * tm1 * tm2 * tm3 * tm4  / 720.0;
        d[2] = -tp3 * tp2 * t0 * tm1 * tm2 * tm3 * tm4 / 240.0;
        d[3] = tp3 * tp2 * tp1 * tm1 * tm2 * tm3 * tm4 / 144.0;
        d[4] = -tp3 * tp2 * tp1 * t0 * tm2 * tm3 * tm4 / 144.0;
        d[5] = tp3 * tp2 * tp1 * t0 * tm1 * tm3 * tm4  / 240.0;
        d[6] = -tp3 * tp2 * tp1 * t0 * tm1 * tm2 * tm4 / 720.0;
        d[7] = tp3 * tp2 * tp1 * t0 * tm1 * tm2 * tm3  / 5040.0;
      }
    
      // Compute interpolated point
      int indx0 = index - order / 2 + 1;
      for (int i = 0; i < vectorLength; i++) {
        valueVector[i] = 0.0;
      }
    
      for (int i = 0; i < order; i++) {
        int jndex = vectorLength * (indx0 + i);
        for (int j = 0; j < vectorLength; j++) {
          valueVector[j] += d[i] * valueArray[jndex + j];
        }
      }
    }
    
    double brentRoot(double lowerBound, double upperBound,
                     std::function<double(double)> func, double epsilon) {
      double counterPoint = lowerBound;
      double currentPoint = upperBound;
      double counterFunc = func(counterPoint);
      double currentFunc = func(currentPoint);
      if (counterFunc * currentFunc > 0.0) {
        throw std::invalid_argument(
            "Function values at the boundaries have the same sign [brentRoot].");
      }
      if (fabs(counterFunc) < fabs(currentFunc)) {
        std::swap(counterPoint, currentPoint);
        std::swap(counterFunc, currentFunc);
      }
    
      double previousPoint = counterPoint;
      double previousFunc = counterFunc;
      double evenOlderPoint = previousPoint;
      double nextPoint;
      double nextFunc;
      int iteration = 0;
      bool bisected = true;
    
      do {
        // Inverse quadratic interpolation
        if (counterFunc != previousFunc && counterFunc != currentFunc &&
            currentFunc != previousFunc) {
          nextPoint = (counterPoint * currentFunc * previousFunc) /
                      ((counterFunc - currentFunc) * (counterFunc - previousFunc));
          nextPoint += (currentPoint * counterFunc * previousFunc) /
                       ((currentFunc - counterFunc) * (currentFunc - previousFunc));
          nextPoint +=
              (previousPoint * currentFunc * counterFunc) /
              ((previousFunc - counterFunc) * (previousFunc - currentFunc));
        } else {
          // Secant method
          nextPoint = currentPoint - currentFunc * (currentPoint - counterPoint) /
                                         (currentFunc - counterFunc);
        }
    
        // Bisection method
        if (((currentPoint - nextPoint) *
                 (nextPoint - (3 * counterPoint + currentPoint) / 4) <
             0) ||
            (bisected && fabs(nextPoint - currentPoint) >=
                             fabs(currentPoint - previousPoint) / 2) ||
            (!bisected && fabs(nextPoint - currentPoint) >=
                              fabs(previousPoint - evenOlderPoint) / 2) ||
            (bisected && fabs(currentPoint - previousPoint) < epsilon) ||
            (!bisected && fabs(previousPoint - evenOlderPoint) < epsilon)) {
          nextPoint = (currentPoint + counterPoint) / 2;
          bisected = true;
        } else {
          bisected = false;
        }
    
        // Setup for next iteration
        evenOlderPoint = previousPoint;
        previousPoint = currentPoint;
        previousFunc = currentFunc;
        nextFunc = func(nextPoint);
    
        // This is a bugfix. Without it, the code gets lost and can't find the solution.
        // See also the implementation at https://en.wikipedia.org/wiki/Brent%27s_method
        if (nextFunc == 0)
          return nextPoint;
    
        if (counterFunc * nextFunc < 0) {
          currentPoint = nextPoint;
          currentFunc = nextFunc;
        } else {
          counterPoint = nextPoint;
          counterFunc = nextFunc;
        }
      } while (++iteration < 30 && fabs(counterPoint - currentPoint) > epsilon);
    
      return nextPoint;
    }
    
    double evaluatePolynomial(const std::vector<double> &coeffs, double x) {
      if (coeffs.empty()) {
        throw std::invalid_argument("Polynomial coeffs must be non-empty.");
      }
      auto revIt = coeffs.crbegin();
      double value = *revIt;
      ++revIt;
      for (; revIt != coeffs.crend(); ++revIt) {
        value *= x;
        value += *revIt;
      }
      return value;
    }
    
    double evaluatePolynomialDerivative(const std::vector<double> &coeffs,
                                        double x) {
      if (coeffs.empty()) {
        throw std::invalid_argument("Polynomial coeffs must be non-empty.");
      }
      int i = coeffs.size() - 1;
      double value = i * coeffs[i];
      --i;
      for (; i > 0; --i) {
        value *= x;
        value += i * coeffs[i];
      }
      return value;
    }
    
    double polynomialRoot(const std::vector<double> &coeffs, double guess,
                          double threshold, int maxIterations) {
      double root = guess;
      double polyValue = evaluatePolynomial(coeffs, root);
      double polyDeriv = 0.0;
      for (int iteration = 0; iteration < maxIterations + 1; iteration++) {
        if (fabs(polyValue) < threshold) {
          return root;
        }
        polyDeriv = evaluatePolynomialDerivative(coeffs, root);
        if (fabs(polyDeriv) < 1e-15) {
          throw std::invalid_argument("Derivative at guess (" +
                                      std::to_string(guess) +
                                      ") is too close to 0.");
        }
        root -= polyValue / polyDeriv;
        polyValue = evaluatePolynomial(coeffs, root);
      }
      throw std::invalid_argument("Root finder did not converge after " +
                                  std::to_string(maxIterations) + " iterations");
    }
    
    double computeEllipsoidElevation(double x, double y, double z, double semiMajor,
                                     double semiMinor, double desired_precision,
                                     double *achieved_precision) {
      // Compute elevation given xyz
      // Requires semi-major-axis and eccentricity-square
      const int MKTR = 10;
      double ecc_sqr = 1.0 - semiMinor * semiMinor / semiMajor / semiMajor;
      double ep2 = 1.0 - ecc_sqr;
      double d2 = x * x + y * y;
      double d = sqrt(d2);
      double h = 0.0;
      int ktr = 0;
      double hPrev, r;
    
      // Suited for points near equator
      if (d >= z) {
        double tt, zz, n;
        double tanPhi = z / d;
        do {
          hPrev = h;
          tt = tanPhi * tanPhi;
          r = semiMajor / sqrt(1.0 + ep2 * tt);
          zz = z + r * ecc_sqr * tanPhi;
          n = r * sqrt(1.0 + tt);
          h = sqrt(d2 + zz * zz) - n;
          tanPhi = zz / d;
          ktr++;
        } while (MKTR > ktr && fabs(h - hPrev) > desired_precision);
      } else {
        // Suited for points near the poles
        double cc, dd, nn;
        double cotPhi = d / z;
        do {
          hPrev = h;
          cc = cotPhi * cotPhi;
          r = semiMajor / sqrt(ep2 + cc);
          dd = d - r * ecc_sqr * cotPhi;
          nn = r * sqrt(1.0 + cc) * ep2;
          h = sqrt(dd * dd + z * z) - nn;
          cotPhi = dd / z;
          ktr++;
        } while (MKTR > ktr && fabs(h - hPrev) > desired_precision);
      }
    
      if (achieved_precision) {
        *achieved_precision = fabs(h - hPrev);
      }
    
      return h;
    }
    
    csm::EcefVector operator*(double scalar, const csm::EcefVector &vec) {
      return csm::EcefVector(scalar * vec.x, scalar * vec.y, scalar * vec.z);
    }
    
    csm::EcefVector operator*(const csm::EcefVector &vec, double scalar) {
      return scalar * vec;
    }
    
    csm::EcefVector operator/(const csm::EcefVector &vec, double scalar) {
      return 1.0 / scalar * vec;
    }
    
    csm::EcefVector operator+(const csm::EcefVector &vec1,
                              const csm::EcefVector &vec2) {
      return csm::EcefVector(vec1.x + vec2.x, vec1.y + vec2.y, vec1.z + vec2.z);
    }
    
    csm::EcefVector operator-(const csm::EcefVector &vec1,
                              const csm::EcefVector &vec2) {
      return csm::EcefVector(vec1.x - vec2.x, vec1.y - vec2.y, vec1.z - vec2.z);
    }
    
    double dot(const csm::EcefVector &vec1, const csm::EcefVector &vec2) {
      return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
    }
    
    csm::EcefVector cross(const csm::EcefVector &vec1,
                          const csm::EcefVector &vec2) {
      return csm::EcefVector(vec1.y * vec2.z - vec1.z * vec2.y,
                             vec1.z * vec2.x - vec1.x * vec2.z,
                             vec1.x * vec2.y - vec1.y * vec2.x);
    }
    
    double norm(const csm::EcefVector &vec) {
      return sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);
    }
    
    csm::EcefVector normalized(const csm::EcefVector &vec) {
      return vec / norm(vec);
    }
    
    csm::EcefVector projection(const csm::EcefVector &vec1,
                               const csm::EcefVector &vec2) {
      return dot(vec1, vec2) / dot(vec2, vec2) * vec2;
    }
    
    csm::EcefVector rejection(const csm::EcefVector &vec1,
                              const csm::EcefVector &vec2) {
      return vec1 - projection(vec1, vec2);
    }
    
    // convert a measurement
    double metric_conversion(double val, std::string from, std::string to) {
      json typemap = {{"m", 0}, {"km", 3}};
    
      // everything to lowercase
      std::transform(from.begin(), from.end(), from.begin(), ::tolower);
      std::transform(to.begin(), to.end(), to.begin(), ::tolower);
      return val * pow(10, typemap[from].get<int>() - typemap[to].get<int>());
    }
    
    std::string getSensorModelName(json isd, csm::WarningList *list) {
      std::string name = "";
      try {
        name = isd.at("name_model");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the sensor model name.",
                                       "Utilities::getSensorModelName()"));
        }
      }
      return name;
    }
    
    std::string getImageId(json isd, csm::WarningList *list) {
      std::string id = "";
      try {
        id = isd.at("image_identifier");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the image identifier.",
                                       "Utilities::getImageId()"));
        }
      }
      return id;
    }
    
    std::string getSensorName(json isd, csm::WarningList *list) {
      std::string name = "";
      try {
        name = isd.at("name_sensor");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the sensor name.",
                                       "Utilities::getSensorName()"));
        }
      }
      return name;
    }
    
    std::string getPlatformName(json isd, csm::WarningList *list) {
      std::string name = "";
      try {
        name = isd.at("name_platform");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the platform name.",
                                       "Utilities::getPlatformName()"));
        }
      }
      return name;
    }
    
    std::string getLogFile(nlohmann::json isd, csm::WarningList *list) {
      std::string file = "";
      try {
        file = isd.at("log_file");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the log filename.",
                                       "Utilities::getLogFile()"));
        }
      }
      return file;
    }
    
    int getTotalLines(json isd, csm::WarningList *list) {
      int lines = 0;
      try {
        lines = isd.at("image_lines");
      } catch (...) {
        if (list) {
          list->push_back(
              csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                           "Could not parse the number of lines in the image.",
                           "Utilities::getTotalLines()"));
        }
      }
      return lines;
    }
    
    int getTotalSamples(json isd, csm::WarningList *list) {
      int samples = 0;
      try {
        samples = isd.at("image_samples");
      } catch (...) {
        if (list) {
          list->push_back(
              csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                           "Could not parse the number of samples in the image.",
                           "Utilities::getTotalSamples()"));
        }
      }
      return samples;
    }
    
    double getStartingTime(json isd, csm::WarningList *list) {
      double time = 0.0;
      try {
        time = isd.at("starting_ephemeris_time");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the image start time.",
                                       "Utilities::getStartingTime()"));
        }
      }
      return time;
    }
    
    double getCenterTime(json isd, csm::WarningList *list) {
      double time = 0.0;
      try {
        time = isd.at("center_ephemeris_time");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the center image time.",
                                       "Utilities::getCenterTime()"));
        }
      }
      return time;
    }
    
    double getEndingTime(json isd, csm::WarningList *list) {
      double time = 0.0;
      try {
        time = isd.at("ending_ephemeris_time");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the ending image time.",
                                       "Utilities::getEndingTime()"));
        }
      }
      return time;
    }
    
    std::vector<double> getIntegrationStartLines(
        std::vector<std::vector<double>> lineScanRate, csm::WarningList *list) {
      std::vector<double> lines;
      try {
        for (auto &scanRate : lineScanRate) {
          if (scanRate.size() != 3) {
            throw csm::Error(
                csm::Error::SENSOR_MODEL_NOT_CONSTRUCTIBLE,
                "Unable to parse integration start lines from line "
                "scan rate due to malformed vector. Expected vector size of 3.",
                "Utilities::getIntegrationStartLines()");
          }
          lines.push_back(scanRate[0]);
        }
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the integration start "
                                       "lines in the integration time table.",
                                       "Utilities::getIntegrationStartLines()"));
        }
      }
      return lines;
    }
    
    std::vector<double> getIntegrationStartTimes(
        std::vector<std::vector<double>> lineScanRate, csm::WarningList *list) {
      std::vector<double> times;
      try {
        for (auto &scanRate : lineScanRate) {
          if (scanRate.size() != 3) {
            throw csm::Error(
                csm::Error::SENSOR_MODEL_NOT_CONSTRUCTIBLE,
                "Unable to parse integration start times from line "
                "scan rate due to malformed vector. Expected vector size of 3.",
                "Utilities::getIntegrationStartTimes()");
          }
          times.push_back(scanRate[1]);
        }
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the integration start "
                                       "times in the integration time table.",
                                       "Utilities::getIntegrationStartTimes()"));
        }
      }
      return times;
    }
    
    std::vector<double> getIntegrationTimes(
        std::vector<std::vector<double>> lineScanRate, csm::WarningList *list) {
      std::vector<double> times;
      try {
        for (auto &scanRate : lineScanRate) {
          if (scanRate.size() != 3) {
            throw csm::Error(
                csm::Error::SENSOR_MODEL_NOT_CONSTRUCTIBLE,
                "Unable to parse integration times from line "
                "scan rate due to malformed vector. Expected vector size of 3.",
                "Utilities::getIntegrationTimes()");
          }
          times.push_back(scanRate[2]);
        }
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the integration times in "
                                       "the integration time table.",
                                       "Utilities::getIntegrationTimes()"));
        }
      }
      return times;
    }
    
    int getSampleSumming(json isd, csm::WarningList *list) {
      int summing = 0;
      try {
        summing = isd.at("detector_sample_summing");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(
              csm::Warning::DATA_NOT_AVAILABLE,
              "Could not parse the sample direction detector pixel summing.",
              "Utilities::getSampleSumming()"));
        }
      }
      return summing;
    }
    
    int getLineSumming(json isd, csm::WarningList *list) {
      int summing = 0;
      try {
        summing = isd.at("detector_line_summing");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(
              csm::Warning::DATA_NOT_AVAILABLE,
              "Could not parse the line direction detector pixel summing.",
              "Utilities::getLineSumming()"));
        }
      }
      return summing;
    }
    
    double getFocalLength(json isd, csm::WarningList *list) {
      double length = 0.0;
      try {
        length = isd.at("focal_length_model").at("focal_length");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the focal length.",
                                       "Utilities::getFocalLength()"));
        }
      }
      return length;
    }
    
    double getFocalLengthEpsilon(json isd, csm::WarningList *list) {
      double epsilon = 0.0;
      try {
        epsilon = isd.at("focal_length_model").at("focal_epsilon");
      } catch (...) {
        if (list) {
          list->push_back(
              csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                           "Could not parse the focal length uncertainty.",
                           "Utilities::getFocalLengthEpsilon()"));
        }
      }
      return epsilon;
    }
    
    std::vector<double> getFocal2PixelLines(json isd, csm::WarningList *list) {
      std::vector<double> transformation;
      try {
        transformation = isd.at("focal2pixel_lines").get<std::vector<double>>();
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the focal plane coordinate "
                                       "to detector lines transformation.",
                                       "Utilities::getFocal2PixelLines()"));
        }
      }
      return transformation;
    }
    
    std::vector<double> getFocal2PixelSamples(json isd, csm::WarningList *list) {
      std::vector<double> transformation;
      try {
        transformation = isd.at("focal2pixel_samples").get<std::vector<double>>();
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the focal plane coordinate "
                                       "to detector samples transformation.",
                                       "Utilities::getFocal2PixelSamples()"));
        }
      }
      return transformation;
    }
    
    double getDetectorCenterLine(json isd, csm::WarningList *list) {
      double line;
      try {
        line = isd.at("detector_center").at("line");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the detector center line.",
                                       "Utilities::getDetectorCenterLine()"));
        }
      }
      return line;
    }
    
    double getDetectorCenterSample(json isd, csm::WarningList *list) {
      double sample;
      try {
        sample = isd.at("detector_center").at("sample");
      } catch (...) {
        if (list) {
          list->push_back(
              csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                           "Could not parse the detector center sample.",
                           "Utilities::getDetectorCenterSample()"));
        }
      }
      return sample;
    }
    
    double getDetectorStartingLine(json isd, csm::WarningList *list) {
      double line;
      try {
        line = isd.at("starting_detector_line");
      } catch (...) {
        if (list) {
          list->push_back(
              csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                           "Could not parse the detector starting line.",
                           "Utilities::getDetectorStartingLine()"));
        }
      }
      return line;
    }
    
    double getDetectorStartingSample(json isd, csm::WarningList *list) {
      double sample;
      try {
        sample = isd.at("starting_detector_sample");
      } catch (...) {
        if (list) {
          list->push_back(
              csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                           "Could not parse the detector starting sample.",
                           "Utilities::getDetectorStartingSample()"));
        }
      }
      return sample;
    }
    
    double getMinHeight(json isd, csm::WarningList *list) {
      double height = 0.0;
      try {
        json referenceHeight = isd.at("reference_height");
        json minHeight = referenceHeight.at("minheight");
        json unit = referenceHeight.at("unit");
        height =
            metric_conversion(minHeight.get<double>(), unit.get<std::string>());
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(
              csm::Warning::DATA_NOT_AVAILABLE,
              "Could not parse the minimum height above the reference ellipsoid.",
              "Utilities::getMinHeight()"));
        }
      }
      return height;
    }
    
    double getMaxHeight(json isd, csm::WarningList *list) {
      double height = 0.0;
      try {
        json referenceHeight = isd.at("reference_height");
        json maxHeight = referenceHeight.at("maxheight");
        json unit = referenceHeight.at("unit");
        height =
            metric_conversion(maxHeight.get<double>(), unit.get<std::string>());
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(
              csm::Warning::DATA_NOT_AVAILABLE,
              "Could not parse the maximum height above the reference ellipsoid.",
              "Utilities::getMaxHeight()"));
        }
      }
      return height;
    }
    
    double getSemiMajorRadius(json isd, csm::WarningList *list) {
      double radius = 0.0;
      try {
        json radii = isd.at("radii");
        json semiMajor = radii.at("semimajor");
        json unit = radii.at("unit");
        radius =
            metric_conversion(semiMajor.get<double>(), unit.get<std::string>());
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(
              csm::Warning::DATA_NOT_AVAILABLE,
              "Could not parse the reference ellipsoid semimajor radius.",
              "Utilities::getSemiMajorRadius()"));
        }
      }
      return radius;
    }
    
    double getSemiMinorRadius(json isd, csm::WarningList *list) {
      double radius = 0.0;
      try {
        json radii = isd.at("radii");
        json semiMinor = radii.at("semiminor");
        json unit = radii.at("unit");
        radius =
            metric_conversion(semiMinor.get<double>(), unit.get<std::string>());
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(
              csm::Warning::DATA_NOT_AVAILABLE,
              "Could not parse the reference ellipsoid semiminor radius.",
              "Utilities::getSemiMinorRadius()"));
        }
      }
      return radius;
    }
    
    // Converts the distortion model name from the ISD (string) to the enumeration
    // type. Defaults to transverse
    DistortionType getDistortionModel(json isd, csm::WarningList *list) {
      try {
        json distoriton_subset = isd.at("optical_distortion");
    
        json::iterator it = distoriton_subset.begin();
    
        std::string distortion = (std::string)it.key();
    
        if (distortion.compare("transverse") == 0) {
          return DistortionType::TRANSVERSE;
        } else if (distortion.compare("radial") == 0) {
          return DistortionType::RADIAL;
        } else if (distortion.compare("kaguyalism") == 0) {
          return DistortionType::KAGUYALISM;
        } else if (distortion.compare("dawnfc") == 0) {
          return DistortionType::DAWNFC;
        } else if (distortion.compare("lrolrocnac") == 0) {
          return DistortionType::LROLROCNAC;
        }
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the distortion model.",
                                       "Utilities::getDistortionModel()"));
        }
      }
      return DistortionType::TRANSVERSE;
    }
    
    DistortionType getDistortionModel(int aleDistortionModel,
                                      csm::WarningList *list) {
      try {
        ale::DistortionType aleDistortionType;
        aleDistortionType = (ale::DistortionType)aleDistortionModel;
    
        if (aleDistortionType == ale::DistortionType::TRANSVERSE) {
          return DistortionType::TRANSVERSE;
        } else if (aleDistortionType == ale::DistortionType::RADIAL) {
          return DistortionType::RADIAL;
        } else if (aleDistortionType == ale::DistortionType::KAGUYALISM) {
          return DistortionType::KAGUYALISM;
        } else if (aleDistortionType == ale::DistortionType::DAWNFC) {
          return DistortionType::DAWNFC;
        } else if (aleDistortionType == ale::DistortionType::LROLROCNAC) {
          return DistortionType::LROLROCNAC;
        }else if (aleDistortionType == ale::DistortionType::CAHVOR) {
          return DistortionType::CAHVOR;
        }
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the distortion model.",
                                       "Utilities::getDistortionModel()"));
        }
      }
      return DistortionType::TRANSVERSE;
    }
    
    std::vector<double> getDistortionCoeffs(json isd, csm::WarningList *list) {
      std::vector<double> coefficients;
    
      DistortionType distortion = getDistortionModel(isd);
    
      switch (distortion) {
        case DistortionType::TRANSVERSE: {
          try {
            std::vector<double> coefficientsX, coefficientsY;
    
            coefficientsX = isd.at("optical_distortion")
                                .at("transverse")
                                .at("x")
                                .get<std::vector<double>>();
            coefficientsX.resize(10, 0.0);
    
            coefficientsY = isd.at("optical_distortion")
                                .at("transverse")
                                .at("y")
                                .get<std::vector<double>>();
            coefficientsY.resize(10, 0.0);
    
            coefficients = coefficientsX;
    
            coefficients.insert(coefficients.end(), coefficientsY.begin(),
                                coefficientsY.end());
            return coefficients;
          } catch (...) {
            if (list) {
              list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                           "Could not parse a set of transverse "
                                           "distortion model coefficients.",
                                           "Utilities::getDistortion()"));
            }
            coefficients = std::vector<double>(20, 0.0);
            coefficients[1] = 1.0;
            coefficients[12] = 1.0;
          }
        } break;
        case DistortionType::RADIAL: {
          try {
            coefficients = isd.at("optical_distortion")
                               .at("radial")
                               .at("coefficients")
                               .get<std::vector<double>>();
    
            return coefficients;
          } catch (...) {
            if (list) {
              list->push_back(csm::Warning(
                  csm::Warning::DATA_NOT_AVAILABLE,
                  "Could not parse the radial distortion model coefficients.",
                  "Utilities::getDistortion()"));
            }
            coefficients = std::vector<double>(3, 0.0);
          }
        } break;
        case DistortionType::KAGUYALISM: {
          try {
            std::vector<double> coefficientsX = isd.at("optical_distortion")
                                                    .at("kaguyalism")
                                                    .at("x")
                                                    .get<std::vector<double>>();
            std::vector<double> coefficientsY = isd.at("optical_distortion")
                                                    .at("kaguyalism")
                                                    .at("y")
                                                    .get<std::vector<double>>();
            double boresightX = isd.at("optical_distortion")
                                    .at("kaguyalism")
                                    .at("boresight_x")
                                    .get<double>();
            double boresightY = isd.at("optical_distortion")
                                    .at("kaguyalism")
                                    .at("boresight_y")
                                    .get<double>();
    
            coefficientsX.resize(4, 0.0);
            coefficientsY.resize(4, 0.0);
            coefficientsX.insert(coefficientsX.begin(), boresightX);
            coefficientsY.insert(coefficientsY.begin(), boresightY);
            coefficientsX.insert(coefficientsX.end(), coefficientsY.begin(),
                                 coefficientsY.end());
    
            return coefficientsX;
          } catch (...) {
            if (list) {
              list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                           "Could not parse a set of Kaguya LISM "
                                           "distortion model coefficients.",
                                           "Utilities::getDistortion()"));
            }
            coefficients = std::vector<double>(8, 0.0);
          }
        } break;
        case DistortionType::DAWNFC: {
          try {
            coefficients = isd.at("optical_distortion")
                               .at("dawnfc")
                               .at("coefficients")
                               .get<std::vector<double>>();
    
            return coefficients;
          } catch (...) {
            if (list) {
              list->push_back(csm::Warning(
                  csm::Warning::DATA_NOT_AVAILABLE,
                  "Could not parse the dawn radial distortion model coefficients.",
                  "Utilities::getDistortion()"));
            }
            coefficients = std::vector<double>(1, 0.0);
          }
        } break;
        case DistortionType::LROLROCNAC: {
          try {
            coefficients = isd.at("optical_distortion")
                               .at("lrolrocnac")
                               .at("coefficients")
                               .get<std::vector<double>>();
            return coefficients;
          } catch (...) {
            if (list) {
              list->push_back(csm::Warning(
                  csm::Warning::DATA_NOT_AVAILABLE,
                  "Could not parse the lrolrocnac distortion model coefficients.",
                  "Utilities::getDistortion()"));
            }
            coefficients = std::vector<double>(1, 0.0);
          }
        } break;
        case DistortionType::CAHVOR: {
          try {
            coefficients = isd.at("optical_distortion")
                               .at("cahvor")
                               .at("coefficients")
                               .get<std::vector<double>>();
    
            return coefficients;
          } catch (...) {
            if (list) {
              list->push_back(csm::Warning(
                  csm::Warning::DATA_NOT_AVAILABLE,
                  "Could not parse the radial distortion model coefficients.",
                  "Utilities::getDistortion()"));
            }
            coefficients = std::vector<double>(6, 0.0);
          }
        } break;
      }
      if (list) {
        list->push_back(
            csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                         "Could not parse the distortion model coefficients.",
                         "Utilities::getDistortion()"));
      }
    
      return coefficients;
    }
    
    std::vector<double> getSunPositions(json isd, csm::WarningList *list) {
      std::vector<double> positions;
      try {
        json jayson = isd.at("sun_position");
        json unit = jayson.at("unit");
        for (auto &location : jayson.at("positions")) {
          positions.push_back(metric_conversion(location[0].get<double>(),
                                                unit.get<std::string>()));
          positions.push_back(metric_conversion(location[1].get<double>(),
                                                unit.get<std::string>()));
          positions.push_back(metric_conversion(location[2].get<double>(),
                                                unit.get<std::string>()));
        }
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the sun positions.",
                                       "Utilities::getSunPositions()"));
        }
      }
      return positions;
    }
    
    std::vector<double> getSunVelocities(json isd, csm::WarningList *list) {
      std::vector<double> velocities;
      try {
        json jayson = isd.at("sun_position");
        json unit = jayson.at("unit");
        for (auto &location : jayson.at("velocities")) {
          velocities.push_back(metric_conversion(location[0].get<double>(),
                                                 unit.get<std::string>()));
          velocities.push_back(metric_conversion(location[1].get<double>(),
                                                 unit.get<std::string>()));
          velocities.push_back(metric_conversion(location[2].get<double>(),
                                                 unit.get<std::string>()));
        }
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the sun velocities.",
                                       "Utilities::getSunVelocities()"));
        }
      }
      return velocities;
    }
    
    std::vector<double> getSensorPositions(json isd, csm::WarningList *list) {
      std::vector<double> positions;
      try {
        json jayson = isd.at("sensor_position");
        json unit = jayson.at("unit");
        for (auto &location : jayson.at("positions")) {
          positions.push_back(metric_conversion(location[0].get<double>(),
                                                unit.get<std::string>()));
          positions.push_back(metric_conversion(location[1].get<double>(),
                                                unit.get<std::string>()));
          positions.push_back(metric_conversion(location[2].get<double>(),
                                                unit.get<std::string>()));
        }
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the sensor positions.",
                                       "Utilities::getSensorPositions()"));
        }
      }
      return positions;
    }
    
    std::vector<double> getSensorVelocities(json isd, csm::WarningList *list) {
      std::vector<double> velocities;
      try {
        json jayson = isd.at("sensor_position");
        json unit = jayson.at("unit");
        for (auto &velocity : jayson.at("velocities")) {
          velocities.push_back(metric_conversion(velocity[0].get<double>(),
                                                 unit.get<std::string>()));
          velocities.push_back(metric_conversion(velocity[1].get<double>(),
                                                 unit.get<std::string>()));
          velocities.push_back(metric_conversion(velocity[2].get<double>(),
                                                 unit.get<std::string>()));
        }
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the sensor velocities.",
                                       "Utilities::getSensorVelocities()"));
        }
      }
      return velocities;
    }
    
    std::vector<double> getSensorOrientations(json isd, csm::WarningList *list) {
      std::vector<double> quaternions;
      try {
        for (auto &quaternion : isd.at("sensor_orientation").at("quaternions")) {
          quaternions.push_back(quaternion[0]);
          quaternions.push_back(quaternion[1]);
          quaternions.push_back(quaternion[2]);
          quaternions.push_back(quaternion[3]);
        }
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the sensor orientations.",
                                       "Utilities::getSensorOrientations()"));
        }
      }
      return quaternions;
    }
    
    double getExposureDuration(nlohmann::json isd, csm::WarningList *list) {
      double duration;
      try {
        duration = isd.at("line_exposure_duration");
      } catch (...) {
        if (list) {
          list->push_back(
              csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                           "Could not parse the line exposure duration.",
                           "Utilities::getExposureDuration()"));
        }
      }
      return duration;
    }
    
    double getScaledPixelWidth(nlohmann::json isd, csm::WarningList *list) {
      double width;
      try {
        width = isd.at("scaled_pixel_width");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the scaled pixel width.",
                                       "Utilities::getScaledPixelWidth()"));
        }
      }
      return width;
    }
    
    std::string getLookDirection(nlohmann::json isd, csm::WarningList *list) {
      std::string lookDirection = "";
      try {
        lookDirection = isd.at("look_direction");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the scaled pixel width.",
                                       "Utilities::getScaledPixelWidth()"));
        }
      }
      return lookDirection;
    }
    
    std::vector<double> getScaleConversionTimes(nlohmann::json isd,
                                                csm::WarningList *list) {
      std::vector<double> time;
      try {
        time = isd.at("range_conversion_times").get<std::vector<double>>();
      } catch (...) {
        if (list) {
          list->push_back(
              csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                           "Could not parse the range conversion times.",
                           "Utilities::getScaleConversionTimes()"));
        }
      }
      return time;
    }
    
    std::vector<double> getScaleConversionCoefficients(nlohmann::json isd,
                                                       csm::WarningList *list) {
      std::vector<double> coefficients;
      try {
        for (auto &location : isd.at("range_conversion_coefficients")) {
          coefficients.push_back(location[0]);
          coefficients.push_back(location[1]);
          coefficients.push_back(location[2]);
          coefficients.push_back(location[3]);
        }
      } catch (...) {
        if (list) {
          list->push_back(
              csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                           "Could not parse the range conversion coefficients.",
                           "Utilities::getScaleConversionCoefficients()"));
        }
      }
      return coefficients;
    }
    
    double getWavelength(json isd, csm::WarningList *list) {
      double wavelength = 0.0;
      try {
        wavelength = isd.at("wavelength");
      } catch (...) {
        if (list) {
          list->push_back(csm::Warning(csm::Warning::DATA_NOT_AVAILABLE,
                                       "Could not parse the wavelength.",
                                       "Utilities::getWavelength()"));
        }
      }
      return wavelength;
    }
    
    json stateAsJson(std::string modelState) {
      // Remove special characters from string
      sanitize(modelState);
    
      std::size_t foundFirst = modelState.find_first_of("{");
      std::size_t foundLast = modelState.find_last_of("}");
    
      if (foundFirst == std::string::npos) {
        foundFirst = 0;
      }
      return json::parse(modelState.begin() + foundFirst, modelState.begin() + foundLast + 1);
    }
    
    void sanitize(std::string &input){
      // Replaces characters from the string that are not printable with newlines
      std::replace_if(input.begin(), input.end(), [](int c){return !::isprint(c);}, '\n');
    }
    
    // Read a file's content in a single string
    bool readFileInString(std::string const& filename, std::string & str) {
    
      str.clear(); // clear the output
    
      std::ifstream ifs(filename.c_str());
      if (!ifs.is_open()) {
        std::cout << "Cannot open file: " << filename << std::endl;
        return false;
      }
    
      ifs.seekg(0, std::ios::end);
      str.reserve(ifs.tellg());
      ifs.seekg(0, std::ios::beg);
      str.assign((std::istreambuf_iterator<char>(ifs)),
                 std::istreambuf_iterator<char>());
      ifs.close();
    
      return true;
    }
    
    // Apply a rotation to a vector of quaternions.
    void applyRotationToQuatVec(ale::Rotation const& r, std::vector<double> & quaternions) {
      int num_quat = quaternions.size();
    
      for (int it = 0; it < num_quat/4; it++) {
    
        double * q = &quaternions[4*it];
    
        // Note that quaternion in q is stored in order x, y, z, w, while
        // the rotation matrix wants it as w, x, y, z.
    
        std::vector<double> trans_q = (r * ale::Rotation(q[3], q[0], q[1], q[2])).toQuaternion();
    
        // Modify in-place
        q[0] = trans_q[1];
        q[1] = trans_q[2];
        q[2] = trans_q[3];
        q[3] = trans_q[0];
      }
    }
    
    // Apply a rotation and translation to a vector of xyz vectors.
    void applyRotationTranslationToXyzVec(ale::Rotation const& r, ale::Vec3d const& t,
                                          std::vector<double> & xyz) {
    
      int num_xyz = xyz.size();
      for (int it = 0; it < num_xyz/3; it++) {
    
        double * p = &xyz[3*it];
    
        ale::Vec3d p_vec(p[0], p[1], p[2]);
    
        // Apply the rotation
        p_vec = r(p_vec);
    
        // Apply the translation
        p_vec += t;
    
        // Modify in-place
        p[0] = p_vec.x;
        p[1] = p_vec.y;
        p[2] = p_vec.z;
    
      }
    }
    
    // Convert ephemeris time, in seconds since January 1, 2000 12:00:00 AM GMT,
    // to a calendar time string, such as 2000-01-01T00:16:40Z.
    std::string ephemTimeToCalendarTime(double ephemTime) {
    
      // Care must be taken to not use mktime() or localtime() as their
      // precise value depends on if a location respects Daylight Savings Time.
    
      std::time_t y2k = 946684800; // January 1, 2000 12:00:00 AM GMT
      std::time_t finalTime = ephemTime + y2k;
      char buffer[22];
      strftime(buffer, 22, "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&finalTime));
      buffer[21] = '\0';
      return buffer;
    }