diff --git a/CMakeLists.txt b/CMakeLists.txt
index f145a3954c20a75ae4eb6872e1cdc654615dbcbc..ef9f7c64303dfda8a36841001b15e55132d68e57 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,36 +14,48 @@ include(GNUInstallDirs)
 
 set(CMAKE_CXX_STANDARD 11)
 
+set(ALE_BUILD_LOAD ON CACHE BOOL "If the C++ Python load interface should be built.")
+
 # Third Party Dependencies
 find_package(Eigen3 3.3    REQUIRED NO_MODULE)
 find_package(nlohmann_json REQUIRED)
 
-# If there is an Anaconda environment activated, search that for Python first
-if(EXISTS $ENV{CONDA_PREFIX})
-  message("Searching $ENV{CONDA_PREFIX} for Python libraries")
-  set(Python_ROOT_DIR $ENV{CONDA_PREFIX})
-  set(Python_FIND_STRATEGY LOCATION)
+if(ALE_BUILD_LOAD)
+  # If there is an Anaconda environment activated, search that for Python first
+  if(EXISTS $ENV{CONDA_PREFIX})
+    message("Searching $ENV{CONDA_PREFIX} for Python libraries")
+    set(Python_ROOT_DIR $ENV{CONDA_PREFIX})
+    set(Python_FIND_STRATEGY LOCATION)
+  endif()
+  find_package(Python REQUIRED COMPONENTS Development)
 endif()
-find_package(Python        REQUIRED COMPONENTS Development)
 
 # Library setup
 set(ALE_BUILD_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/")
 set(ALE_INSTALL_INCLUDE_DIR "include/ale")
-add_library(ale SHARED
-            ${CMAKE_CURRENT_SOURCE_DIR}/src/ale.cpp
-            ${CMAKE_CURRENT_SOURCE_DIR}/src/InterpUtils.cpp
-            ${CMAKE_CURRENT_SOURCE_DIR}/src/Rotation.cpp
-            ${CMAKE_CURRENT_SOURCE_DIR}/src/Orientations.cpp
-            ${CMAKE_CURRENT_SOURCE_DIR}/src/States.cpp
-            ${CMAKE_CURRENT_SOURCE_DIR}/src/Isd.cpp
-            ${CMAKE_CURRENT_SOURCE_DIR}/src/Util.cpp)
-set(ALE_HEADERS "${ALE_BUILD_INCLUDE_DIR}/ale.h"
-                "${ALE_BUILD_INCLUDE_DIR}/InterpUtils.h"
-                "${ALE_BUILD_INCLUDE_DIR}/Rotation.h"
-                "${ALE_BUILD_INCLUDE_DIR}/Orientations.h"
-                "${ALE_BUILD_INCLUDE_DIR}/States.h"
-                "${ALE_BUILD_INCLUDE_DIR}/Isd.h"
-                "${ALE_BUILD_INCLUDE_DIR}/Util.h")
+set(ALE_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/InterpUtils.cpp
+                  ${CMAKE_CURRENT_SOURCE_DIR}/src/Rotation.cpp
+                  ${CMAKE_CURRENT_SOURCE_DIR}/src/Orientations.cpp
+                  ${CMAKE_CURRENT_SOURCE_DIR}/src/States.cpp
+                  ${CMAKE_CURRENT_SOURCE_DIR}/src/Isd.cpp
+                  ${CMAKE_CURRENT_SOURCE_DIR}/src/Util.cpp)
+set(ALE_HEADER_FILES ${ALE_BUILD_INCLUDE_DIR}/InterpUtils.h
+                     ${ALE_BUILD_INCLUDE_DIR}/Rotation.h
+                     ${ALE_BUILD_INCLUDE_DIR}/Orientations.h
+                     ${ALE_BUILD_INCLUDE_DIR}/States.h
+                     ${ALE_BUILD_INCLUDE_DIR}/Isd.h
+                     ${ALE_BUILD_INCLUDE_DIR}/Util.h)
+set(ALE_PRIVATE_LINKS Eigen3::Eigen)
+set(ALE_PUBLIC_LINKS nlohmann_json::nlohmann_json)
+
+if(ALE_BUILD_LOAD)
+  list(APPEND ALE_SRC_FILES    ${CMAKE_CURRENT_SOURCE_DIR}/src/Load.cpp)
+  list(APPEND ALE_HEADER_FILES ${ALE_BUILD_INCLUDE_DIR}/Load.h)
+  list(APPEND ALE_PRIVATE_LINKS Python::Python)
+endif()
+
+add_library(ale SHARED ${ALE_SRC_FILES})
+
 set_target_properties(ale PROPERTIES
                       VERSION   ${PROJECT_VERSION}
                       SOVERSION 0)
@@ -53,11 +65,8 @@ target_include_directories(ale
                            $<BUILD_INTERFACE:${ALE_BUILD_INCLUDE_DIR}>
                            $<INSTALL_INTERFACE:include>)
 
-target_link_libraries(ale
-                      PRIVATE
-                      Eigen3::Eigen
-                      Python::Python
-                      nlohmann_json::nlohmann_json)
+target_link_libraries(ale PRIVATE ${ALE_PRIVATE_LINKS}
+                          PUBLIC ${ALE_PUBLIC_LINKS})
 
 # Optional build tests
 option (ALE_BUILD_TESTS "Build tests" ON)
@@ -89,7 +98,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake
               DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
 
 # Install the headers
-install(FILES ${ALE_HEADERS} DESTINATION ${ALE_INSTALL_INCLUDE_DIR})
+install(FILES ${ALE_HEADER_FILES} DESTINATION ${ALE_INSTALL_INCLUDE_DIR})
 
 # Install the library
 install(TARGETS ale
diff --git a/docs/config b/docs/config
index 451ddca521ab483ad7a8e57d110cc68edec9f33a..372715057989a7c44574fbdf7c500ed19c7e28f0 100644
--- a/docs/config
+++ b/docs/config
@@ -791,7 +791,7 @@ WARN_LOGFILE           =
 # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = ./include/ale.h ./ale ./src/
+INPUT                  = ./include/Load.h ./ale ./src/
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/docs/source/library/capi/capi.rst b/docs/source/library/capi/capi.rst
index 243294f78ed4275d5e06e8cf0c7a5d8cd26738c7..fb7b1bdacb73d86008c60b1ff27887ed587e88fd 100644
--- a/docs/source/library/capi/capi.rst
+++ b/docs/source/library/capi/capi.rst
@@ -3,7 +3,7 @@
 
 The :mod:`src` module
 
-.. doxygenfile:: ale.h
+.. doxygenfile:: Load.h
   :project: ale
 
 .. versionadded:: 0.1.0
diff --git a/include/Isd.h b/include/Isd.h
index e5e609ae6925666af713cc8574e8ba4255c97707..d4ce223db3c7219ca1f748f042865da0b8f6e512 100644
--- a/include/Isd.h
+++ b/include/Isd.h
@@ -15,8 +15,6 @@
 
 namespace ale {
 
-  using json = nlohmann::json;
-
   class Isd {
     public:
 
@@ -26,7 +24,7 @@ namespace ale {
     std::string name_platform;
     std::string image_id;
     std::string name_sensor;
-    
+
     double semi_major;
     double semi_minor;
 
@@ -61,11 +59,11 @@ namespace ale {
     double starting_ephemeris_time;
     double center_ephemeris_time;
 
-    json naif_keywords;
-    
+    nlohmann::json naif_keywords;
+
     PositionInterpolation interpMethod;
-    
-    Rotation const_rotation; 
+
+    Rotation const_rotation;
 
     States inst_pos;
     States sun_pos;
diff --git a/include/Load.h b/include/Load.h
new file mode 100644
index 0000000000000000000000000000000000000000..b9f0bbc7cc70a0c163e97516cf29c2db0a4b2e5e
--- /dev/null
+++ b/include/Load.h
@@ -0,0 +1,39 @@
+#ifndef ALE_INCLUDE_ALE_H
+#define ALE_INCLUDE_ALE_H
+
+#include <nlohmann/json.hpp>
+
+#include <string>
+
+namespace ale {
+  /**
+   * Load all of the metadata for an image into an ISD string.
+   * This method calls the Python driver structure in ALE to load all
+   * of the metadata for an image into an ISD string. See the Python
+   * loads method for how this is implemented on the Python side.
+   *
+   * @param filename The filename of the image to load metadata for
+   * @param props A JSON formatted properties string to pass to the Python drivers.
+   *              Users can specify certain properties that the drivers will use.
+   *              Currently kernels and nadir properties are allowed. See the
+   *              data_naif driver mix-in for details.
+   * @param formatter A string specifying the format of the output ISD string.
+   *                  Currently supported formatters are isis, usgscsm, and ale.
+   *                  The isis and usgscsm formatters will be deprecated in the future.
+   * @param verbose A flag to output what the load function is attempting to do.
+   *                If set to true, information about the drivers load attempts
+   *                to use will be output to standard out.
+   *
+   * @returns A string containing a JSON formatted ISD for the image.
+   */
+  std::string loads(std::string filename, std::string props="", std::string formatter="usgscsm", bool verbose=true);
+
+  /**
+   * Load all of the metadata fro an image into a JSON ISD.
+   * This method is a convenience wrapper around the loads method that parses the
+   * string output of loads into a JSON object.
+   */
+  nlohmann::json load(std::string filename, std::string props="", std::string formatter="usgscsm", bool verbose=true);
+}
+
+#endif // ALE_H
diff --git a/include/Util.h b/include/Util.h
index 6d1d69b3279463397eb9d16c6cc7734cc53f5da6..7150a37b2dc7eee54e55c16179a37a3bf2cb71e1 100644
--- a/include/Util.h
+++ b/include/Util.h
@@ -11,10 +11,9 @@
 #include "Vectors.h"
 
 namespace ale {
-  using json = nlohmann::json;
 
   template<typename T>
-  std::vector<T> getJsonArray(json obj) {
+  std::vector<T> getJsonArray(nlohmann::json obj) {
     std::vector<T> positions;
     try {
       for (auto &location : obj) {
@@ -27,45 +26,45 @@ namespace ale {
   }
 
 
-  PositionInterpolation getInterpolationMethod(json isd);
+  PositionInterpolation getInterpolationMethod(nlohmann::json isd);
   double getMinHeight(nlohmann::json isd);
-  std::string getSensorModelName(json isd);
-  std::string getImageId(json isd);
-  std::string getSensorName(json isd);
-  std::string getPlatformName(json isd);
+  std::string getSensorModelName(nlohmann::json isd);
+  std::string getImageId(nlohmann::json isd);
+  std::string getSensorName(nlohmann::json isd);
+  std::string getPlatformName(nlohmann::json isd);
   std::string getLogFile(nlohmann::json isd);
-  std::string getIsisCameraVersion(json isd);
-  int getTotalLines(json isd);
-  int getTotalSamples(json isd);
-  double getStartingTime(json isd);
-  double getCenterTime(json isd);
-  std::vector<std::vector<double>> getLineScanRate(json isd);
-  int getSampleSumming(json isd);
-  int getLineSumming(json isd);
-  double getFocalLength(json isd);
-  double getFocalLengthUncertainty(json isd);
-  std::vector<double> getFocal2PixelLines(json isd);
-  std::vector<double> getFocal2PixelSamples(json isd);
-  double getDetectorCenterLine(json isd);
-  double getDetectorCenterSample(json isd);
-  double getDetectorStartingLine(json isd);
-  double getDetectorStartingSample(json isd);
-  double getMinHeight(json isd);
-  double getMaxHeight(json isd);
-  double getSemiMajorRadius(json isd);
-  double getSemiMinorRadius(json isd);
-  DistortionType getDistortionModel(json isd);
-  std::vector<double> getDistortionCoeffs(json isd);
-  
-  std::vector<double> getJsonDoubleArray(json obj);
-  std::vector<Vec3d> getJsonVec3dArray(json obj);
-  std::vector<Rotation> getJsonQuatArray(json obj);
+  std::string getIsisCameraVersion(nlohmann::json isd);
+  int getTotalLines(nlohmann::json isd);
+  int getTotalSamples(nlohmann::json isd);
+  double getStartingTime(nlohmann::json isd);
+  double getCenterTime(nlohmann::json isd);
+  std::vector<std::vector<double>> getLineScanRate(nlohmann::json isd);
+  int getSampleSumming(nlohmann::json isd);
+  int getLineSumming(nlohmann::json isd);
+  double getFocalLength(nlohmann::json isd);
+  double getFocalLengthUncertainty(nlohmann::json isd);
+  std::vector<double> getFocal2PixelLines(nlohmann::json isd);
+  std::vector<double> getFocal2PixelSamples(nlohmann::json isd);
+  double getDetectorCenterLine(nlohmann::json isd);
+  double getDetectorCenterSample(nlohmann::json isd);
+  double getDetectorStartingLine(nlohmann::json isd);
+  double getDetectorStartingSample(nlohmann::json isd);
+  double getMinHeight(nlohmann::json isd);
+  double getMaxHeight(nlohmann::json isd);
+  double getSemiMajorRadius(nlohmann::json isd);
+  double getSemiMinorRadius(nlohmann::json isd);
+  DistortionType getDistortionModel(nlohmann::json isd);
+  std::vector<double> getDistortionCoeffs(nlohmann::json isd);
+
+  std::vector<double> getJsonDoubleArray(nlohmann::json obj);
+  std::vector<Vec3d> getJsonVec3dArray(nlohmann::json obj);
+  std::vector<Rotation> getJsonQuatArray(nlohmann::json obj);
 
-  States getInstrumentPosition(json isd);
-  States getSunPosition(json isd);
+  States getInstrumentPosition(nlohmann::json isd);
+  States getSunPosition(nlohmann::json isd);
 
-  Orientations getBodyRotation(json isd);
-  Orientations getInstrumentPointing(json isd);
+  Orientations getBodyRotation(nlohmann::json isd);
+  Orientations getInstrumentPointing(nlohmann::json isd);
 }
 
 #endif
diff --git a/include/ale.h b/include/ale.h
deleted file mode 100644
index a6469a3c986a9eb5dc9dca8fd9f662c07e6137c6..0000000000000000000000000000000000000000
--- a/include/ale.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef ALE_INCLUDE_ALE_H
-#define ALE_INCLUDE_ALE_H
-
-#include <string>
-#include <vector>
-
-
-#include <nlohmann/json.hpp>
-
-namespace ale {
-
-  std::string loads(std::string filename, std::string props="", std::string formatter="usgscsm", bool verbose=true);
-
-  nlohmann::json load(std::string filename, std::string props="", std::string formatter="usgscsm", bool verbose=true);
-
-
-}
-
-#endif // ALE_H
diff --git a/src/InterpUtils.cpp b/src/InterpUtils.cpp
index ae44954822a5ed8ad3c548cf9cc9d30f0b8ad62b..75fbe156f437dcf15f93bc61fe9535c95184b732 100644
--- a/src/InterpUtils.cpp
+++ b/src/InterpUtils.cpp
@@ -7,6 +7,7 @@
 #include <unordered_set>
 
 namespace ale {
+
   double linearInterpolate(double x, double y, double t) {
     return x + t * (y - x);
   }
diff --git a/src/Isd.cpp b/src/Isd.cpp
index 5f099b13ac86784f350df65ecf06597f92d8ebea..8cb674b849146ec3820378fb146d6022c2dd6edb 100644
--- a/src/Isd.cpp
+++ b/src/Isd.cpp
@@ -2,6 +2,8 @@
 #include "Isd.h"
 #include "Util.h"
 
+using json = nlohmann::json;
+
 ale::Isd::Isd(std::string isd_file) {
   json isd = json::parse(isd_file);
 
@@ -41,12 +43,12 @@ ale::Isd::Isd(std::string isd_file) {
 
   distortion_model = getDistortionModel(isd);
   distortion_coefficients = getDistortionCoeffs(isd);
-  
-  interpMethod = getInterpolationMethod(isd); 
-   
+
+  interpMethod = getInterpolationMethod(isd);
+
   inst_pos = getInstrumentPosition(isd);
   sun_pos = getSunPosition(isd);
-  
+
   inst_pointing = getInstrumentPointing(isd);
-  body_rotation = getBodyRotation(isd); 
+  body_rotation = getBodyRotation(isd);
  }
diff --git a/src/ale.cpp b/src/Load.cpp
similarity index 88%
rename from src/ale.cpp
rename to src/Load.cpp
index 1fab2d98b006b4ff5925708f626f9a6145de2eca..676dd6d351175790fac506bd08edb356895559ac 100644
--- a/src/ale.cpp
+++ b/src/Load.cpp
@@ -1,8 +1,7 @@
-#include "ale.h"
+#include "Load.h"
 
 #include <nlohmann/json.hpp>
 
-#include <iostream>
 #include <Python.h>
 
 #include <string>
@@ -10,9 +9,9 @@
 #include <stdexcept>
 
 using json = nlohmann::json;
+using namespace std;
 
 namespace ale {
-
  std::string getPyTraceback() {
     PyObject* err = PyErr_Occurred();
     if (err != NULL) {
@@ -33,7 +32,7 @@ namespace ale {
         Py_DECREF(module_name);
 
         if (pyth_module == NULL) {
-            throw std::runtime_error("getPyTraceback - Failed to import Python traceback Library");
+            throw runtime_error("getPyTraceback - Failed to import Python traceback Library");
         }
 
         pyth_func = PyObject_GetAttrString(pyth_module, "format_exception");
@@ -60,7 +59,6 @@ namespace ale {
     return "";
  }
 
-
  std::string loads(std::string filename, std::string props, std::string formatter, bool verbose) {
      static bool first_run = true;
      if(first_run) {
@@ -72,7 +70,7 @@ namespace ale {
      // Import the file as a Python module.
      PyObject *pModule = PyImport_Import(PyUnicode_FromString("ale"));
      if(!pModule) {
-       throw std::runtime_error("Failed to import ale. Make sure the ale python library is correctly installed.");
+       throw runtime_error("Failed to import ale. Make sure the ale python library is correctly installed.");
      }
      // Create a dictionary for the contents of the module.
      PyObject *pDict = PyModule_GetDict(pModule);
@@ -82,7 +80,7 @@ namespace ale {
      if(!pFunc) {
        // import errors do not set a PyError flag, need to use a custom
        // error message instead.
-       throw std::runtime_error("Failed to import ale.loads function from Python."
+       throw runtime_error("Failed to import ale.loads function from Python."
                            "This Usually indicates an error in the Ale Python Library."
                            "Check if Installed correctly and the function ale.loads exists.");
      }
@@ -90,7 +88,7 @@ namespace ale {
      // Create a Python tuple to hold the arguments to the method.
      PyObject *pArgs = PyTuple_New(3);
      if(!pArgs) {
-       throw std::runtime_error(getPyTraceback());
+       throw runtime_error(getPyTraceback());
      }
 
      // Set the Python int as the first and second arguments to the method.
@@ -107,14 +105,14 @@ namespace ale {
      PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
 
      if(!pResult) {
-        throw std::invalid_argument("No Valid instrument found for label.");
+        throw invalid_argument("No Valid instrument found for label.");
      }
 
      PyObject *pResultStr = PyObject_Str(pResult);
      PyObject *temp_bytes = PyUnicode_AsUTF8String(pResultStr); // Owned reference
 
      if(!temp_bytes){
-       throw std::invalid_argument(getPyTraceback());
+       throw invalid_argument(getPyTraceback());
      }
      std::string cResult;
      char *temp_str = PyBytes_AS_STRING(temp_bytes); // Borrowed pointer
diff --git a/src/Util.cpp b/src/Util.cpp
index 0a042e4d33473d670d87f37be13e6f28cc7fc510..103ecee900916d82122cf44cabdbe6b312eddc77 100644
--- a/src/Util.cpp
+++ b/src/Util.cpp
@@ -4,6 +4,8 @@
 
 #include "Util.h"
 
+using json = nlohmann::json;
+
 namespace ale {
 
 bool iequals(const std::string& a, const std::string& b) {
@@ -122,20 +124,20 @@ PositionInterpolation getInterpolationMethod(json isd) {
   std::string interpMethod = "linear";
   try {
     interpMethod = isd.at("interpolation_method");
-     
+
     if (iequals(interpMethod, "linear")) {
       return PositionInterpolation::LINEAR;
     }
-    else if (iequals(interpMethod, "spline")){ 
+    else if (iequals(interpMethod, "spline")){
       return PositionInterpolation::SPLINE;
-    } 
+    }
     else if (iequals(interpMethod, "lagrange")) {
       return PositionInterpolation::LAGRANGE;
     }
   } catch (...) {
     throw std::runtime_error("Could not parse the interpolation method.");
   }
-  
+
   return PositionInterpolation::LINEAR;
 }
 
@@ -488,16 +490,16 @@ States getInstrumentPosition(json isd) {
   try {
     json ipos = isd.at("instrument_position");
     std::vector<Vec3d> positions = getJsonVec3dArray(ipos.at("positions"));
-    std::vector<double> times = getJsonArray<double>(ipos.at("ephemeris_times")); 
+    std::vector<double> times = getJsonArray<double>(ipos.at("ephemeris_times"));
     int refFrame = ipos.at("reference_frame").get<int>();
-    
+
     bool hasVelocities = ipos.find("velocities") != ipos.end();
-    
+
     if (hasVelocities) {
-      std::vector<Vec3d> velocities = getJsonVec3dArray(ipos.at("velocities")); 
-      return States(times, positions, velocities, refFrame);  
+      std::vector<Vec3d> velocities = getJsonVec3dArray(ipos.at("velocities"));
+      return States(times, positions, velocities, refFrame);
     }
-    
+
     return States(times, positions, refFrame);
   } catch (...) {
     throw std::runtime_error("Could not parse the instrument position");
@@ -509,37 +511,37 @@ States getSunPosition(json isd) {
   try {
     json spos = isd.at("sun_position");
     std::vector<Vec3d> positions = getJsonVec3dArray(spos.at("positions"));
-    std::vector<double> times = getJsonArray<double>(spos.at("ephemeris_times")); 
+    std::vector<double> times = getJsonArray<double>(spos.at("ephemeris_times"));
     int refFrame = spos.at("reference_frame").get<int>();
     bool hasVelocities = spos.find("velocities") != spos.end();
-    
+
     if (hasVelocities) {
-      std::vector<Vec3d> velocities = getJsonVec3dArray(spos.at("velocities")); 
-      return States(times, positions, velocities, refFrame);  
+      std::vector<Vec3d> velocities = getJsonVec3dArray(spos.at("velocities"));
+      return States(times, positions, velocities, refFrame);
     }
-    
+
     return States(times, positions, refFrame);
-  
+
   } catch (...) {
     throw std::runtime_error("Could not parse the sun position");
   }
 }
 
-Orientations getInstrumentPointing(json isd) { 
+Orientations getInstrumentPointing(json isd) {
   try {
     json pointing = isd.at("instrument_pointing");
-    
+
     std::vector<Rotation> rotations = getJsonQuatArray(pointing.at("quaternions"));
-    std::vector<double> times = getJsonArray<double>(pointing.at("ephemeris_times")); 
+    std::vector<double> times = getJsonArray<double>(pointing.at("ephemeris_times"));
     std::vector<Vec3d> velocities = getJsonVec3dArray(pointing.at("angular_velocities"));
-    int refFrame = pointing.at("reference_frame").get<int>(); 
-    
+    int refFrame = pointing.at("reference_frame").get<int>();
+
     std::vector<int> constFrames;
     if (pointing.find("constant_frames") != pointing.end()){
       constFrames  = getJsonArray<int>(pointing.at("constant_frames"));
     }
-    
-    std::vector<int> timeDepFrames; 
+
+    std::vector<int> timeDepFrames;
     if (pointing.find("time_dependent_frames") != pointing.end()){
       timeDepFrames = getJsonArray<int>(pointing.at("time_dependent_frames"));
     }
@@ -547,34 +549,34 @@ Orientations getInstrumentPointing(json isd) {
     std::vector<double> rotArray = {1,0,0,0,1,0,0,0,1};
     if (pointing.find("time_dependent_frames") != pointing.end()){
       rotArray = getJsonArray<double>(pointing.at("constant_rotation"));
-    } 
+    }
 
     Rotation constRot(rotArray);
 
-    Orientations orientation(rotations, times, velocities, refFrame, constRot, constFrames, timeDepFrames);  
-    
+    Orientations orientation(rotations, times, velocities, refFrame, constRot, constFrames, timeDepFrames);
+
     return orientation;
-  
+
   } catch (...) {
     throw std::runtime_error("Could not parse the instrument pointing");
-  } 
+  }
 }
 
-Orientations getBodyRotation(json isd) { 
+Orientations getBodyRotation(json isd) {
   try {
     json bodrot = isd.at("body_rotation");
     std::vector<Rotation> rotations = getJsonQuatArray(bodrot.at("quaternions"));
-    std::vector<double> times = getJsonArray<double>(bodrot.at("ephemeris_times")); 
-    std::vector<Vec3d> velocities = getJsonVec3dArray(bodrot.at("angular_velocities")); 
-   
-    int refFrame = bodrot.at("reference_frame").get<int>(); 
-    
+    std::vector<double> times = getJsonArray<double>(bodrot.at("ephemeris_times"));
+    std::vector<Vec3d> velocities = getJsonVec3dArray(bodrot.at("angular_velocities"));
+
+    int refFrame = bodrot.at("reference_frame").get<int>();
+
     std::vector<int> constFrames;
     if (bodrot.find("constant_frames") != bodrot.end()){
       constFrames  = getJsonArray<int>(bodrot.at("constant_frames"));
     }
-    
-    std::vector<int> timeDepFrames; 
+
+    std::vector<int> timeDepFrames;
     if (bodrot.find("time_dependent_frames") != bodrot.end()){
       timeDepFrames = getJsonArray<int>(bodrot.at("time_dependent_frames"));
     }
@@ -582,16 +584,16 @@ Orientations getBodyRotation(json isd) {
     std::vector<double> rotArray = {1,0,0,0,1,0,0,0,1};
     if (bodrot.find("constant_rotation") != bodrot.end()){
       rotArray = getJsonArray<double>(bodrot.at("constant_rotation"));
-    } 
+    }
 
     Rotation constRot(rotArray);
 
-    Orientations orientation(rotations, times, velocities, refFrame, constRot, constFrames, timeDepFrames);  
+    Orientations orientation(rotations, times, velocities, refFrame, constRot, constFrames, timeDepFrames);
     return orientation;
-  
+
   } catch (...) {
     throw std::runtime_error("Could not parse the body rotation");
-  } 
+  }
 }
 
 }
diff --git a/tests/ctests/AleTest.cpp b/tests/ctests/AleTest.cpp
deleted file mode 100644
index 2afe618d0729a1471ad970e643a5422185e2ce2e..0000000000000000000000000000000000000000
--- a/tests/ctests/AleTest.cpp
+++ /dev/null
@@ -1,253 +0,0 @@
-#include "gtest/gtest.h"
-
-#include "ale.h"
-#include "Isd.h"
-
-#include <stdexcept>
-#include <cmath>
-
-#include <Eigen/Core>
-#include <Eigen/Geometry>
-
-using namespace std;
-
-
-TEST(LinearInterpTest, ExampleInterpolation) {
-  vector<double> times = {0,  1,  2, 3};
-  vector<double> data = {0, 2, 1, 0};
-
-  EXPECT_DOUBLE_EQ(0.0, ale::interpolate(data, times, 0.0, ale::LINEAR, 0));
-  EXPECT_DOUBLE_EQ(1.0, ale::interpolate(data, times, 0.5, ale::LINEAR, 0));
-  EXPECT_DOUBLE_EQ(2.0, ale::interpolate(data, times, 1.0, ale::LINEAR, 0));
-  EXPECT_DOUBLE_EQ(1.5, ale::interpolate(data, times, 1.5, ale::LINEAR, 0));
-  EXPECT_DOUBLE_EQ(1.0, ale::interpolate(data, times, 2.0, ale::LINEAR, 0));
-  EXPECT_DOUBLE_EQ(0.5, ale::interpolate(data, times, 2.5, ale::LINEAR, 0));
-  EXPECT_DOUBLE_EQ(0.0, ale::interpolate(data, times, 3.0, ale::LINEAR, 0));
-}
-
-
-TEST(LinearInterpTest, NoPoints) {
-  vector<double> times = {};
-  vector<double> data = {};
-
-  EXPECT_THROW(ale::interpolate(data, times, 0.0, ale::LINEAR, 0), invalid_argument);
-}
-
-
-TEST(LinearInterpTest, DifferentCounts) {
-  vector<double> times = { -3, -2, -1,  0,  2};
-  vector<double> data = { -3, -2, 1,  2};
-
-  EXPECT_THROW(ale::interpolate(data, times, 0.0, ale::LINEAR, 0), invalid_argument);
-}
-
-
-TEST(LinearInterpTest, Extrapolate) {
-  vector<double> times = {0,  1,  2, 3};
-  vector<double> data = {0, 2, 1, 0};
-
-  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, -1.0, ale::LINEAR, 0), -2);
-  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 4.0, ale::LINEAR, 0), -1);
-}
-
-
-TEST(SplineInterpTest, ExampleInterpolation) {
-  // From http://www.maths.nuigalway.ie/~niall/teaching/Archive/1617/MA378/2-2-CubicSplines.pdf
-  vector<double> times = {0, 1, 2, 3};
-  vector<double> data  = {2, 4, 3, 2};
-  // function is f(t) = 0.5t^3 - 3t^2 + 4.5t + 2
-
-  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 0.0, ale::SPLINE, 0), 2.0);
-  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 0.5, ale::SPLINE, 0), 3.0);
-  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 1.0, ale::SPLINE, 0), 4.0);
-  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 1.5, ale::SPLINE, 0), 3.6875);
-  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 2.0, ale::SPLINE, 0), 3.0);
-  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 2.5, ale::SPLINE, 0), 2.5);
-  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 3.0, ale::SPLINE, 0), 2.0);
-}
-
-
-TEST(SplineInterpTest, NoPoints) {
-  vector<double> times = {};
-  vector<double> data = {};
-
-  EXPECT_THROW(ale::interpolate(data, times, 0.0, ale::SPLINE, 0), invalid_argument);
-}
-
-
-TEST(SplineInterpTest, DifferentCounts) {
-  vector<double> times = { -3, -2, -1,  0,  2};
-  vector<double> data = { -3, -2, 1,  2};
-
-  EXPECT_THROW(ale::interpolate(data, times, 0.0, ale::SPLINE, 0), invalid_argument);
-}
-
-
-TEST(PyInterfaceTest, LoadInvalidLabel) {
-  std::string label = "Not a Real Label";
-  EXPECT_THROW(ale::load(label), invalid_argument);
-}
-
-
-TEST(PyInterfaceTest, LoadValidLabel) {
-  std::string label = "../pytests/data/EN1072174528M/EN1072174528M_spiceinit.lbl";
-  ale::load(label, "", "isis");
-}
-
-
-TEST(Interpolation, Derivative1) {
-  vector<double> points = {0, 2, 4};
-  vector<double> times = {0, 1, 2};
-  EXPECT_NO_THROW(ale::interpolate(points, times, 1, ale::LINEAR, 1));
-}
-
-
-TEST(Interpolation, Derivative2) {
-  vector<double> points = {0, 0, 0};
-  vector<double> times = {0, 1, 2};
-  EXPECT_THROW(ale::interpolate(points, times, 1, ale::LINEAR, 2), invalid_argument);
-}
-
-
-TEST(Interpolation, InvalidDerivative) {
-  vector<double> points = {0, 0, 0};
-  vector<double> times = {0, 1, 2};
-
-  EXPECT_THROW(ale::interpolate(points, times, 1, ale::LINEAR, 3), invalid_argument);
-}
-
-
-TEST(InterpIndex, InvalidTimes) {
-  std::vector<double> times = {};
-
-  EXPECT_THROW(ale::interpolationIndex(times, 0), invalid_argument);
-}
-
-
-TEST(EvaluateCubicHermite, SimplePolynomial) {
-  // Cubic function is y = x^3 - 2x^2 + 1
-  // derivative is dy/dx = 3x^2 - 4x
-  std::vector<double> derivs = {7.0, -1.0};
-  std::vector<double> x = {-1.0, 1.0};
-  std::vector<double> y = {-2.0, 0.0};
-
-  EXPECT_DOUBLE_EQ(ale::evaluateCubicHermite(0.0, derivs, x, y), 1.0);
-}
-
-TEST(EvaluateCubicHermite, InvalidDervisXY) {
-  std::vector<double> derivs = {};
-  std::vector<double> x = {1.0};
-  std::vector<double> y = {1.0};
-
-  EXPECT_THROW(ale::evaluateCubicHermite(0.0, derivs, x, y), invalid_argument);
-}
-
-
-TEST(EvaluateCubicHermiteFirstDeriv, SimplyPolynomial) {
-  // Cubic function is y = x^3 - 2x^2 + 1
-  // derivative is dy/dx = 3x^2 - 4x
-  std::vector<double> derivs = {7.0, -1.0};
-  std::vector<double> x = {-1.0, 1.0};
-  std::vector<double> y = {-2.0, 0.0};
-
-  EXPECT_DOUBLE_EQ(ale::evaluateCubicHermiteFirstDeriv(0.5, derivs, x, y), -1.25);
-}
-
-
-TEST(EvaluateCubicHermiteFirstDeriv, InvalidDervisTimes) {
-  std::vector<double> derivs = {};
-  std::vector<double> times = {1.0};
-  std::vector<double> y = {1.0};
-
-  EXPECT_THROW(ale::evaluateCubicHermiteFirstDeriv(0.0, derivs, times, y), invalid_argument);
-}
-
-
-TEST(EvaluateCubicHermiteFirstDeriv, InvalidVelocities) {
-  std::vector<double> derivs = {5.0, 6.0};
-  std::vector<double> times = {1.0, 1.0};
-  std::vector<double> y = {1.0};
-
-  EXPECT_THROW(ale::evaluateCubicHermiteFirstDeriv(0.0, derivs, times, y), invalid_argument);
-}
-
-// The following tests all use the following equations
-//
-// v = -t^3 - 7x^2 + 3x + 16
-//
-// The full set of values is:
-//  t   v
-// -3  -83
-// -2  -26
-// -1   5
-//  0   16
-//  1   13
-//  2   2
-//  3  -11
-//  4  -20
-//  5  -19
-//  6  -2
-//  7   37
-
-TEST(LagrangeInterpolate, SecondOrder) {
-  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
-  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
-
-  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolate(times, values, 1.5, 2), 7);
-}
-
-
-TEST(LagrangeInterpolate, FourthOrder) {
-  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
-  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
-
-  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolate(times, values, 1.5, 4), 8.125);
-}
-
-
-TEST(LagrangeInterpolate, ReducedOrder) {
-  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
-  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
-
-  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolate(times, values, 3.5, 4), -13);
-  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolate(times, values, -2.5, 4), -54.5);
-}
-
-
-TEST(LagrangeInterpolate, InvalidArguments) {
-  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
-  std::vector<double> values = {-83, -26, 5, 16, 13};
-  EXPECT_THROW(ale::lagrangeInterpolate(times, values, 3.5, 4), invalid_argument);
-}
-
-
-TEST(lagrangeInterpolateDerivative, SecondOrder) {
-  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
-  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
-
-  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolateDerivative(times, values, 1.5, 2), -12);
-}
-
-
-TEST(LagrangeInterpolateDerivative, FourthOrder) {
-  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
-  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
-
-  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolateDerivative(times, values, 1.5, 4), -11.25);
-}
-
-
-TEST(LagrangeInterpolateDerivative, ReducedOrder) {
-  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
-  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
-
-  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolateDerivative(times, values, 3.5, 4), -4);
-  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolateDerivative(times, values, -2.5, 4), 57);
-}
-
-
-TEST(LagrangeInterpolateDerivative, InvalidArguments) {
-  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
-  std::vector<double> values = {-83, -26, 5, 16, 13};
-  EXPECT_THROW(ale::lagrangeInterpolateDerivative(times, values, 3.5, 4), invalid_argument);
-}
diff --git a/tests/ctests/CMakeLists.txt b/tests/ctests/CMakeLists.txt
index 226f9292ea8e74766c3ec78054021d02dfa24fb3..cf4ab5253f6039794675d9d8ed5be31b3d485358 100644
--- a/tests/ctests/CMakeLists.txt
+++ b/tests/ctests/CMakeLists.txt
@@ -1,10 +1,19 @@
 cmake_minimum_required(VERSION 3.10)
 
 # collect all of the test sources
-file(GLOB test_source "${CMAKE_SOURCE_DIR}/tests/ctests/*.cpp")
+set (ALE_TEST_SOURCE ${CMAKE_SOURCE_DIR}/tests/ctests/IsdTests.cpp
+                     ${CMAKE_SOURCE_DIR}/tests/ctests/OrientationsTests.cpp
+                     ${CMAKE_SOURCE_DIR}/tests/ctests/RotationTests.cpp
+                     ${CMAKE_SOURCE_DIR}/tests/ctests/StatesTests.cpp
+                     ${CMAKE_SOURCE_DIR}/tests/ctests/TestInterpUtils.cpp
+                     ${CMAKE_SOURCE_DIR}/tests/ctests/TestMain.cpp)
+
+if(ALE_BUILD_LOAD)
+  list(APPEND ALE_TEST_SOURCE ${CMAKE_SOURCE_DIR}/tests/ctests/LoadTests.cpp)
+endif()
 
 # setup test executable
-add_executable(runAleTests ${test_source})
+add_executable(runAleTests ${ALE_TEST_SOURCE})
 target_link_libraries(runAleTests
                       PRIVATE
                       ale
diff --git a/tests/ctests/IsdTests.cpp b/tests/ctests/IsdTests.cpp
index 1e5bc3b510a68215e34467429e487c6d8abfdb6f..242e97ddfae4d116505f54740fef71d9b74c2585 100644
--- a/tests/ctests/IsdTests.cpp
+++ b/tests/ctests/IsdTests.cpp
@@ -4,7 +4,6 @@
 
 #include "gtest/gtest.h"
 
-#include "ale.h"
 #include "Isd.h"
 #include "Util.h"
 #include "Vectors.h"
@@ -34,8 +33,8 @@ TEST(Isd, Constructor) {
   std::string json_str = "{\"image_identifier\":\"TEST_IMAGE\",\"instrument_position\":{\"ephemeris_times\":[297088762.24158406,297088762.3917441,297088762.5419041,297088762.69206405,297088762.84222406,297088762.9923841],\"positions\":[[-1885.29806756,913.1652236,-2961.966828],[-1885.59280128,912.7436266,-2961.91056824],[-1885.88749707,912.32201117,-2961.85424884],[-1886.18215477,911.90037749,-2961.79786985],[-1886.47677475,911.47872522,-2961.7414312],[-1886.77135665,911.05705456,-2961.68493293]],\"velocities\":[[-1.9629237646703683,-2.80759072221274,0.37446657801485306],[-1.9626712192798401,-2.807713482051373,0.3748636774173111],[-1.9624186346660286,-2.807836185534424,0.3752607691067297],[-1.9621660109346446,-2.8079588326107823,0.37565785291714804],[-1.9619133478903363,-2.8080814233753033,0.37605492915558875],[-1.961660645638678,-2.8082039577768683,0.37645199765665144]],\"position_units\":\"KM\",\"time_units\":\"S\",\"reference_frame\":1},\"sun_position\":{\"ephemeris_times\":[297088762.24158406],\"positions\":[[-1885.29806756,913.1652236,-2961.966828]],\"velocities\":[[-1.9629237646703683,-2.80759072221274,0.37446657801485306]],\"position_units\":\"KM\",\"time_units\":\"S\",\"reference_frame\":1},\"instrument_pointing\":{\"time_dependent_frames\":[-74000,-74900,1],\"constant_frames\":[-74021,-74020,-74699,-74690,-74000],\"reference_frame\":1,\"constant_rotation\":[0.9999995608798441,-1.51960241928035e-05,0.0009370214510594064,1.5276552075356694e-05,0.9999999961910578,-8.593317911879532e-05,-0.000937020141647677,8.594745584079714e-05,0.9999995573030465],\"ephemeris_times\":[297088762.24158406,297088762.3917441,297088762.5419041,297088762.69206405,297088762.84222406,297088762.9923841],\"quaternions\":[[0.42061125,0.18606223,-0.23980124,0.85496338],[0.42062261,0.18612356,-0.23976951,0.85495335],[0.42063547,0.18618438,-0.23973759,0.85494273],[0.42064763,0.18624551,-0.2397057,0.85493237],[0.42065923,0.18630667,-0.23967382,0.85492228],[0.42067144,0.18636687,-0.23964185,0.85491211]],\"angular_velocities\":[[-0.0006409728984903079,0.0005054077299115119,0.0004718267948468069],[-0.0006410700774431097,0.0005044862657976017,0.0004731836236807216],[-0.0006408186407087456,0.0004992170698116158,0.0004802237192760833],[-0.0006363961683672021,0.0004989647975959612,0.00047654664046286975],[-0.0006376443791903504,0.0004996117504290811,0.00047678850931380653],[-0.0006404093657132724,0.0005028749658176146,0.0004805228583087444]]},\"body_rotation\":{\"time_dependent_frames\":[10014,1],\"reference_frame\":1,\"ephemeris_times\":[297088762.24158406,297088762.3917441,297088762.5419041,297088762.69206405,297088762.84222406,297088762.9923841],\"quaternions\":[[-0.8371209459443085,0.2996928944391797,0.10720760458181891,0.4448811306448063],[-0.8371185783490869,0.2996934649760026,0.1072060096645597,0.4448855856569007],[-0.8371162107293473,0.2996940355045328,0.10720441474371896,0.44489004065791765],[-0.8371138430875174,0.2996946060241849,0.1072028198209324,0.44489449564328926],[-0.8371114754203602,0.2996951765357392,0.10720122489401934,0.44489895061910595],[-0.8371091077303039,0.29969574703861046,0.10719962996461516,0.4449034055807993]],\"angular_velocities\":[[3.16238646979841e-05,-2.880432898124293e-05,5.6520131658726165e-05],[3.1623864697983686e-05,-2.880432898124763e-05,5.652013165872402e-05],[3.162386469798325e-05,-2.880432898125237e-05,5.652013165872185e-05],[3.162386469798283e-05,-2.880432898125708e-05,5.6520131658719694e-05],[3.1623864697982405e-05,-2.8804328981261782e-05,5.6520131658717505e-05],[3.162386469798195e-05,-2.88043289812665e-05,5.652013165871536e-05]]},\"radii\":{\"semimajor\":3396.19,\"semiminor\":3376.2,\"unit\":\"km\"},\"detector_sample_summing\":1,\"detector_line_summing\":1,\"focal_length_model\":{\"focal_length\":352.9271664},\"detector_center\":{\"line\":0.430442527,\"sample\":2542.96099},\"starting_detector_line\":0,\"starting_detector_sample\":0,\"focal2pixel_lines\":[0.0,142.85714285714,0.0],\"focal2pixel_samples\":[0.0,0.0,142.85714285714],\"optical_distortion\":{\"radial\":{\"coefficients\":[-0.0073433925920054505,2.8375878636241697e-05,1.2841989124027099e-08]}},\"image_lines\":400,\"image_samples\":5056,\"name_platform\":\"MARS_RECONNAISSANCE_ORBITER\",\"name_sensor\":\"CONTEXT CAMERA\",\"reference_height\":{\"maxheight\":1000,\"minheight\":-1000,\"unit\":\"m\"},\"name_model\":\"USGS_ASTRO_LINE_SCANNER_SENSOR_MODEL\",\"interpolation_method\":\"lagrange\",\"line_scan_rate\":[[0.5,-0.37540000677108765,0.001877]],\"starting_ephemeris_time\":297088762.24158406,\"center_ephemeris_time\":297088762.61698407,\"t0_ephemeris\":-0.37540000677108765,\"dt_ephemeris\":0.15016000270843505,\"t0_quaternion\":-0.37540000677108765,\"dt_quaternion\":0.15016000270843505,\"naif_keywords\":{}}";
 
   ale::Isd isd(json_str);
-  
-  
+
+
   ASSERT_EQ(isd.usgscsm_name_model, "USGS_ASTRO_LINE_SCANNER_SENSOR_MODEL");
   ASSERT_EQ(isd.name_platform, "MARS_RECONNAISSANCE_ORBITER");
   ASSERT_EQ(isd.image_id, "TEST_IMAGE");
@@ -73,13 +72,13 @@ TEST(Isd, Constructor) {
 }
 
 TEST(Isd, LogFile) {
-  ale::json j;
+  nlohmann::json j;
   j["log_file"] = "fake/path";
   EXPECT_STREQ(ale::getLogFile(j).c_str(), "fake/path");
 }
 
 TEST(Isd, TransverseDistortion) {
-  ale::json trans;
+  nlohmann::json trans;
   trans["optical_distortion"]["transverse"]["x"] = {1};
   trans["optical_distortion"]["transverse"]["y"] = {2};
 
@@ -91,7 +90,7 @@ TEST(Isd, TransverseDistortion) {
 }
 
 TEST(Isd, RadialDistortion) {
-  ale::json radial;
+  nlohmann::json radial;
   radial["optical_distortion"]["radial"]["coefficients"] = {1, 2};
 
   std::vector<double> coeffs = ale::getDistortionCoeffs(radial);
@@ -102,7 +101,7 @@ TEST(Isd, RadialDistortion) {
 }
 
 TEST(Isd, KaguyaLISMDistortion) {
-  ale::json kaguya;
+  nlohmann::json kaguya;
   kaguya["optical_distortion"]["kaguyalism"]["x"] = {1};
   kaguya["optical_distortion"]["kaguyalism"]["y"] = {2};
   kaguya["optical_distortion"]["kaguyalism"]["boresight_x"] = 3;
@@ -118,7 +117,7 @@ TEST(Isd, KaguyaLISMDistortion) {
 }
 
 TEST(Isd, DawnFCDistortion) {
-  ale::json dawn;
+  nlohmann::json dawn;
   dawn["optical_distortion"]["dawnfc"]["coefficients"] = {1, 2};
   std::vector<double> coeffs = ale::getDistortionCoeffs(dawn);
   EXPECT_EQ(ale::getDistortionModel(dawn), ale::DistortionType::DAWNFC);
@@ -128,7 +127,7 @@ TEST(Isd, DawnFCDistortion) {
 }
 
 TEST(Isd, LroLrocNACDistortion) {
-  ale::json lro;
+  nlohmann::json lro;
   lro["optical_distortion"]["lrolrocnac"]["coefficients"] = {1, 2};
   std::vector<double> coeffs = ale::getDistortionCoeffs(lro);
   EXPECT_EQ(ale::getDistortionModel(lro), ale::DistortionType::LROLROCNAC);
@@ -138,19 +137,19 @@ TEST(Isd, LroLrocNACDistortion) {
 }
 
 TEST(Isd, UnrecognizedDistortion) {
-  ale::json j;
+  nlohmann::json j;
   j["optical_distortion"]["foo"]["x"] = {1};
 
   EXPECT_EQ(ale::getDistortionModel(j), ale::DistortionType::TRANSVERSE);
 }
 
 TEST(Isd, BadLogFile) {
-  ale::json j;
+  nlohmann::json j;
   EXPECT_THROW(ale::getLogFile(j), std::runtime_error);
 }
 
 TEST(Isd, GetSunPositions) {
-  ale::json jsunpos  = {
+  nlohmann::json jsunpos  = {
     {"sun_position",{
        {"ephemeris_times", {300}},
        {"positions", {{10, 12, 13}}},
@@ -164,7 +163,7 @@ TEST(Isd, GetSunPositions) {
   ASSERT_EQ(sunPosObj.getStates().size(), 1);
   ASSERT_EQ(sunPosObj.getReferenceFrame(), 2);
   ASSERT_EQ(sunPosObj.getTimes()[0], 300);
-  
+
   ASSERT_EQ(position[0].x, 10);
   ASSERT_EQ(position[0].y, 12);
   ASSERT_EQ(position[0].z, 13);
@@ -174,17 +173,17 @@ TEST(Isd, GetSunPositions) {
   ASSERT_EQ(velocity[0].z, 0);
 
   jsunpos["sun_position"]["velocities"] = {{1, 2, 3}};
-  
+
   sunPosObj = ale::getSunPosition(jsunpos);
   velocity = sunPosObj.getVelocities();
-  
+
   ASSERT_EQ(velocity[0].x, 1);
   ASSERT_EQ(velocity[0].y, 2);
   ASSERT_EQ(velocity[0].z, 3);
 }
 
 TEST(Isd, NoSunPositions) {
-  ale::json j;
+  nlohmann::json j;
   try {
     ale::getSunPosition(j);
     FAIL() << "Expected an exception to be thrown";
@@ -198,7 +197,7 @@ TEST(Isd, NoSunPositions) {
 }
 
 TEST(Isd, GetInstrumentPositions) {
-  ale::json jinstpos  = {
+  nlohmann::json jinstpos  = {
     {"instrument_position",{
        {"ephemeris_times", {300, 400}},
        {"positions", {{10, 11, 12}, {11, 12,13}}},
@@ -208,13 +207,13 @@ TEST(Isd, GetInstrumentPositions) {
   ale::States instPosObj = ale::getInstrumentPosition(jinstpos);
   std::vector<ale::Vec3d> positions = instPosObj.getPositions();
   std::vector<ale::Vec3d> velocities = instPosObj.getVelocities();
-  
+
   ASSERT_EQ(instPosObj.getStates().size(), 2);
   ASSERT_EQ(instPosObj.getReferenceFrame(), 4);
-  
+
   ASSERT_EQ(instPosObj.getTimes()[0], 300);
   ASSERT_EQ(instPosObj.getTimes()[1], 400);
-  
+
   ASSERT_EQ(positions[0].x, 10);
   ASSERT_EQ(positions[0].y, 11);
   ASSERT_EQ(positions[0].z, 12);
@@ -222,16 +221,16 @@ TEST(Isd, GetInstrumentPositions) {
   ASSERT_EQ(positions[1].x, 11);
   ASSERT_EQ(positions[1].y, 12);
   ASSERT_EQ(positions[1].z, 13);
-  
+
   ASSERT_EQ(velocities[0].x, 0);
   ASSERT_EQ(velocities[0].y, 0);
   ASSERT_EQ(velocities[0].z, 0);
 
   jinstpos["instrument_position"]["velocities"] = {{0, 1, 2}, {3, 4, 5}};
-  
+
   instPosObj = ale::getInstrumentPosition(jinstpos);
   velocities = instPosObj.getVelocities();
-  
+
   ASSERT_EQ(velocities[0].x, 0);
   ASSERT_EQ(velocities[0].y, 1);
   ASSERT_EQ(velocities[0].z, 2);
@@ -243,7 +242,7 @@ TEST(Isd, GetInstrumentPositions) {
  }
 
 TEST(Isd, NoSensorPositions) {
-  ale::json j;
+  nlohmann::json j;
   try {
     ale::getInstrumentPosition(j);
     FAIL() << "Expected an exception to be thrown";
@@ -257,7 +256,7 @@ TEST(Isd, NoSensorPositions) {
 }
 
 TEST(Isd, GetInstrumentPointing) {
-  ale::json pointing  = {
+  nlohmann::json pointing  = {
     {"instrument_pointing",{
        {"ephemeris_times", {300, 600}},
        {"quaternions", {{1,2,3,4}, {4,3,2,1}}},
@@ -271,8 +270,8 @@ TEST(Isd, GetInstrumentPointing) {
   ale::Orientations instPointing = ale::getInstrumentPointing(pointing);
   std::vector<ale::Rotation> rotations = instPointing.getRotations();
   std::vector<ale::Vec3d> velocities = instPointing.getAngularVelocities();
-  std::vector<int> constFrames = instPointing.getConstantFrames(); 
-  std::vector<int> timeDepFrames = instPointing.getTimeDependentFrames(); 
+  std::vector<int> constFrames = instPointing.getConstantFrames();
+  std::vector<int> timeDepFrames = instPointing.getTimeDependentFrames();
 
   ASSERT_EQ(rotations.size(), 2);
   ASSERT_EQ(instPointing.getReferenceFrame(), 2);
@@ -291,20 +290,20 @@ TEST(Isd, GetInstrumentPointing) {
   ASSERT_DOUBLE_EQ(rotations[0].toQuaternion()[1], 0.36514837167011072);
   ASSERT_DOUBLE_EQ(rotations[0].toQuaternion()[2], 0.54772255750516607);
   ASSERT_DOUBLE_EQ(rotations[0].toQuaternion()[3], 0.73029674334022143);
- 
+
   ASSERT_DOUBLE_EQ(rotations[1].toQuaternion()[0], 0.73029674334022143);
   ASSERT_DOUBLE_EQ(rotations[1].toQuaternion()[1], 0.54772255750516607);
   ASSERT_DOUBLE_EQ(rotations[1].toQuaternion()[2], 0.36514837167011072);
   ASSERT_DOUBLE_EQ(rotations[1].toQuaternion()[3], 0.18257418583505536);
-   
+
   ASSERT_DOUBLE_EQ(velocities[0].x, 11);
   ASSERT_DOUBLE_EQ(velocities[0].y, 12);
-  ASSERT_DOUBLE_EQ(velocities[0].z, 13); 
-  
+  ASSERT_DOUBLE_EQ(velocities[0].z, 13);
+
   ASSERT_DOUBLE_EQ(velocities[1].x, 21);
   ASSERT_DOUBLE_EQ(velocities[1].y, 22);
   ASSERT_DOUBLE_EQ(velocities[1].z, 23);
-  
+
   std::vector<double> rotmat = instPointing.getConstantRotation().toQuaternion();
   ASSERT_DOUBLE_EQ(rotmat[0], 0);
   ASSERT_DOUBLE_EQ(rotmat[1], 1);
@@ -314,7 +313,7 @@ TEST(Isd, GetInstrumentPointing) {
 
 
 TEST(Isd, NoSensorOrientations) {
-  ale::json j;
+  nlohmann::json j;
   try {
     ale::getInstrumentPointing(j);
     FAIL() << "Expected an exception to be thrown";
@@ -328,7 +327,7 @@ TEST(Isd, NoSensorOrientations) {
 }
 
 TEST(Isd, GetBodyRotation) {
-  ale::json br = {
+  nlohmann::json br = {
     {"body_rotation",{
        {"ephemeris_times", {300, 600}},
        {"quaternions", {{1,2,3,4}, {4,3,2,1}}},
@@ -342,8 +341,8 @@ TEST(Isd, GetBodyRotation) {
   ale::Orientations bodyRot = ale::getBodyRotation(br);
   std::vector<ale::Rotation> rotations = bodyRot.getRotations();
   std::vector<ale::Vec3d> velocities = bodyRot.getAngularVelocities();
-  std::vector<int> constFrames = bodyRot.getConstantFrames(); 
-  std::vector<int> timeDepFrames = bodyRot.getTimeDependentFrames(); 
+  std::vector<int> constFrames = bodyRot.getConstantFrames();
+  std::vector<int> timeDepFrames = bodyRot.getTimeDependentFrames();
 
   ASSERT_EQ(rotations.size(), 2);
   ASSERT_EQ(bodyRot.getReferenceFrame(), 2);
@@ -362,20 +361,20 @@ TEST(Isd, GetBodyRotation) {
   ASSERT_DOUBLE_EQ(rotations[0].toQuaternion()[1], 0.36514837167011072);
   ASSERT_DOUBLE_EQ(rotations[0].toQuaternion()[2], 0.54772255750516607);
   ASSERT_DOUBLE_EQ(rotations[0].toQuaternion()[3], 0.73029674334022143);
- 
+
   ASSERT_DOUBLE_EQ(rotations[1].toQuaternion()[0], 0.73029674334022143);
   ASSERT_DOUBLE_EQ(rotations[1].toQuaternion()[1], 0.54772255750516607);
   ASSERT_DOUBLE_EQ(rotations[1].toQuaternion()[2], 0.36514837167011072);
   ASSERT_DOUBLE_EQ(rotations[1].toQuaternion()[3], 0.18257418583505536);
-  
+
   ASSERT_DOUBLE_EQ(velocities[0].x, 11);
   ASSERT_DOUBLE_EQ(velocities[0].y, 12);
-  ASSERT_DOUBLE_EQ(velocities[0].z, 13); 
-  
+  ASSERT_DOUBLE_EQ(velocities[0].z, 13);
+
   ASSERT_DOUBLE_EQ(velocities[1].x, 21);
   ASSERT_DOUBLE_EQ(velocities[1].y, 22);
   ASSERT_DOUBLE_EQ(velocities[1].z, 23);
-  
+
   std::vector<double> rotmat = bodyRot.getConstantRotation().toQuaternion();
   ASSERT_DOUBLE_EQ(rotmat[0], 0);
   ASSERT_DOUBLE_EQ(rotmat[1], 1);
@@ -627,7 +626,7 @@ TEST(Isd, BadDistortionModel) {
 }
 
 TEST(Isd, BadDistortionTransverse) {
-  ale::json bad_json;
+  nlohmann::json bad_json;
   bad_json["optical_distortion"]["transverse"]["x"] = {"NaN"};
   bad_json["optical_distortion"]["transverse"]["y"] = {"NaN"};
 
@@ -645,7 +644,7 @@ TEST(Isd, BadDistortionTransverse) {
 }
 
 TEST(Isd, BadDistortionRadial) {
-  ale::json bad_json;
+  nlohmann::json bad_json;
   bad_json["optical_distortion"]["radial"]["coefficients"] = {"NaN"};
 
   try {
@@ -662,7 +661,7 @@ TEST(Isd, BadDistortionRadial) {
 }
 
 TEST(Isd, BadDistortionDawnFC) {
-  ale::json bad_json;
+  nlohmann::json bad_json;
   bad_json["optical_distortion"]["dawnfc"]["coefficients"] = {"NaN"};
 
   try {
@@ -679,7 +678,7 @@ TEST(Isd, BadDistortionDawnFC) {
 }
 
 TEST(Isd, BadDistortionKaguyaLISM) {
-  ale::json bad_json;
+  nlohmann::json bad_json;
   bad_json["optical_distortion"]["kaguyalism"]["x"] = {"NaN"};
   bad_json["optical_distortion"]["kaguyalism"]["y"] = {"NaN"};
   try {
@@ -696,7 +695,7 @@ TEST(Isd, BadDistortionKaguyaLISM) {
 }
 
 TEST(Isd, BadDistortionLroLrocNac) {
-  ale::json bad_json;
+  nlohmann::json bad_json;
   bad_json["optical_distortion"]["lrolrocnac"]["coefficients"] = {"NaN"};
   try {
     ale::getDistortionCoeffs(bad_json);
diff --git a/tests/ctests/LoadTests.cpp b/tests/ctests/LoadTests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..78c4383ef69c5a69e3f33d5d1b3395357afd13c9
--- /dev/null
+++ b/tests/ctests/LoadTests.cpp
@@ -0,0 +1,18 @@
+#include "gtest/gtest.h"
+
+#include "Load.h"
+
+#include <stdexcept>
+
+using namespace std;
+
+TEST(PyInterfaceTest, LoadInvalidLabel) {
+  std::string label = "Not a Real Label";
+  EXPECT_THROW(ale::load(label), invalid_argument);
+}
+
+
+TEST(PyInterfaceTest, LoadValidLabel) {
+  std::string label = "../pytests/data/EN1072174528M/EN1072174528M_spiceinit.lbl";
+  ale::load(label, "", "isis");
+}
diff --git a/tests/ctests/OrientationsTest.cpp b/tests/ctests/OrientationsTests.cpp
similarity index 100%
rename from tests/ctests/OrientationsTest.cpp
rename to tests/ctests/OrientationsTests.cpp
diff --git a/tests/ctests/RotationTest.cpp b/tests/ctests/RotationTests.cpp
similarity index 100%
rename from tests/ctests/RotationTest.cpp
rename to tests/ctests/RotationTests.cpp
diff --git a/tests/ctests/StatesTest.cpp b/tests/ctests/StatesTests.cpp
similarity index 100%
rename from tests/ctests/StatesTest.cpp
rename to tests/ctests/StatesTests.cpp
diff --git a/tests/ctests/TestInterpUtils.cpp b/tests/ctests/TestInterpUtils.cpp
index 6dc2e996d19be95c9c595c40245cd958ee8d1f9f..c1f84530587324c19698bb86a2eac28464e4cb5b 100644
--- a/tests/ctests/TestInterpUtils.cpp
+++ b/tests/ctests/TestInterpUtils.cpp
@@ -8,6 +8,214 @@
 using namespace std;
 using namespace ale;
 
+
+TEST(Interpolation, Derivative1) {
+  vector<double> points = {0, 2, 4};
+  vector<double> times = {0, 1, 2};
+  EXPECT_NO_THROW(ale::interpolate(points, times, 1, ale::LINEAR, 1));
+}
+
+TEST(Interpolation, Derivative2) {
+  vector<double> points = {0, 0, 0};
+  vector<double> times = {0, 1, 2};
+  EXPECT_THROW(ale::interpolate(points, times, 1, ale::LINEAR, 2), invalid_argument);
+}
+
+TEST(Interpolation, InvalidDerivative) {
+  vector<double> points = {0, 0, 0};
+  vector<double> times = {0, 1, 2};
+
+  EXPECT_THROW(ale::interpolate(points, times, 1, ale::LINEAR, 3), invalid_argument);
+}
+
+TEST(InterpIndex, InvalidTimes) {
+  std::vector<double> times = {};
+
+  EXPECT_THROW(ale::interpolationIndex(times, 0), invalid_argument);
+}
+
+TEST(EvaluateCubicHermite, SimplePolynomial) {
+  // Cubic function is y = x^3 - 2x^2 + 1
+  // derivative is dy/dx = 3x^2 - 4x
+  std::vector<double> derivs = {7.0, -1.0};
+  std::vector<double> x = {-1.0, 1.0};
+  std::vector<double> y = {-2.0, 0.0};
+
+  EXPECT_DOUBLE_EQ(ale::evaluateCubicHermite(0.0, derivs, x, y), 1.0);
+}
+
+TEST(EvaluateCubicHermite, InvalidDervisXY) {
+  std::vector<double> derivs = {};
+  std::vector<double> x = {1.0};
+  std::vector<double> y = {1.0};
+
+  EXPECT_THROW(ale::evaluateCubicHermite(0.0, derivs, x, y), invalid_argument);
+}
+
+TEST(EvaluateCubicHermiteFirstDeriv, SimplyPolynomial) {
+  // Cubic function is y = x^3 - 2x^2 + 1
+  // derivative is dy/dx = 3x^2 - 4x
+  std::vector<double> derivs = {7.0, -1.0};
+  std::vector<double> x = {-1.0, 1.0};
+  std::vector<double> y = {-2.0, 0.0};
+
+  EXPECT_DOUBLE_EQ(ale::evaluateCubicHermiteFirstDeriv(0.5, derivs, x, y), -1.25);
+}
+
+TEST(EvaluateCubicHermiteFirstDeriv, InvalidDervisTimes) {
+  std::vector<double> derivs = {};
+  std::vector<double> times = {1.0};
+  std::vector<double> y = {1.0};
+
+  EXPECT_THROW(ale::evaluateCubicHermiteFirstDeriv(0.0, derivs, times, y), invalid_argument);
+}
+
+TEST(EvaluateCubicHermiteFirstDeriv, InvalidVelocities) {
+  std::vector<double> derivs = {5.0, 6.0};
+  std::vector<double> times = {1.0, 1.0};
+  std::vector<double> y = {1.0};
+
+  EXPECT_THROW(ale::evaluateCubicHermiteFirstDeriv(0.0, derivs, times, y), invalid_argument);
+}
+
+// The following tests all use the following equations
+//
+// v = -t^3 - 7x^2 + 3x + 16
+//
+// The full set of values is:
+//  t   v
+// -3  -83
+// -2  -26
+// -1   5
+//  0   16
+//  1   13
+//  2   2
+//  3  -11
+//  4  -20
+//  5  -19
+//  6  -2
+//  7   37
+
+TEST(LagrangeInterpolate, SecondOrder) {
+  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
+  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
+
+  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolate(times, values, 1.5, 2), 7);
+}
+
+TEST(LagrangeInterpolate, FourthOrder) {
+  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
+  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
+
+  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolate(times, values, 1.5, 4), 8.125);
+}
+
+TEST(LagrangeInterpolate, ReducedOrder) {
+  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
+  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
+
+  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolate(times, values, 3.5, 4), -13);
+  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolate(times, values, -2.5, 4), -54.5);
+}
+
+TEST(LagrangeInterpolate, InvalidArguments) {
+  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
+  std::vector<double> values = {-83, -26, 5, 16, 13};
+  EXPECT_THROW(ale::lagrangeInterpolate(times, values, 3.5, 4), invalid_argument);
+}
+
+TEST(lagrangeInterpolateDerivative, SecondOrder) {
+  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
+  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
+
+  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolateDerivative(times, values, 1.5, 2), -12);
+}
+
+TEST(LagrangeInterpolateDerivative, FourthOrder) {
+  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
+  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
+
+  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolateDerivative(times, values, 1.5, 4), -11.25);
+}
+
+TEST(LagrangeInterpolateDerivative, ReducedOrder) {
+  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
+  std::vector<double> values = {-83, -26, 5, 16, 13, -11, -19};
+
+  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolateDerivative(times, values, 3.5, 4), -4);
+  EXPECT_DOUBLE_EQ(ale::lagrangeInterpolateDerivative(times, values, -2.5, 4), 57);
+}
+
+TEST(LagrangeInterpolateDerivative, InvalidArguments) {
+  std::vector<double> times  = {-3,  -2, -1, 0,  1,   3,   5};
+  std::vector<double> values = {-83, -26, 5, 16, 13};
+  EXPECT_THROW(ale::lagrangeInterpolateDerivative(times, values, 3.5, 4), invalid_argument);
+}
+
+TEST(LinearInterpTest, ExampleInterpolation) {
+  vector<double> times = {0,  1,  2, 3};
+  vector<double> data = {0, 2, 1, 0};
+
+  EXPECT_DOUBLE_EQ(0.0, ale::interpolate(data, times, 0.0, ale::LINEAR, 0));
+  EXPECT_DOUBLE_EQ(1.0, ale::interpolate(data, times, 0.5, ale::LINEAR, 0));
+  EXPECT_DOUBLE_EQ(2.0, ale::interpolate(data, times, 1.0, ale::LINEAR, 0));
+  EXPECT_DOUBLE_EQ(1.5, ale::interpolate(data, times, 1.5, ale::LINEAR, 0));
+  EXPECT_DOUBLE_EQ(1.0, ale::interpolate(data, times, 2.0, ale::LINEAR, 0));
+  EXPECT_DOUBLE_EQ(0.5, ale::interpolate(data, times, 2.5, ale::LINEAR, 0));
+  EXPECT_DOUBLE_EQ(0.0, ale::interpolate(data, times, 3.0, ale::LINEAR, 0));
+}
+
+TEST(LinearInterpTest, NoPoints) {
+  vector<double> times = {};
+  vector<double> data = {};
+
+  EXPECT_THROW(ale::interpolate(data, times, 0.0, ale::LINEAR, 0), invalid_argument);
+}
+
+TEST(LinearInterpTest, DifferentCounts) {
+  vector<double> times = { -3, -2, -1,  0,  2};
+  vector<double> data = { -3, -2, 1,  2};
+
+  EXPECT_THROW(ale::interpolate(data, times, 0.0, ale::LINEAR, 0), invalid_argument);
+}
+
+TEST(LinearInterpTest, Extrapolate) {
+  vector<double> times = {0,  1,  2, 3};
+  vector<double> data = {0, 2, 1, 0};
+
+  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, -1.0, ale::LINEAR, 0), -2);
+  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 4.0, ale::LINEAR, 0), -1);
+}
+
+TEST(SplineInterpTest, ExampleInterpolation) {
+  // From http://www.maths.nuigalway.ie/~niall/teaching/Archive/1617/MA378/2-2-CubicSplines.pdf
+  vector<double> times = {0, 1, 2, 3};
+  vector<double> data  = {2, 4, 3, 2};
+  // function is f(t) = 0.5t^3 - 3t^2 + 4.5t + 2
+
+  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 0.0, ale::SPLINE, 0), 2.0);
+  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 0.5, ale::SPLINE, 0), 3.0);
+  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 1.0, ale::SPLINE, 0), 4.0);
+  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 1.5, ale::SPLINE, 0), 3.6875);
+  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 2.0, ale::SPLINE, 0), 3.0);
+  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 2.5, ale::SPLINE, 0), 2.5);
+  EXPECT_DOUBLE_EQ(ale::interpolate(data, times, 3.0, ale::SPLINE, 0), 2.0);
+}
+
+TEST(SplineInterpTest, NoPoints) {
+  vector<double> times = {};
+  vector<double> data = {};
+
+  EXPECT_THROW(ale::interpolate(data, times, 0.0, ale::SPLINE, 0), invalid_argument);
+}
+
+TEST(SplineInterpTest, DifferentCounts) {
+  vector<double> times = { -3, -2, -1,  0,  2};
+  vector<double> data = { -3, -2, 1,  2};
+
+  EXPECT_THROW(ale::interpolate(data, times, 0.0, ale::SPLINE, 0), invalid_argument);
+}
+
 TEST(InterpUtilsTest, LinearInterpolate) {
   EXPECT_EQ(linearInterpolate(1, 3, 0.5), 2);
   EXPECT_EQ(linearInterpolate(1, 1, 0.5), 1);