diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0fd2d78adc..3260fc0bf3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -59,6 +59,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 54e67abafe..9fca007e68 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 c5b6c55a59..c4af96fdb7 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 &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 330d422815..34b3aa1549 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
+#include
#include
#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 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 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 c5db233568..7179141ed6 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 12384d46c4..7e72df9915 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
+#include
#include
#include
@@ -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 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 ed610f38db..0df1a3cff2 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 fieldNames, int numOfFieldValues);
~TableRecord();
diff --git a/isis/src/control/apps/jigsaw/jigsaw.cpp b/isis/src/control/apps/jigsaw/jigsaw.cpp
index 57f39a6370..ce9a4064b1 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
+#include
+#include
+#include
+#include
+#include
#include
#include
@@ -14,6 +19,7 @@ find files of those names at the top level of this repository. **/
#include
#include
+
#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();
+ Table cmatrixTable(cmatrixName, cmatrixData, ',');
+
+ datasetRead = fileRead.getDataSet(spvectorKey);
+ auto spvectorData = datasetRead.read();
+ 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(cmatrixKey, cmatrixTableStr);
+ std::string spvectorTableStr = Table::toString(spvector).toStdString();
+ dataset = file.createDataSet(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 118c834b89..08abf03688 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 cube labels if the bundle
converges and the UPDATE parameter is selected.
+
+ In order to apply bundle values using ADJUSTMENT_INPUT, only the option FROMLIST
+ must be set. If the ADJUSTMENT_INPUT is set to an HDF5 file containing bundle adjustment values
+ (usually the HDF5 output file from OUTADJUSTMENTH5), those values can be applied to the
+ cube labels. The adjustment file entails a group of HDF5 datasets where each dataset
+ contains the instrument pointing and position for each cube. The dataset key is composed
+ of the cube serial number and the table name, either "InstrumentPointing" or
+ "InstrumentPosition", separated by a forward slash "/". For example:
+
+
+ SerialNumber: MRO/CTX/1016816309:030
+ Dataset key for instrument pointing: MRO/CTX/1016816309:030/InstrumentPointing
+
The input control net may be created by finding each image footprint with footprintinit,
@@ -53,7 +66,10 @@
file with the IMAGESCSV option and likewise for the point statistics with the
OUTPUT_CSV option. RESIDUALS_CSV provides a table of the measured image
coordinates, the final sample, line, and overall residuals
- of every control measure in both millimeters and pixels.
+ of every control measure in both millimeters and pixels. OUTADJUSTMENTH5 stores bundle
+ adjustment values in an HDF5 file. Currently, only ISIS adjustment values are saved to file so
+ if all the cubes in the FROMLIST contain a CSM state blob then the adjustment_out.h5
+ file will be created but empty.
Observation Equations
@@ -711,6 +727,9 @@
autoseed or qnet. It contains the control points
and associated measures.
+
+ - ADJUSTMENT_INPUT
+
*.net
@@ -727,6 +746,9 @@
the final coordinates of the control points and residuals for each
measurement.
+
+ - ADJUSTMENT_INPUT
+
*.net
@@ -875,6 +897,9 @@
POINT_RADIUS_SIGMA
+
+ - ADJUSTMENT_INPUT
+
@@ -891,6 +916,9 @@
- No
+
+ - ADJUSTMENT_INPUT
+
@@ -1283,6 +1311,7 @@
- Yes
+
Fit polynomial over the existing pointing
@@ -1769,7 +1798,7 @@
Output file prefix
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.
@@ -1835,6 +1864,91 @@
- no
+
+
+ Outputs bundle adjustment values to HDF5 file - adjustment_out.h5
+
+ 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.
+
+ boolean
+
+ - yes
+
+
+
+
+ filename
+ none
+ input
+
+ Reads in and applies adjustment values
+
+
+ Reads in and applies bundle adjustment values from an HDF5 file,
+ usually the outputted adjustment_out.h5 file.
+
+
+ *.h5
+
+
+ - ONET
+ - CNET
+ - LIDARDATA
+ - OLIDARDATA
+ - OLIDARFORMAT
+ - SCCONFIG
+ - OBSERVATIONS
+ - RADIUS
+ - UPDATE
+ - OUTLIER_REJECTION
+ - REJECTION_MULTIPLIER
+ - ERRORPROPAGATION
+ - MODEL1
+ - MAX_MODEL1_C_QUANTILE
+ - MODEL2
+ - MAX_MODEL2_C_QUANTILE
+ - MODEL3
+ - MAX_MODEL3_C_QUANTILE
+ - SIGMA0
+ - MAXITS
+ - CKDEGREE
+ - CKSOLVEDEGREE
+ - CAMSOLVE
+
+ - OVEREXISTING
+ - SPKDEGREE
+ - SPKSOLVEDEGREE
+ - SPSOLVE
+ - OVERHERMITE
+ - CSMSOLVESET
+ - CSMSOLVETYPE
+ - CSMSOLVELIST
+ - SOLVETARGETBODY
+ - TBPARAMETERS
+ - CONTROL_POINT_COORDINATE_TYPE_BUNDLE
+ - CONTROL_POINT_COORDINATE_TYPE_REPORTS
+ - POINT_LATITUDE_SIGMA
+ - POINT_LONGITUDE_SIGMA
+ - POINT_RADIUS_SIGMA
+ - POINT_X_SIGMA
+ - POINT_Y_SIGMA
+ - POINT_Z_SIGMA
+ - SPACECRAFT_POSITION_SIGMA
+ - SPACECRAFT_VELOCITY_SIGMA
+ - SPACECRAFT_ACCELERATION_SIGMA
+ - CAMERA_ANGLES_SIGMA
+ - CAMERA_ANGULAR_VELOCITY_SIGMA
+ - CAMERA_ANGULAR_ACCELERATION_SIGMA
+
+
+
diff --git a/isis/tests/FunctionalTestsJigsaw.cpp b/isis/tests/FunctionalTestsJigsaw.cpp
index 3abcc3cfe2..61d957a41c 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
+#include
+
#include "jigsaw.h"
@@ -1867,3 +1872,47 @@ TEST_F(LidarNetwork, FunctionalTestJigsawLidar) {
}
}
+
+TEST_F(ApolloNetwork, FunctionalTestJigsawSaveApplyValues) {
+ QVector 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();
+ Table cmatrixTable(cmatrixName, cmatrixData, ',');
+ std::string cmatrixTableStr = Table::toString(cmatrixTable).toStdString();
+
+ datasetRead = file.getDataSet(spvectorKey);
+ auto spvectorData = datasetRead.read();
+ 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 b8da823ec9..ee00d25515 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(table.RecordFields()) << std::endl;
+ std::cout << "Table recordSize=" << static_cast(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());
+}