diff --git a/CHANGELOG.md b/CHANGELOG.md
index b84badcae3a523fd88be276c5ecf5ca2ed175451..e3d555def1500fbf152a9780d4fee42dba878e4a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -70,6 +70,7 @@ release.
 - Fixed gllssi2isis to support V1.1 data [#5396](https://github.com/DOI-USGS/ISIS3/issues/5396)
 
 ### Added
+- Added option to save and apply bundle adjustment values in `jigsaw` [#4474](https://github.com/DOI-USGS/ISIS3/issues/4474)
 - Added versioned default values to lrowacphomap's PHOALGO and PHOPARCUBE parameters and updated lrowacphomap to handle them properly. [#5452](https://github.com/DOI-USGS/ISIS3/pull/5452)
 
 ## [8.2.0] - 2024-04-18
diff --git a/environment.yml b/environment.yml
index 54e67abafe4d8a8b99feb50246f6ec737f3bfac1..9fca007e686de895fb5b174725303baf7e81f2b2 100644
--- a/environment.yml
+++ b/environment.yml
@@ -27,6 +27,7 @@ dependencies:
   - graphviz
   - conda-forge::gsl >=2.6, <2.7
   - hdf5
+  - highfive
   - icu
   - inja
   - jama
diff --git a/isis/src/base/objs/SpiceRotation/SpiceRotation.cpp b/isis/src/base/objs/SpiceRotation/SpiceRotation.cpp
index c5b6c55a595de35c0fa74ec865b51b12f45ca9fb..c4af96fdb75c5bdbde5c40e384e83254473aa6a6 100644
--- a/isis/src/base/objs/SpiceRotation/SpiceRotation.cpp
+++ b/isis/src/base/objs/SpiceRotation/SpiceRotation.cpp
@@ -1880,6 +1880,11 @@ namespace Isis {
                                     const std::vector<double> &coeffAng3,
                                     const Source type) {
 
+    if (type == PolyFunctionOverSpice && !m_orientation) {
+      QString msg = "The quaternion SPICE tables are no longer available. " 
+                    "Either re-run spiceinit or set OVEREXISTING to False.";
+      throw IException(IException::User, msg, _FILEINFO_);
+    }                             
     NaifStatus::CheckErrors();
     Isis::PolynomialUnivariate function1(p_degree);
     Isis::PolynomialUnivariate function2(p_degree);
@@ -3253,7 +3258,7 @@ namespace Isis {
    * @see SpiceRotation::SetEphemerisTime
    */
   void SpiceRotation::setEphemerisTimeMemcache() {
-   // If the cache has only one rotation, set it
+    // If the cache has only one rotation, set it
     NaifStatus::CheckErrors();
     if (p_cacheTime.size() == 1) {
       p_CJ = m_orientation->getRotations()[0].toRotationMatrix();
@@ -3487,23 +3492,23 @@ namespace Isis {
       p_av[index] += cacheVelocity[index];
     }
 
-   if (angles[0] <= -1 * pi_c()) {
-     angles[0] += twopi_c();
-   }
-   else if (angles[0] > pi_c()) {
-     angles[0] -= twopi_c();
-   }
+    if (angles[0] <= -1 * pi_c()) {
+      angles[0] += twopi_c();
+    }
+    else if (angles[0] > pi_c()) {
+      angles[0] -= twopi_c();
+    }
 
-   if (angles[2] <= -1 * pi_c()) {
-     angles[2] += twopi_c();
-   }
-   else if (angles[2] > pi_c()) {
-     angles[2] -= twopi_c();
-   }
+    if (angles[2] <= -1 * pi_c()) {
+      angles[2] += twopi_c();
+    }
+    else if (angles[2] > pi_c()) {
+      angles[2] -= twopi_c();
+    }
 
-   eul2m_c((SpiceDouble) angles[2], (SpiceDouble) angles[1], (SpiceDouble) angles[0],
-           p_axis3,                 p_axis2,                 p_axis1,
-           (SpiceDouble( *)[3]) &p_CJ[0]);
+    eul2m_c((SpiceDouble) angles[2], (SpiceDouble) angles[1], (SpiceDouble) angles[0],
+            p_axis3,                 p_axis2,                 p_axis1,
+            (SpiceDouble( *)[3]) &p_CJ[0]);
   }
 
 
diff --git a/isis/src/base/objs/Table/Table.cpp b/isis/src/base/objs/Table/Table.cpp
index 330d422815467a23ea04b2cdf266b57189baff69..34b3aa1549d417b3e2a3b36b175ab9db516de35f 100644
--- a/isis/src/base/objs/Table/Table.cpp
+++ b/isis/src/base/objs/Table/Table.cpp
@@ -7,6 +7,7 @@ find files of those names at the top level of this repository. **/
 #include "Table.h"
 
 #include <fstream>
+#include <sstream>
 #include <string>
 
 #include "Blob.h"
@@ -137,6 +138,63 @@ namespace Isis {
     }
   }
 
+  /**
+   * This constructor takes in a string to create a Table object.
+   * 
+   * @param tableName The name of the Table to be read
+   * @param tableStr The table string
+   * @param fieldDelimiter The delimiter to separate fields with
+  */
+  Table::Table(const QString &tableName, const std::string &tableString, const char &fieldDelimiter) {
+    p_name = tableName;
+
+    std::stringstream tableStream;
+    tableStream << tableString;
+
+    std::vector<std::string> tableLinesStringList;
+    std::string line;
+    while(std::getline(tableStream, line, '\n')) {
+      tableLinesStringList.push_back(line);
+    }
+
+    int numOfFieldValues = tableLinesStringList.size() - 1; // minus the header line 
+
+    std::string fieldNamesLineString = tableLinesStringList.front();
+    std::stringstream fieldNamesStringStream;
+    fieldNamesStringStream << fieldNamesLineString;
+
+    std::vector<QString> fieldNames;
+    std::string fieldNameString;
+    while(std::getline(fieldNamesStringStream, fieldNameString, fieldDelimiter)) {
+      fieldNames.push_back(QString::fromStdString(fieldNameString));
+    }
+
+    // Clear error flags and set pointer back to beginning
+    tableStream.clear();
+    tableStream.seekg(0, ios::beg);
+
+    // Add records to table
+    std::string recordString;
+    int index = 0;
+    while(std::getline(tableStream, recordString, '\n')) {
+      // skip first line bc that's the header line
+      if (index == 0) {
+        index++;
+        continue;
+      }
+      
+      TableRecord tableRecord(recordString, fieldDelimiter, fieldNames, numOfFieldValues);
+      p_record = tableRecord;
+      this->operator+=(tableRecord);
+      index++;
+    }
+
+    // Add fields
+    for (int f = 0; f < p_record.Fields(); f++) {
+      p_label.addGroup(p_record[f].pvlGroup());
+    }
+  }
+
 
   /**
    * Initialize a Table from a Blob that has been read from a file.
diff --git a/isis/src/base/objs/Table/Table.h b/isis/src/base/objs/Table/Table.h
index c5db2335680e1a42c64e338327b7ccf22c13002f..7179141ed6f8ef12a8304a92758befbaf7d8f4a5 100644
--- a/isis/src/base/objs/Table/Table.h
+++ b/isis/src/base/objs/Table/Table.h
@@ -79,6 +79,7 @@ namespace Isis {
       Table(const QString &tableName, const QString &file,
             const Pvl &fileHeader);
       Table(const Table &other);
+      Table(const QString &tableName, const std::string &tableString, const char &fieldDelimiter);
       Table &operator=(const Isis::Table &other);
 
       ~Table();
diff --git a/isis/src/base/objs/TableRecord/TableRecord.cpp b/isis/src/base/objs/TableRecord/TableRecord.cpp
index 12384d46c49cb8af3004346b8179581c157cb52e..7e72df99155d5a0c1ce8749b52482eaa8eb5be06 100644
--- a/isis/src/base/objs/TableRecord/TableRecord.cpp
+++ b/isis/src/base/objs/TableRecord/TableRecord.cpp
@@ -8,6 +8,7 @@ find files of those names at the top level of this repository. **/
 #include "TableRecord.h"
 
 #include <iostream>
+#include <sstream>
 #include <string>
 #include <vector>
 
@@ -21,6 +22,29 @@ namespace Isis {
   TableRecord::TableRecord(){
   }
 
+  /**
+  * TableRecord constructor
+  * 
+  * @param tableRecordStr Table record string
+  * @param fieldDelimiter The delimiter to separate fields with
+  * @param fieldNames Table header names
+  * @param numOfFieldValues Number of fields (rows)
+  */
+  TableRecord::TableRecord(std::string tableRecordStr, char fieldDelimiter, 
+                            std::vector<QString> fieldNames, int numOfFieldValues) {
+    std::stringstream tableRecordStream;
+    tableRecordStream << tableRecordStr;
+
+    std::string fieldStr;
+    int i = 0;
+    while(std::getline(tableRecordStream, fieldStr, fieldDelimiter)) {
+      TableField tableField(fieldNames[i], TableField::Double);
+      tableField = std::stod(fieldStr); // convert string to double
+      this->operator+=(tableField);
+      i++;
+    }
+  }
+
   //! Destroys the TableRecord object
   TableRecord::~TableRecord() {
   }
@@ -155,7 +179,7 @@ namespace Isis {
       Isis::TableField &field = p_fields[f];
       field = (void *)&buf[sbyte];
       sbyte += field.bytes();
-    }
+    } 
   }
 
   /**
diff --git a/isis/src/base/objs/TableRecord/TableRecord.h b/isis/src/base/objs/TableRecord/TableRecord.h
index ed610f38dbbb63d8b6f4f7ff5dc1c1b4094862ee..0df1a3cff2ffddd525475890baeb8f3ccc1e3d62 100644
--- a/isis/src/base/objs/TableRecord/TableRecord.h
+++ b/isis/src/base/objs/TableRecord/TableRecord.h
@@ -38,6 +38,8 @@ namespace Isis {
   class TableRecord {
     public:
       TableRecord();
+      TableRecord(std::string tableRecordStr, char fieldDelimiter, 
+                            std::vector<QString> fieldNames, int numOfFieldValues); 
       ~TableRecord();
 
       
diff --git a/isis/src/control/apps/jigsaw/jigsaw.cpp b/isis/src/control/apps/jigsaw/jigsaw.cpp
index 57f39a6370a2da11020f54280006fab5894b3bd2..ce9a4064b1e115f23f5cc397bc38e6c6f1d73065 100644
--- a/isis/src/control/apps/jigsaw/jigsaw.cpp
+++ b/isis/src/control/apps/jigsaw/jigsaw.cpp
@@ -7,6 +7,11 @@ find files of those names at the top level of this repository. **/
 /* SPDX-License-Identifier: CC0-1.0 */
 
 #include <iostream>
+#include <highfive/H5File.hpp>
+#include <highfive/H5DataType.hpp>
+#include <highfive/H5DataSet.hpp>
+#include <highfive/H5Group.hpp>
+#include <vector>
 
 #include <QDir>
 #include <QList>
@@ -14,6 +19,7 @@ find files of those names at the top level of this repository. **/
 #include <QSharedPointer>
 #include <QString>
 
+
 #include "Blob.h"
 #include "BundleAdjust.h"
 #include "BundleObservationSolveSettings.h"
@@ -35,6 +41,8 @@ find files of those names at the top level of this repository. **/
 #include "jigsaw.h"
 
 using namespace std;
+using namespace HighFive;
+
 
 namespace Isis {
 
@@ -47,6 +55,77 @@ namespace Isis {
 
   void jigsaw(UserInterface &ui, Pvl *log) {
 
+    QString cubeList = ui.GetFileName("FROMLIST");
+
+    // Check for ADJUSTMENT_INPUT file and apply
+    try {
+      if (ui.WasEntered("ADJUSTMENT_INPUT")) {
+        
+        // Set default values
+        ui.Clear("TWIST");
+        ui.PutBoolean("TWIST", false);
+        ui.PutBoolean("BUNDLEOUT_TXT", false);
+        ui.PutBoolean("IMAGESCSV", false);
+        ui.PutBoolean("OUTPUT_CSV", false);
+        ui.PutBoolean("RESIDUALS_CSV", false);
+        ui.PutBoolean("LIDAR_CSV", false);
+        ui.PutBoolean("OUTADJUSTMENTH5", false);
+        
+        File fileRead(ui.GetFileName("ADJUSTMENT_INPUT").toStdString(), File::ReadOnly);
+        SerialNumberList *snList = new SerialNumberList(cubeList);
+
+        QString jigApplied = "JigApplied = " + Isis::iTime::CurrentLocalTime();
+
+        for (int i = 0; i < snList->size(); i++) {
+          Process p;
+          CubeAttributeInput inAtt;
+          Cube *c = p.SetInputCube(snList->fileName(i), inAtt, ReadWrite);
+
+          if (c->hasBlob("CSMState", "String")) {
+            QString msg = "Unable to apply bundle adjustment values to cubes with CSMState blobs.";
+            throw IException(IException::User, msg, _FILEINFO_);
+          }
+
+          QString serialNumber = snList->serialNumber(i);
+          QString cmatrixName = "InstrumentPointing";
+          QString spvectorName = "InstrumentPosition";
+
+          std::string cmatrixKey = serialNumber.toStdString() + "/" + cmatrixName.toStdString();
+          std::string spvectorKey = serialNumber.toStdString() + "/" + spvectorName.toStdString();
+
+          // Read h5 into table
+          DataSet datasetRead = fileRead.getDataSet(cmatrixKey);
+          auto cmatrixData = datasetRead.read<std::string>();
+          Table cmatrixTable(cmatrixName, cmatrixData, ',');
+
+          datasetRead = fileRead.getDataSet(spvectorKey);
+          auto spvectorData = datasetRead.read<std::string>();
+          Table spvectorTable(spvectorName, spvectorData, ',');
+
+          // Write bundle adjustment values out
+          cmatrixTable.Label().addComment(jigApplied);
+          c->write(cmatrixTable);
+          spvectorTable.Label().addComment(jigApplied);
+          c->write(spvectorTable);
+
+          p.WriteHistory(*c);
+        }
+
+        if (log) {
+          PvlGroup gp("JigsawResults");
+          
+          gp += PvlKeyword("Status", "Bundle adjustment values from [" + ui.GetFileName("ADJUSTMENT_INPUT") 
+                            + "] were applied to the cubes in [" + cubeList+ "]");
+          log->addLogGroup(gp);
+        }
+
+        return;
+      }
+    } catch (IException &e) {
+      QString msg = "Unable to apply bundle adjustment values from [" + ui.GetFileName("ADJUSTMENT_INPUT") + "]";
+      throw IException(e, IException::User, msg, _FILEINFO_);
+    }
+
     // Check to make sure user entered something to adjust... Or can just points be in solution?
     // YES - we should be able to just TRIANGULATE the points in the control net
     // right now to do this we have to fake out jigsaw by
@@ -59,7 +138,6 @@ namespace Isis {
     }
 
     QString cnetFile = ui.GetFileName("CNET");
-    QString cubeList = ui.GetFileName("FROMLIST");
 
     // retrieve settings from jigsaw gui
     BundleSettingsQsp settings = bundleSettings(ui);
@@ -77,6 +155,7 @@ namespace Isis {
       }
     }
     settings->setCubeList(cubeList);
+
     BundleAdjust *bundleAdjustment = NULL;
     try {
       // Get the held list if entered and prep for bundle adjustment
@@ -130,24 +209,63 @@ namespace Isis {
         bundleSolution->outputResiduals();
       }
 
-    // write lidar csv output file
-    if (ui.GetBoolean("LIDAR_CSV")) {
-      bundleSolution->outputLidarCSV();
-    }
+      // write lidar csv output file
+      if (ui.GetBoolean("LIDAR_CSV")) {
+        bundleSolution->outputLidarCSV();
+      }
 
       // write updated control net
       bundleAdjustment->controlNet()->Write(ui.GetFileName("ONET"));
 
-    // write updated lidar data file
-    if (ui.WasEntered("LIDARDATA")) {
-      if (ui.GetString("OLIDARFORMAT") == "JSON") {
-        bundleAdjustment->lidarData()->write(ui.GetFileName("OLIDARDATA"),LidarData::Format::Json);
-      }
-      else {
-        bundleAdjustment->lidarData()->write(ui.GetFileName("OLIDARDATA"),LidarData::Format::Binary);
+      // write updated lidar data file
+      if (ui.WasEntered("LIDARDATA")) {
+        if (ui.GetString("OLIDARFORMAT") == "JSON") {
+          bundleAdjustment->lidarData()->write(ui.GetFileName("OLIDARDATA"),LidarData::Format::Json);
+        }
+        else {
+          bundleAdjustment->lidarData()->write(ui.GetFileName("OLIDARDATA"),LidarData::Format::Binary);
+        }
       }
-    }
+
       PvlGroup gp("JigsawResults");
+      QString jigComment = "Jigged = " + Isis::iTime::CurrentLocalTime();
+
+      std::string outputFilePrefix = settings->outputFilePrefix().toStdString();
+
+      // ALWAYS* WRITE OUT ADJUSTMENT VALUES
+      // Do NOT write out for cubes w/ CSMState (TODO)
+      if (ui.GetBoolean("OUTADJUSTMENTH5")) {
+        std::string adjustmentFilename = outputFilePrefix + "adjustment_out.h5";
+        
+        File file(adjustmentFilename, File::Truncate);
+
+        for (int i = 0; i < bundleAdjustment->numberOfImages(); i++) {
+          Process p;
+          CubeAttributeInput inAtt;
+          Cube *c = p.SetInputCube(bundleAdjustment->fileName(i), inAtt, ReadWrite);
+
+          // Only for ISIS adjustment values
+          if (!c->hasBlob("CSMState", "String")) {
+            Table cmatrix = bundleAdjustment->cMatrix(i);
+            Table spvector = bundleAdjustment->spVector(i);
+
+            QString serialNumber = bundleAdjustment->serialNumberList()->serialNumber(i);
+            QString cmatrixName = cmatrix.Name();
+            QString spvectorName = spvector.Name();
+
+            std::string cmatrixKey = serialNumber.toStdString() + "/" + cmatrixName.toStdString();
+            std::string spvectorKey = serialNumber.toStdString() + "/" + spvectorName.toStdString();
+
+            // Save bundle adjustment values to HDF5 file
+            std::string cmatrixTableStr = Table::toString(cmatrix).toStdString();
+            DataSet dataset = file.createDataSet<std::string>(cmatrixKey, cmatrixTableStr);
+            std::string spvectorTableStr = Table::toString(spvector).toStdString();
+            dataset = file.createDataSet<std::string>(spvectorKey, spvectorTableStr);
+          }
+        }
+        file.flush();
+      }
+
       // Update the cube pointing if requested but ONLY if bundle has converged
       if (ui.GetBoolean("UPDATE") ) {
         if ( !bundleAdjustment->isConverged() ) {
@@ -156,6 +274,8 @@ namespace Isis {
           throw IException(IException::Unknown, msg, _FILEINFO_);
         }
         else {
+
+          // Loop through images
           for (int i = 0; i < bundleAdjustment->numberOfImages(); i++) {
             Process p;
             CubeAttributeInput inAtt;
@@ -174,8 +294,7 @@ namespace Isis {
               break;
             }
 
-            //  Update the image parameters
-            QString jigComment = "Jigged = " + Isis::iTime::CurrentLocalTime();
+            // Only apply adjustment_input values for non-CSM for now
             if (c->hasBlob("CSMState", "String")) {
               Blob csmStateBlob("CSMState", "String");
               // Read the BLOB from the cube to propagate things like the model
@@ -185,8 +304,8 @@ namespace Isis {
               csmStateBlob.setData(modelState.c_str(), modelState.size());
               csmStateBlob.Label().addComment(jigComment);
               c->write(csmStateBlob);
-            }
-            else {
+            } else {
+              // Write bundle adjustment values to cube
               Table cmatrix = bundleAdjustment->cMatrix(i);
               cmatrix.Label().addComment(jigComment);
               Table spvector = bundleAdjustment->spVector(i);
@@ -194,6 +313,7 @@ namespace Isis {
               c->write(cmatrix);
               c->write(spvector);
             }
+
             p.WriteHistory(*c);
           }
           gp += PvlKeyword("Status", "Camera pointing updated");
diff --git a/isis/src/control/apps/jigsaw/jigsaw.xml b/isis/src/control/apps/jigsaw/jigsaw.xml
index 118c834b89209ae9067efd2718dcab989c8d283b..08abf0368811edb68878aa104d3324588b5a2e83 100644
--- a/isis/src/control/apps/jigsaw/jigsaw.xml
+++ b/isis/src/control/apps/jigsaw/jigsaw.xml
@@ -38,6 +38,19 @@
       Updated camera information is only written to the <def>cube</def> labels if the bundle
       converges and the <b>UPDATE</b> parameter is selected.
     </p>
+    <p>
+      In order to apply bundle values using <b>ADJUSTMENT_INPUT</b>, only the option <b>FROMLIST</b> 
+      must be set. If the <b>ADJUSTMENT_INPUT</b> is set to an HDF5 file containing bundle adjustment values 
+      (usually the HDF5 output file from <b>OUTADJUSTMENTH5</b>), those values can be applied to the 
+      <def>cube</def> labels. The adjustment file entails a group of HDF5 datasets where each dataset 
+      contains the instrument pointing and position for each <def>cube</def>. The dataset key is composed 
+      of the <def>cube</def> serial number and the table name, either "InstrumentPointing" or 
+      "InstrumentPosition", separated by a forward slash "/". For example:
+    </p>
+    <pre>
+      SerialNumber: MRO/CTX/1016816309:030
+      Dataset key for instrument pointing: MRO/CTX/1016816309:030/InstrumentPointing
+    </pre>
 
     <p>
       The input control net may be created by finding each image footprint with <b>footprintinit</b>,
@@ -53,7 +66,10 @@
       file with the <b>IMAGESCSV</b> option and likewise for the point statistics with the
       <b>OUTPUT_CSV</b> option. <b>RESIDUALS_CSV</b> provides a table of the measured image
       coordinates, the final <def>sample</def>, <def>line</def>, and overall residuals
-      of every control measure in both millimeters and pixels.
+      of every control measure in both millimeters and pixels. <b>OUTADJUSTMENTH5</b> stores bundle 
+      adjustment values in an HDF5 file. Currently, only ISIS adjustment values are saved to file so
+      if all the cubes in the <b>FROMLIST</b> contain a CSM state blob then the <i>adjustment_out.h5</i> 
+      file will be created but empty.
     </p>
 
     <h3>Observation Equations</h3>
@@ -711,6 +727,9 @@
           <b>autoseed</b> or <b>qnet</b>.  It contains the <def>control point</def>s
           and associated measures.
         </description>
+        <exclusions>
+          <item>ADJUSTMENT_INPUT</item>
+        </exclusions>
         <filter>
           *.net
         </filter>
@@ -727,6 +746,9 @@
           the final coordinates of the <def>control point</def>s and residuals for each
           measurement.
         </description>
+        <exclusions>
+          <item>ADJUSTMENT_INPUT</item>
+        </exclusions>
         <filter>
           *.net
         </filter>
@@ -875,6 +897,9 @@
             POINT_RADIUS_SIGMA
           </item>
         </inclusions>
+        <exclusions>
+          <item>ADJUSTMENT_INPUT</item>
+        </exclusions>
       </parameter>
 
       <parameter name="UPDATE">
@@ -891,6 +916,9 @@
         <default>
           <item>No</item>
         </default>
+        <exclusions>
+          <item>ADJUSTMENT_INPUT</item>
+        </exclusions>
       </parameter>
 
       <parameter name="OUTLIER_REJECTION">
@@ -1283,6 +1311,7 @@
           <item>Yes</item>
         </default>
       </parameter>
+
       <parameter name="OVEREXISTING">
         <brief> Fit polynomial over the existing pointing</brief>
         <description>
@@ -1769,7 +1798,7 @@
          <brief>Output file prefix</brief>
          <description>
            File prefix to prepend for the generated output files. Any prefix that is not a
-	   file path will have an underscore placed between the prefix and file name.
+	         file path will have an underscore placed between the prefix and file name.
          </description>
         </parameter>
 
@@ -1835,6 +1864,91 @@
             <item>no</item>
           </default>
         </parameter>
+
+        <parameter name="OUTADJUSTMENTH5">
+          <brief> Outputs bundle adjustment values to HDF5 file - adjustment_out.h5</brief>
+          <description>
+            Selection of this parameter flags output of bundle adjustment
+            values to an HDF5 file. The HDF5 file may be empty if all the cubes 
+            have CSM states.
+          </description>
+          <type>boolean</type>
+          <default>
+            <item>yes</item>
+          </default>
+        </parameter>
+
+        <parameter name="ADJUSTMENT_INPUT">
+          <type>filename</type>
+          <internalDefault>none</internalDefault>
+          <fileMode>input</fileMode>
+          <brief>
+            Reads in and applies adjustment values
+          </brief>
+          <description>
+            Reads in and applies bundle adjustment values from an HDF5 file,
+            usually the outputted adjustment_out.h5 file. 
+          </description>
+          <filter>
+            *.h5
+          </filter>
+          <exclusions>
+            <item>ONET</item>
+            <item>CNET</item>
+            <item>LIDARDATA</item>
+            <item>OLIDARDATA</item>
+            <item>OLIDARFORMAT</item>
+            <item>SCCONFIG</item>
+            <item>OBSERVATIONS</item>
+            <item>RADIUS</item>
+            <item>UPDATE</item>
+            <item>OUTLIER_REJECTION</item>
+            <item>REJECTION_MULTIPLIER</item>
+            <item>ERRORPROPAGATION</item>
+            <item>MODEL1</item>
+            <item>MAX_MODEL1_C_QUANTILE</item>
+            <item>MODEL2</item>
+            <item>MAX_MODEL2_C_QUANTILE</item>
+            <item>MODEL3</item>
+            <item>MAX_MODEL3_C_QUANTILE</item>
+            <item>SIGMA0</item>
+            <item>MAXITS</item>
+            <item>CKDEGREE</item>
+            <item>CKSOLVEDEGREE</item>
+            <item>CAMSOLVE</item>
+            <!-- <item>TWIST</item> -->
+            <item>OVEREXISTING</item>
+            <item>SPKDEGREE</item>
+            <item>SPKSOLVEDEGREE</item>
+            <item>SPSOLVE</item>
+            <item>OVERHERMITE</item>
+            <item>CSMSOLVESET</item>
+            <item>CSMSOLVETYPE</item>
+            <item>CSMSOLVELIST</item>
+            <item>SOLVETARGETBODY</item>
+            <item>TBPARAMETERS</item>
+            <item>CONTROL_POINT_COORDINATE_TYPE_BUNDLE</item>
+            <item>CONTROL_POINT_COORDINATE_TYPE_REPORTS</item>
+            <item>POINT_LATITUDE_SIGMA</item>
+            <item>POINT_LONGITUDE_SIGMA</item>
+            <item>POINT_RADIUS_SIGMA</item>
+            <item>POINT_X_SIGMA</item>
+            <item>POINT_Y_SIGMA</item>
+            <item>POINT_Z_SIGMA</item>
+            <item>SPACECRAFT_POSITION_SIGMA</item>
+            <item>SPACECRAFT_VELOCITY_SIGMA</item>
+            <item>SPACECRAFT_ACCELERATION_SIGMA</item>
+            <item>CAMERA_ANGLES_SIGMA</item>
+            <item>CAMERA_ANGULAR_VELOCITY_SIGMA</item>
+            <item>CAMERA_ANGULAR_ACCELERATION_SIGMA</item>
+            <!-- <item>BUNDLEOUT_TXT</item>
+            <item>IMAGESCSV</item>
+            <item>OUTPUT_CSV</item>
+            <item>RESIDUALS_CSV</item>
+            <item>LIDAR_CSV</item>
+            <item>OUTADJUSTMENTH5</item> -->
+          </exclusions>
+        </parameter>
       </group>
 
     </groups>
diff --git a/isis/tests/FunctionalTestsJigsaw.cpp b/isis/tests/FunctionalTestsJigsaw.cpp
index 3abcc3cfe21d879ffe661fc8a4b20a78d4e47949..61d957a41ccc7c08b7982b2d428828cad82a7394 100644
--- a/isis/tests/FunctionalTestsJigsaw.cpp
+++ b/isis/tests/FunctionalTestsJigsaw.cpp
@@ -15,6 +15,11 @@
 #include "CSMCamera.h"
 #include "LidarData.h"
 #include "SerialNumber.h"
+#include "BundleAdjust.h"
+#include "BundleSettings.h"
+#include <highfive/H5DataSet.hpp>
+#include <highfive/H5File.hpp>
+
 
 #include "jigsaw.h"
 
@@ -1867,3 +1872,47 @@ TEST_F(LidarNetwork, FunctionalTestJigsawLidar) {
   }
 
 }
+
+TEST_F(ApolloNetwork, FunctionalTestJigsawSaveApplyValues) {
+  QVector<QString> args = {"spsolve=position",
+                            "update=yes",
+                            "bundleout_txt=no",
+                            "cnet="+controlNetPath,
+                            "fromlist="+tempDir.path() + "/cubes.lis",
+                            "onet="+tempDir.path()+"/apollo_out.net",
+                            "file_prefix="+tempDir.path()+"/"};
+
+  UserInterface ui(APP_XML, args);
+
+  jigsaw(ui);
+
+  // Check apollo_jigsaw.h5 was created
+  QString bundleOutput = tempDir.path()+"/adjustment_out.h5";
+  HighFive::File file(bundleOutput.toStdString(), HighFive::File::ReadWrite);
+
+  std::string datasetName = "/APOLLO15/METRIC/1971-08-01T15:37:39.428";
+  QString cmatrixName = "InstrumentPointing";
+  QString spvectorName = "InstrumentPosition";
+  std::string cmatrixKey = datasetName + "/" + cmatrixName.toStdString();
+  std::string spvectorKey = datasetName + "/" + spvectorName.toStdString();
+
+  HighFive::DataSet datasetRead = file.getDataSet(cmatrixKey);
+  auto cmatrixData = datasetRead.read<std::string>();
+  Table cmatrixTable(cmatrixName, cmatrixData, ',');
+  std::string cmatrixTableStr = Table::toString(cmatrixTable).toStdString();  
+
+  datasetRead = file.getDataSet(spvectorKey);
+  auto spvectorData = datasetRead.read<std::string>();
+  Table spvectorTable(spvectorName, spvectorData, ',');
+  std::string spvectorTableStr = Table::toString(spvectorTable).toStdString();
+
+  EXPECT_EQ(cmatrixTable.RecordFields(), 8);
+  EXPECT_EQ(spvectorTable.RecordFields(), 7);
+
+  EXPECT_EQ(cmatrixTableStr,
+            "J2000Q0,J2000Q1,J2000Q2,J2000Q3,AV1,AV2,AV3,ET\n0.72889620121855,0.66172757646101,-0.1261913882606,0.12207651669777,4.29360266307594e-04,6.9419874212449e-04,-6.23609851587137e-04,-896818899.38874\n");
+  EXPECT_EQ(spvectorTableStr, 
+            "J2000X,J2000Y,J2000Z,J2000XV,J2000YV,J2000ZV,ET\n491.19844009026,1198.1045282857,1313.7703671439,1.5198029518433,-0.58925196165899,-0.046463883259045,-896818899.38874\n");
+
+  file.flush();
+}
diff --git a/isis/tests/TableTests.cpp b/isis/tests/TableTests.cpp
index b8da823ec9f5a4c4ab77b191b73f73f52575e4e6..ee00d2551507f4942d1498974e5a71363700ed64 100644
--- a/isis/tests/TableTests.cpp
+++ b/isis/tests/TableTests.cpp
@@ -295,3 +295,33 @@ TEST(TableTests, Clear) {
 
   EXPECT_EQ(t.Records(), 0);
 }
+
+TEST(TableTests, FromString) {
+  std::string tableStr = "J2000Ang1,J2000Ang2,J2000Ang3\n"
+    "-1.0261086365746,1.3843980236775,0.97666760713915\n"
+    "-0.026127047776247,0.034245411189199,0.0052635095732964\n"
+    "-0.005717949450684,-0.0039014897927048,2.3750859084069e-05\n"
+    "260093852.48957,46.12915199995,2.0\n";
+
+  std::cout << "tableStr=" << tableStr << std::endl;
+
+  QString tableName = QString::fromStdString("TestTableName");
+
+  std::stringstream tableStrStream;
+  tableStrStream << tableStr;
+  
+  Table table(tableName, tableStr, ',');
+  std::cout << "Created table with table string" << std::endl;
+  std::cout << "Table name=" << table.Name().toStdString() << std::endl;
+  std::cout << "Table recordFields=" << static_cast<int>(table.RecordFields()) << std::endl;
+  std::cout << "Table recordSize=" << static_cast<int>(table.RecordSize()) << std::endl;
+
+  for (int i = 0; i < table.Records(); i++) {
+    std::cout << "Table[" << i << "] record=" << TableRecord::toString(table[i]).toStdString() << std::endl;
+  }
+
+  QString tableToString = Table::toString(table);
+  std::cout << "tableToString=" << tableToString.toStdString() << std::endl;
+
+  EXPECT_EQ(tableStr, tableToString.toStdString());
+}